# 1.14.0

**BoxLang 1.14.0** is an innovative release that pushes the language forward with first-class **dynamic sets**, **ranges**, **inner classes**, **template classes**, **class references as callable constructors**, deep **query transformation**, **JSONPath** capabilities and much more. This release closes **65 issues** spanning new features, developer experience enhancements, formatter maturity, CFML compatibility parity, and extensive runtime hardening.

This release introduces the **Dynamic Set** type — a new first-class collection with literal syntax, operator overloads for set algebra, functional pipelines, and three backing variants (hash, linked, sorted). We also introduce the **Dynamic Range** type, which revolutionizes interval operations with lazy iteration, multiple element types (integers, decimals, characters, dates, and custom `IRangeable` types), exclusive boundaries, custom stepping, Java Stream integration, and unbounded/half-bounded support. On the language evolution side, **inner classes** and **template classes** bring new structural capabilities, while class references now double as callable constructors via functional callbacks. The **DataNavigator** gains full JSONPath-style query support with `get()`, `has()`, and the new `query()` method. The **MiniServer** health endpoint now includes Undertow and WebSocket metrics. The formatter becomes even more production-ready with ignore-comment support (`@formatter:off` / `bxformat-ignore-start`), multiple-source input, `--excludes` flag, and fine-grained `template.enabled` / `property_spacing` rules. Phew! So much to cover, let's dive in!

## 🚀 Major Highlights

### 🔢 Dynamic Set — First-Class Collection Type

BoxLang 1.14.0 introduces `BoxSet` as a brand-new first-class type, wrapping `java.util.Set` with full BoxLang integration including member-function dispatch, change listeners, metadata, and JSON serialization. Sets provide a powerful collection model for working with **unique values**, making them ideal for deduplication, membership testing, tagging systems, permissions, caching, filtering, mathematics, and data comparison workflows.

Unlike arrays, sets enforce uniqueness by design and offer highly efficient lookup operations. BoxLang elevates sets to a first-class citizen with literal syntax, functional collection operations, and rich operator overloads for set algebra—including unions, intersections, differences, and symmetric differences—making complex data manipulation both expressive and concise.

Whether you're comparing datasets, managing unique identifiers, processing large collections, implementing access-control rules, or building recommendation and analytics engines, `BoxSet` provides a performant and elegant foundation for working with distinct values at scale.

Sets support three backing variants:

* `DEFAULT` — `HashSet`, fastest, no ordering
* `LINKED` — `LinkedHashSet`, preserves insertion order
* `SORTED` — `TreeSet`, natural ordering via `Compare.invoke`

```js
// BIF construction
s = setNew()                                  // empty default (hash)
s = setNew( type="linked", values=[1,2,3] )   // ordered, deduped
s = setOf( 1, 2, 2, 3 )                       // varargs, deduped

// Literal syntax (soft-keyword "set", parser-gated)
s = set{ 1, 2, 3 }                            // default (hash)
s = set{}                                     // empty

// From an Array
s = [1, 2, 3].toSet()
s = [1, 2, 3].toSet( "linked" )
```

**Set algebra via operators:**

```js
union     = a + b      // setUnion
diff      = a - b      // setDifference
intersect = a * b      // setIntersection
symdiff   = a ^ b      // setSymmetricDifference
```

**Rich member-function API** — sets support `.add()`, `.contains()`, `.has()`, `.remove()`, `.delete()`, `.size()`, `.len()`, `.isEmpty()`, `.addAll()`, `.removeAll()`, `.retainAll()`, `.clear()`, `.containsAll()`, `.equals()`, `.isSubsetOf()`, `.isSupersetOf()`, `.isDisjointFrom()`, `.union()`, `.intersection()`, `.difference()`, `.symmetricDifference()`, and functional pipelines: `.each()`, `.map()`, `.filter()`, `.reject()`, `.reduce()`, `.every()`, `.some()`, `.none()`, `.find()`.

```js
s = setOf( 1, 2, 3, 4, 5 )
s.filter( ( item ) => item > 2 )
    .map( ( item ) -> item * 10 )
    .toList( ", " )
// → "30, 40, 50"
```

#### Set Built-In Functions Reference

**Construction & Creation**

| BIF                           | Member Method                                          | Description                                           |
| ----------------------------- | ------------------------------------------------------ | ----------------------------------------------------- |
| `setNew( [type], [values] )`  | —                                                      | Create a new Set, optionally seeded from a collection |
| `setOf( ...values )`          | —                                                      | Build a default (hash) Set from positional arguments  |
| `toSet( collection, [type] )` | `Array.toSet()`, `Query.toSet()`, `String.listToSet()` | Convert a collection into a Set, deduplicating        |
| `structKeySet( struct )`      | `Struct.keySet()`                                      | Build a Set containing the keys of a Struct           |
| `structValueSet( struct )`    | `Struct.valueSet()`                                    | Build a Set containing the values of a Struct         |

**Mutation**

| BIF                                  | Member Method(s)         | Description                                          |
| ------------------------------------ | ------------------------ | ---------------------------------------------------- |
| `boxSetAdd( set, value )`            | `.add()`, `.append()`    | Add an element — duplicates ignored, returns the Set |
| `boxSetAddAll( set, collection )`    | `.addAll()`              | Add every element of a collection to a Set           |
| `boxSetRemove( set, value )`         | `.remove()`, `.delete()` | Remove an element — returns the Set for chaining     |
| `boxSetRemoveAll( set, collection )` | `.removeAll()`           | Remove every element of a collection from a Set      |
| `boxSetRetainAll( set, collection )` | `.retainAll()`           | Retain only elements also present in the collection  |
| `boxSetClear( set )`                 | `.clear()`               | Remove all elements, leaving the Set empty           |

**Query & Membership**

