BoxLang has always embraced a simple truth: the way you organize code shapes the way you think about problems. For a long time, if you needed a helper class, you needed a file. One class, one .bx file, no exceptions. This also stemmed from the CFML days. That's clean and predictable, but it creates real friction when a class is tightly coupled to exactly one caller and has no business existing anywhere else.
BoxLang 1.14.0 removes that friction entirely. You can now define classes inline - inside scripts, inside templates, and nested inside other classes. No separate file required. No ceremony. The class lives exactly where it belongs.
This post covers both flavors of locally defined classes shipped in 1.14.0: Template Classes and Inner Classes. You can read more about them in our docs:
- https://boxlang.ortusbooks.com/boxlang-language/classes/inner-classes
- https://boxlang.ortusbooks.com/boxlang-language/classes/template-classes
The Two Flavors
Before diving in, a quick orientation:
| Feature | Template Classes | Inner Classes |
|---|---|---|
| Defined in | .bxs scripts or .bxm template <bx:script> blocks | Inside a .bx class body |
| Scope | Local to that compilation unit | Accessible externally via $ syntax |
| Hoisting | Yes | Yes |
| Inheritance | Full | Full |
| Interfaces | Full | Full |
| Java interop | Full | Full |
Both share the same fundamental capability: define a class right where you need it, with zero boilerplate. The difference is where you need it. Please also note that in BoxLang you cannot define classes using template markup and this feature applies ONLY to BoxLang templates: bx, bxs, bxm
Template Classes
A template class is a named class declared inline inside a .bxs script or a .bxm template's <bx:script> block.
class Greeter {
function greet( name ) {
return "Hello, " & name & "!"
}
}
result = new Greeter().greet( "World" )
// → "Hello, World!"
That is a complete, fully functional BoxLang script. No imports, no file path, no module resolution. The class is defined and used in the same compilation unit.
Hoisting
Template classes are hoisted to the top of their compilation unit. You can instantiate a class before its textual definition appears - which keeps the "main logic first" narrative flow that makes scripts readable:
// Use before definition - perfectly valid
result = new Greeter().greet( "BoxLang" )
class Greeter {
function greet( name ) {
return "Hello, " & name & "!"
}
}
Multiple Classes in One Script
Multiple template classes coexist naturally. They can even reference each other:
class Adder {
function add( a, b ) {
return a + b
}
}
class Multiplier {
function multiply( a, b ) {
return a * b
}
}
adder = new Adder()
multiplier = new Multiplier()
result = multiplier.multiply( adder.add( 2, 3 ), 4 )
// → 20
Properties, Constructors, and Static Members
Template classes are full-featured BoxLang classes. Properties, init() constructors, and static blocks all work exactly as they do in file-based classes:
class Counter {
property numeric count default=0
function increment() {
variables.count++
}
function getCount() {
return variables.count
}
}
c = new Counter()
c.increment()
c.increment()
c.increment()
c.getCount() // → 3
Static members work too - useful for shared constants and utility methods:
class MathUtil {
static {
PI = 3.14159265358979
}
static function circleArea( radius ) {
return MathUtil::PI * radius ^ 2
}
}
MathUtil::circleArea( 5 ) // → ~78.54
Inheritance
Template classes can extend other template classes defined in the same script, including multi-level chains and super delegation:
abstract class Shape {
abstract function area()
function describe() {
return "Area: " & this.area()
}
}
class Circle extends="Shape" {
function init( radius ) {
variables.radius = radius
return this
}
function area() {
return 3.14159 * variables.radius ^ 2
}
}
new Circle( 5 ).describe() // → "Area: 78.53975"
Java Interoperability
Template classes can implement Java interfaces and extend Java classes, making them a clean fit for interop patterns:
class MyRunnable implements="java:java.lang.Runnable" {
property name="didRun" default=false
void function run() {
variables.didRun = true
}
}
r = new MyRunnable()
thread = new java:Thread( r )
thread.start()
thread.join()
r.getDidRun() // → true
Imports Are Shared
Template classes inherit the enclosing script's imports. Java types are available directly without any extra ceremony:
import java.util.Date
class Event {
function init( name ) {
variables.name = name
variables.timestamp = new Date()
return this
}
function getInfo() {
return variables.name & " at " & variables.timestamp.toString()
}
}
new Event( "Launch" ).getInfo() // → "Launch at Wed Jun 03 ..."
Template Classes in .bxm Files
Template classes work inside <bx:script> islands in markup templates, bringing the same capability to your view layer:
<bx:script>
class Point {
function init( x, y ) {
variables.x = x
variables.y = y
return this
}
function toString() {
return "(" & variables.x & "," & variables.y & ")"
}
}
result = new Point( 3, 4 ).toString()
</bx:script>
<bx:output>#result#</bx:output>
Inner Classes
An inner class is a named class declared inside the body of another class in a .bx file. Where template classes are scoped to a single compilation unit, inner classes are part of their enclosing class's compiled output and are accessible from outside via the $ separator syntax.
// models/Container.bx
class Container {
class Widget {
function init( label ) {
variables.label = label
return this
}
function getLabel() {
return variables.label
}
}
function createWidget( label ) {
return new Widget( label )
}
}
c = new Container()
w = c.createWidget( "header-nav" )
w.getLabel() // → "header-nav"
Hoisting in Inner Classes
Like template classes, inner classes are hoisted within the class body. You can instantiate an inner class in a function that appears before the inner class definition:
class Outer {
function getWidget() {
return new Widget() // Works - Widget is hoisted
}
class Widget {
function getName() {
return "widget"
}
}
}
new Outer().getWidget().getName() // → "widget"
Multiple and Nested Inner Classes
A class can contain as many inner classes as it needs. Inner classes can themselves contain inner classes:
class Outer {
class First {
class Second {
function getDepth() {
return "second"
}
}
function getSecond() {
return new Second()
}
function getDepth() {
return "first"
}
}
function getFirst() {
return new First()
}
}
outer = new Outer()
first = outer.getFirst()
first.getDepth() // → "first"
first.getSecond().getDepth() // → "second"
Inheritance Between Inner Classes
Inner classes can extend other inner classes in the same outer class, enabling polymorphic patterns without the overhead of separate files:
class Zoo {
class Animal {
function speak() {
return "..."
}
}
class Dog extends="Animal" {
function speak() {
return "Woof!"
}
}
class Cat extends="Animal" {
function speak() {
return "Meow!"
}
}
function getDog() { return new Dog() }
function getCat() { return new Cat() }
}
zoo = new Zoo()
zoo.getDog().speak() // → "Woof!"
zoo.getCat().speak() // → "Meow!"
Accessing Outer Class Statics
Inner classes can reach back into their enclosing class's static members via dot or double-colon notation:
class Config {
static {
MAX_POOL_SIZE = 50
APP_NAME = "MyApp"
}
class Validator {
function validate( size ) {
return size <= Config::MAX_POOL_SIZE
}
function getAppName() {
return Config.APP_NAME
}
}
}
v = new Config().getValidator()
v.validate( 20 ) // → true
v.validate( 100 ) // → false
v.getAppName() // → "MyApp"
External Access via $ Syntax
Inner classes are compiled as sibling JVM classes with $-delimited names. This means they are accessible from anywhere - not just from within the outer class:
// Fully qualified external instantiation
widget = new models.Container$Widget( "my-widget" )
widget.getLabel() // → "my-widget"
// Static access on a nested inner class
second = new models.Outer$First$Second()
second.getDepth() // → "second"
Importing Inner Classes
You can import inner classes directly, with or without an alias:
// Direct import
import models.Container$Widget
widget = new Widget( "imported" )
widget.getLabel() // → "imported"
// Import with alias
import models.Container$Widget as NavWidget
nav = new NavWidget( "top-nav" )
nav.getLabel() // → "top-nav"
You can also reference inner classes via the outer class name after importing it:
import models.Container
// Both of these work
widgetClass = Container.Widget
widgetClass = Container::Widget
w = new widgetClass( "via-reference" )
Java Interoperability
Inner classes are especially powerful for Java interop patterns like Iterator implementations, where the inner class is tightly coupled to its parent but needs to satisfy a Java interface contract:
class BoxList implements="java:java.lang.Iterable" {
property Array items
function init( array items = [] ) {
variables.items = arguments.items
return this
}
function iterator() {
return new BoxIterator( variables.items )
}
class BoxIterator implements="java:java.util.Iterator" {
property name="data"
property name="position"
function init( array data ) {
variables.data = arguments.data
variables.position = 0
return this
}
boolean function hasNext() {
return variables.position < variables.data.len()
}
function next() {
if ( !hasNext() ) {
throw(
type = "java.util.NoSuchElementException",
message = "No more elements"
)
}
variables.position++
return variables.data[ variables.position ]
}
}
}
list = new BoxList( [ "a", "b", "c" ] )
iter = list.iterator()
while ( iter.hasNext() ) {
println( iter.next() )
}
// → a
// → b
// → c
Introspection and Metadata
Both template classes and inner classes expose full metadata through BoxLang's standard reflection API.
// Template class metadata
meta = getMetadata( new Circle( 5 ) )
meta.name // → "Circle"
meta.type // → "Class"
meta.properties // → array of property descriptors
meta.functions // → array of function descriptors
// Inner class metadata
widget = new models.Container$Widget( "test" )
meta = getMetadata( widget )
meta.name // → "models.Container$Widget"
meta.simpleName // → "Widget"
meta.enclosingClass // → "models.Container"
meta.innerClasses // → {} (empty unless Widget itself has inner classes)
isInstanceOf() works naturally with both the simple name and the fully qualified $ path:
isInstanceOf( widget, "Widget" ) // → true
isInstanceOf( widget, "models.Container$Widget" ) // → true
When to Use Which
Use template classes when:
- Working in a
.bxsscript or REPL session - Prototyping on try.boxlang.io
- Defining a helper class that is consumed in one template or script and has no standalone value
- Writing test fixtures inline in TestBox specs
Use inner classes when:
- The helper class is tightly coupled to a specific parent class in a
.bxfile - You want external code to be able to instantiate or import the inner class independently
- Implementing Java interface contracts (iterators, comparators, runnables) that belong to a specific parent
- Building builder patterns, strategy variants, or value types that only make sense in context
Use file-based classes when:
- The class is shared across multiple files or modules
- The class is a primary domain model, service, or handler
- Reusability and discoverability matter more than co-location
Getting Started
Both features are available in BoxLang 1.14.0 with no configuration required.
Update via CommandBox:
box update boxlang
Or grab the latest from boxlang.io.
Full documentation:
We would love to hear how you are using locally defined classes in your BoxLang applications. Share what you build in the Ortus Community.
If you want extended capabilities - bx-ai, bx-mcp, bx-jwt, bx-redis, and the full BoxLang+ module ecosystem - visit boxlang.io/plans to see what fits your team.
Add Your Comment