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

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 dispatchinvokeConstructor(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 @FunctionalInterface is 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:

Prefix
Resolver
Resolves

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:

  1. EXACT — type identity.

  2. ASSIGNABLE — the value's type is a subtype or implements the interface.

  3. COERCE — BoxLang runtime coercion (e.g., Stringint).

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:

Type
Description

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 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:

  • ClassLocator resolves 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_NAME reads 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:

Add dependencies to ~/.boxlang/pom.xml, run mvn install, and the JARs land in ~/.boxlang/lib/, which BoxLang loads at startup.

See Maven Integration for the complete workflow.

2. this.javaSettings in Application.bx

Declare per-application class loading in Application.bx:

Key
Type
Default
Description

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:

BIF
Purpose

CreateObject

Instantiate a Java class; returns an uninitialized DynamicObject for type="java".

JavaCast

Explicitly cast a value to a Java primitive or typed array.

CreateDynamicProxy

Wrap a BoxLang class as a Java proxy implementing one or more interfaces.

IsInstanceOf

BIF form of the instanceOf operator.

Invoke

Invoke a method by name at runtime (reflection-style).

GetClassMetadata

Return metadata (methods, fields, constructors) for a Java class.

GetBoxContext

Return the current IBoxContext for programmatic class loading.

GetRequestClassLoader

Return the per-request DynamicClassLoader.

SystemCacheClear

Clear the class resolver cache: SystemCacheClear("class").

PagePoolClear

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

  • new vs createObject: new always calls a constructor; createObject does not — you must call .init(args) explicitly after createObject.

  • Overload disambiguation: If automatic coercion selects the wrong overload, use castAs or javaCast to pin the argument type.

  • Checked exceptions: Java checked exceptions surface as JavaException in BoxLang and can be caught with catch( 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 loadPaths misses: Paths in this.javaSettings.loadPaths that do not exist are silently ignored. Verify with fileExists() if class loading fails unexpectedly.

  • Maven restart required: ~/.boxlang/lib/ is scanned at startup only. Restart the runtime after mvn install.

  • Java arrays are 1-indexed in BoxLang: Index 0 throws. The first element is always arr[1].

  • @overrideJava is required: Omitting @overrideJava on a BoxLang method that overrides a Java parent method may cause incorrect dispatch, especially for overloaded methods.

Last updated

Was this helpful?