Blog

Luis Majano

June 03, 2026

Spread the word


Share your thoughts

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:


The Two Flavors

Before diving in, a quick orientation:

FeatureTemplate ClassesInner Classes
Defined in.bxs scripts or .bxm template <bx:script> blocksInside a .bx class body
ScopeLocal to that compilation unitAccessible externally via $ syntax
HoistingYesYes
InheritanceFullFull
InterfacesFullFull
Java interopFullFull

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 .bxs script 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 .bx file
  • 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

Recent Entries

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

MatchBox and WebAssembly: Running BoxLang in the Browser and at the Edge

The MatchBox open beta is live at https://boxlang.ortusbooks.com/boxlang-framework/matchbox, and it brings something genuinely new to the BoxLang ecosystem: a path into WebAssembly.

That means BoxLang code can now move into browser applications, static-site deployments, edge runtimes, and WASI-style containers - without requiring a JVM. The feature is still beta, but the core direction is already useful: write BoxLang, compile it with MatchBox, and ship the generated WASM artifact to wherever a small portable runtime makes sense.

Jacob Beers
Jacob Beers
June 04, 2026
One Language, Every Runtime: BoxLang Expands Beyond the Server

One Language, Every Runtime: BoxLang Expands Beyond the Server

Discover how BoxLang’s multi-runtime architecture helps developers build beyond the server with support for serverless functions, desktop applications, CI/CD workflows, Java integrations, containers, runtime management, and more.

Maria Jose Herrera
Maria Jose Herrera
June 04, 2026