| BIF                                    | Member Method(s)        | Description                                               |
| -------------------------------------- | ----------------------- | --------------------------------------------------------- |
| `boxSetContains( set, value )`         | `.contains()`, `.has()` | Test whether a Set contains a given value                 |
| `boxSetContainsAll( set, collection )` | `.containsAll()`        | Test whether a Set contains every element of a collection |
| `boxSetIsEmpty( set )`                 | `.isEmpty()`            | Test whether a Set has no elements                        |
| `boxSetEquals( setA, setB )`           | `.equals()`             | Test whether two Sets contain the same elements           |
| `boxSetIsSubsetOf( setA, setB )`       | `.isSubsetOf()`         | Test whether every element of A is also in B              |
| `boxSetIsSupersetOf( setA, setB )`     | `.isSupersetOf()`       | Test whether every element of B is also in A              |
| `boxSetIsDisjointFrom( setA, setB )`   | `.isDisjointFrom()`     | Test whether two Sets share no elements                   |
| `boxSetFind( set, predicate )`         | `.find()`               | Return the first element matching the predicate           |
| `boxSetEvery( set, predicate )`        | `.every()`              | True if every element matches the predicate               |
| `boxSetSome( set, predicate )`         | `.some()`, `.any()`     | True if at least one element matches the predicate        |
| `boxSetNone( set, predicate )`         | `.none()`               | True if no element matches the predicate                  |

**Set Algebra**

| BIF                                       | Member Method            | Operator | Description                              |
| ----------------------------------------- | ------------------------ | -------- | ---------------------------------------- |
| `boxSetUnion( setA, setB )`               | `.union()`               | `+`      | Compute the union (A ∪ B)                |
| `boxSetIntersection( setA, setB )`        | `.intersection()`        | `*`      | Compute the intersection (A ∩ B)         |
| `boxSetDifference( setA, setB )`          | `.difference()`          | `-`      | Compute the difference (A − B)           |
| `boxSetSymmetricDifference( setA, setB )` | `.symmetricDifference()` | `^`      | Compute the symmetric difference (A △ B) |

**Functional Pipelines**

| BIF                                           | Member Method | Description                                                |
| --------------------------------------------- | ------------- | ---------------------------------------------------------- |
| `boxSetEach( set, callback )`                 | `.each()`     | Invoke a callback for each element                         |
| `boxSetMap( set, callback )`                  | `.map()`      | Transform each element, deduplicate results into a new Set |
| `boxSetFilter( set, predicate )`              | `.filter()`   | Return a new Set of elements matching the predicate        |
| `boxSetReject( set, predicate )`              | `.reject()`   | Return a new Set of elements NOT matching the predicate    |
| `boxSetReduce( set, callback, initialValue )` | `.reduce()`   | Left-fold with an accumulator function and initial value   |

**Export & Conversion**

| BIF                                | Member Method | Description                                           |
| ---------------------------------- | ------------- | ----------------------------------------------------- |
| `boxSetToArray( set )`             | `.toArray()`  | Convert a Set to an Array, preserving iteration order |
| `boxSetToList( set, [delimiter] )` | `.toList()`   | Join elements into a delimited string                 |

