๐Ÿ”ท Core

boxlang-core-dev-interceptors

Use this skill when creating BoxLang interceptors: Observer/Intercepting Filter patterns, interceptor pools, BoxLang class vs Java interceptors, lambda interceptors, registration via BIFs/InterceptorService/ModuleConfig, interception points, and announcing custom events.

$ npx skills add ortus-boxlang/skills/boxlang-core-development/interceptors
$ coldbox ai skills install ortus-boxlang/skills/boxlang-core-development/interceptors
๐Ÿ”— https://skills.boxlang.io/skills/raw/ortus-boxlang/skills/boxlang-core-development~interceptors

BoxLang Interceptors

BoxLang is the AI-native software productivity platform for building, modernizing and running applications, with developers and AI agents working together.

Overview

BoxLang's interceptor system implements the Observer and Intercepting Filter design patterns. Interceptors listen for named events (interception points) announced by the runtime, modules, or application code. They enable cross-cutting concerns like logging, security, AOP, routing, and content manipulation without modifying core code.

Interceptor Pools

Three pools exist in the runtime. Each pool is an independent event emitter:

PoolScopeUse Case
Global RuntimeEntire BoxLang processModule events, parse events, global AOP
Application Request ListenerPer-application request lifecycleAuth, routing, request logging
CacheProvidersPer cache instanceCache event hooks

Creating a BoxLang Class Interceptor

Method names in the class directly correspond to interception point names:

// interceptors/RequestLogger.bx
class {

    // Auto-injected by the runtime
    property name="name"              setter="false"
    property name="properties"        setter="false"
    property name="log"               setter="false"  // module logger
    property name="interceptorService" setter="false"
    property name="boxRuntime"        setter="false"
    property name="moduleRecord"      setter="false"  // if module-based

    /**
     * Called when the interceptor is registered.
     * Use this to validate properties and set up resources.
     */
    function configure() {
        // Access settings passed at registration time
        log.info( "RequestLogger interceptor configured. Level: #properties.logLevel ?: 'INFO'#" )
    }

    // ---------------------------------------------------------------
    // Interception point methods โ€” name must EXACTLY match the event
    // ---------------------------------------------------------------

    /**
     * Fires before every HTTP request.
     * @event Struct with request context data (mutable)
     */
    function onRequestStart( struct event={} ) {
        log.info( "โ†’ #cgi.request_method# #cgi.script_name##cgi.query_string ?: ''#" )
        event.requestStartTime = getTickCount()
    }

    /**
     * Fires after every HTTP request.
     */
    function onRequestEnd( struct event={} ) {
        var elapsed = getTickCount() - (event.requestStartTime ?: getTickCount())
        log.info( "โ† #cgi.script_name# completed in #elapsed#ms" )
    }

    /**
     * Fires when an unhandled exception occurs.
     */
    function onException( struct event={} ) {
        var ex = event.exception ?: {}
        log.error( "Exception: #ex.message ?: 'unknown'# | #ex.stackTrace ?: ''#" )
    }

}

Creating a Java Interceptor

// src/main/java/com/example/interceptors/RequestLogger.java
package com.example.interceptors;

import ortus.boxlang.runtime.events.BaseInterceptor;
import ortus.boxlang.runtime.events.InterceptionPoint;
import ortus.boxlang.runtime.scopes.Key;
import ortus.boxlang.runtime.types.IStruct;

@ortus.boxlang.runtime.events.Interceptor
public class RequestLogger extends BaseInterceptor {

    @Override
    public void configure() {
        // this.properties is available for config
        // this.log is available for logging
        // this.interceptorService is available
        // this.boxRuntime is available
    }

    // @InterceptionPoint marks this method as an event handler
    @InterceptionPoint
    public void onRequestStart( IStruct event ) {
        String method = (String) event.getOrDefault( Key.of("requestMethod"), "GET" );
        this.log.info( "Request started: " + method );
        event.put( Key.of("startTime"), System.currentTimeMillis() );
    }

    @InterceptionPoint
    public void onRequestEnd( IStruct event ) {
        Long startTime = (Long) event.getOrDefault( Key.of("startTime"), System.currentTimeMillis() );
        long elapsed   = System.currentTimeMillis() - startTime;
        this.log.info( "Request completed in " + elapsed + "ms" );
    }

    @InterceptionPoint
    public void preFunctionInvoke( IStruct event ) {
        // event contains: functionName, arguments, context
        String fnName = event.getAsString( Key.of("functionName") );
        this.log.debug( "Invoking function: " + fnName );
    }

}

Lambda and Closure Interceptors

Closures/lambdas must explicitly specify which events they handle:

// Closure interceptor โ€” must specify states
var myListener = function( struct event={} ) {
    writeOutput( "Request started!" )
}

// Register for specific events
boxRegisterInterceptor(
    myListener,
    ["onRequestStart", "onRequestEnd"]
)

// Lambda form
boxRegisterInterceptor(
    (event) -> logEvent( event ),
    ["onHTTPRequest"]
)

Registering Interceptors

