# 1.12.0

**BoxLang 1.12.0** marks a meaningful turning point: after establishing a rock-solid foundation across our runtime, compiler, CFML compatibility layer, and module ecosystem, BoxLang has entered its innovation cycle. The language is mature, battle-tested, and production-deployed by many custmoers now.

Now we're accelerating forward with the kinds of expressive, modern features that make writing BoxLang genuinely enjoyable.

This release packs a lot. On the language side, you get full **struct and array destructuring**, **spread syntax** in both literals and function calls, and a new and clean `..` **range operator** — features you'd expect from any contemporary dynamic language, now native to BoxLang.

The new **directory and file watcher** framework brings real-time filesystem monitoring with a flexible listener API that integrates directly with any `Application.bx`, globally or a-la-carte. **Custom `assert` messages**, a new `getLocalHostIp()` BIF, and **pluggable `getSystemSetting()` namespace providers** round out the feature set. There are also meaningful performance wins: a new method-handle caching strategy, a Java CDS shared archive for faster cold starts, and improved struct iteration.

Under the hood, 1.12.0 continues to sharpen the parser, broaden JDBC coverage, improve compat fidelity, and squash a long list of bugs across locales, date formatting, Oracle stored procedures, symlinks, and more.

If you haven't yet adopted BoxLang for production work, 1.12.0 is an excellent release to do so. The platform is stable, the community is growing, and the language is only getting better from here.

## 🚀 Major Highlights

### 🧩 Struct and Array Destructuring

BoxLang now supports destructuring for both structs and arrays.

If you're new to the term, destructuring is a concise way to pull values out of a struct or array and assign them to variables in a single step, instead of reading each value manually one at a time.

```js
user = { name: "Luis", role: "admin", meta: { team: "core" } }
({ name, meta: { team } } = user)

numbers = [ 1, 2, 3, 4, 5 ]
[ first, ...middle, last ] = numbers
```

In this example, the struct assignment pulls `name` from `user.name` and `team` from the nested `user.meta.team`. The array assignment pulls out the first and last values, while `...middle` captures everything in between as an array. In both cases, destructuring lets you unpack data in one statement instead of writing several separate assignments.

Highlights:

* Struct destructuring with shorthand and explicit renaming
* Array destructuring with positional bindings
* Nested destructuring patterns
* Default values
* Rest capture
* Scoped assignment targets when explicitly named
* Middle-rest support for arrays

### 🌟 Spread Syntax

BoxLang now supports spread syntax in literals and function calls.

If you're new to the term, spread syntax lets you take the contents of an existing array or struct and expand them into a function call or into a new array or struct literal.

```js
args = [ 1, 2, 3 ]
result = add( ...args )

base = { retries: 2 }
config = { host, ...base, debug: true }

merged = [ 0, ...left, ...right ]
```

In this example, `...args` expands the array so its values are passed to `add()` as normal arguments. In the struct literal, `...base` copies the existing keys from `base` into `config` alongside the other values you declare. In the array literal, `...left` and `...right` insert the contents of those arrays into `merged` instead of nesting them. Spread syntax is essentially a way to expand an existing collection into a new call or literal.

Highlights:

* Function-call spread for arrays and structs
* Array literal spread
* Unordered and ordered struct literal spread
* Multi-source merging with declaration-order precedence
* Struct shorthand keys like `{ host, port }`

### 🔢 Inclusive Range Operator

BoxLang now supports the inclusive `..` range operator.

If you're new to the term, a range operator is a compact way to describe a sequence of values between two endpoints. In BoxLang, `..` creates an inclusive integer range, which means it includes both the starting and ending numbers.

```js
1..5   // [1,2,3,4,5]
5..1   // [5,4,3,2,1]
a..b   // endpoints resolved at runtime
```

In this example, `1..5` produces all integers from 1 through 5, while `5..1` produces the same kind of range in descending order. `a..b` means the start and end values can come from variables or expressions, so the final range is determined at runtime based on whatever `a` and `b` contain.

This gives BoxLang a compact way to generate ascending or descending integer ranges.

### 👀 Directory and File Watchers

BoxLang now ships with a built-in `WatcherService` for monitoring directories and reacting to filesystem events in real time. Watchers support multiple listener patterns and integrate cleanly with the application framework.

