Blog

Luis Majano

November 05, 2026

Spread the word


Share your thoughts

Ortus Solutions today announces the release of BoxLang 1.19.0, the software productivity platform for building, modernizing and running applications, with developers and AI agents working together. This release is about observability: built-in code coverage and execution profiling, the missing post-invocation events for BIFs, and new query and transaction events, and it pairs with the new BxLens beta, the debug bar and ops console that exposes hotspots and bytecode profilers. It also adds your own global settings in boxlang.json, static method interception, raw binary HTTP streaming and a lot of hardening across JDBC, Java interop and CFML compatibility.

45 issues landed: 6 new features, 18 improvements, 20 bugs and 1 task. πŸš€

Why observability, and why now

Developers and AI agents share one problem: they can only fix what they can see. A human wants to know which code ran and where the time went. An agent building and verifying an app needs the same signals, in a form it can consume. 1.19 gives the runtime much better answers to "what just happened?" and keeps the cost at zero when you are not asking.

πŸ“ˆ Code coverage and execution profiling

BoxLang can now record which code ran, how often, and where the time went while your code executes, with no changes to that code.

  • Coverage. Per file, whether each executable span ran (covered or missed) and how many times.
  • Timing. Accumulated self time per span, so hot code is easy to find.
  • Fine grained. Branches, short-circuit operators, ternaries, function and closure bodies and class code are tracked independently, so you can see which branch actually ran.
  • Aggregation. Covered, executable and total counts, and a coverage percentage, across files.
  • Exports. Built-in export to JSON (per line or per span) and SonarQube generic coverage XML for CI, plus HTML reports for line coverage, spans and a span heatmap.
  • Off by default. Nothing is instrumented unless you turn it on.

Capture is a single flag:

{
	"codeProfilerEnabled": true
}

The collected data is exposed through the runtime's CodeProfilerService, which coverage reporters and CI exporters consume. BxLens uses the same engine for its Bytecode profiler, covered below.

πŸ”” BIF interception points, fixed

postBIFInvocation was declared but never announced, and onBIFInvocation fired twice per call. BIF invocation now mirrors user-defined functions:

PointWhenData
onBIFInvocationOnce, before the callcontext, arguments, bif and name
postBIFInvocationAfter a successful callThe same data plus result and elapsedNanos. Listeners may override result
onBIFExceptionNew. When the BIF throwsexception and elapsedNanos. The exception is rethrown unchanged

With no listeners registered there is no allocation and no clock read on the hot path. Interceptors announced without a context (BIF, query and transaction events) now also run on the current request when there is one, so they see its settings and no longer rebuild the runtime config on every read. See Life-cycle Events.

πŸ—„οΈ Query and transaction observability

  • onQueryExecuteError is announced when a query fails, with pendingQuery, context, exception and the elapsed executionTime in milliseconds. The original exception is always rethrown unchanged, even if a listener throws.
  • Cached results are visible. Results served from the query cache now announce preQueryExecute and postQueryExecute with cached=true and an executionTime of 0. preQueryExecute now includes dbtype and cached.
  • onTransactionReleaseSavepoint joins the transaction lifecycle events.
  • Better stored procedure logs. With debug=true, logs show which variables and types were created from which OUT parameter names.

Savepoint release

The new transactionReleaseSavepoint( name ) BIF and the releasesavepoint action of bx:transaction discard a savepoint you no longer need. Release is optional cleanup. It does not commit or roll back data, it frees driver resources in long transactions, and it lets you reuse savepoint names. Drivers that do not support release raise a DatabaseException.

transaction {
	for ( var item in batch ) {
		var recoveryPoint = "item_" & createUUID().replace( "-", "", "all" )
		transactionSetSavepoint( recoveryPoint )
		try {
			importItem( item )
		} catch ( any error ) {
			transactionRollback( recoveryPoint )
			recordImportFailure( item, error )
		}
		transactionReleaseSavepoint( recoveryPoint )
	}
}