// ModuleConfig.bx
class {

    this.interceptors = [
        {
            // Full invocation path to the interceptor class
            class      : "#moduleRecord.invocationPath#.interceptors.RequestLogger",
            properties : {
                logLevel  : "INFO",
                maxErrors : 100
            }
        },
        {
            class      : "#moduleRecord.invocationPath#.interceptors.SecurityFilter",
            properties : { enabled: true }
        }
    ]

}

Method 2: BIF Registration

// Global runtime interceptor
boxRegisterInterceptor( new interceptors.MyInterceptor() )

// With properties
boxRegisterInterceptor(
    new interceptors.AuditLog(),
    [],           // states: empty = listen to all matching methods
    { logPath: "/var/log/audit.log" }
)

// Application request listener (web context)
boxRegisterRequestInterceptor( new interceptors.AuthFilter() )

Method 3: InterceptorService API (Java)

InterceptorService service = BoxRuntime.getInstance().getInterceptorService();

// Register a Java-based interceptor
service.register( new RequestLogger() );

// Register with properties
IStruct props = new Struct();
props.put( Key.of("logLevel"), "DEBUG" );
service.register( new RequestLogger(), props );

// Register a BoxLang class
IClassRunnable bxInterceptor = (IClassRunnable) context.loadClass("interceptors.MyBxInterceptor");
service.register( bxInterceptor );

// Register a closure for specific states
service.register(
    (IInterceptorLambda) event -> System.out.println("Event: " + event),
    Key.of("onRequestStart"), Key.of("onRequestEnd")
);

Common Interception Points

All canonical event names are defined in the BoxEvent enum: ortus.boxlang.runtime.events.BoxEvent

Use BoxEvent.ON_REQUEST_START.key() in Java, or the string name in BoxLang.

Runtime Events

EventWhenPayload Keys
onRuntimeStartRuntime fully startedruntime
onRuntimeShutdownBefore shutdownruntime
onRuntimeConfigurationLoadConfig loadedconfig
onRuntimeBoxContextStartupFirst context createdcontext
onServerScopeCreationServer scope initializedserverScope
onConfigurationLoadConfig file loadedconfig
onConfigurationOverrideLoadOverride config loadedconfig
onParseBefore source is parsedsource, result
onMissingMappingMapping cannot be resolvedmapping
onPreSourceInvokeBefore any source file runscontext, source
onPostSourceInvokeAfter any source file runscontext, source

Class Instantiation Events (v1.18+)

EventWhenPayload Keys
afterBoxClassCreationInstance defined, before init(). Also fires for noInit creations (createObject(), deserialization). Never fires for super classes in an extends chain.instance, className, noInit, context
afterBoxClassInitAfter init() or the implicit constructor completes. Skipped for noInit creations and when init() throws.instance, result, className, context

Module Events

EventWhen
onModuleServiceStartupModule service starts
onModuleServiceShutdownModule service stops
afterModuleRegistrationsAll modules registered
preModuleRegistrationBefore a module is registered
postModuleRegistrationAfter a module is registered
afterModuleActivationsAll modules activated
preModuleLoadBefore a module is loaded
postModuleLoadAfter a module is loaded
preModuleUnloadBefore a module is unloaded
postModuleUnloadAfter a module is unloaded

Application / Request Events (Web Context)

EventWhenNotes
onApplicationStartApplication started
onApplicationEndApplication ended
onApplicationRestartApplication restarted
onApplicationDefinedApplication.bx found
beforeApplicationListenerLoadBefore Application.bx loads
afterApplicationListenerLoadAfter Application.bx loads
onRequestStartBefore request startsFires before Application.bx lifecycle
onRequestMain request handling
onRequestEndAfter request completes
onClassRequestClass/component requested
onRequestFlushBufferOutput buffer flushed
onSessionStartNew session createdSession struct available
onSessionEndSession expires or is invalidatedSession data available
onSessionCreatedSession object created
onSessionDestroyedSession object destroyed
onErrorUnhandled exception
onMissingTemplateTemplate not found
onAbortExecution aborted
onRequestContextConfigRequest context configured

HTTP Events

EventWhen
onHTTPRequestEvery HTTP request
onHTTPRawResponseBefore raw HTTP response sent
onHTTPResponseBefore HTTP response sent
onWebExecutorRequestWeb executor request (route modification)

BIF / Component Lifecycle Events

EventWhen
onBIFInstanceBIF instantiated
onBIFInvocationBefore BIF invoked
postBIFInvocationAfter BIF invoked
onComponentInstanceComponent instantiated
onComponentInvocationComponent invoked
onCacheComponentActionCache component action
onFileComponentActionFile component action
onCreateObjectRequestcreateObject() called
afterDynamicObjectCreationJava object wrapped in DynamicObject

Template / Function Events

EventWhenPayload
preTemplateInvokeBefore template executestemplate, context
postTemplateInvokeAfter template executestemplate, context
preFunctionInvokeBefore any function callfunctionName, arguments, context
postFunctionInvokeAfter any function callfunctionName, result
onFunctionExceptionFunction throws exceptionfunctionName, exception