```js
// Closure listener — quick and simple
watcher = watcherNew(
    name = "sourceWatcher",
    paths = [ "./src" ],
    listener = ( event ) => println( "[#event.kind#] #event.relativePath#" ),
    recursive = true,
    debounce = 250
).start()

// Class instance listener — full control
watcher = watcherNew(
    name = "hotReload",
    paths = [ "./src" ],
    listener = new app.listeners.HotReloadListener(),
    debounce = 300,
    atomicWrites = true
).start()
```

Four listener patterns are supported:

* **Closure** — single function receives all events
* **Struct of closures** — per-kind handlers (`onCreate`, `onModify`, `onDelete`, `onOverflow`) plus optional `onEvent` catch-all
* **Class name string** — runtime instantiates and calls the class
* **Class instance** — a pre-created BoxLang class instance is reused across all events

Application-scoped watchers can also be declared directly in `Application.bx` via `this.watchers` and are auto-started-stopped with the application life-cycle.

```js
// Application.bx
this.watchers = {
    sourceWatcher : {
        paths : [ expandPath( "./src" ) ],
        listener : new app.listeners.HotReloadListener(),
        recursive : true,
        debounce : 250
    }
}
```

Watchers can also be configured globally in `boxlang.json` under the `watchers` key, allowing you to establish file system monitoring at runtime startup without requiring an `Application.bx`.

**New Watcher BIFs:**

| BIF                    | Purpose                                                                      |
| ---------------------- | ---------------------------------------------------------------------------- |
| `watcherNew()`         | Create and register a new filesystem watcher without starting it             |
| `watcherStart()`       | Start a registered filesystem watcher by name                                |
| `watcherStop()`        | Stop a running filesystem watcher by name                                    |
| `watcherRestart()`     | Restart a filesystem watcher by name (stop then start)                       |
| `watcherShutdown()`    | Stop and unregister a filesystem watcher by name                             |
| `watcherStopAll()`     | Stop all running filesystem watchers without removing them from the registry |
| `watcherShutdownAll()` | Stop all filesystem watchers and remove them from the registry               |
| `watcherList()`        | Return an array of all registered filesystem watcher names                   |
| `watcherExists()`      | Check whether a filesystem watcher with the given name is registered         |
| `watcherGet()`         | Get a registered filesystem watcher by name                                  |
| `watcherGetAll()`      | Return all registered filesystem watchers as a struct keyed by name          |

See [Directory + File Watchers](https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/directory-file-watchers) for the full API, BIF reference, listener contracts, and global `boxlang.json` configuration examples.

### ✅ Custom `assert` Messages

The `assert` statement now accepts a custom message after a colon, so you can provide more context when an assertion fails. This message is included in the thrown `AssertException` if the assertion condition evaluates to falsy.

```js
assert user != null : "User must be provided before processing"
assert user.isActive() : "Cannot process an inactive user: #user.name#"
```

### 🌍 `getLocalHostIp()` BIF

A new `getLocalHostIp()` BIF returns the resolved IP address(es) of the local machine:

```js
ip = getLocalHostIp()
println( "Running on: #ip#" )  // e.g. 192.168.1.100
```

### ⚙️ `getSystemSetting()` Pluggable Namespace Providers

`getSystemSetting()` now supports pluggable namespace providers, enabling modules to register custom namespaces for system-setting resolution. Provider lookup is chained so the first match wins.

```js
// Built-in env and Java system-property namespaces still work
dbUrl = getSystemSetting( "DB_URL" )

// Module-registered providers add new namespaces
secret = getSystemSetting( "vault:myapp/db_password" )
```

### 🛑 Custom Error Pages (Web Runtime)

Web applications can now configure custom error pages through `Application.bx` or the runtime configuration, replacing the default BoxLang error output with branded or user-friendly HTML.

### 🔄 CFC Script Wrappers

CFML compatibility now includes script wrapper support for CFC tags, ensuring that ColdFusion CFC patterns compile and execute correctly inside BoxLang.

### 📝 Arbitrary Java Logging Category Redirection