Connection pool and abort fixes

  • JDBC pool leak fixed (BL-2763). Connections acquired inside a transaction for another datasource or with credential overrides, stored procedure calls that fail during setup, and connections still open when a request ends are now returned to the pool. A failure while closing one transaction or in one shutdown listener no longer stops the remaining cleanup.
  • Transactions roll back on abort. A database transaction is now rolled back when abort runs inside it, matching Adobe ColdFusion and Lucee.
  • Clearer datasource names (BL-2770). On-the-fly datasources created by overriding credentials are now named {datasource}_user_{username}_{hash}.
  • Nested database exceptions (BL-2743). A cause that was already set no longer makes the exception chain throw. It is stored as a suppressed exception and still shows in the stack trace.

πŸ”­ BxLens: the debug bar and ops console for BoxLang (beta)

The runtime now emits the signals. BxLens is how you see them. It is the debug bar and ops console for BoxLang, it needs BoxLang 1.18 or newer and Java 21, and it is currently in beta. Beyond the bar and the console, the beta exposes Hot spots and a Bytecode profiler, so you can go from "this request is slow" to "this is the exact expression that is slow." The Bytecode profiler is built on the code profiler that ships in BoxLang 1.19, and no other BoxLang tool shows the time of every line of your code like it.

BxLens has two surfaces, and both ship off by default:

  • The bar is a strip docked on every HTML page. It shows what that one request did: timeline, queries, HTTP calls, exceptions, timers and scopes.
  • The console is a password protected page for the whole server: requests, errors, queries, datasources, caches, logs, tasks, the JVM and threads.

The BxLens bar docked at the bottom of a page, showing the Timeline waterfall

The bar on a page: a waterfall of templates, queries and HTTP calls for one request.

The BxLens console overview

The console overview: the health of the server at a glance.

The BxLens Queries page

Queries: the slowest and the most repeated statements.

What you get:

  • A full waterfall for one request: templates, queries, HTTP calls and transactions, with repeated queries (N+1) and the line that ran them
  • Errors grouped into issues you can resolve or ignore
  • Outgoing HTTP calls by host and endpoint
  • The JVM: memory, garbage collection and deadlock detection
  • ColdBox debugging: events, views, routes, modules and findings
  • Hot spots and the Bytecode profiler (BoxLang+) for finding the slow expression
  • AI observability for agents, models, MCP servers, tokens and cost
  • Lensy, the assistant that answers in plain words and asks before it changes anything (BoxLang+)

BxLens is freeware with limits. The debug bar and the basics of the console are free for one server, in production too. BoxLang+ lifts the limits and adds history, alerts, many servers, AI observability and Lensy.

πŸ”₯ Hot spots: where did the time go?

A slow request has a time, and the question is where it went. While a request runs, BxLens looks at what it is doing about ten times a second and shows the result as one picture, widest first. The widest bar is where to start.

  • Your code is a template, function or class of your application, with the line.
  • Database is the statement that was running, with its values hidden.
  • Outgoing calls are the HTTP calls that were running, with host and path.
  • Waiting is a lock, a sleep or a wait for another thread.
  • BoxLang and server is the runtime and web server, hidden by default so their time goes to the code that called them.

The Hot spots picture of a slow request

Hot spots of one slow request: your code, the database, outgoing calls and waits, widest first.

You can open it three ways:

  • A finished request: every request that runs longer than one second gets a Hot spots section
  • A running request: pick it on the In flight page and watch the picture build live
  • A URL: merge the pictures of every run of a URL, across every server that shares the store

The merged Hot spots of a URL: the widest places, the time by kind and the slowest runs

The merged Hot spots of a URL, with the slowest runs underneath.

Rules let you look at more than the slow ones. Add a rule for a path and method, or press Watch this URL for 10 minutes, an hour or a day and the rule removes itself. You can also ask Lensy to read the picture, or share it by mail or to a chat channel.