Query / Transaction Events

EventWhen
onQueryBuildBefore query is built
preQueryExecuteBefore query executes
postQueryExecuteAfter query executes
queryAddRowRow added to query
onTransactionBeginTransaction started
onTransactionEndTransaction ended
onTransactionAcquireConnection acquired
onTransactionReleaseConnection released
onTransactionCommitTransaction committed
onTransactionRollbackTransaction rolled back
onTransactionSetSavepointSavepoint created

Cache Events

EventWhen
afterCacheElementInsertItem inserted
beforeCacheElementRemovedBefore item removed
afterCacheElementRemovedAfter item removed
afterCacheElementUpdatedItem updated
afterCacheClearAllCache cleared
afterCacheRegistrationCache provider registered
afterCacheRemovalCache provider removed
beforeCacheRemovalBefore cache provider removed
beforeCacheReplacementBefore cache entry replaced
beforeCacheShutdownBefore cache shuts down
afterCacheShutdownAfter cache shuts down
afterCacheServiceStartupCacheService started
beforeCacheServiceShutdownBefore CacheService shuts down
afterCacheServiceShutdownAfter CacheService shuts down

Scheduler Events

EventWhen
onSchedulerStartupScheduler started
onSchedulerShutdownScheduler stopped
onSchedulerRestartScheduler restarted
schedulerBeforeAnyTaskBefore any task runs
schedulerAfterAnyTaskAfter any task runs
schedulerOnAnyTaskSuccessTask succeeded
schedulerOnAnyTaskErrorTask threw an error
onSchedulerServiceStartupSchedulerService started
onSchedulerServiceShutdownSchedulerService stopped
onAllSchedulersStartedAll schedulers running
onSchedulerRemovalScheduler removed
onSchedulerRegistrationScheduler registered

Datasource Events

EventWhen
onDatasourceConfigLoadDatasource config loaded
onDatasourceServiceStartupDatasourceService started
onDatasourceServiceShutdownDatasourceService stopped
onDatasourceStartupDatasource initialized

Object Marshaller Events

EventWhen
beforeObjectMarshallSerializeBefore serialization
afterObjectMarshallSerializeAfter serialization
beforeObjectMarshallDeserializeBefore deserialization
afterObjectMarshallDeserializeAfter deserialization
onJSONQuerySerializeQuery serialized to JSON

Dump / Other Events

EventWhen
onBXDumpwriteDump() called
onMissingDumpOutputNo dump output handler found
logMessageLog message emitted

Announcing Custom Events

You can announce your own events for other interceptors to listen to:

// Announce a custom event with a data payload
boxAnnounce( "onUserRegistered", {
    userId  : newUser.getId(),
    email   : newUser.getEmail(),
    source  : "web"
})

// Listeners elsewhere can react to this:
// function onUserRegistered( struct event={} ) {
//     sendWelcomeEmail( event.email )
//     createDefaultWorkspace( event.userId )
// }
// Java: announce via InterceptorService
IStruct data = new Struct();
data.put( Key.of("userId"), userId );
data.put( Key.of("email"),  email );

BoxRuntime.getInstance()
    .getInterceptorService()
    .announce( Key.of("onUserRegistered"), data );

Use Cases

A/B Routing and Feature Flags (pre-request)

// interceptors/FeatureFlagRouter.bx
// v1.11+: fires BEFORE Application.bx onRequestStart โ€” enables rerouting
class {
    function onRequestStart( struct event={} ) {
        var userId = session.userId ?: ""
        if ( len(userId) && featureFlags.isEnabled("new-ui", userId) ) {
            event.overridePath = "/new-ui" & cgi.path_info
        }
    }
}

Maintenance Mode

// interceptors/MaintenanceMode.bx
class {
    function onRequestStart( struct event={} ) {
        if ( application.maintenanceMode && !isAdminIp( cgi.remote_addr ) ) {
            include "maintenance.bxm"
            abort
        }
    }
}

AOP Method Tracing

// interceptors/MethodTracer.bx
class {
    function preFunctionInvoke( struct event={} ) {
        var fn    = event.functionName ?: "unknown"
        var start = getTickCount()
        event.traceStart = start
        log.debug( "โ†’ #fn# called" )
    }

    function postFunctionInvoke( struct event={} ) {
        var elapsed = getTickCount() - (event.traceStart ?: getTickCount())
        log.debug( "โ† #event.functionName ?: 'unknown'# completed in #elapsed#ms" )
    }
}

Content Manipulation

// interceptors/HtmlMinifier.bx
class {
    function onRequestEnd( struct event={} ) {
        // Minify HTML output before sending to client
        var output = event.output ?: ""
        if ( len(output) ) {
            event.output = minifyHtml( output )
        }
    }
}

Unregistering Interceptors

// Unregister by reference
boxUnregisterInterceptor( myInterceptorInstance )

// Via InterceptorService (Java)
BoxRuntime.getInstance().getInterceptorService().unregister( myInterceptor )

References