> For the complete documentation index, see [llms.txt](https://boxlang.ortusbooks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://boxlang.ortusbooks.com/readme/release-history/1.15.0.md).

# 1.15.0

**BoxLang 1.15.0** is a high-impact release with two major headlines and a sweeping round of hardening. The first headline is the new first-class **`BoxStringBuilder`** type — together with compile-time literal folding, smarter `&=` semantics, and an auto-switching runtime concat strategy — making string-heavy code dramatically faster with no code changes required. The second is foundational and architectural: **every class loader in the BoxLang runtime and module system has been fully abstracted** behind a pluggable `IClassLoaderFactory` interface, paving the road to our upcoming **Android runtime** and **AOT (Ahead-of-Time compiled) runtimes** such as GraalVM Native Images and more. Beyond these two pillars, this release delivers two new type-check BIFs (`isBoxSet()` and `isRange()`), an improved `threadCurrent()` BIF, synchronized set support, security hardening for the web runtime, QoQ performance improvements, and an extensive round of CFML compatibility fixes, formatter corrections, and runtime hardening. This release closes **35 issues** spanning new features, improvements, and bug fixes.

## 🚀 Major Highlights

### 🔤 BoxStringBuilder — First-Class Mutable String Type

BoxLang 1.15.0 introduces `BoxStringBuilder` as a brand-new first-class type, wrapping `java.lang.StringBuilder` with full BoxLang integration including member-function dispatch, 1-based positional semantics, and silent coercion to `String` when a string value is required. This pairs with four complementary compiler and runtime optimizations that make string concatenation dramatically faster — often with **zero code changes** required.

**Why this matters:** String objects in Java are immutable. Joining two strings always creates a new object and copies both buffers. In loops and accumulators this can dominate runtime. `BoxStringBuilder` provides a mutable buffer that appends in place, avoiding repeated allocations.

**Three creation forms — all equivalent:**

```js
// BIF: optional initial value and buffer capacity
sbA = stringBuilderNew( "Hello", 128 )

// Long literal form
sbB = stringbuilder{ "Hello" }

// Short literal form (preferred)
sbC = sb{ "Hello" }
sbC.append( " World" )
writeOutput( sbC.toString() )  // "Hello World"
```

The `sb{ ... }` literal accepts **any expression**, not just quoted text:

```js
sb{ "Hello #name#" }
sb{ myVar }
sb{ getGreeting() }
sb{ foo.bar() }
```

**Member method API (1-based positional where applicable):**

| Method                                     | Description                                              |
| ------------------------------------------ | -------------------------------------------------------- |
| `append( value )`                          | Append a value to the end                                |
| `prepend( value )`                         | Prepend a value to the beginning                         |
| `insert( position, value )`                | Insert at a 1-based position                             |
| `delete( start, end )`                     | Delete characters from start to end (inclusive, 1-based) |
| `replace( start, end, value )`             | Replace range with new value                             |
| `reverse()`                                | Reverse the character sequence                           |
| `clear()`                                  | Reset to empty                                           |
| `trim()`                                   | Remove leading/trailing whitespace                       |
| `left( count )`                            | Return leftmost N characters                             |
| `right( count )`                           | Return rightmost N characters                            |
| `mid( start [, count ] )`                  | Substring from 1-based position                          |
| `find( substring [, start [, noCase ] ] )` | Find a substring                                         |
| `contains( substring [, noCase ] )`        | Return boolean membership                                |
| `startsWith( prefix )`                     | True if starts with value                                |
| `endsWith( suffix )`                       | True if ends with value                                  |
| `length()`                                 | Current length of the buffer                             |
| `isEmpty()`                                | True if the buffer is empty                              |
| `toString()`                               | Materialize as a Java String                             |

**Example: Building an HTML fragment efficiently:**

```js
sb = sb{}
sb.append( "<ul>" )
for ( item in items ) {
    sb.append( "<li>" ).append( item ).append( "</li>" )
}
sb.append( "</ul>" )
writeOutput( sb.toString() )
```

**Silent string coercion** — pass a `BoxStringBuilder` anywhere a `String` is required and it is automatically converted:

```js
sb = sb{ "Hello " }
writeOutput( sb & "World" )  // "Hello World"
len( sb )                    // works — sb is coerced to string
```

#### Optimization 1 — `&=` in-place append for builders

Compound concat now uses in-place append semantics for `BoxStringBuilder` values:

```js
sb = sb{ "Hello" }

// &= desugars to sb.append( " World" )
sb &= " World"

// sb is mutated in place — no allocation, no reference change
writeOutput( sb )  // "Hello World"
```

This also applies to raw `java.lang.StringBuilder` instances, not just `BoxStringBuilder`.

#### Optimization 2 — Compiler self-assignment rewrite

The compiler recognizes explicit self-concat patterns and rewrites them to the faster compound form:

```js
// Written as:
data = data & chunk

// Internally rewritten to:
data &= chunk
```

This rewrite applies to identifier, dot-access, and array-access targets (`variables.data`, `arr[1]`, etc.), producing the in-place append path automatically.

#### Optimization 3 — Compile-time literal folding

Contiguous string literals are combined **at compile time**, reducing runtime work:

```js
// Source:
result = "foo" & "bar" & "baz" & "qux"

// Compiled as:
result = "foobarbazqux"
```

Mixed expressions are **partially folded**:

```js
// Source:
result = "foo" & "bar" & name & "baz" & "qux"

// Compiled as:
result = "foobar" & name & "bazqux"
```

#### Optimization 4 — Auto-switching runtime concat strategy

At runtime, concat behavior is tiered based on segment count:

| Segments  | Strategy                                               |
| --------- | ------------------------------------------------------ |
| 0–1       | Trivial return                                         |
| 2–3       | Direct String concat path                              |
| 4 or more | `StringBuilder`-backed path with pre-computed capacity |

This means expression chains like `"foo" & bar & "baz" & bum` and interpolation strings like `"foo#bar#baz#bum#"` are **automatically** lowered to:

```js
sb = new StringBuilder( precomputedCapacity )
sb.append( "foo" ).append( bar ).append( "baz" ).append( bum )
result = sb.toString()
```

**Performance benchmarks** (100,000 iterations, 2–8 string segments):

* String concat is faster for exactly 2 strings
* Break-even occurs around 3 segments
* From 2 to 8 strings, concat growth is **\~4.2× steeper** than builder growth
* BoxLang automatically uses StringBuilder for **4 or more segments**

**Java interop nuance:** `BoxStringBuilder` member methods are NOT injected onto a raw `java.lang.StringBuilder` to avoid mixing 1-based (BoxLang) and 0-based (Java) positional semantics. Wrap first if needed:

```js
javaSB = createObject( "java", "java.lang.StringBuilder" ).init( "Hello" )
boxSB  = stringBuilderNew( javaSB )
boxSB.delete( 1, 1 )  // BoxStringBuilder semantics (1-based)

javaSB.delete( 2, 2 )  // Java semantics (0-based, no-op for equal start/end)
```

{% hint style="info" %}
For a complete guide to `BoxStringBuilder` including practical patterns and benchmarks, see [StringBuilder](/boxlang-language/syntax/string-builder.md) in the Syntax & Semantics section.
{% endhint %}

For the full breakdown of what shipped, see the community post: [StringBuilder in BoxLang: Fast Concatenation, Better &=, and Compile-Time Folding](https://community.ortussolutions.com/t/stringbuilder-in-boxlang-fast-concatenation-better-and-compile-time-folding/11123).

### 🚀 Runtime Architecture — Pluggable ClassLoader Factory (BL-2526)

BoxLang has always had ambitions beyond the JVM. We want BoxLang to run on **Android**, to compile **ahead-of-time** via GraalVM Native Image, and eventually to support other constrained or specialized execution targets that differ fundamentally from a standard JVM process. What has stood in the way — everywhere — has been class loading.

In a standard JVM, loading a class means calling `URLClassLoader.defineClass()`, which JIT-compiles bytecode on first use. That model is deeply wired into how BoxLang boots, how it compiles your `.bx` source files, how modules get isolated from each other, and how runtime-generated classes are resolved. On Android, `URLClassLoader` does not exist. On GraalVM Native Image in closed-world mode, you cannot call `defineClass()` at runtime at all.

In BoxLang 1.15.0, **every class-loader creation point in the runtime has been extracted behind a single `IClassLoaderFactory` interface**. This single seam governs three distinct construction points:

* The **runtime root class loader** — parent of all module loaders, used for Java interop and dynamic lookups
* Each **module's isolated class loader** — responsible for isolating a module's JARs from the rest of the runtime
* The **generated class loader** — the loader that receives compiled BoxLang bytecode and resolves it for execution

Swapping a single factory before the runtime boots changes all three in one move. No forks. No patches. No runtime surgery.

```java
// For a standard JVM (the default — no changes needed for existing deployments)
BoxRuntime.setClassLoaderFactory( new DynamicClassLoaderFactory() );

// For Android: return the app ClassLoader as the runtime loader,
// use DexClassLoader for modules, and a resolve-only loader for generated classes
BoxRuntime.setClassLoaderFactory( new AndroidClassLoaderFactory() );

// For GraalVM Native Image (closed-world AOT):
// all classes are pre-registered at build time; no defineClass() at runtime
BoxRuntime.setClassLoaderFactory( new NativeImageClassLoaderFactory() );
```

The default `DynamicClassLoaderFactory` is installed automatically and reproduces the exact behavior of all previous BoxLang releases — **zero breaking changes for existing deployments**.

**What this unlocks:**

| Target                | What changes                            | BoxLang runtime                          | Your code |
| --------------------- | --------------------------------------- | ---------------------------------------- | --------- |
| Standard JVM          | Nothing                                 | `DynamicClassLoaderFactory` (default)    | Unchanged |
| Android (ART)         | `AndroidClassLoaderFactory` at boot     | Uses app ClassLoader + DexClassLoader    | Unchanged |
| GraalVM Native Image  | `NativeImageClassLoaderFactory` at boot | Pre-registered classes, no `defineClass` | Unchanged |
| Custom OSGI / sandbox | Custom factory at boot                  | Full control                             | Unchanged |

This investment does not add a single new BIF or language construct. What it does is make BoxLang **portable at the architecture level** — a prerequisite for everything that follows on our roadmap. The BoxLang language you write today will run, unchanged, on whatever runtime target we ship tomorrow.

{% hint style="info" %}
Module developers: if your module currently constructs class loaders directly instead of going through the module service lifecycle, now is the time to align with the factory pattern. Contact the Ortus team for migration guidance.
{% endhint %}

## ✨ New Features

### `isBoxSet()` BIF (BL-2506)

A dedicated BIF to check whether a value is a BoxLang `BoxSet` instance — complements the existing `isArray()`, `isStruct()`, and `isQuery()` family:

```js
s = setOf( 1, 2, 3 )

isBoxSet( s )        // true
isBoxSet( [1,2,3] )  // false — arrays are not sets
isBoxSet( "hello" )  // false

// Useful for defensive type checking before set operations
function processCollection( data ) {
    if ( isBoxSet( data ) ) {
        return data.toArray().sort( "numeric" )
    }
    if ( isArray( data ) ) {
        return data.toSet().toArray().sort( "numeric" )
    }
    throw( "Unsupported collection type" )
}
```

### `isRange()` BIF (BL-2507)

A dedicated BIF to test whether a value is a BoxLang `Range` instance:

```js
r = 1..10

isRange( r )        // true
isRange( [1,2,3] )  // false
isRange( "1..10" )  // false — string representation, not a range

// Guard range-specific operations
function sumRange( val ) {
    if ( !isRange( val ) ) {
        throw( "Expected a Range, got #val.getClass().getSimpleName()#" )
    }
    return val.reduce( ( acc, item ) -> acc + item, 0 )
}
```

### `threadCurrent()` BIF (BL-2513)

Easily get access to the current native Java thread from BoxLang via the `threadCurrent()` BIF.

```js
javaThread = threadCurrent()
writeOutput( javaThread.getName() )          // java.lang.Thread getName()
writeOutput( javaThread.getState() )         // RUNNABLE, WAITING, etc.
writeOutput( javaThread.isVirtual() )        // true for virtual threads (Java 21+)
writeOutput( javaThread.threadId() )         // JVM thread identifier
```

This is useful for telemetry, profiling integrations, or any scenario where direct Thread API access is required.

### Improved `asString()` on Class Instances (BL-2487)

BoxLang class instances now produce a more useful string representation from `asString()`. If the class defines a `toString()` method or property, that value is used; otherwise the representation now includes the class name and a summary of public properties:

```js
class Product {
    property string name
    property numeric price

    function toString() {
        return "Product(#variables.name#, $#variables.price#)"
    }
}

p = new Product( name="Widget", price=9.99 )
writeOutput( p )           // Product(Widget, $9.99)
writeOutput( "#p#" )       // Product(Widget, $9.99)
```

### Compile Validation for Inner Classes Inside Functions (BL-2490)

The compiler now emits a clear validation error when an inner class is declared inside a function body, rather than producing a confusing runtime failure:

```js
function doSomething() {
    // ❌ Now produces a clear compile-time error:
    class Helper { }
    // Compiler: Inner classes cannot be declared inside a function body.
}
```

### Deserialize JSON Args to Struct/Array in Remote Methods (BL-2505)

Remote methods (those marked `access="remote"`) now automatically deserialize JSON string arguments into `Struct` or `Array` values when the argument type is declared as such:

```js
remote function saveUser( required struct userData ) {
    // When called via HTTP with userData={"name":"Alice","email":"alice@example.com"}
    // userData is automatically deserialized to a BoxLang Struct:
    writeOutput( userData.name )   // Alice
    writeOutput( userData.email )  // alice@example.com
}
```

### `application/json` Whitespace Compression (BL-2547)

The web runtime's whitespace compression (previously only active for HTML responses) is now also applied to `application/json` responses, reducing payload size without any application changes.

### Error Basics in HTML Error Page Comment (BL-2204)

BoxLang's default HTML error page now includes an HTML comment at the very top of the response with essential error information, making it possible to programmatically extract error details during testing and debugging even when full error display is suppressed:

```html
<!-- BoxLang Error
   Type    : application.MyException
   Message : Something went wrong
   Template: /path/to/template.bxm
   Line    : 42
-->
```

### Synchronized Set Support — `setNew( isSynchronized )` (BL-2494)

`setNew()` gains an `isSynchronized` boolean argument. When `true`, the set is wrapped in a thread-safe synchronized wrapper for safe concurrent access:

```js
// Thread-safe set for shared state across threads
sharedSet = setNew( type="linked", isSynchronized=true )

thread name="t1" { sharedSet.add( "alpha" ) }
thread name="t2" { sharedSet.add( "beta" )  }
threadJoin( "t1,t2" )

writeOutput( sharedSet.size() )  // 2
```

## 🔧 Improvements

### Language & Runtime

* [**BL-2538**](https://ortussolutions.atlassian.net/browse/BL-2538) — **QoQ Performance Improvements** — Query of Queries now executes significantly faster for large datasets through internal optimizations to the filtering, sorting, and aggregation pipeline.
* [**BL-2539**](https://ortussolutions.atlassian.net/browse/BL-2539) — **Expression Interpreter Safe Get** — The expression interpreter no longer throws when performing a safe navigation expression that fails to resolve a key; it returns `null` instead:

```js
data = { name: "alice" }
val = data?.age?.years    // null — no exception thrown
```

* [**BL-2543**](https://ortussolutions.atlassian.net/browse/BL-2543) — **`null` is falsey but not boolean** — `isBoolean( null )` now correctly returns `false`. `null` is falsey but that does not make it a boolean value:

```js
isBoolean( null )   // false (was: true in some earlier builds)
if ( !null ) { }    // still works — null IS falsey
```

* [**BL-2500**](https://ortussolutions.atlassian.net/browse/BL-2500) — **Struct keys capped in error messages** — `KeyNotFoundException` error messages now show a capped list of available keys to avoid flooding logs on large structs.
* [**BL-2501**](https://ortussolutions.atlassian.net/browse/BL-2501) — **Range empty-set semantics** — Paradoxical ranges (e.g., `5..3` with a positive step) now produce zero elements rather than throwing:

```js
r = (5..3).step( 1 )
r.toArray()  // [] — paradoxical range returns empty set
```

* [**BL-2512**](https://ortussolutions.atlassian.net/browse/BL-2512) — **Formatter `arguments.separator` config** — The formatter now accepts an `arguments.separator` config key (default `" = "`) controlling spacing around named argument separators:

```json
{
  "arguments": {
    "separator": " = "
  }
}
```

* [**BL-2514**](https://ortussolutions.atlassian.net/browse/BL-2514) — **Session cookie only set when session management is enabled** — The web runtime no longer emits a `BXSESSIONID` cookie for applications that have session management disabled.
* [**BL-2508**](https://ortussolutions.atlassian.net/browse/BL-2508) — **No path rewriting for known script extensions** — The web runtime no longer rewrites paths that already resolve to known BoxLang script extensions, avoiding double-resolution in certain URL routing scenarios.
* [**BL-2509**](https://ortussolutions.atlassian.net/browse/BL-2509) — **File upload copies rather than moves** — `fileUpload` now copies the temporary upload file to the destination, preventing failures when temp and destination paths cross filesystem mount boundaries.
* [**BL-2510**](https://ortussolutions.atlassian.net/browse/BL-2510) — **Stored proc `OUT` params respect `nullEqualsEmptyString`** — Stored procedure output parameters now honor the CFML compat flag, returning `""` instead of `null` where appropriate.
* [**BL-2497**](https://ortussolutions.atlassian.net/browse/BL-2497) — **CFML compat: string dates as numbers** — In CFML compatibility mode, string representations of dates can now participate in numeric operations, matching Adobe ColdFusion behavior.
* [**BL-2548**](https://ortussolutions.atlassian.net/browse/BL-2548) — **Adobe CF `returnFormat` drives `Content-Type`** — Remote methods with `returnFormat="json"` now correctly set `Content-Type: application/json` in the response.
* [**BL-2493**](https://ortussolutions.atlassian.net/browse/BL-2493) — **Set dump template handles Java sets** — The HTML dump template for `BoxSet` now also renders native `java.util.Set` instances correctly.

### Security

* [**BL-2515**](https://ortussolutions.atlassian.net/browse/BL-2515) — **Web runtime blocks requests with null bytes** — Any HTTP request whose URL contains a null byte (`%00`) is rejected with `400 Bad Request` before processing, preventing null-byte injection attacks.

## 🐛 Bug Fixes

### Language & Parser

* [**BL-2491**](https://ortussolutions.atlassian.net/browse/BL-2491) — Class constructor syntax using `new ClassName()` on imported or locally-declared classes was not being recognized correctly; fixed.
* [**BL-2537**](https://ortussolutions.atlassian.net/browse/BL-2537) — Closures declared inside BoxLang `interface` bodies failed to compile in the Java bytecode compiler; they now compile and execute correctly.
* [**BL-2549**](https://ortussolutions.atlassian.net/browse/BL-2549) — Invalid bytecode was generated for the string concatenation (`&`) operator under certain expression patterns; the bytecode emitter now produces correct output in all cases.
* [**BL-2550**](https://ortussolutions.atlassian.net/browse/BL-2550) — After splitting a large method at the bytecode level, source line numbers in stack traces were incorrect; line number tables are now recalculated after every split.

### Casters & Types

* [**BL-2492**](https://ortussolutions.atlassian.net/browse/BL-2492) — `int`, `long`, and `short` casters previously silently accepted values outside their representable range. They now validate the input and throw a meaningful cast exception:

```js
castAs( 9999999999999, "int" )
// throws: Value 9999999999999 is too large to be cast to int

castAs( -9999999999999, "short" )
// throws: Value -9999999999999 is too large to be cast to short
```

### Formatter

* [**BL-2496**](https://ortussolutions.atlassian.net/browse/BL-2496) — Formatter threw an error on variable assignment statements with no initializer (e.g., `var x`); these are now formatted correctly.
* [**BL-2528**](https://ortussolutions.atlassian.net/browse/BL-2528) — Formatter was incorrectly inserting two extra spaces before the `extends` keyword in class definitions.
* [**BL-2529**](https://ortussolutions.atlassian.net/browse/BL-2529) — Formatter was converting CFML component invocations (angle bracket style) into BoxLang `bx:component` style inside `.cfc` files. The formatter now detects the file context and preserves CFML syntax in CFML files:

```html
<!-- CFML file — now preserved as-is -->
<cfcomponent extends="BaseComponent">
    ...
</cfcomponent>
```

### CFML Compatibility

* [**BL-2495**](https://ortussolutions.atlassian.net/browse/BL-2495) — Query filter regression: when the filter value was empty, the CFML compat layer was incorrectly filtering out non-empty rows.
* [**BL-2530**](https://ortussolutions.atlassian.net/browse/BL-2530) — `decimalFormat( "" )` was throwing a cast exception; it now returns `"0.00"` matching Adobe CF behavior:

```js
decimalFormat( "" )   // "0.00"   (was: cast exception)
decimalFormat( 0 )    // "0.00"
```

* [**BL-2531**](https://ortussolutions.atlassian.net/browse/BL-2531) — Improved parser error messages when an unescaped `#` character is found inside a `<cfoutput>` block — the error now points to the correct source location.
* [**BL-2532**](https://ortussolutions.atlassian.net/browse/BL-2532) — `<cfapplication action="update" datasource="...">` was silently ignoring the datasource change; the runtime now correctly applies the update.
* [**BL-2535**](https://ortussolutions.atlassian.net/browse/BL-2535) — When `onWebExecutorRequest()` changed the request URL, file extension handling was applied to the original URL rather than the rewritten one.
* [**BL-2544**](https://ortussolutions.atlassian.net/browse/BL-2544) — Default JSON serialization format was inconsistent between calls on the same data; the serialization path is now deterministic.
* [**BL-2545**](https://ortussolutions.atlassian.net/browse/BL-2545) — `dateCompare()` now correctly handles `"hours"` as a date-part argument (previously only `"hour"` singular was recognized):

```js
dateCompare( now(), now(), "hours" )    // 0 — now works
dateCompare( now(), now(), "hour" )     // 0 — still works
```

### String BIFs

* [**BL-2536**](https://ortussolutions.atlassian.net/browse/BL-2536) — `replace()` threw a `NullPointerException` when the input string was `null`; it now treats `null` as an empty string:

```js
replace( null, "foo", "bar" )    // "" — no longer throws
replace( "", "foo", "bar" )      // ""
```

### Runtime & Security

* [**BL-2503**](https://ortussolutions.atlassian.net/browse/BL-2503) — A race condition in the Encryption utility was caused by a shared non-concurrent `HashMap`; replaced with a thread-safe `ConcurrentHashMap`.
* [**BL-2484**](https://ortussolutions.atlassian.net/browse/BL-2484) — Hikari connection pool was producing excessive `INFO`-level log output at startup and during routine operation; log levels have been tuned for quiet defaults.
* [**BL-2283**](https://ortussolutions.atlassian.net/browse/BL-2283) — When passing a `Date` value as a query parameter, a timezone offset was incorrectly applied when the application server timezone differed from the database timezone; dates are now sent without offset adjustment.

## 📊 Release Snapshot

* **Release Date:** July 2026
* **Total Issues:** 35
* **Distribution:** 9 New Features, 13 Improvements, 13 Bugs
* **Primary Focus:** String performance (`BoxStringBuilder`, compile-time folding, runtime concat strategy), type-check BIFs, CFML compat hardening, security


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## 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, and the optional `goal` query parameter:

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

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

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.
