Blog

Luis Majano

April 08, 2026

Spread the word


Share your thoughts

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

  • JDBCStore datasource lookup and Longβ†’Integer cast 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 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)

πŸ› Notable Bug Fixes

TicketSummary
BL-2219bx:application updates did not persist to subsequent requests
BL-2263systemExecute() could hang when err output buffer filled up
BL-2268JDBCStore threw Long cannot be cast to Integer on MySQL
BL-2274MiniServer didn't follow symlinks
BL-2279Error calling Oracle stored proc with result set after positional args
BL-2285Lexer didn't match non-breaking spaces as whitespace
BL-2293Declaring a UDF inside a catch block caused a compile error
BL-2298Calling a static method from another static method inside a closure threw "not found"
BL-2312XOR operator had incorrect precedence
BL-2318INOUT ref cursor params didn't work in Oracle stored procedures
BL-2325directoryList() 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

Recent Entries

The Loneliness of CTO Leadership: How to Make Important Decisions with Confidence

The Loneliness of CTO Leadership: How to Make Important Decisions with Confidence

Being a CTO can be surprisingly isolated.

Not because of the title, but because of the decisions.

Every day, you’re expected to make calls that impact system stability, performance, security, team productivity, and long term costs. And often, you’re making those decisions with incomplete information, limited resources, and no real sounding board.

That’s where the real challenge begins.


It’s Not a Talent Problem, It’s a Context Problem

Most CTOs don’...

Cristobal Escobar
Cristobal Escobar
April 07, 2026
How to Develop AI Agents Using BoxLang AI: A Practical Guide

How to Develop AI Agents Using BoxLang AI: A Practical Guide

AI agents are transforming how we build software. Unlike traditional chatbots that just answer questions, agents can reason about what tools they need, decide when to use them, chain multiple actions together, and remember what happened earlier in a conversation.

Luis Majano
Luis Majano
April 03, 2026