BoxLang 1.12.0 marks a meaningful turning point. After establishing a rock-solid foundation across runtime, compiler, CFML compatibility, and the module ecosystem, BoxLang has entered its innovation cycle. The language is maturing, battle-tested, and production-deployed across the industry now.
Now we're accelerating forward with the kinds of expressive, modern features that make writing BoxLang genuinely enjoyable. You can find the full release notes here: https://boxlang.ortusbooks.com/readme/release-history/1.12.0
This release packs meaningful improvements across the board. On the language side, you get full struct and array destructuring, spread syntax in both literals and function calls, and a new, 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 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.
Let's dig in.
π§© Struct and Array Destructuring
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.
user = { name: "Luis", role: "admin", meta: { team: "core" } }
({ name, meta: { team } } = user)
println( name ) // Luis
println( team ) // core
numbers = [ 1, 2, 3, 4, 5 ]
[ first, ...middle, last ] = numbers
println( first ) // 1
println( middle ) // [2,3,4]
println( last ) // 5
Struct destructuring supports shorthand bindings, explicit renaming, nested patterns, missing-key defaults, and rest capture for remaining keys. Array destructuring supports positional bindings, nested patterns, rest bindings, middle-rest extraction, and defaults for missing or null values.
Scoped targets are also supported in direct assignments:
({ variables.name, variables.role } = userStruct)
Non-declaration struct destructuring assignments must be wrapped in parentheses to avoid parser ambiguity.
π Spread Syntax
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.
args = [ 1, 2, 3 ]
result = add( ...args )
base = { retries: 2, timeout: 30 }
config = { host: "localhost", ...base, debug: true }
merged = [ 0, ...left, ...right, 99 ]
In the first 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 also works with shorthand keys:
host = "localhost"
port = 8080
config = { host, port } // equivalent to { host: host, port: port }
Struct literal merge precedence is last write wins, so later values override earlier ones:
defaults = { debug: false, retries: 3 }
result = { ...defaults, debug: true } // debug is true, retries is 3
π’ Inclusive Range Operator
BoxLang now supports the inclusive .. range operator for generating integer ranges.
1..5 // [1,2,3,4,5]
5..1 // [5,4,3,2,1]
start..end // endpoints resolved at runtime
This is clean syntactic sugar for the common case of needing a sequence of integers. It works in both ascending and descending directions and resolves endpoints at runtime.
π 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.
Closure Listener
Quick and simple for straightforward use cases:
watcher = watcherNew(
name = "sourceWatcher",
paths = [ "./src" ],
listener = ( event ) => println( "[#event.kind#] #event.relativePath#" ),
recursive = true,
debounce = 250
).start()
Struct of Closures
Per-kind handlers with optional catch-all:
watcher = watcherNew(
name = "hotReload",
paths = [ "./src" ],
listener = {
onCreate = ( event ) => println( "Created: #event.relativePath#" ),
onModify = ( event ) => recompileModule( event.path ),
onDelete = ( event ) => println( "Deleted: #event.relativePath#" ),
onEvent = ( event ) => logEvent( event ) // Optional catch-all
},
recursive = true,
debounce = 250
).start()
Class Instance Listener
Full control with a reusable class instance:
watcher = watcherNew(
name = "fileWatcher",
paths = [ expandPath( "./config" ) ],
listener = new app.listeners.ConfigReloadListener(),
debounce = 300,
atomicWrites = true
).start()
Application-Scoped Watchers
Declare watchers directly in Application.bx and they're auto-started with the app:
// Application.bx
this.watchers = {
sourceWatcher : {
paths : [ expandPath( "./src" ) ],
listener : new app.listeners.HotReloadListener(),
recursive : true,
debounce : 250
},
configWatcher : {
paths : [ expandPath( "./config" ) ],
listener : ( event ) => reloadConfig( event ),
debounce : 500
}
}
The watcher framework is perfect for hot-reload systems, build tools, configuration watchers, and asset processors.
β
Custom assert Messages
The assert statement now accepts a custom message after a colon, matching Java assertion syntax:
assert user != null : "User must be provided before processing"
assert user.isActive() : "Cannot process an inactive user: #user.name#"
If the expression is falsy, the message is included in the thrown AssertException. This makes assertions far more actionable in production diagnostics.
π getLocalHostIp() BIF
A new getLocalHostIp() BIF returns the resolved IP address of the local machine:
ip = getLocalHostIp()
println( "Running on: #ip#" ) // e.g. 192.168.1.100
Useful for distributed systems, clustering, and health-check endpoints.
βοΈ 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.
// 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" )
apiKey = getSystemSetting( "secrets:api_key" )
This enables modules like bx-vault to integrate seamlessly into the getSystemSetting() call stack without requiring the calling code to change.
π Performance Improvements
Method Handle Caching
A new strategy ties method-handle caches to their owning class loader, dramatically reducing cache pollution across application reloads. PagePoolClear now also flushes these caches, and entrySet() on structs has been optimized for better performance (BL-2282). This is a considerable boost across the language.
Java Shared Class Data Archive
A Java CDS (Class Data Sharing) archive is now generated during install. The native binary skips class verification on startup, shaving significant time off cold starts (BL-2299). This will give your operating system and miniserver startups a boost of 30-50% in cold start startups.
Struct Iteration
Improved performance when iterating over struct entries.
π οΈ JDBC and Database Improvements
Nested Transactions (Lucee Compatible)
Inner transactions now use savepoints instead of silently rolling back
transaction {
insertRecord( data )
transaction { // Inner transaction uses a savepoint
updateRecord( id, newData )
}
}
Isolation Level Validation
Isolation levels are validated on inner transactions to prevent unsupported combinations (BL-2289).
Datasource Improvements
JDBCStoredatasource lookup andLongβIntegercast issues on MySQL are fixed- Inline datasource structs are now accepted in all locations that previously required a named string (BL-2311)
// Inline datasource definition now works everywhere
transaction datasource={ driver: "mysql", url: "jdbc:mysql: //...", username: "root", password: "pass" } {
// Your SQL here
}
π‘ Execution and Logging
bx:execute / systemExecute() Exit Codes
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).
Arbitrary Java Category Redirection
Any Java logging category (e.g., third-party library loggers) can now be redirected to a pre-defined BoxLang logger via boxlang.json configuration, giving full control over noisy library output (BL-2310).
π Locale and Date Format Fixes
Several long-standing locale and date-format issues have been resolved:
monthAsString()now correctly honours thelocaleargument (BL-2259)DateFormatmethods no longer apply the wrong locale when only a language string is supplied (BL-2260)- European
dd.MM.yyyydates parse correctly (BL-2264) datetimeFormat()in compat now accepts bothtandTISO separators and passes through non-mask characters as literals (BL-2322, BL-2323)- Compat
TimeFormatmmmask behaviour corrected (BL-2295)
π Notable Bug Fixes
| Ticket | Summary |
|---|---|
| BL-2219 | bx:application updates did not persist to subsequent requests |
| BL-2263 | systemExecute() could hang when err output buffer filled up |
| BL-2268 | JDBCStore threw Long cannot be cast to Integer on MySQL |
| BL-2274 | MiniServer didn't follow symlinks |
| BL-2279 | Error calling Oracle stored proc with result set after positional args |
| BL-2285 | Lexer didn't match non-breaking spaces as whitespace |
| BL-2293 | Declaring a UDF inside a catch block caused a compile error |
| BL-2298 | Calling a static method from another static method inside a closure threw "not found" |
| BL-2312 | XOR operator had incorrect precedence |
| BL-2318 | INOUT ref cursor params didn't work in Oracle stored procedures |
| BL-2325 | directoryList() errored on invalid symlinks in the listing |
π What This Means
BoxLang 1.12.0 is a statement of intent: the platform is stable, the compiler is mature, and the language is now moving into the features that make development genuinely enjoyable.
Destructuring and spread syntax remove boilerplate. Watchers enable real-time application patterns. Better error messages and assertion support make debugging faster. And the performance wins mean your apps get faster with zero code changes.
If you haven't yet adopted BoxLang for production work, 1.12.0 is an excellent release to do so. The platform is battle-tested, the community is growing, and the language is only getting better from here.
Get started at boxlang.io.
π€ Support Professional Open Source
BoxLang is professional open source. The core is free and always will be, but we've also built BoxLang+ and BoxLang++ plans that unlock exclusive modules, priority support, and advanced enterprise features. Your subscription directly funds the team that builds BoxLang, ensuring the language keeps evolving at this pace.
With BoxLang+, you get access to premium modules like bx-spreadsheet, bx-pdf, bx-redis, bx-couchbase, and advanced AI integrations via bx-ai. With **BoxLang++, **add priority support, security audits, and custom module development.
Whether you're using BoxLang in a hobby project, a startup, or enterprise production, there's a plan that works for you. Explore the options at boxlang.io/plans and help us keep building the best modern dynamic language on the JVM.
Add Your Comment