The Hot spots rules in the BxLens settings

It is a sample, not a measurement, so trust the wide bars. Hot spots is off until you switch it on, pauses itself when the server CPU is high, and never looks at /health* or the console itself. It is a BoxLang+ feature. Read Hot spots and the Find and fix a slow page guide, or watch the walkthrough.

🧬 Bytecode profiler: every expression, counted and timed

Hot spots tells you which function and line took the time. The Bytecode profiler tells you which expression inside it, how many times it ran, and how long each run took, and paints it on your own source. It uses the code profiler built into BoxLang 1.19.

The Bytecode profiler: the source of a page with the time of every line and the hot expressions marked

A heatmap on your real source. The colour bar runs from red (most time) to blue (little), and hot expressions show how many times they ran.

  • Sessions. Record the whole server for 5 to 60 minutes, started by hand. A session always ends by itself, and only one runs at a time.
  • Rules. For a problem you cannot reproduce, add a rule: "when this request arrives, record 10 minutes."
  • Two views. Files shows the templates and classes that ran with their share of the time. Expressions shows the widest expressions, how many times they ran, and the time per run.
  • Jump from Hot spots. A row that is your own code has an Expressions button that opens the whole function with its heat.
  • Ask Lensy or share. Lensy reads the files and widest expressions and says what to change first. Share a session by mail or to a channel.

A Bytecode profiler session: the files that ran and the time in your code

A session: the files that ran and the time in your code.

Turn it on with the flag from the 1.19 profiling engine, set before the deploy so that templates compiled while it is on carry the counters, then switch on the collector in BxLens:

{
	"codeProfilerEnabled": true
}

A few honest notes. Recording covers the whole server, so pages run slower while a session runs. The times include a small cost for every expression, so use them as a ranking and trust the counts. Templates precompiled with boxlang compile had no source to paint on in the BxLens tests. Nothing is recorded outside a session, and it needs BoxLang 1.19 or newer. It is a BoxLang+ feature. Read the Bytecode profiler docs or watch the walkthrough.

Turn the bar on in boxlang.json:

{
	"modules": {
		"bxLens": {
			"settings": {
				"bar": { "enabled": true }
			}
		}
	}
}

Install it with the BoxLang binary or CommandBox:

# BoxLang binary
bx-module install bx-lens

# CommandBox
box install bx-lens

Watch the trailer:

More screenshots and nine narrated walkthroughs are in the BxLens gallery and walkthroughs. Want to try it on real problems? Run the BxLens demo app.

πŸŽ›οΈ Your own global settings: runtimeSettings

Store your own named values in boxlang.json and override them per application. BoxLang loads and serves the struct. It never interprets it.

{
	"runtimeSettings": {
		"apiUrl": "${Setting: env.API_URL:http: //localhost:8080 not found}",
		"stripeKey": "bxsecret:AbC123...",
		"features": { "beta": true, "regions": [ "us", "eu" ] }
	}
}
  • Any value type. Strings, numbers, booleans, structs and arrays.
  • Environment variables and secrets. ${Setting: env.VAR not found}, ${Setting: env.VAR:default not found} and bxsecret: values resolve at any depth, including inside arrays.
  • Application overrides. this.runtimeSettings in Application.bx is deep merged over the global values and the application wins by key. Arrays and scalars are replaced, not merged. The application layer re-resolves placeholders and secrets on every request.
  • Read only. Returned structs and arrays are deeply unmodifiable.
// Application.bx
class {

	this.name = "myApp"

	this.runtimeSettings = {
		features : { level : 7 }
	}

}

getRuntimeSetting( "features.level" )          // 7, from the application
getRuntimeSetting( "features.beta" )           // true, from boxlang.json
getRuntimeSetting( "features.regions.1" )      // "us", array indexes are 1-based
getRuntimeSetting( "timeout", 30 )             // default when missing
getRuntimeSetting()                            // the whole merged struct

