Runtime architecture
Skill ortus-boxlang/skills/boxlang-core-development/runtime-architecture
Use this skill when understanding BoxLang internals: BoxRuntime services, IBoxContext hierarchy, scope chain resolution, DynamicObject, type system (IBoxType), parsing pipeline (source to bytecode), class loader isolation, virtual threads, or using --bx-printast for AST debugging.From its SKILL.md
npx -y skills add ortus-boxlang/skills --skill runtime-architectureAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
2 things to look at
- no licenseNo license file was found in the repository. Code published without one is not open source by default, so using it at work is a question for whoever answers licensing questions where you are.
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
16.7 KB, ~4.0k tokens by cl100k_base, as published. Nobody here has run it
BoxLang Runtime Architecture
Overview
BoxLang is a dynamic JVM language (JRE 21+) that compiles source to Java bytecode
at runtime. The central singleton BoxRuntime acts as a service locator providing
access to all core services. Understanding the architecture is essential for building
modules, BIFs, interceptors, and custom language extensions.
BoxRuntime — The Central Singleton
import ortus.boxlang.runtime.BoxRuntime;
// Get the singleton instance
BoxRuntime runtime = BoxRuntime.getInstance();
// Start programmatically (e.g., in tests or embedding scenarios)
BoxRuntime runtime = BoxRuntime.getInstance( true ); // true = start immediately
// Core service accessors
runtime.getInterceptorService() // Event system
runtime.getModuleService() // Module management
runtime.getCacheService() // Cache providers
runtime.getAsyncService() // Executors, futures
runtime.getSchedulerService() // Cron scheduling
runtime.getDataSourceService() // JDBC connections
runtime.getFunctionService() // BIF registry
runtime.getComponentService() // Component/tag registry
runtime.getClassLocator() // Class resolution
runtime.getConfiguration() // Runtime config
runtime.getTypeService() // Type system
Key Services
InterceptorService
Manages the global event bus. See interceptors skill for full details.
InterceptorService svc = runtime.getInterceptorService();
svc.announce( Key.of("myEvent"), dataStruct );
svc.register( myInterceptor );
svc.unregister( myInterceptor );
ModuleService
ModuleService moduleSvc = runtime.getModuleService();
// Check if a module is loaded
boolean loaded = moduleSvc.hasModule( Key.of("bx-redis") );
// Get module record
ModuleRecord record = moduleSvc.getModuleRecord( Key.of("bx-redis") );
String version = record.version;
String physPath = record.physicalPath;
IStruct settings = record.settings;
// List all loaded modules
Set<Key> modules = moduleSvc.getLoadedModules();
FunctionService (BIF Registry)
FunctionService fnSvc = runtime.getFunctionService();
// Register a custom BIF at runtime
fnSvc.registerGlobalFunction( Key.of("myBif"), new MyBIF() );
// Check if a BIF exists
boolean exists = fnSvc.hasGlobalFunction( Key.of("len") );
// Get a BIF instance
BIF len = fnSvc.getGlobalFunction( Key.of("len") );
AsyncService
AsyncService asyncSvc = runtime.getAsyncService();
// Create a named executor
IBoxExecutor executor = asyncSvc.newExecutor( "myPool", ExecutorType.FIXED, 4 );
// Get an existing executor
IBoxExecutor exec = asyncSvc.getExecutor( "boxlang-tasks" );
// Create a future
BoxFuture<Object> future = asyncSvc.newFuture( () -> expensiveWork() );
IBoxContext — Execution Context Hierarchy
Every BoxLang execution has a context. Contexts form a linked chain for scope resolution.
BoxRuntime
└── ScriptingRequestBoxContext (CLI / script execution)
└── FunctionBoxContext (inside a function call)
└── ClosureBoxContext (inside a closure)
└── LambdaBoxContext (inside a lambda)
└── WebRequestBoxContext (HTTP request)
└── ApplicationBoxContext (Application.bx scope)
└── SessionBoxContext (Session scope)
└── FunctionBoxContext
import ortus.boxlang.runtime.context.IBoxContext;
import ortus.boxlang.runtime.context.FunctionBoxContext;
import ortus.boxlang.runtime.context.ScriptingRequestBoxContext;
// Traverse parent contexts
IBoxContext current = context;
while ( current != null ) {
System.out.println( current.getClass().getSimpleName() );
current = current.getParent();
}
// Find a scope nearby (walks up the context chain)
IScope localScope = context.getScopeNearby( LocalScope.name );
IScope sessionScope = context.getScopeNearby( SessionScope.name );
// Resolve a variable (checks all scopes in order)
ScopeSearchResult result = context.scopeFindNearby( Key.of("myVar"), null );
if ( result != null ) {
Object value = result.value();
IScope foundIn = result.scope();
}
// Invoke a BIF from Java code
Object result = context.invokeFunction( Key.of("len"), new Object[]{ "hello" } );
Scope Chain Resolution Order
When BoxLang resolves an unqualified variable name, it searches these scopes in order:
local— declared withvarin current functionarguments— function parametersvariables— class/component private scope- Named scopes:
url,form,cgi,cookie request— per-request shared scopesession— user sessionapplication— application-wideserver— server-wide
DynamicObject — BoxLang's Java Object Wrapper
DynamicObject wraps Java objects and provides BoxLang-style method invocation:
import ortus.boxlang.runtime.dynamic.DynamicObject;
// Wrap any Java object
DynamicObject wrapped = DynamicObject.of( new java.util.ArrayList() );
// Invoke methods
wrapped.invoke( context, "add", new Object[]{ "item" } );
Object size = wrapped.invoke( context, "size", new Object[]{} );
// Get/set fields
Object field = wrapped.getField( Key.of("fieldName") );
wrapped.setField( Key.of("fieldName"), value );
IBoxType — BoxLang Native Types
All BoxLang values implement IBoxType:
import ortus.boxlang.runtime.types.*;
import ortus.boxlang.runtime.types.IType;
// Core type classes
BoxArray bxArray = new BoxArray(); // Array
BoxStruct bxStruct = BoxStruct.of("k","v"); // Struct
BoxString bxStr = BoxString.of("hello"); // String (immutable)
BoxInteger bxInt = BoxInteger.of(42); // Integer
BoxDouble bxDbl = BoxDouble.of(3.14); // Double
BoxBoolean bxBool = BoxBoolean.of(true); // Boolean
BoxNull bxNull = BoxNull.INSTANCE; // Null singleton
// Check type
if ( value instanceof BoxArray arr ) {
// work with array
}
if ( value instanceof IStruct struct ) {
// work with struct
}
// Key — interned string for scope/struct access
Key nameKey = Key.of("name"); // interned (fast equality)
Key idKey = Key.of("id");
Parsing Pipeline
Source code → AST → Java Bytecode:
Source (.bx / .bxs / .bxm / .cfm / .cfc)
│
▼
Lexer (ANTLR4) → Token stream
│
▼
Parser (ANTLR4) → Parse tree
│
▼
AST Builder → BoxLang AST (BoxNode tree)
│
▼
Transpiler/Transformer → Java AST (JavaParser)
│
▼
Java Compiler → Java bytecode (.class)
│
▼
ClassLoader → Loaded & executed by JVM
Debug the AST
# Print the AST for a file
boxlang --bx-printast myScript.bxs
# Print AST from stdin
echo 'var x = 1 + 2' | boxlang --bx-printast -
# Print AST with file path context
boxlang --bx-printast /path/to/file.bxs
Compiled Class Caching
Once compiled, the generated .class files are cached by the ClassLoader.
Re-compilation only happens when the source timestamp changes or when
reloadOnChange=true is configured.
ClassLoader Isolation
Each module gets its own isolated ClassLoader for the libs/ directory:
BoxLang Runtime ClassLoader
├── Core BIFs & Runtime Classes
├── Module A ClassLoader
│ └── libs/moduleA-dependency.jar
├── Module B ClassLoader
│ └── libs/moduleB-dependency.jar (no conflict with Module A)
└── Application ClassLoader
└── Application-specific JARs (this.javaSettings)
This prevents JAR version conflicts between modules — each module's libs/
directory is visible only to that module.
Key Java Packages
| Package | Contents |
|---|---|
ortus.boxlang.runtime | BoxRuntime, configuration |
ortus.boxlang.runtime.bifs | BIF base classes, annotations |
ortus.boxlang.runtime.context | Context classes, scope resolution |
ortus.boxlang.runtime.scopes | Scope implementations, Key |
ortus.boxlang.runtime.types | BoxArray, BoxStruct, IStruct, etc. |
ortus.boxlang.runtime.events | BaseInterceptor, InterceptorService |
ortus.boxlang.runtime.services | All service classes |
ortus.boxlang.runtime.modules | ModuleRecord, ModuleService |
ortus.boxlang.runtime.dynamic | DynamicObject, Java interop |
ortus.boxlang.runtime.loader | Class loading, bytecode generation |
ortus.boxlang.compiler.ast | AST node classes |
Virtual Threads and Project Loom
BoxLang uses JRE 21 virtual threads (Project Loom) for its default executor:
// Virtual thread executor (default in BoxLang)
// - Lightweight: millions can coexist
// - Ideal for I/O-bound work (HTTP, DB, file I/O)
// - Automatically unmounted during blocking operations
ExecutorService virtualExec = Executors.newVirtualThreadPerTaskExecutor();
// BoxLang's AsyncService uses virtual threads by default:
// Type.VIRTUAL → Executors.newVirtualThreadPerTaskExecutor()
// Type.FIXED → Executors.newFixedThreadPool(n)
// Type.CACHED → Executors.newCachedThreadPool()
Java CDS (Class Data Sharing)
BoxLang supports Java CDS archives for faster startup:
# Generate a CDS archive
java -Xshare:dump -XX:SharedArchiveFile=boxlang.jsa -jar boxlang.jar
# Use the archive on startup (significantly faster cold starts)
java -Xshare:on -XX:SharedArchiveFile=boxlang.jsa -jar boxlang.jar
Runtime Configuration Access
// Get boxlang.json configuration
BoxLangConfiguration config = runtime.getConfiguration();
boolean debugMode = config.debugMode;
String timezone = config.timezone;
IStruct modules = config.modules;
IStruct caches = config.caches;
IStruct datasources = config.datasources;
// Get a module's settings
IStruct moduleSettings = config.modules
.getAsStruct( Key.of("bx-redis") )
.getAsStruct( Key.of("settings") );
Embedding BoxLang in a Java Application
import ortus.boxlang.runtime.BoxRuntime;
import ortus.boxlang.runtime.context.ScriptingRequestBoxContext;
import ortus.boxlang.runtime.scopes.Key;
// Start the runtime
BoxRuntime runtime = BoxRuntime.getInstance( true );
// Create a scripting context
ScriptingRequestBoxContext context = new ScriptingRequestBoxContext( runtime.getRuntimeContext() );
// Set variables
context.getScope( VariablesScope.name ).put( Key.of("greeting"), "Hello!" );
// Execute BoxLang code
runtime.executeStatement( "println( greeting )", context );
// Execute a file
runtime.executeTemplate( "/path/to/script.bxs", context );
// Shutdown
runtime.shutdown();
Attempt — Null-Safe Value Wrapper
Attempt<T> is a fluent, immutable wrapper for values that may be null or invalid.
It is analogous to Java's Optional but with built-in BoxLang validation support.
Used extensively in BIFs and services, especially for optional argument handling.
import ortus.boxlang.runtime.dynamic.Attempt;
// Create an attempt from any value (including null)
Attempt<String> attempt = Attempt.of( arguments.get( Key.of("name") ) );
Attempt<Integer> empty = Attempt.empty();
// Basic presence checks
if ( attempt.isPresent() ) { ... }
if ( attempt.isEmpty() ) { ... }
// Get value (throws NoElementException if empty)
String value = attempt.get();
// Get with default
String value = attempt.orElse( "default" );
String value = attempt.orElseGet( () -> computeDefault() );
// Throw a specific exception if empty
Attempt.of( null ).orThrow( "mymodule.NotFound", "Value not found" );
// Map and filter
Attempt<Integer> len = attempt.map( s -> s.length() );
Attempt<String> filtered = attempt.filter( s -> s.length() > 3 );
// Build a stream
attempt.stream().forEach( v -> process(v) );
Attempt with Validation
// Validate type (uses BoxLang's isValid() function)
Attempt.of( value ).toBeType( "email" ).isValid(); // true/false
// Range validation (inclusive)
Attempt.of( 42 ).toBeBetween( 1.0, 100.0 ).isValid();
// Regex pattern
Attempt.of( "hello" ).toMatchRegex( "^[a-z]+$" ).isValid();
Attempt.of( "HELLO" ).toMatchRegex( "^[a-z]+$", false ).isValid(); // case-insensitive
// Custom predicate
Attempt.of( user ).toSatisfy( u -> u.isActive() ).isValid();
Using Attempt in BIF Argument Handling
@Override
public Object invoke( IBoxContext context, ArgumentsScope arguments ) {
// getAsAttempt returns Attempt<T> — safe for optional params
String name = arguments.getAsAttempt( Key.of("name"), String.class ).orElse( "World" );
Integer timeout = arguments.getAsAttempt( Key.of("timeout"), Integer.class ).orElse( 30 );
return "Hello, " + name + "!";
}
Type Casters
BoxLang provides a rich set of type casters in ortus.boxlang.runtime.dynamic.casters.
Each returns a CastAttempt<T> (similar to Attempt) with .wasSuccessful() and .get().
import ortus.boxlang.runtime.dynamic.casters.*;
// Safe casting — returns CastAttempt
CastAttempt<String> strAttempt = StringCaster.attempt( value );
CastAttempt<Integer> intAttempt = IntegerCaster.attempt( value );
CastAttempt<Long> longAttempt = LongCaster.attempt( value );
CastAttempt<Double> dblAttempt = DoubleCaster.attempt( value );
CastAttempt<Boolean> boolAttempt = BooleanCaster.attempt( value );
if ( intAttempt.wasSuccessful() ) {
int n = intAttempt.get();
}
// Direct casting (throws if fails)
String str = StringCaster.cast( value );
Integer num = IntegerCaster.cast( value );
Boolean bool = BooleanCaster.cast( value );
Array arr = ArrayCaster.cast( value );
IStruct strc = StructCaster.cast( value );
// Specific casters available:
// ArrayCaster, BooleanCaster, DateTimeCaster, DoubleCaster,
// FloatCaster, IntegerCaster, KeyCaster, LongCaster,
// NumberCaster, QueryCaster, StringCaster, StructCaster,
// UUIDCaster, XMLCaster, ThrowableCaster, FunctionCaster
IService — Creating Custom Runtime Services
Implement IService to create a service that's lifecycle-managed by the BoxLang runtime.
The runtime discovers and calls lifecycle methods automatically.
import ortus.boxlang.runtime.services.IService;
import ortus.boxlang.runtime.scopes.Key;
public class MyCustomService implements IService {
private static final Key SERVICE_NAME = Key.of( "MyCustomService" );
private BoxLangLogger logger;
@Override
public Key getName() {
return SERVICE_NAME;
}
@Override
public void onConfigurationLoad() {
// Called when boxlang.json is loaded; access config here
this.logger = BoxRuntime.getInstance().getLoggingService().getLogger( "mycustomservice" );
logger.debug( "Configuration loaded" );
}
@Override
public void onStartup() {
logger.info( "MyCustomService started" );
// Initialize resources, open connections, warm up caches, etc.
}
@Override
public void onShutdown( Boolean force ) {
logger.info( "MyCustomService shutting down (force={})", force );
// Release resources, close connections, flush buffers
}
// Add your service-specific methods:
public String doSomething( String input ) {
return input.toUpperCase();
}
}
Registering a Service in a Module
// ModuleConfig.bx (via Java in onLoad)
// or register directly from Java:
BoxRuntime.getInstance().registerService( new MyCustomService() );
// ModuleConfig.bx
class {
function onLoad() {
var svc = createObject( "java", "com.example.services.MyCustomService" ).init()
boxRuntime.registerService( svc )
log.info( "MyCustomService registered" )
}
}
Accessing a Registered Service
// Retrieve by name
MyCustomService svc = (MyCustomService) BoxRuntime.getInstance()
.getService( Key.of("MyCustomService") );
svc.doSomething( "hello" );
References
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most architecture codebase skills give in ~4.0k tokens
Counted across 811 of the 1,134 authors here whose files we hold, read 2026-08-07
- Ask the user which candidate to explorein 45 of 811, across 15 files
- Apply the deletion test to suspected shallow modulesin 43 of 811, across 15 files
- Read any relevant architecture decision records firstin 31 of 811, across 8 files
- Use exact glossary terms in every suggestionin 30 of 811, across 10 files
- Accept dependencies instead of creating themin 24 of 811, across 5 files
- Include before and after visualisations for each candidatein 24 of 811, across 5 files
- Read the domain glossary before exploringin 24 of 811, across 6 files
- Return results instead of producing side effectsin 23 of 811, across 4 files
- Explore the codebase for shallow modules and frictionin 23 of 811, across 3 files
- Introduce seams only where things varyin 22 of 811, across 3 files
- Reduce the number of methodsin 21 of 811, across 2 files
- Design deep modules with small interfacesin 21 of 811, across 3 files
Said here and by no other author read
- use BoxRuntime as the service locator
- traverse parent contexts for scope resolution
- wrap java objects in DynamicObject
- use Attempt for null-safe value wrapping
- assign isolated classloaders to modules
- use virtual threads for io-bound work
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.