Any Java logging category (e.g., third-party library loggers) can now be redirected to a specific BoxLang logger via `boxlang.json` configuration, giving you full control over noisy library output. This is invaluable when integrating third-party dependencies that produce verbose or unwanted log noise.

```json
{
  "logging": {
    "loggers": {
      "thirdparty": {
        "level": "WARN",
        "appender": "file",
        "additive": false,
        "categories": [
          "org.hibernate",
          "com.zaxxer.hikari",
          "org.springframework"
        ]
      }
    }
  }
}
```

You can also silence a category completely by routing it to a logger configured with `"level": "OFF"`.

## ✨ New Features

### Destructuring declarations and assignments

Struct destructuring supports:

* shorthand bindings
* explicit renaming
* nested patterns
* missing-key defaults
* rest capture for remaining keys
* scoped targets in direct assignments

Array destructuring supports:

* positional bindings
* nested patterns
* rest bindings
* middle-rest extraction
* predictable short-array behavior
* defaults for missing or `null` values

### Spread in function arguments

Function-call spread is supported in both BoxLang and CFML grammars.

Arrays expand as positional arguments.\
Structs expand as named arguments.

This works with:

* built-in functions
* member methods
* closures
* lambdas
* returned function expressions

### Literal spread and shorthand keys

Spread syntax is now supported in these literal forms:

* Array literals like `[ 1, ...values, 4 ]`
* Unordered struct literals like `{ ...defaults, debug: true }`
* Ordered struct literals like `[ ...defaults, retries: 5 ]`

Struct literals also support shorthand keys.

```js
fooBar = "value"
result = { fooBar }
```

### Ambiguous spread-only bracket literals

Spread-only bracket literals are resolved at runtime:

* array sources resolve to arrays
* struct sources resolve to ordered structs
* mixed source types are rejected

{% hint style="warning" %}
Spread-only bracket literal resolution is an experimental edge case. Use explicit keyed members when you want to force ordered-struct syntax.
{% endhint %}

## 🛠️ Improvements

### Performance Improvements

1.12.0 ships a new strategy for method handle caching (BL-2304, BL-2306) that ties caches to their owning class loader, dramatically reducing cache pollution across application reloads. `PagePoolClear` now also flushes these method handle caches (BL-2303), and `entrySet()` on structs has been made more efficient (BL-2282).

A Java Shared Class Data Archive (CDS/SAR) is now generated during install, allowing the native binary to skip class verification on startup and shave significant time off cold starts (BL-2299).

### JDBC Improvements

* **Nested transactions** are now Lucee-compatible — inner transactions use savepoints instead of silently rolling back (BL-2271)
* **Isolation levels** are validated on inner transactions to prevent unsupported combinations (BL-2289)
* **`JDBCStore`** datasource lookup and `Long`→`Integer` cast issues on MySQL are fixed (BL-2265, BL-2268)
* **Inline datasource structs** are now accepted in all locations that previously required a named string (BL-2311)

### `bx:execute` / `systemExecute()` Improvements

Exit codes are now exposed as a return value and via `exitCodeVariable` on both the component and BIF (BL-2262). A blocking deadlock that occurred when the `err` stream buffer filled up has also been resolved (BL-2263).

### Locale and Date Format Fixes

Several long-standing locale and date-format issues have been resolved:

* `monthAsString()` now correctly honours the `locale` argument (BL-2259)
* `DateFormat` methods no longer apply the wrong locale when only a language string is supplied (BL-2260)
* European `dd.MM.yyyy` dates parse correctly (BL-2264)
* `datetimeFormat()` in compat now accepts both `t` and `T` ISO separators and passes through non-mask characters as literals (BL-2322, BL-2323)
* Compat `TimeFormat` `mm` mask behaviour corrected (BL-2295)

## 📚 Documentation

See the new and updated language pages for full syntax and examples:

* [Destructuring](https://boxlang.ortusbooks.com/boxlang-language/syntax/destructuring)
* [Spread Syntax](https://boxlang.ortusbooks.com/boxlang-language/syntax/spread-syntax)
* [Operators](https://boxlang.ortusbooks.com/boxlang-language/syntax/operators)
* [Arrays](https://boxlang.ortusbooks.com/boxlang-language/syntax/arrays)
* [Structures](https://boxlang.ortusbooks.com/boxlang-language/syntax/structures)
* [Directory + File Watchers](https://boxlang.ortusbooks.com/boxlang-framework/asynchronous-programming/directory-file-watchers)
* [Application.bx](https://boxlang.ortusbooks.com/boxlang-framework/applicationbx)

***

## 🎶 Release Notes

### Bugs

[BL-2219](https://ortussolutions.atlassian.net/browse/BL-2219) Application updates made with \`bx:application\` do not persist to subsequent requests

[BL-2239](https://ortussolutions.atlassian.net/browse/BL-2239) template case statement is checking delimiter instead of delimiters

[BL-2259](https://ortussolutions.atlassian.net/browse/BL-2259) MonthAsString Not honoring locale argument correctly

[BL-2260](https://ortussolutions.atlassian.net/browse/BL-2260) Incorrect Locale Being Applied on DateFormat Methods When only only lang string is supplied.

[BL-2261](https://ortussolutions.atlassian.net/browse/BL-2261) bx:execute not setting the \`errorVariable\` correctly

[BL-2263](https://ortussolutions.atlassian.net/browse/BL-2263) systemExecute() can hang when err out buffer is full

[BL-2264](https://ortussolutions.atlassian.net/browse/BL-2264) European dd.MM.yyyy format date parses incorrectly

[BL-2265](https://ortussolutions.atlassian.net/browse/BL-2265) Datasource not found error creating JDBCStore cache

[BL-2266](https://ortussolutions.atlassian.net/browse/BL-2266) Compat: Improve Parsing Performance with Explicit Mask

[BL-2268](https://ortussolutions.atlassian.net/browse/BL-2268) JDBCStore throws Long cannot be cast to class java.lang.Integer on mysql datasources

[BL-2272](https://ortussolutions.atlassian.net/browse/BL-2272) improve fall back serializer for JSON

[BL-2274](https://ortussolutions.atlassian.net/browse/BL-2274) miniserver doesn't follow symlinks

[BL-2278](https://ortussolutions.atlassian.net/browse/BL-2278) parse error on #foo\[1]# neq bar

[BL-2279](https://ortussolutions.atlassian.net/browse/BL-2279) Error calling Oracle proc with proc result after positional args

[BL-2285](https://ortussolutions.atlassian.net/browse/BL-2285) lexer doesn't match non-breaking spaces as whitespace

[BL-2287](https://ortussolutions.atlassian.net/browse/BL-2287) CF truncates startRow and endRow attributes to cfloop

[BL-2288](https://ortussolutions.atlassian.net/browse/BL-2288) content component doesn't handle spreadsheet object passed as binary variable

[BL-2293](https://ortussolutions.atlassian.net/browse/BL-2293) Declare UDF inside of catch block

[BL-2295](https://ortussolutions.atlassian.net/browse/BL-2295) Compat: TimeFormat mm mask behaviour

[BL-2298](https://ortussolutions.atlassian.net/browse/BL-2298) Calling static method from another static method within a closure throws a not found exception

[BL-2301](https://ortussolutions.atlassian.net/browse/BL-2301) CF compat allow invalid cflock types

[BL-2302](https://ortussolutions.atlassian.net/browse/BL-2302) Put actual path in server.coldfusion.rootdir

[BL-2308](https://ortussolutions.atlassian.net/browse/BL-2308) QoQ allow table to have more than one dot

[BL-2312](https://ortussolutions.atlassian.net/browse/BL-2312) XOR operator precedence

[BL-2314](https://ortussolutions.atlassian.net/browse/BL-2314) mis-identifying tag component with comment at end of line

[BL-2315](https://ortussolutions.atlassian.net/browse/BL-2315) queryNew - Unknown Column Type "long"

[BL-2317](https://ortussolutions.atlassian.net/browse/BL-2317) parse error with text operator before decimal

[BL-2318](https://ortussolutions.atlassian.net/browse/BL-2318) inout refcursor params don't work on Oracle stored procs

[BL-2321](https://ortussolutions.atlassian.net/browse/BL-2321) fileWrite() should accept file object, not just a string

[BL-2322](https://ortussolutions.atlassian.net/browse/BL-2322) datetimeFormat() in compat allows t but not T

[BL-2323](https://ortussolutions.atlassian.net/browse/BL-2323) datetime format should passthru non-mask chars

[BL-2325](https://ortussolutions.atlassian.net/browse/BL-2325) directoryList() errors on invalid symlinks in listing

[BL-2329](https://ortussolutions.atlassian.net/browse/BL-2329) Adobe compat

[BL-2330](https://ortussolutions.atlassian.net/browse/BL-2330) CF compat preserveSingleQuotes() not required for UDF calls

### Improvements

[BL-417](https://ortussolutions.atlassian.net/browse/BL-417) Directory / File Watchers

[BL-2220](https://ortussolutions.atlassian.net/browse/BL-2220) Add a custom message to an assert expression statement: assert expression : message

[BL-2252](https://ortussolutions.atlassian.net/browse/BL-2252) Add cfhtmlbody tag for cfml compatibility

[BL-2262](https://ortussolutions.atlassian.net/browse/BL-2262) Allow retrieving exit code in execute component and systemExecute() BIF

[BL-2271](https://ortussolutions.atlassian.net/browse/BL-2271) \[JDBC] Nested transactions are not Lucee-compatible

[BL-2289](https://ortussolutions.atlassian.net/browse/BL-2289) JDBC - Validate isolation levels on inner transactions

[BL-2296](https://ortussolutions.atlassian.net/browse/BL-2296) Return early from isDate for blank strings

[BL-2299](https://ortussolutions.atlassian.net/browse/BL-2299) Introduce the usage and creation of a Java shared archive to improve native binary startups

[BL-2303](https://ortussolutions.atlassian.net/browse/BL-2303) PagePoolClear now clears the method handle caches as well.

[BL-2304](https://ortussolutions.atlassian.net/browse/BL-2304) New approach to caching method handles by leveraging class loader location and unique cachekey resolution class

[BL-2309](https://ortussolutions.atlassian.net/browse/BL-2309) Add missing arrayNew() features needed for compat

[BL-2310](https://ortussolutions.atlassian.net/browse/BL-2310) Allow arbitrary Java logging categories to be redirected to the pre-defined BoxLang loggers

[BL-2311](https://ortussolutions.atlassian.net/browse/BL-2311) Allow datasource as struct in all places

[BL-2313](https://ortussolutions.atlassian.net/browse/BL-2313) repeatString() truncate decimal count

[BL-2319](https://ortussolutions.atlassian.net/browse/BL-2319) Allow compat's onServerStart to run inside of request context

[BL-2320](https://ortussolutions.atlassian.net/browse/BL-2320) getter on runtime to see config path used to load runtime

[BL-2326](https://ortussolutions.atlassian.net/browse/BL-2326) Refactor Config Load to only Merge Environment Overrides Once

[BL-2332](https://ortussolutions.atlassian.net/browse/BL-2332) create dump template for BoxFile

### New Features

[BL-310](https://ortussolutions.atlassian.net/browse/BL-310) Spread operator

[BL-311](https://ortussolutions.atlassian.net/browse/BL-311) Implement Array Destructuring assignment

[BL-312](https://ortussolutions.atlassian.net/browse/BL-312) Implement Struct Destructuring assignment

[BL-2275](https://ortussolutions.atlassian.net/browse/BL-2275) getSystemSetting() pluggable namespace provider support

[BL-2276](https://ortussolutions.atlassian.net/browse/BL-2276) range operator

[BL-2280](https://ortussolutions.atlassian.net/browse/BL-2280) Allow custom error pages used in web runtime

[BL-2282](https://ortussolutions.atlassian.net/browse/BL-2282) Improve performance of entrySet() on structs

[BL-2300](https://ortussolutions.atlassian.net/browse/BL-2300) getLocalHostIp() bif

[BL-2306](https://ortussolutions.atlassian.net/browse/BL-2306) Move method handle caching into related class loader

[BL-2307](https://ortussolutions.atlassian.net/browse/BL-2307) Add new types to systemCacheClear()

### Story

[BL-1827](https://ortussolutions.atlassian.net/browse/BL-1827) CFC script wrappers