A missing key with no default throws, the same convention as getSystemSetting(). Read the Runtime Settings guide.

🧩 Static method interception: onMissingStaticMethod()

Classes have always had onMissingMethod() for instance calls. Static calls to a missing method used to throw a KeyNotFoundException. A class can now handle them, which enables dynamic static APIs such as User::findByEmail( "a@b.com" ).

class {

	static function onMissingStaticMethod( string missingMethodName, struct missingMethodArguments ) {
		return "called #missingMethodName#"
	}

}

MyClass::anything( 1, 2 )    // "called anything"
  • It runs only when the static method is missing, so normal static calls cost nothing extra.
  • It works when declared in the class and when a module adds the function to a class's static scope at runtime.
  • A handler can cache by putting a function into the static scope under the missing name, so later calls are normal static calls.
  • It also works for classes that extend a Java class. A call goes to the Java parent only when the parent has a matching method.

Two related fixes: Foo::counter = 42 now assigns a static variable from outside the class, and static[ name ] = value inside a function is no longer parsed as an array destructuring declaration. See Static Constructs.

πŸ“‘ Raw binary HTTP streaming: onBinaryChunk

http().onChunk() decodes the body as text and splits it on newlines, which corrupts binary streams such as audio. The new onBinaryChunk callback receives raw bytes per network read.

http( url, "POST" )
	.header( "Authorization", "Bearer #key#" )
	.body( payload )
	.timeout( 30 )
	.onBinaryChunk( ( bytes, info ) => {
		sendToClient( bytes )
		return !clientGone    // an explicit false stops the stream and closes the connection
	} )
	.onError( ( e, result ) => handleError( e ) )
	.send()
  • info carries chunkNumber, totalBytes, headers (first chunk only), result and httpClient.
  • Stop on false. Only an explicit false stops the stream. The response closes immediately and the result is flagged as not completed, not as an error.
  • Idle timeout for streaming. In binary and SSE modes timeout becomes the longest wait for headers or between received bytes. A stalled stream is reported as a timeout error (408).
  • Errors. In binary and SSE modes a non-2xx status skips the chunk callback and is reported as HTTP <status>: <body> through onError and the result struct.
  • SSE gets the same treatment, including stop-on-false, idle timeout and Content-Encoding decoding.

Also available as an attribute on bx:http, and fully backward compatible. See HTTP Calls and Server-Sent Events.

πŸ”‘ generateKeyPair()

A new BIF next to generateSecretKey() for asymmetric key pairs, with no Java interop needed.

pair = generateKeyPair( "Ed25519" )
pair.publicPem
pair.privatePem
  • Algorithms: Ed25519, Ed448, X25519, X448, RSA (2048, 3072, 4096) and EC (secp256r1, secp384r1, secp521r1).
  • Returns a struct with algorithm, privateKey (base64 PKCS8 DER), publicKey (base64 X.509 SPKI DER), privatePem and publicPem.
  • Randomness comes from the JDK SecureRandom, supplemented with the runtime's global secret seed.

β˜• Extending Java classes

A BoxLang class that uses extends="java:..." is now much more capable:

  • Call super-class methods that need argument coercion, such as add() on a class extending java.util.ArrayList (BL-2771)
  • Override protected Java methods (BL-2772)
  • Extend Java classes supplied by a module or a runtime JAR (BL-2773)

⚑ Performance

  • Class resolution no longer lists every directory on the path for each lookup miss. On case-sensitive file systems the cost grew with the number of class files, and it is no longer quadratic (BL-2747).
  • replace() and replaceNoCase() were refactored to avoid allocating substrings (BL-2756).
  • Case-insensitive searches compare in place instead of lower-casing whole strings. That speeds up findNoCase(), replaceNoCase(), the CONTAINS operator, stringStartsWithNoCase() and stringEndsWithNoCase() (BL-2765).
  • Date compares no longer rebuild the whole runtime config struct (BL-2758).
  • Debug mode caches source snippets for exception contexts in a new bxCodeSnippets cache, so repeated errors no longer re-read source files (BL-2764).

