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):
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
&= in-place append for buildersCompound 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:
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:
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)
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)
isRange() BIF (BL-2507)A dedicated BIF to test whether a value is a BoxLang Range instance:
threadCurrent() BIF (BL-2513)
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)
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)
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( 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-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 — Expression Interpreter Safe Get — The expression interpreter no longer throws when performing a safe navigation expression that fails to resolve a key; it returns
nullinstead:
BL-2543 —
nullis falsey but not boolean —isBoolean( null )now correctly returnsfalse.nullis falsey but that does not make it a boolean value:
BL-2500 — Struct keys capped in error messages —
KeyNotFoundExceptionerror messages now show a capped list of available keys to avoid flooding logs on large structs.BL-2501 — Range empty-set semantics — Paradoxical ranges (e.g.,
5..3with a positive step) now produce zero elements rather than throwing:
BL-2512 — Formatter
arguments.separatorconfig — The formatter now accepts anarguments.separatorconfig key (default" = ") controlling spacing around named argument separators:
BL-2514 — Session cookie only set when session management is enabled — The web runtime no longer emits a
BXSESSIONIDcookie for applications that have session management disabled.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 — File upload copies rather than moves —
fileUploadnow copies the temporary upload file to the destination, preventing failures when temp and destination paths cross filesystem mount boundaries.BL-2510 — Stored proc
OUTparams respectnullEqualsEmptyString— Stored procedure output parameters now honor the CFML compat flag, returning""instead ofnullwhere appropriate.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 — Adobe CF
returnFormatdrivesContent-Type— Remote methods withreturnFormat="json"now correctly setContent-Type: application/jsonin the response.BL-2493 — Set dump template handles Java sets — The HTML dump template for
BoxSetnow also renders nativejava.util.Setinstances correctly.
Security
BL-2515 — Web runtime blocks requests with null bytes — Any HTTP request whose URL contains a null byte (
%00) is rejected with400 Bad Requestbefore 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
interfacebodies 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-2492 —
int,long, andshortcasters 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
extendskeyword in class definitions.BL-2529 — Formatter was converting CFML component invocations (angle bracket style) into BoxLang
bx:componentstyle inside.cfcfiles. 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-2530 —
decimalFormat( "" )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-2545 —
dateCompare()now correctly handles"hours"as a date-part argument (previously only"hour"singular was recognized):
String BIFs
BL-2536 —
replace()threw aNullPointerExceptionwhen the input string wasnull; it now treatsnullas 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-safeConcurrentHashMap.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
Datevalue 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?
