Java Interop
Complete guide to Java interoperability in BoxLang — class resolution, object creation, type coercion, SAM auto-coercion, inheritance, proxies, and custom class loaders.
BoxLang compiles to JVM bytecode and runs directly on the JVM, making Java classes first-class citizens. The runtime resolves Java names through a ClassLocator → resolver pipeline and handles coercion in both directions — BoxLang values passed to Java methods and Java return values coming back. Any Java library on the classpath is immediately usable without glue code.
DynamicObject — The Engine Behind Java Interop
Every Java reference in BoxLang — instance, static, or the value returned by createObject — is wrapped in a DynamicObject. All dot-access, method calls, field reads, and operator forms (castAs, instanceOf, new) are sugar over DynamicObject + DynamicInteropService.
Key behaviours live in those two classes:
Constructor dispatch —
invokeConstructor(args)finds the best-matching overload.Method dispatch — EXACT → ASSIGNABLE → COERCE matching; hot paths are cached via
MethodHandle.Field access — public instance and static fields are readable and writable.
Varargs — packed transparently into the expected array type.
SAM auto-coercion — any BoxLang closure/lambda/UDF passed where a
@FunctionalInterfaceis expected is auto-wrapped (see the SAM Interfaces section below).
Sources: runtime/interop/DynamicObject.java, runtime/interop/DynamicInteropService.java.
Resolver Prefixes (bx: and java:)
BoxLang ships two built-in class resolvers:
bx: (default)
BoxResolver
BoxLang classes and modules
java:
JavaResolver
JDK and classpath Java classes
The prefix is optional. When omitted, ClassLocator.resolveFromSystem() walks bx: → java: automatically (DEFAULT_RESOLVER = BX_PREFIX). Use an explicit prefix to force a resolver, disambiguate a name collision, or make the Java origin visually clear in the source.
// explicit prefix — unambiguous, self-documenting
var map = new java:java.util.HashMap();
// no prefix — resolver cascade picks it up automatically
var map2 = new java.util.HashMap();Custom resolvers are a Java-only API: ClassLocator.registerResolver(IClassResolver). This is an advanced extensibility point for module authors embedding BoxLang.
Cache clearing after dynamic class reloads:
See SystemCacheClear and PagePoolClear.
Importing Java Classes
Use import at the top of a script or inside a class body to bring a Java type into scope.
Inside a class:
Creating Java Objects
The new Operator
new resolves the class, invokes the best-matching constructor via DynamicObject.invokeConstructor, and returns an initialized instance ready to use.
Class References as Constructors
Imported Java classes are class references, and class references are callable constructors. This gives Java classes the same construction model as BoxLang classes: use new, call .init() on the class reference, or invoke the class reference directly.
The same pattern applies to BoxLang classes:
Because class references are callable, they can be passed directly to higher-order functions. This is useful when mapping raw data into Java objects without wrapping the constructor in a lambda.
If the class reference is stored in a variable or returned from a function, invoke that reference the same way:
createObject()
For type = "java", createObject returns an uninitialized DynamicObject. You must call .init(args) explicitly to invoke a constructor. Static methods and fields are accessible without calling .init().
Side-by-side comparison:
Static Method and Field Access
Static members are accessible on an uninitialized DynamicObject without calling .init().
Type Coercion
Automatic Coercion
DynamicObject tries three strategies in order when matching a BoxLang value to a Java method parameter:
EXACT — type identity.
ASSIGNABLE — the value's type is a subtype or implements the interface.
COERCE — BoxLang runtime coercion (e.g.,
String→int).
Resolved MethodHandle instances are cached so the matching cost is paid only on the first call.
castAs Operator (Preferred)
castAs is the preferred approach for explicit casting. It is a native language operator and reads more naturally than a BIF call.
See Operators — CastAs for the full syntax reference.
javaCast() BIF
Use javaCast() when the function-call form is needed — for example, inside array literals or nested expressions.
Full primitive table:
boolean
primitive boolean
byte
primitive byte
char
primitive char
short
primitive short
int
primitive int
long
primitive long
float
primitive float
double
primitive double
bigdecimal
java.math.BigDecimal
string
java.lang.String
null
null reference
Append [] to cast to an array type:
See JavaCast for the full BIF reference.
When to Cast Manually
Overload disambiguation — two overloads have the same arity; automatic coercion picks the wrong one.
Variadic
Object...methods — auto-coercion may not pack arguments into the expected array.Typed array parameters — methods expecting
int[],String[],byte[], etc.
Working with Java Collections and Arrays
Java arrays are 1-based in BoxLang. javaArray[1] is the first element. Accessing index 0 throws an IndexOutOfBoundsException.
Java Nulls
The null keyword is the preferred way to pass a Java null. The legacy BIF alternatives remain available.
Use isNull(), the safe-navigation operator ?., and the Elvis operator ?: to guard null return values from Java methods:
See Null and Nothingness for the full language treatment.
Auto-coercing BoxLang Functions to Java Lambdas / SAM Interfaces
When a Java method expects a @FunctionalInterface (Single Abstract Method interface), BoxLang automatically wraps any closure (=>), lambda (->), or UDF. No manual createDynamicProxy call is needed.
Built-in proxy types in runtime/interop/proxies/ (no proxy generation needed):
Function, BiFunction, Consumer, BiConsumer, Supplier, Predicate, BiPredicate, Comparator, UnaryOperator, BinaryOperator, Runnable, Callable, ToIntFunction, ToLongFunction, ToDoubleFunction
For any SAM interface not in that list, InterfaceProxyService generates the proxy at runtime.
Extending Java Classes
A BoxLang class can extend a Java class by using the extends attribute with the java: prefix.
Key points:
ClassLocatorresolves the Java parent class at load time.super.init(args)calls the Java parent constructor.super.methodName(args)delegates to the Java parent method implementation.super.FIELD_NAMEreads an inherited public or protected field.
@overrideJava Annotation
Apply @overrideJava to any BoxLang method that overrides a Java parent method. Without it, the runtime may dispatch incorrectly — particularly when the Java parent has multiple overloaded forms.
Implementing Java Interfaces
A BoxLang class can implement one or more Java interfaces using the implements attribute.
Backed by InterfaceProxyService. Return values from BoxLang methods are coerced back to the Java return type via GenericProxy.coerceReturnValue.
Constraints:
The target must be a Java interface, not a class or abstract class.
The interface must be non-sealed and non-hidden.
The BoxLang class must implement every abstract method declared by the interface.
For a real-world example, see Custom Eviction Policies, which uses implements="java:ortus.boxlang.runtime.cache.policies.ICachePolicy".
Dynamic Proxies (createDynamicProxy)
createDynamicProxy is the programmatic alternative — useful when you need a stateful or multi-method proxy and the automatic SAM coercion in §7 is not sufficient.
See CreateDynamicProxy for the full BIF reference.
BaseProxy and loadContext()
When a proxy method is invoked from a Java-managed thread (e.g., inside a thread pool), the BoxLang request context is not on that thread. Call loadContext() at the top of each interface method to restore it.
Prebuilt proxy classes live in runtime/interop/proxies/ and cover the same functional types listed in the SAM section above.
Loading Custom JARs and Class Loaders
Four layered mechanisms from broadest to narrowest scope:
1. Maven (Recommended)
Add dependencies to ~/.boxlang/pom.xml, run mvn install, and the JARs land in ~/.boxlang/lib/, which BoxLang loads at startup.
~/.boxlang/lib/ is scanned once at startup. Restart the BoxLang runtime after running mvn install.
See Maven Integration for the complete workflow.
2. this.javaSettings in Application.bx
Declare per-application class loading in Application.bx:
loadPaths
Array
[]
Dirs, JARs, or individual .class files. Missing paths are silently ignored.
loadSystemClassPath
Boolean
false
Include the JVM system classpath in the application loader.
reloadOnChange
Boolean
false
Include file modification timestamps when caching the application classloader, so changed classes and JARs receive a new classloader on the next application classloader initialization.
When reloadOnChange is false, BoxLang still creates a new classloader when the loadPaths list itself changes. When it is true, BoxLang also detects changes to the files at those paths by including their last-modified timestamps in the cache key. Older classloaders for the same file set are removed from the cache after the replacement loader is created.
This check happens while the application classloader is initialized; reloadOnChange does not start a continuous filesystem watcher or poll at a configurable interval.
See Application.bx for the full reference.
3. Per-Call Class Loading
Pass a path (string) or array of paths as the third argument to createObject for isolated, one-off class loading:
This is useful for task runners, scripted utilities, or components that must use an isolated class loader.
4. Programmatic (getBoxContext / getRequestClassLoader)
Retrieve the per-request DynamicClassLoader and pass it explicitly to createObject or createDynamicProxy:
This is the narrowest scope — the class is visible only within the current request.
See GetBoxContext and GetRequestClassLoader.
Type Checking — the instanceOf Operator
The instanceOf operator is preferred over the IsInstanceOf() BIF for inline boolean expressions.
instanceOf performs case-insensitive matching and supports short names (e.g., String instead of java.lang.String).
See Operators — InstanceOf for syntax details and IsInstanceOf for the BIF form.
Java Interop BIFs
Quick reference for built-in functions directly relevant to Java interop:
Instantiate a Java class; returns an uninitialized DynamicObject for type="java".
Explicitly cast a value to a Java primitive or typed array.
Wrap a BoxLang class as a Java proxy implementing one or more interfaces.
BIF form of the instanceOf operator.
Invoke a method by name at runtime (reflection-style).
Return metadata (methods, fields, constructors) for a Java class.
Return the current IBoxContext for programmatic class loading.
Return the per-request DynamicClassLoader.
Clear the class resolver cache: SystemCacheClear("class").
Flush the page pool (also clears the class cache).
Method References and Higher-Order Functions
Passing BoxLang Functions to Java
Any BoxLang closure, lambda, or UDF can be passed directly to a Java method that expects a @FunctionalInterface. Auto-coercion handles the wrapping transparently:
Capturing and Passing Method References
Bind a method from a Java object into a variable and invoke it later like any BoxLang UDF:
Gotchas
newvscreateObject:newalways calls a constructor;createObjectdoes not — you must call.init(args)explicitly aftercreateObject.Overload disambiguation: If automatic coercion selects the wrong overload, use
castAsorjavaCastto pin the argument type.Checked exceptions: Java checked exceptions surface as
JavaExceptionin BoxLang and can be caught withcatch( type="java.lang.Exception" )or a more specific type.Wildcard import latency: Imports like
import java:java.util.*resolve each class lazily — the first use of each class in the package pays the lookup cost.implements=interfaces only: Target must be a non-sealed, non-hidden Java interface. Abstract classes and concrete classes are not supported.Silent
loadPathsmisses: Paths inthis.javaSettings.loadPathsthat do not exist are silently ignored. Verify withfileExists()if class loading fails unexpectedly.Maven restart required:
~/.boxlang/lib/is scanned at startup only. Restart the runtime aftermvn install.Java arrays are 1-indexed in BoxLang: Index
0throws. The first element is alwaysarr[1].@overrideJavais required: Omitting@overrideJavaon a BoxLang method that overrides a Java parent method may cause incorrect dispatch, especially for overloaded methods.
Last updated
Was this helpful?
