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

What's New With 8.2.0

September 22, 2026

ColdBox 8.2.0 is a feature release focused on request-level composition and control: route-scoped middleware, standards-based HTTP caching, first-class Server-Sent Events, and conversational context for AI routing — plus a security hardening fix, a handful of correctness fixes, and hot-path performance work in the Renderer and routing layers.

Major Highlights

🧵 Route-Scoped Middleware — .middleware()

Routes can now carry their own middleware chain instead of relying solely on app-wide interceptors. .middleware() attaches a closure, a WireBox ID, or any object with a method named after the interception point (preProcess by default, or postProcess) to a single route — reusing the same dispatch mechanism ColdBox interceptors already use, just scoped to one route.

route( "/admin/:action" )
    .middleware( ( event, rc, prc ) => {
        if ( !auth.isLoggedIn() ) {
            event.relocate( "login" );
            return true; // short-circuits the rest of this route's middleware
        }
    } )
    .toHandler( "admin" );

Two companion features round this out:

  • middlewareGroup( name, [ ...targets ] ) — register a named, reusable bundle once, then reference it by name from .middleware() or a group()'s middleware option, instead of repeating the same target list everywhere.

  • .withoutMiddleware( target ) — opt a single route out of middleware it would otherwise inherit, by target name, by the group name it expanded from, or "*" for everything.

middlewareGroup( "api", [ "RequireApiKey", "RateLimiter" ] );

group( { pattern : "/api", middleware : [ "api" ] }, () => {
    route( "/users" ).toHandler( "users" );                              // runs "api"
    route( "/health" ).withoutMiddleware( "api" ).toHandler( "health" ); // opts out
} );

🗄️ HTTP Caching Primitives — ETag, Last-Modified, Cache-Control

Standards-based conditional-GET support, usable from any handler:

  • event.etag() / event.lastModified() — set the corresponding header and short-circuit with a 304 Not Modified on a match. Never short-circuits an unsafe HTTP method.

  • event.cacheControl() — build a Cache-Control header from a directives struct.

  • Response.withETag() / Response.withCacheControl() — the same primitives, as fluent methods on the REST Response object.

  • An already cache="true" event handler action can opt into an automatically computed ETag/Last-Modified with new etag/etagWeak/lastModified/cacheControl annotations, piggybacking on event caching with no extra per-request work.

📡 First-Class Server-Sent Events (BoxLang)

Streaming is now a first-class concern instead of something bolted onto AI routing. event.sse() takes over the response and hands your callback an SSEEmitter with send(), sendView(), sendData(), sendError(), keep-alives, and graceful disconnect handling:

For routes that always stream, Router.toSSE() mirrors toResponse(). Three new interception points (preSSEConnection, postSSEConnection, onSSEError) let you reject a connection before it opens, observe stream completion, or react to mid-stream errors, and a new this.sse settings block controls keep-alive interval, reconnect hints, and CORS defaults.

🤖 AI Routing Gets Conversational Context

