For the complete documentation index, see llms.txt. This page is also available as Markdown.

1.15.0

July 9, 2026

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:

// 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:

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:

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

Optimization 1 — &= in-place append for builders

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

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:

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:

Mixed expressions are partially folded:

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:

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:

For a complete guide to BoxStringBuilder including practical patterns and benchmarks, see StringBuilder in the Syntax & Semantics section.

For the full breakdown of what shipped, see the community post: StringBuilder in BoxLang: Fast Concatenation, Better &=, and Compile-Time Folding.

🚀 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.

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.

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.

✨ 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:

isRange() BIF (BL-2507)

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

threadCurrent() BIF (BL-2513)

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

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:

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:

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:

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:

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:

🔧 Improvements

Language & Runtime

  • BL-2538QoQ Performance Improvements — Query of Queries now executes significantly faster for large datasets through internal optimizations to the filtering, sorting, and aggregation pipeline.

  • BL-2539Expression 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:

  • BL-2543null is falsey but not booleanisBoolean( null ) now correctly returns false. null is falsey but that does not make it a boolean value:

  • BL-2500Struct keys capped in error messagesKeyNotFoundException error messages now show a capped list of available keys to avoid flooding logs on large structs.

  • BL-2501Range empty-set semantics — Paradoxical ranges (e.g., 5..3 with a positive step) now produce zero elements rather than throwing:

  • BL-2512Formatter arguments.separator config — The formatter now accepts an arguments.separator config key (default " = ") controlling spacing around named argument separators:

  • BL-2514Session 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-2508No 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-2509File upload copies rather than movesfileUpload now copies the temporary upload file to the destination, preventing failures when temp and destination paths cross filesystem mount boundaries.

  • BL-2510Stored proc OUT params respect nullEqualsEmptyString — Stored procedure output parameters now honor the CFML compat flag, returning "" instead of null where appropriate.

  • BL-2497CFML compat: string dates as numbers — In CFML compatibility mode, string representations of dates can now participate in numeric operations, matching Adobe ColdFusion behavior.

  • BL-2548Adobe CF returnFormat drives Content-Type — Remote methods with returnFormat="json" now correctly set Content-Type: application/json in the response.

  • BL-2493Set dump template handles Java sets — The HTML dump template for BoxSet now also renders native java.util.Set instances correctly.

Security

  • BL-2515Web 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 — Class constructor syntax using new ClassName() on imported or locally-declared classes was not being recognized correctly; fixed.

  • BL-2537 — Closures declared inside BoxLang interface bodies failed to compile in the Java bytecode compiler; they now compile and execute correctly.

  • 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 — 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-2492int, long, and short casters previously silently accepted values outside their representable range. They now validate the input and throw a meaningful cast exception:

Formatter

  • BL-2496 — Formatter threw an error on variable assignment statements with no initializer (e.g., var x); these are now formatted correctly.

  • BL-2528 — Formatter was incorrectly inserting two extra spaces before the extends keyword in class definitions.

  • 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:

CFML Compatibility

  • BL-2495 — Query filter regression: when the filter value was empty, the CFML compat layer was incorrectly filtering out non-empty rows.

  • BL-2530decimalFormat( "" ) was throwing a cast exception; it now returns "0.00" matching Adobe CF behavior:

  • 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<cfapplication action="update" datasource="..."> was silently ignoring the datasource change; the runtime now correctly applies the update.

  • BL-2535 — When onWebExecutorRequest() changed the request URL, file extension handling was applied to the original URL rather than the rewritten one.

  • BL-2544 — Default JSON serialization format was inconsistent between calls on the same data; the serialization path is now deterministic.

  • BL-2545dateCompare() now correctly handles "hours" as a date-part argument (previously only "hour" singular was recognized):

String BIFs

  • BL-2536replace() threw a NullPointerException when the input string was null; it now treats null as an empty string:

Runtime & Security

  • 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 — 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 — 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

Last updated

Was this helpful?