For a deeper guide on BoxLang sets visit our docs: [BoxSet Documentation](https://boxlang.ortusbooks.com/boxlang-language/syntax/sets).

### 📏 Ranges — Lazy, Typed, Iterable Intervals

BoxLang 1.14.0 introduces **Ranges** as a first-class type—a massive evolution from the original `..` operator that simply materialized arrays. Instead of eagerly generating every value up front, ranges are now **lazy, iterable objects** that produce values on demand, making them far more memory-efficient, composable, and expressive for everything from loops and data processing pipelines to scheduling, analytics, and domain modeling. [oai\_citation:0‡Ortus Solutions Community](https://community.ortussolutions.com/t/boxlang-ranges-supercharged/11106?utm_source=chatgpt.com)

What makes BoxLang's implementation particularly unique is that ranges are no longer limited to integers. They natively support **integers, decimals, characters, dates**, and even **custom user-defined types** through the new `IRangeable` interface. Developers can create ascending or descending ranges, define custom step increments, use inclusive or exclusive boundaries, build unbounded or half-bounded ranges, perform membership and boundary checks, and seamlessly integrate with Java Streams for high-performance functional processing. [oai\_citation:1‡Ortus Solutions Community](https://community.ortussolutions.com/t/boxlang-ranges-supercharged/11106?utm_source=chatgpt.com)

Unlike many languages where ranges are little more than syntactic sugar for numeric loops, BoxLang elevates ranges into a rich abstraction for representing intervals and sequences of values. Need every day in a month? Every quarter in a fiscal year? Every character from `a` to `z`? Every version number, semantic identifier, or business object in a custom progression? Ranges make these operations natural, fluent, and type-safe.

Perhaps the most groundbreaking capability is the introduction of the **`IRangeable` interface**, which allows developers to teach BoxLang how to create ranges for their own domain objects. This means you can define ranges over custom types such as software versions, inventory SKUs, workflow states, fiscal periods, geographic coordinates, or any object that has a logical progression. This transforms ranges from a language feature into an extensible framework for modeling real-world sequences and intervals directly in your applications.

By combining lazy evaluation, multiple built-in data types, customizable boundaries and stepping, Java Stream interoperability, and user-extensible range semantics, BoxLang delivers one of the most powerful and flexible range implementations available in any dynamic language today. [oai\_citation:2‡Ortus Solutions Community](https://community.ortussolutions.com/t/boxlang-ranges-supercharged/11106?utm_source=chatgpt.com)

**Basic range syntax:**

```js
// Inclusive range — 1, 2, 3, 4, 5
1..5

// Exclusive boundaries
1>..5      // exclude start: 2, 3, 4, 5
1..<5      // exclude end:   1, 2, 3, 4
1>..<5     // exclude both:  2, 3, 4

// Half-bounded and unbounded
1..        // open-ended from 1 (infinite)
..10       // open-start up to 10 (not iterable)
..         // fully unbounded (contains everything)
```

Ranges are **not arrays** — they're lightweight objects that generate values on demand:

```js
result = arrayToList( 1..5, "," )  // "1,2,3,4,5"
len    = arrayLen( 1..10 )         // 10
```

**Multiple element types:**

```js
// Integers and decimals
1..5                        // 1, 2, 3, 4, 5
3.5..1.5                    // 3.5, 2.5, 1.5 (descending auto-detected)
(0..1).step(0.25)           // 0, 0.25, 0.50, 0.75, 1.00

// Characters
for( c in "a".."e" ) { }    // a, b, c, d, e
for( c in "z".."v" ) { }    // z, y, x, w, v

// DateTime with unit stepping
start = createDate( 2024, 1, 1 )
end   = createDate( 2024, 1, 5 )
(start..end).step( 1, "month" )   // iterate by month
(start..end).step( 1, "week" )    // iterate by week

// Any Comparable type (contains-only, not iterable)
r = "aaa".."zzz"
r.contains( "foo" )    // true — lexicographically between bounds
r.isIterable()         // false — can't enumerate strings
```

**Lazy iteration and streaming:**

Ranges don't allocate memory for their values — huge ranges are cheap:

```js
// This does NOT allocate 100 billion integers
for( i in 1..100_000_000_000 ) {
    result = i
    break  // instant
}

// Full Java Stream API integration
(1..100_000_000_000).stream().limit( 5 ).toList()  // [1, 2, 3, 4, 5]
(1..).stream().limit( 5 ).toList()                 // [1, 2, 3, 4, 5]
```

**Custom stepping:**

```js
arrayToList( (1..10).step(2), "," )   // "1,3,5,7,9"
arrayToList( (10..1).step(-3), "," )  // "10,7,4,1"
```

**Contains semantics:**

Simple ranges use bounds checks, but **stepped ranges** verify step-reachability:

```js
r = 1..10
r.contains( 5 )    // true

// Stepped ranges check reachability
r = (1..10).step(3)  // produces: 1, 4, 7, 10
r.contains( 4 )      // true  (reachable)
r.contains( 5 )      // false (within bounds but NOT reachable by step)
```

**Clamping and position checks:**

```js
(1..10).clamp( 11 )           // 10 — snapped to high boundary
(1..10).clamp( 0 )            // 1  — snapped to low boundary
(1..10).isValueBefore( -3 )   // true
(1..10).isValueAfter( 50 )    // true
```

**Typed unbounded ranges:**

Use `.type()` to constrain an unbounded range to a specific BoxLang type:

```js
(..).contains( "foo" )                    // true (no type constraint)
(..).type("number").contains( "foo" )     // false (wrong type)
(..).type("number").contains( "5" )       // true (coercible to number)
(..).type("integer").contains( 5.5 )      // false (not a whole integer)
```

For exact Java class matching with no coercion, pass a Class reference:

```js
import java:java.lang.Number
(..).type( Number ).contains( 42 )    // true — Integer is a Number
(..).type( Number ).contains( "5" )   // false — strict instanceof
```

**Custom rangeable types via `IRangeable`:**

Any BoxLang or Java class can implement `ortus.boxlang.runtime.types.IRangeable` to participate in ranges:

```js
// Example: Fibonacci sequence as an infinite range
(new Fib()..).stream().limit(10).map( .getCurrent() ).toList()
// [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

(new Fib()..).contains( 13 )   // true — 13 is a Fibonacci number
(new Fib()..).contains( 14 )   // false — 14 is not
```

For a comprehensive guide to BoxLang ranges, including advanced usage patterns and `IRangeable` implementation details, visit our docs: [BoxLang Ranges Documentation](https://boxlang.ortusbooks.com/boxlang-language/syntax/ranges).

### 🧩 Inner Classes and Template Classes

BoxLang now supports **classes defined inline** within scripts (`.bxs`), templates (`.bxm`), or even inside other classes, which enables encapsulation, organization, and scope-local class definitions. These are sometimes called **script classes** or **local classes**.

**Local classes in `.bxs` scripts** define a named class anywhere in a script file and instantiate it with `new`. Classes are hoisted, so you can use `new` before the textual definition:

```js
// Instantiate before the definition — class is hoisted!
result = new Greeter().greet( "World" )

class Greeter {
    function greet( name ) {
        return "Hello, " & name & "!"
    }
}
```

```js
// Multiple local classes with properties and init
class Counter {
    property numeric count default=0

    function increment() {
        variables.count++
    }

    function getCount() {
        return variables.count
    }
}

c = new Counter()
c.increment()
c.increment()
c.increment()
// c.getCount() → 3
```

```js
// Static variables and methods in local classes
class MathUtil {
    static {
        static.MAX_RETRIES = 5
        static.APP_NAME = "MyApp"
    }

    static function add( a, b ) {
        return a + b
    }
}

maxRetries = MathUtil::MAX_RETRIES      // 5
sum        = MathUtil::add( 3, 4 )      // 7
```

**Local classes in `.bxm` templates** — define a class directly inside a `<bx:script>` island within a markup template:

```html
<bx:script>
    class Point {
        function init( x, y ) {
            variables.x = x
            variables.y = y
            return this
        }
        function toString() {
            return "(" & variables.x & "," & variables.y & ")"
        }
    }
    result = new Point( 3, 4 ).toString()
</bx:script>
```

**Classes that extend other classes** — local classes can extend other local classes, abstract classes, and even top-level `.bx` classes, with full polymorphism:

```js
class Animal {
    function speak() {
        return "..."
    }
}

class Dog extends="Animal" {
    function speak() {
        return "Woof!"
    }
}

result = new Dog().speak()  // "Woof!"
```

```js
abstract class Shape {
    abstract function area()
}

class Circle extends="Shape" {
    function init( radius ) {
        variables.radius = radius
        return this
    }
    function area() {
        return 3.14159 * variables.radius ^ 2
    }
}

c = new Circle( 5 )
c.area()  // ~78.54
```

**Local classes with imports** — classes inherit their enclosing script's imports, allowing them to use Java types directly:

```js
import java.util.Date

class Event {
    function init( name ) {
        variables.name       = name
        variables.timestamp  = new Date()
        return this
    }
    function getInfo() {
        return variables.name & " at " & variables.timestamp.toString()
    }
}

result = new Event( "Party" ).getInfo()
// → "Party at Tue Jun 03 12:34:56 UTC 2026"
```

**Inner classes** — classes can also be nested inside other classes:

```js
class Outer {
    class Nested {
        function getValue() {
            return "nested-value"
        }
    }

    function getNested() {
        return new Nested()
    }
}
```

Inner classes are accessed externally via the `$` separator syntax — fully qualified, imported, or with aliases:

```js
// Fully qualified
result = new src.test.java.TestCases.phase3.InnerClassExternal$Widget( "my-widget" )

// Imported with alias
import src.test.java.TestCases.phase3.InnerClassExternal$Widget as MyWidget
result = new MyWidget( "aliased-widget" )

// Static member access
import src.test.java.TestCases.phase3.InnerClassExternal$Widget
typeName = Widget::WIDGET_TYPE
```

You can read more about this feature and see additional examples in our docs: [Inner Classes Documentation](https://boxlang.ortusbooks.com/boxlang-language/classes/inner-classes) and [Template Classes Documentation](https://boxlang.ortusbooks.com/boxlang-language/classes/template-classes).

### 🏗️ Class References as Callable Constructors

BoxLang 1.14.0 introduces a major evolution in object construction by treating **class references as first-class callable objects**. Whether the class originates from BoxLang or Java, imported class references can now participate directly in the language's functional programming model. This removes much of the ceremony traditionally associated with object creation and opens the door to more expressive, composable, and concise code.

Traditionally, object creation required the `new` keyword, which remains fully supported. However, class references can now invoke their constructors directly using `.init()` without requiring `new`, making object construction feel more natural and consistent with the rest of the language. This is particularly useful when working with dynamically resolved classes, dependency injection scenarios, factory patterns, or APIs that return class references at runtime.

Even more powerful is the introduction of **functional constructors**, where the class reference itself becomes callable. Invoking a class reference as a function automatically executes the appropriate constructor and returns a new instance. This provides a clean, Python-inspired syntax that reduces noise while preserving full constructor semantics and compatibility with both Java and BoxLang classes.

Beyond syntactic convenience, this feature unlocks entirely new functional programming patterns. Since class references are now callable objects, they can be passed directly to higher-order functions such as `map()`, `reduce()`, `filter()`, and custom functional pipelines. Transforming collections of raw data into fully instantiated objects becomes remarkably concise, allowing developers to express object creation as a first-class operation rather than wrapping constructors in anonymous functions or lambdas.

This capability also strengthens BoxLang's interoperability story. Java classes and BoxLang classes now share a consistent construction model, reducing cognitive overhead when moving between the two ecosystems. Whether you're instantiating a `StringBuilder`, a custom domain object, or a framework component, the same fluent syntax applies.

Under the hood, class references are wrapped in a specialized `ClassInvokerFunction`, allowing them to participate in the runtime exactly like any other callable object. Constructor invocation is delegated to the same proven instantiation pipeline used by the `new` operator, ensuring consistent behavior, compatibility, and performance while dramatically expanding the expressive power of the language.

```js
// Import a BoxLang Class
import models.User
// Call .init() directly on the class reference — no "new" needed
user = User.init( "Alice", "alice@example.com" )

// Java class — same pattern
import java.lang.StringBuilder
builder = StringBuilder.init( "hello" )

// Works on any expression returning a class
getBuilderClass = () => StringBuilder
builder2 = getBuilderClass().init( "world" )
```

**Class references as functional constructors** — the imported class reference itself can be invoked as a function, which executes the constructor and returns an instance. This provides Python-style constructor ergonomics for both Java and BoxLang classes:

```js
// Import classes
import java.lang.StringBuilder
import models.User

// These three forms are equivalent:
b1 = new StringBuilder( "abc" )
b2 = StringBuilder.init( "abc" )
// b3 is the new functional constructor syntax — call the class reference directly
b3 = StringBuilder( "abc" )

// Same for BoxLang classes:
u1 = new User( "Bob", "bob@example.com" )
u2 = User.init( "Bob", "bob@example.com" )
u3 = User( "Bob", "bob@example.com" )
```

Because class references are callable, they can be passed directly to higher-order functions — a clean functional style for mapping data to objects:

```js
import models.User

names = [ "Alice", "Bob", "Charlie" ]

// Shorthand Functional object creation — all equivalent:
users = names.map( User )
// Long Form approaches
users = names.map( name -> new User( name ) )
users = names.map( name -> User.init( name ) )
users = names.map( name -> User( name ) )
```

Under the hood, class references are wrapped in a `ClassInvokerFunction`, whose `_invoke()` delegates to the same constructor plumbing as the `new` keyword.

### 🔍 DataNavigator JSONPath Support

**What is a DataNavigator?** A `DataNavigator` is BoxLang's fluent helper for safely moving through nested data structures such as Structs, Arrays, parsed JSON, runtime configuration, and metadata. It gives you a consistent API for scoping into data with `from()`, checking whether values exist with `has()`, reading values with `get()`, and applying defaults or strict access rules without scattering null checks throughout your code.

The `DataNavigator` fluent API gains full JSONPath-style expression support in `get()`, `has()`, `from()`, and the new `query()` method for multiple-return scenarios. This makes it much easier to inspect, extract, and reshape deeply nested configuration, JSON, API payloads, module metadata, and mixed Array/Struct data without writing repetitive defensive traversal code.

Developers can now use compact path expressions with dot notation, array indexes, slices, wildcards, recursive descent, and filter expressions directly inside the existing navigator workflow. `get()` remains the best fit when you expect a single value, `has()` can validate deep paths before use, `from()` can scope a navigator to a nested segment, and `query()` returns every match as a BoxLang Array when a path fans out across collections.

This release also adds `getOrDefault()` for explicit fallback values and `getByKey()` / `hasByKey()` for exact-key lookups when real key names contain dots or brackets. Together, these additions make data navigation safer, more expressive, and easier to read, especially in code that consumes external JSON where fields may be optional, irregular, or deeply nested.

**Benefits:**

* Less boilerplate for nested Array/Struct traversal
* Safer handling of optional values with clear fallback behavior
* Multi-result extraction without manual loops
* Better support for real-world JSON payloads and configuration documents
* Exact-key access for data models that use dotted or bracketed key names

**Path expression support:**

```js
nav = dataNavigate( jsonData )

// Dot-notation path expressions in get/has/from
value = nav.get( "boxlang.settings.hello" )
exists = nav.has( "users[?(@.active == true)].email" )
moduleNav = nav.from( "app.modules.auth" )

// Recursive descent — find "key1" anywhere in the tree
found = nav.has( "..key1" )

// Array slicing — 1-based inclusive range
slice = nav.get( "list[1:3]" )

// Wildcard — all values / all elements
all = nav.get( "items[*].name" )
allStructVals = nav.query( "settings.*" )

// Filter expressions with @-current, AND/OR/NOT
active = nav.query( "items[?(@.active == true && @.priority > 2)]" )
named  = nav.query( "items[?(@.active)].name" )
```

**New methods:**

```js
// query() — returns ALL matching values as a BoxLang Array
results = nav.query( "store.products[?(@.price > 100)].name" )

// getOrDefault() — guaranteed non-null return with explicit fallback
port = nav.getOrDefault( "server.port", 8080 )

// getByKey() / hasByKey() — exact key lookup (dots/brackets are literal)
nav.getByKey( "value.sep" )    // treats "value.sep" as a literal key name
```

All path expressions are **whitespace-tolerant** — leading, trailing, and separator-adjacent whitespace is ignored.

For the complete guide, examples, and method reference, visit the [DataNavigator documentation](https://boxlang.ortusbooks.com/boxlang-language/syntax/data-navigators).

### 🧵 Query Transformers & Global Query Options

`queryExecute()` and `bx:query` are locked into three hardcoded return types: `query`, `array`, and `struct`. Users who want tabular arrays, rich column descriptors, JSON, domain objects, or any other format must post-process results in a separate step. Adding new native return types for every use case is unsustainable.

A powerful new **Query Transformer** framework (`BL-2476`) solves this by allowing you to register transformers that process query result sets natively and return exactly what you need — eliminating boilerplate post-processing, since you have access to the full query object and metadata at transformation time.

**Three transformer input types:**

1. **Closure/Lambda** — `(query, metadata) => any` or `(query, metadata) → any`
2. **Class instance** — any class with a `transform(query, metadata)` method
3. **String** — name of a registered transformer from `this.queryTransformers` in `Application.bx`

When `transformer` is provided, it **takes precedence** over `returnType`. The transformer receives two arguments:

* **`query`** — the raw Query object (`.recordCount`, `.toArrayOfStructs()`, `.getData()`, `.getColumnNames()`, etc.)
* **`metadata`** — a struct containing `sql`, `parameters`, `executionTime`, `columnMetadata`, and more

#### Transformer Examples

**1. Inline Closure — Custom Struct with Metadata**

```js
var result = queryExecute( "SELECT * FROM users", [], {
    datasource: "app",
    transformer: ( query, meta ) => {
        return {
            data: query.toArrayOfStructs(),
            total: query.recordCount,
            executedAt: now(),
            sql: meta.sql
        }
    }
} )
// => { data: [...], total: 42, executedAt: ..., sql: "SELECT..." }
```

**2. Inline Closure — Domain Objects**

```js
var users = queryExecute( "SELECT * FROM users", [], {
    datasource: "app",
    transformer: ( query, meta ) => query.toArrayOfStructs().map( row => new User( row ) )
} )
// => [ User{...}, User{...}, ... ]
```

**3. Inline Closure — "Rich" Format with Column Descriptors**

```js
var rich = queryExecute( "SELECT id, name, price, status FROM products", [], {
    datasource: "app",
    transformer: ( query, meta ) => {
        var colMeta = query.getColumnMeta()
        return {
            count: query.recordCount,
            columns: query.getColumnNames().map( name => {
                var info = colMeta[ name ]
                return {
                    name: name,
                    type: info.type,
                    nullable: info.nullable,
                    readOnly: info.readOnly,
                    decimals: info.decimals,
                    maxLength: info.maxLength
                }
            } ),
            data: query.getData().map( row => arrayNew( row ) )
        }
    }
} )
// => { count: 3, columns: [...], data: [[1,"Widget",9.99],...] }
```

**4. Inline Closure — "Tabular" Format (Near Zero-Copy)**

```js
var tabular = queryExecute( "SELECT id, name, price FROM products", [], {
    datasource: "app",
    transformer: ( query, meta ) => {
        return {
            columns: query.getColumnNames(),
            data: query.getData().map( row => arrayNew( row ) )
        }
    }
} )
// => { columns: ["id","name","price"], data: [[1,"Widget",9.99],[2,"Gadget",19.99]] }
```

**5. Class Instance Transformer**

```js
// RichTransformer.bx
class RichTransformer {
    function transform( query, metadata ) {
        var colMeta = query.getColumnMeta()
        return {
            count: query.recordCount,
            columns: query.getColumnNames().map( name => {
                var info = colMeta[ name ]
                return {
                    name: name, type: info.type,
                    nullable: info.nullable, readOnly: info.readOnly,
                    decimals: info.decimals, maxLength: info.maxLength
                }
            } ),
            data: query.getData().map( row => arrayNew( row ) )
        }
    }
}

// Usage
var transformer = new RichTransformer()
var result = queryExecute( sql, params, { transformer: transformer } )
```

**6. Registered Transformers (Application.bx)**

```js
// In Application.bx
this.queryTransformers = {
    "rich": new RichTransformer(),
    "tabular": ( query, meta ) => {
        return {
            columns: query.getColumnNames(),
            data: query.getData().map( row => arrayNew( row ) )
        }
    },
    "json": ( query, meta ) => serializeJson( query.toArrayOfStructs() ),
    "domainUsers": "models.transformers.UserTransformer"
}

// Usage anywhere in the app:
var rich    = queryExecute( sql, params, { transformer: "rich" } )
var tabular = queryExecute( sql, params, { transformer: "tabular" } )
var json    = queryExecute( sql, params, { transformer: "json" } )
var users   = queryExecute( sql, params, { transformer: "domainUsers" } )
```

**7. Transformer Takes Precedence Over returnType**

```js
var result = queryExecute( sql, params, {
    returnType: "array",   // ← ignored when transformer is present
    transformer: ( q, m ) => "custom result"
} )
// => "custom result"
```

**8. bx:query Component**

```html
<bx:query name="result" datasource="app"
    transformer=(( q, m ) => serializeJson( q.toArrayOfStructs() ))>
    SELECT * FROM users
</bx:query>
```

#### Global Query Options (`BL-2477`)

A new **`queries` configuration section** in `boxlang.json` allows global query option defaults, while `this.queryOptions = {}` in `Application.bx` provides application-level default query behaviors.

**boxlang.json:**

```json
// Query Default Options
"queries": {
    // The default timeout for queries in seconds. 0 means no timeout.
    "timeout": 0,
    // The default return type: "query", "array", or "struct"
    "returnType": "query",
    // Number of rows to fetch from database at once (0 = all rows)
    "fetchSize": 0,
    // Maximum number of rows to return (0 = all rows)
    "maxRows": 0,
    // The default named cache to use for query caching
    "cacheProvider": "default"
}
```

**Application.bx:**

```js
// Query Default Options for this application
this.queryOptions = {
    "timeout": 0,
    "returnType": "query",
    "fetchSize": 0,
    "maxRows": 0,
    "cacheProvider": "default"
}
```

All options can be overridden per query via `queryExecute()` options or `bx:query` attributes. Per-query options take highest priority, followed by `this.queryOptions`, with `boxlang.json` as the fallback.

## ✨ New Features

### `schedulerNew()` BIF (BL-2408)

Create and register schedulers directly with a single BIF call — no class file required. Unlike `schedulerStart()` which requires a class path, `schedulerNew()` creates a lightweight, ad-hoc scheduler ready for task registration.

```js
myScheduler = schedulerNew(
    name     = "email-scheduler",
    timezone = "America/Chicago",
    force    = false
)

// Register tasks directly on the scheduler
myScheduler.task( "welcome-email" )
    .call( () => sendWelcomeEmails() )
    .everyHour()
    .startup()
```

Use `schedulerNew()` for lightweight runtime schedulers. Use `schedulerStart()` when you need lifecycle callbacks (`onStartup`, `onShutdown`, `onAnyTaskError`) via a dedicated scheduler class.

### `webMode` Server Identifier (BL-1790)

The `server` scope now exposes a `webMode` boolean indicating whether the runtime is operating in web (servlet/MiniServer) mode.

```js
if ( server.webMode ) {
    // web-specific initialization
}
```

### New String BIFs: `stringStartsWith`, `stringEndsWith` (BL-2439)

Four new Built-In Functions with member-method support for checking string prefix/suffix:

```js
stringStartsWith( "Hello World", "Hello" )     // true
stringEndsWith( "Hello World", "World" )       // true
stringStartsWithNoCase( "HELLO", "hello" )     // true
stringEndsWithNoCase( "WORLD", "world" )       // true

// Member methods
"Hello World".startsWith( "Hello" )
"Hello World".endsWith( "World" )
```

### Left/Right Arrow Key REPL Navigation (BL-2409)

The REPL now supports left and right arrow keys for cursor movement within the current input line — a long-requested quality-of-life improvement for interactive development.

### Formatter: Multiple File Sources & Excludes (BL-2417, BL-2418)

The `boxlang format` command now accepts multiple `--source` files and a new `--excludes` flag for skipping files or directories:

```bash
# Format multiple specific files
boxlang format --source models/User.bx,models/Product.bx

# Format directory but exclude generated and vendor folders
boxlang format --source . --excludes generated,vendor
```

### Formatter: Ignore Comments (BL-2440)

Three styles of formatter-ignore comments are now supported, matching the most popular conventions:

```js
// @formatter:off
uglyCode =    {foo:   "bar"}  // preserved as-is
// @formatter:on

// bxformat-ignore-start
legacyQuery = "SELECT  * FROM    users"
// bxformat-ignore-end

// cfformat-ignore-start
{ unformatted:   true }
// cfformat-ignore-end
```

### Formatter: `template.enabled` Flag (BL-2442)

A new formatter config flag `template.enabled` (defaults to `false`) gates template (`.bxm` / `.cfm`) formatting until it exits experimental mode:

```json
{
  "template": {
    "enabled": false
  }
}
```

### Formatter: `property_spacing` Rule (BL-2443)

The new `class.property_spacing` rule (default: `1`) controls blank lines between property declarations in class bodies — matching Ortus coding standards with a single blank line between properties.

```json
{
  "class": {
    "property_spacing": 1
  }
}
```

### Pretty Print Config Clone (BL-2422)

The `Config` object now supports `.clone()`, enabling safe configuration mutation for multi-pass formatting and tooling scenarios without side effects.

### ResolvedFilePath Dump Template (BL-2428)

`ResolvedFilePath` instances now render with a dedicated HTML dump template showing mapping name, mapping path, relative path, and absolute path — invaluable for debugging module resolution and class loading issues.

### Skills & AGENTS.md Standards (BL-2415)

BoxLang now ships with AI agent skills in `.agents/skills/` and updated `AGENTS.md` standards, providing structured domain knowledge for AI coding assistants working with BoxLang codebases.

### `ON_DATASOURCE_INITIALIZED` Interception Point (BL-2454)

A new interception point fires after a datasource config is loaded but **before** the connection pool is established — giving modules full access to the raw HikariCP configuration for advanced customization:

```js
// In your interceptor
function onDatasourceInitialized( event, interceptData ) {
    var hikariConfig = interceptData.hikariConfig
    hikariConfig.setMaximumPoolSize( 50 )
    hikariConfig.addDataSourceProperty( "cachePrepStmts", true )
}
```

### MiniServer Health Metrics (BL-2455, BL-2456)

The MiniServer `/health` endpoint now includes Undertow worker pool statistics, WebSocket session counts, and additional JVM metrics. The MiniServer also supports static retrieval of the Undertow server instance and XNIO worker for programmatic metrics access.

## 🔧 Improvements

### Language & Runtime

* **`BL-1012`** — `BooleanFormat` and `TrueFalseFormat` consolidated: `TrueFalseFormat` is now a deprecated alias for `BooleanFormat`, scheduled for removal in 2.0.

```js
// Both work; prefer BooleanFormat
booleanFormat( true, "Yes", "No" )   // "Yes"
trueFalseFormat( true, "Ja", "Nein" ) // "Ja" (deprecated alias)
```

* **`BL-2379`** / **`BL-2450`** — Function argument names now blocked from overlapping with import names; validation extended to closure/lambda argument names that shadow imports.
* **`BL-2432`** — Java interop varargs improved: BoxLang arrays passed to Java varargs methods no longer require manual unpacking into `Object[]`.

```js
// Before: manual array packing
javaObj.someMethod( [ arg1, arg2 ].toArray() )

// Now: varargs pass-through
javaObj.someMethod( arg1, arg2 )
```

* **`BL-2436`** — Auto-generate Java method stubs when a BoxLang class extends a Java class, filling in required abstract/interface method implementations.
* **`BL-2437`** — Support import aliases in `extends` and `implements` clauses for Java classes:

```js
import java.util.HashMap as MyMap

class MyCollection extends MyMap {
    // ...
}
```

* **`BL-2459`** — Range type improvements with better element category detection (NUMBER, STRING, CHARACTER, IRANGEABLE, OTHER), enabling faster coercion and broader type support for range operations.
* **`BL-2471`** — Application objects now expose `.getWatchers()`, `.getSchedulers()`, and `.getAppDuration()` for runtime introspection of active watchers, registered schedulers, and application uptime.

### CFML Compatibility & Transpiler

* **`BL-2395`** — CF compat: `form.getPartsArray()` added for compatibility with Adobe CF's form part enumeration.
* **`BL-2397`** / **`BL-2399`** — CF transpiler CLI now supports config loading (by convention or explicit path) and new CLI flags for fine-tuned transpilation control.
* **`BL-2410`** — `FileUpload` now supports `mimeType` as an alias for the `accept` attribute:

```html
<bx:file action="upload" destination="/uploads" mimeType="image/*" />
```

* **`BL-2460`** — Transpile `parameterExists()` to `isDefined()` and quote the parameter name for correct resolution.
* **`BL-2467`** — Transpile `hash( string='test' )` to `hash( input='test' )` — the `string` argument is renamed to `input` to match BoxLang's `hash()` BIF signature.

### Formatter

* **`BL-2397`** — Pretty print config can now be loaded by convention for the CF transpiler CLI tool.
* **`BL-2442`** — New `template.enabled` flag (defaults to `false`) gates experimental template formatting.
* **`BL-2443`** — New `class.property_spacing` rule (defaults to `1`) for consistent property declaration spacing.
* **`BL-2462`** — Semicolons now correctly emitted on required rules when `preserve` is `false`.

### Caching & Data

* **`BL-2472`** — `ICacheStats.toStruct()` now includes cache hit/miss ratios that were previously missing from the stats output.
* **`BL-2473`** — The `remove()` method on cache providers no longer double-JSON-serializes when the result is already a JSON string.
* **`BL-2482`** — UUID values are now JSON-serialized as strings instead of objects, preventing downstream deserialization issues.

### MiniServer & Web

* **`BL-2469`** — Whitespace management now enabled on `text/plain` content types, matching the behavior already present for `text/html`.

### Query & JDBC

* **`BL-2468`** — `fromPendingQuery` and query announcements now include the execution context that was previously missing from the event data.
* **`BL-2485`** — Duplicate logic between `arrayUnique()` and `listRemoveDuplicates()` consolidated into a shared implementation for consistency and maintainability.

### String & Case Utilities

* **`BL-2478`** — PascalCase and KebabCase BIFs updated with additional edge case handling. All case-conversion functions (`snakeCase`, `pascalCase`, `kebabCase`) now robustly handle camelCase, PascalCase, snake\_case, kebab-case, space-separated, and mixed inputs:

```js
snakeCase( "parseXMLHTTPRequest" )    // "parse_xmlhttp_request"
pascalCase( "my-variable" )           // "MyVariable"
kebabCase( "MyClass" )                // "my-class"
```

## 🐛 Bug Fixes (By Area)

### Language & Parser

* **`BL-2424`** — Required function argument with no type no longer parsed as being of type `"required"` under certain conditions.
* **`BL-2425`** — Large `if`/`else` blocks no longer throw `LargeMethodErrors` — the compiler now splits oversized conditional blocks.
* **`BL-2426`** — Current template now correctly reported in an include from inside a function inside a class.
* **`BL-2429`** — Tag-based functions with `output=true` now correctly interpolate variables during parsing.
* **`BL-2430`** — CF transpiler no longer adds `output=true` to functions that don't match CF's default behavior.
* **`BL-2445`** — Interfaces with methods that lack `default` no longer have the `default` modifier incorrectly added, which broke interface contracts.
* **`BL-2452`** — Parser now correctly handles method chaining to expression invocation patterns.
* **`BL-2470`** — Unscoped internal call to a generated accessor setter no longer mis-resolves to a BIF (regression in 1.14.0-snapshot+4460).
* **`BL-2475`** — `try`/`catch` now works correctly within static blocks.

### CFML Compatibility

* **`BL-2331`** — `DateFormat` no longer throws "Can't cast Number to a DateTime" on valid numeric date representations.
* **`BL-2392`** — Cached query metadata no longer mutated by subsequent cache retrievals — a defensive copy is returned.
* **`BL-2405`** — CF's query column passed to array now applies to bracket notation correctly.
* **`BL-2406`** — Template switch cases no longer incorrectly use `break` statements — alignment with CF semantics.
* **`BL-2419`** — `ParseDateTime` now supports `yymmdd` format parsing.
* **`BL-2435`** — CF compat: `arrayMax()`, `arrayMin()`, and `arraySum()` now accept dates in addition to numbers.
* **`BL-2414`** — Adobe CF compatibility: empty form fields removed from form scope list of same-named fields.

### Formatter

* **`BL-2441`** — Formatter no longer adds double assignment expressions when using the `include` keyword.
* **`BL-2444`** — Empty blocks with comments no longer collapse into invalid code.
* **`BL-2447`** — Formatter now adds a line break when an expression is followed by HTML.
* **`BL-2448`** — Include issue fixed where formatter was adding an extra attribute that broke other engines.
* **`BL-2462`** — Semicolons now correctly emitted on required rules when `preserve` is `false`.

### Query & JDBC

* **`BL-2411`** — JDBC trailing semicolon removal now handles multiple semicolons (not just one).
* **`BL-2412`** — QoQ single column in parentheses now takes the column name instead of defaulting to `column_0`.
* **`BL-2413`** — Improved thread safety of large QoQ operations under contention.
* **`BL-2420`** — Data validation now enforced when setting values into query objects.
* **`BL-2421`** — QoQ math operations no longer fail on string data — values are coerced automatically.
* **`BL-2464`** — Query timeout bug fixed — was using the wrong configuration key internally.

### Web & HTTP

* **`BL-2416`** — WebService creation/usage now promotes HTTP/SSL exception messages to the main error message for better debuggability.
* **`BL-2423`** — OData `POST` requests fixed: a `Content-Length: 0` header is now sent to resolve "411 Length Required" errors.
* **`BL-2427`** — `enableOutputOnly` setting now correctly uses output components from the parent context.
* **`BL-2480`** — `writeToBrowser` no longer overrides existing `Content-Disposition` header.
* **`BL-2483`** — Fixed `URISyntaxException` when executing files from paths containing spaces.

### Caching, Serialization & Runtime

* **`BL-2042`** — `LoggingService` concurrent modification exception fixed with thread-safe logger management.
* **`BL-2400`** — Regression fix: `serializeJSON()` no longer stack overflows (1.13 regression).
* **`BL-2402`** — `fileSetLastModified()` now accepts a numeric timestamp in addition to date objects.
* **`BL-2403`** — `Decrypt` BIF now correctly decrypts complex/structured objects — previously corrupted nested data.
* **`BL-2407`** — Scheduler task methods using `(Double period, String timeUnit)` now handle plural time units correctly.
* **`BL-2434`** — `dataNavigate` now honors the default value parameter; a new `getOrDefault()` method provides explicit fallback behavior.
* **`BL-2438`** — Preserve primitive type case (e.g., `int` vs `Int`) in `@overrideJava` fallback stubs.
* **`BL-2458`** — Jackson JR JSON parsing no longer silently ignores trailing content — raises a parse error instead.
* **`BL-2461`** — Return values from interface methods are now properly coerced to the declared return type.
* **`BL-2479`** — `BoxCacheStats.hitRate()` no longer always returns `0` due to integer division — now uses floating-point arithmetic.
* **`BL-2483`** — Fixed `URISyntaxException` when executing files from paths containing spaces.

### Scheduler & Async

* **`BL-2407`** — `ScheduledTask` methods using `(period, timeUnit)` with plural time units (e.g., "hours", "minutes") now work correctly — previously only singular forms were recognized.

### String BIFs

* **`BL-1007`** — `SnakeCase` BIF now correctly converts from camelCase to snake\_case instead of simply replacing spaces. All case-conversion BIFs (`snakeCase`, `pascalCase`, `kebabCase`) handle camelCase, PascalCase, snake\_case, kebab-case, and space-separated inputs.

## 📊 Release Snapshot

* **Release Date:** June 3, 2026
* **Total Issues:** 65
* **Distribution:** 24 New Features, 20 Improvements, 21 Bugs
* **Primary Focus:** Language evolution (sets, inner classes, template classes) and developer experience (formatter, data navigation, REPL)

{% hint style="success" %}
BoxLang 1.14.0 is the recommended update for all teams. The new Dynamic Set type, inner/template class support, JSONPath data navigation, and query transformers are production-ready and bring significant expressiveness and productivity improvements over previous releases.
{% endhint %}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://boxlang.ortusbooks.com/readme/release-history/1.14.0.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