πŸ› οΈ Language, tooling and CFML compatibility

  • print() and println() accept any number of arguments. print( "Age:", 21 ) outputs Age: 21.
  • duplicate() handles Java List, Map and Set instances directly, honors deep=false, and is faster.
  • Java class dumps now show public instance and static methods in separate sections.
  • systemCacheClear( "classfiles" ) clears compiled class files cached on disk.
  • BigDecimal and BigInteger casters accept strings with leading whitespace such as " 1".
  • cftranspile: docblock @return no longer becomes an invalid annotation, script components without a bx: prefix now compile, tag-form <cffunction> in .cfm templates is emitted as bx:function instead of being silently dropped, and the Lucee argument aliases of invoke() are mapped to BoxLang's.
  • Fixed: a VerifyError on scripts with import plus try/catch, a parser error with not listFindNoCase() in a struct literal, return +expr;, finally skipped when abort ran inside a try with a catch, and cf_ custom tags losing their template and name attributes.
  • Platform: environment variables are now imported on Windows with boxlang.bat, and MiniServer getRequestBody() no longer returns an empty string when the body arrives after the headers.
  • Modules: bx-orm fixes for generator="identity" on PostgreSQL reserved-word tables and for fieldtype="collection", and bx-image now rejects oversized images from the header before decoding them.

πŸ”’ Security

  • Jackson updated to 2.21.7, resolving CVE-2026-89425
  • Mitigation for CVE-2026-102554. The core now depends on javaparser-core only, so Guava, which BoxLang never used, is no longer bundled
  • jsoup updated to 1.23.2 (CVE-2026-75140)

⚑ Migration notes

Read these before upgrading:

  • Thread scope and unscoped lookup (BL-1792). Inside a thread body, an unscoped name no longer falls back to the thread's metadata struct. foo no longer resolves after thread.foo = "bar", so use thread.foo. An unscoped assignment such as status = "x" now goes into the local scope.
  • abort inside a transaction. The open transaction is now rolled back, so writes made before the abort are no longer committed.
  • onBIFInvocation. It now fires once per call, not twice. If you worked around the duplicate, remove the workaround.
  • duplicate() of Java collections. Shallow duplicates of Java List, Map and Set instances are now really shallow. Before, they were always deep copies.
  • Date strings in comparisons. A date string compared inside a request is now read in the request timezone.
  • Credential-override datasource names. On-the-fly datasources are named {datasource}_user_{username}_{hash} instead of onthefly_{hash}.
  • Java integrators. The Compare and comparison operator methods without a context are deprecated in favor of the context-taking overloads.

Get started

Grab BoxLang 1.19.0 from boxlang.io and read the full release notes for every ticket.

Resources

Build it, run it, and now see exactly what it did.

Add Your Comment

Recent Entries

BoxLang AI 3.7: Profiles, Embeddings Defaults and MCP Rate Limiting

BoxLang AI 3.7: Profiles, Embeddings Defaults and MCP Rate Limiting

Ortus Solutions announces the release of BoxLang AI 3.7. Name a provider, model, params and API key once, then pick it by name for a call, an agent, or a single agent run. Embeddings get their own defaults, MCP servers you build get rate limiting and trusted proxies, and a handful of important fixes land along the way.

Luis Majano
Luis Majano
November 05, 2026
Build AI Agents with Less Configuration: Join Luis Majano in MΓ‘laga

Build AI Agents with Less Configuration: Join Luis Majano in MΓ‘laga

Learn how to define, build, and run AI agents with BxAgents in a hands-on session organized by Malaga-AI and sponsored by Ortus Solutions! πŸš€

Building an AI agent means connecting instructions, models, tools, and application logic. A clear project structure makes those pieces easier to organize and develop.

Maria Jose Herrera
Maria Jose Herrera
November 04, 2026