toAi()'s invoke, stream, and batch sub-routes now resolve userId, conversationId, and threadId from the request body and thread them through to the runnable via options:

  • userId defaults to the framework's own session/request tracking identifier when not supplied

  • conversationId is passed through only if supplied - no default is invented

  • threadId is generated if not supplied, and is always echoed back - in the JSON response, an X-Thread-Id header, and a leading event: thread SSE frame on /stream (since browser EventSource clients can't read response headers)

This release also corrects the toAi() reference documentation, which had drifted from the actual run()/stream()-based IAiRunnable interface and request/response shapes. See AI Routing.

🚪 AI Gateway Routing — toAiGateway()

A third AI terminator joins toAi() and toMCP(), covering the direction they don't: a platform talking to your agent, on the platform's terms. One declaration mounts everything a BoxLang AI Gateway needs over HTTP.

Verb
Pattern
Purpose

POST

{pattern}[/:gateway]/events

An inbound platform event

GET

{pattern}[/:gateway]/events

The platform's URL verification handshake

GET

{pattern}/interactions/:requestID

Poll a pending human-in-the-loop approval

POST

{pattern}/interactions/:requestID/decisions

Submit a human's decision

GET

{pattern}/info

What this mount serves

  • GET and POST share /events on purpose: a platform is given one URL and verifies it with a GET before it will POST to it.

  • Pin a mount with toAiGateway( "slack" ), or leave the name out and one mount serves every gateway registered in aiGatewayRegistry(). A pinned name always wins over the URL placeholder.

  • Pass a session and every inbound message is dispatched as an agent turn and acked 202 immediately, without waiting on the turn - a platform webhook times out in seconds, an agent turn does not. The thread each message landed on comes back in the response (and as X-Thread-Id) so you can correlate the reply. Without a session, events are verified and parsed only.

  • Signatures are verified by the gateway itself before anything is parsed or dispatched.

BoxLang + bx-ai only. See AI Gateway Routing.

🔒 Hardened HTTP Method Spoofing

The _method form-field override (GET/POST browsers use to fake PUT/PATCH/DELETE) is now only honored when the original transport-level request is a POST. Previously, a plain GET request carrying ?_method=DELETE was silently treated as a DELETE — enabling CSRF-style attacks via a link, an <img> tag, a crawler, or a browser prefetch. A new event.getOriginalHTTPMethod() returns the raw, un-spoofed verb when you need it. See HTTP Method Spoofing.

🗄️ Route-Level Cache Rules — Router.withCache()

A route-scoped alternative to handler cache="true" annotations, so caching can be declared where the URL is declared instead of buried on the handler action. .withCache() accepts the same knobs as the handler annotations — timeout, provider, suffix, include/exclude/filter — plus the Tier 1 HTTP caching primitives (etag, etagWeak, lastModified, cacheControl). A route that opts in takes full precedence over that event's handler-level cache annotations; routes that don't opt in fall through unchanged.

⏰ Scheduler Timing Fixes

Two related scheduler bugs are fixed:

  • A task combining every() with startOnTime()/between() now waits for the next aligned period boundary instead of firing immediately on registration (or every server/container restart).

  • A task combining withNoOverlaps() with between()/startOnTime() no longer misreads its spacedDelay unit — a 1-minute task no longer re-fires every second.

🌐 Full BoxLang Null-Runtime Support

ColdBox now runs cleanly under BoxLang with enableNullSupport enabled, closing out the framework-wide gaps in null handling this setting exposes.

🧭 Routing & Annotation Fixes

  • Fluent routes now correctly preserve their domain when grouped or resolved through nested routes.

  • Handler metadata annotations nested under other annotations are now read correctly on BoxLang.

⚡ Renderer & Routing Performance

  • A new viewDiscoveryCaching setting (on by default, independent of viewCaching) caches the filesystem work that locates view/layout files, benefiting every render regardless of whether view output caching is enabled. See View Discovery Caching.

  • Several hot-path costs eliminated in HandlerService, RoutingService, and Router — a per-request settings lookup that should have been cached at configuration time, a redundant filesystem check in view dispatch detection, and route response placeholders now pre-parsed once at registration instead of re-parsed by regex on every matching request.

  • The interceptor chain now reports short-circuiting: announce() returns true on the synchronous path when an interceptor short-circuited the chain, false otherwise - previously this was undetectable. See Announcing Interceptions.

Release Notes

New Features

COLDBOX-1415 HTTP caching primitives - ETag, Last-Modified, Cache-Control

COLDBOX-1416 Route-scoped middleware via existing interceptor points

COLDBOX-1417 Conversational context (userId/conversationId/threadId) on toAi() routes

First-class Server-Sent Events streaming - event.sse(), SSEEmitter, Router.toSSE(), interception points, this.sse settings (#676)

AI Gateway routing - Router.toAiGateway() mounts a BoxLang AI Gateway's inbound events, verification handshake, and human-in-the-loop approval endpoints (COLDBOX-1435, #694)

COLDBOX-1418 Route-level cache rules via Router.withCache()

Improvements

COLDBOX-1406 HTTP method spoofing restricted to POST requests only; new getOriginalHTTPMethod()

New viewDiscoveryCaching setting plus Renderer hot-path caching for view/layout discovery (#664)

Hot-path performance optimizations in HandlerService, RoutingService, and Router (#665)

COLDBOX-1447 Interceptor chain now reports and honors short-circuiting via announce()'s return value; new getRouteDefinitionKeys() route-table introspection helper (#677)

COLDBOX-1440 Full BoxLang null-runtime support (enableNullSupport)

COLDBOX-1389 New body argument on test methods for mocking incoming request bodies

COLDBOX-1399 Scheduled-task stack traces are now logged when a task fails to load

COLDBOX-1403 More defensive bootstrap and session-start handling

COLDBOX-1409 Response.setData() gains message and location arguments

COLDBOX-1412 Response object fluent builders and utility methods

COLDBOX-1396 Scheduler and task-registration performance overhaul

isAjax now detects Fetch metadata, not just X-Requested-With (#662)

COLDBOX-1397 Global performance optimizations across startup and interception hot paths (in addition to PRs #664/#665)

Bugs

COLDBOX-1407 url.results collision breaking cache.getOrSet() on any page loaded with ?results=...

COLDBOX-1411 this.EVENT_CACHE_SUFFIX closures were evaluated once and frozen instead of per-request, letting different requests share a cache key

COLDBOX-1421 Scheduled tasks using every() + startOnTime()/between() fired immediately instead of aligning to the next period boundary

COLDBOX-1434 ScheduledTask combining withNoOverlaps() with a daily start time misread the spacedDelay unit, causing rapid re-fire

COLDBOX-1438 Fluent route domain grouping/nesting not preserved

COLDBOX-1439 Nested handler annotations not read correctly

COLDBOX-1446 addRoute() missing ai/aiRunnable/mcp/mcpServer parameters

COLDBOX-1448 DataMarshaller component made thread-safe

COLDBOX-1449 appHash property missing default value

Fixed a startup typo in Bootstrap.cfc (#667)

COLDBOX-1390 Whoops zero-position Right() error when normalizing file content for error displays

Whoops source panel indentation fix (#661)

COLDBOX-1392 Regression when using Application Bootstrap via inheritance (app hash included app name before the application existed)

COLDBOX-1398 contentType() null guard during event caching on some servlet deployments

COLDBOX-1401 Scheduled task missing getTimezon() getter (BIF collision)

COLDBOX-1402 Scheduled task delay() not honored

COLDBOX-1408 BoxLang prime issue when storing handler metadata

Renderer.renderLayout LayoutNotFoundException on empty layout (regression vs 7.x) (#657)

Module convention routing no longer applies the optional-action fallback to mandatory-action routes (#695)

Scheduler onOneServer() lock sized to the task's real cadence instead of the daily poll (#696)

Wrong ForgeBox slug in the BoxLang AI module error message (#656)

ObjectPopulator (ORM) and SchemaInfo null-prime/null-cast fixes for BoxLang runtime

Tasks

COLDBOX-1450 Deprecation markers for legacy tools and Adobe 2023 static scope ahead of v9

Bugs

COLDBOX-1420 Explicit WireBox mapping was deleted when its first metadata lookup failed

Bugs

COLDBOX-1422 BoxLangProvider did not convert CacheBox's minute-based timeouts, causing objects to expire 60x too soon

Bugs

COLDBOX-1400 Root appender could be called before LogBox finished initializing during loading

Last updated

Was this helpful?