Blog

Luis Majano

June 10, 2026

Spread the word


Share your thoughts

BoxLang 1.14 ships with one of the most developer-friendly OOP features we've built yet: local template classes. If you've ever created a throwaway .bx file just to hold a five-line helper class, this one's for you.

The Problem With File-Per-Class

BoxLang, like most class-based languages, traditionally ties class definitions to files. One class, one file. That's great for production code you'll reuse everywhere -- but it's friction when you need a small data container, a quick utility, or a Java interface implementation that lives and dies with a single script. You end up with a folder of .bx files that exist solely to support one other file.

Local template classes eliminate that friction entirely.


What Are Local Template Classes?

A local template class (also called a template class) is a named class defined directly inside a .bxs script or inside a <bx:script> island in a .bxm template. It's compiled as part of the enclosing file, scoped to that compilation unit, and fully featured -- properties, constructors, inheritance, static members, Java interop, the works.

class Greeter {
    function greet( name ) {
        return "Hello, " & name & "!"
    }
}

result = new Greeter().greet( "World" )
// "Hello, World!"

That's it. No separate file. No import statement. The class is available right there in the same script. Try it for yourself on try.boxlang.io.

Once you create an instance, you can pass it anywhere in your application -- the local-only restriction applies to the class definition, not to the instance.

BoxLang-only feature: Template classes are not available in the CFML parser. They are a BoxLang-exclusive language feature.


Hoisting -- Use Before You Define

Template classes are hoisted to the top of the compilation unit. That means you can instantiate a class above its definition in the file and the compiler won't complain.

// Instantiating before definition -- works perfectly
result = new Greeter().greet( "World" )

class Greeter {
    function greet( name ) {
        return "Hello, " & name & "!"
    }
}

println( result )

This keeps your script's main logic at the top and the supporting class definitions below, which often reads more naturally.


Multiple Classes in One File

You can define as many template classes as you need in a single script:

class Adder {
    function add( a, b ) {
        return a + b
    }
}

class Multiplier {
    function multiply( a, b ) {
        return a * b
    }
}

myAdder      = new Adder()
myMultiplier = new Multiplier()
result       = myMultiplier.multiply( myAdder.add( 2, 3 ), 4 )
// 20

Each class name must be unique within the file (case-insensitive). Duplicate names throw a compile-time error.


Properties, Constructors, and Accessors

Template classes support the full property system -- including default values and auto-generated getters/setters (remember, accessors=true is the BoxLang default):

class Counter {
    property name="count" default=0;

    function init( numeric startingValue=0 ) {
        variables.count = arguments.startingValue
    }

    function increment() {
        variables.count++
    }
}

c = new Counter( 5 )
c.increment()
c.increment()
c.increment()
println( c.getCount() ) // 8 -- getCount() generated automatically

Static Members

Static initializer blocks, static variables, and static methods are all supported:

class Config {
    static {
        static.MAX_RETRIES = 5
        static.APP_NAME    = "MyApp"
    }
}

println( Config::MAX_RETRIES ) // 5
println( Config::APP_NAME )    // "MyApp"

class MathUtil {
    static function add( a, b ) {
        return a + b
    }

    static function multiply( a, b ) {
        return a * b
    }
}

result  = MathUtil::add( 3, 4 )       // 7
result2 = MathUtil::multiply( 5, 6 )  // 30

println( result )
println( result2 )

Inheritance, Abstract, and Final

Template classes can extend other template classes defined in the same file, with full multi-level inheritance and super support:

class Vehicle {
    function init( make ) {
        variables.make = make
        return this
    }

    function getMake() {
        return variables.make
    }
}

class Car extends="Vehicle" {
    function init( make, model ) {
        super.init( make )
        variables.model = model
        return this
    }

    function getInfo() {
        return this.getMake() & " " & variables.model
    }
}

result = new Car( "Toyota", "Camry" ).getInfo()
// "Toyota Camry"

Abstract and final modifiers work exactly as you'd expect:

// Abstract -- cannot be instantiated directly
abstract class Shape {
    function describe() {
        return "I am a shape"
    }
}

class Circle extends="Shape" {
    function init( radius ) {
        variables.radius = radius
        return this
    }

    function getRadius() {
        return variables.radius
    }
}

myCircle = new Circle( 5 )
println( myCircle.describe() )   // "I am a shape"
println( myCircle.getRadius() )  // 5
// Final -- cannot be extended
final class Immutable {
    function getValue() {
        return "fixed"
    }
}

There is no limit to inheritance depth -- just like top-level classes.


Java Interop -- Implementing Interfaces and Extending Java Classes

This is where template classes really shine for JVM developers. You can implement Java interfaces and extend Java base classes inline, without any scaffolding:

Implementing Runnable

import java:java.lang.Thread

class MyRunnable implements="java:java.lang.Runnable" {
    property name="didRun" default=false;

    function run() {
        variables.didRun = true
    }
}

r       = new MyRunnable()
jThread = new java:Thread( r )
jThread.start()
jThread.join()
println( r.getDidRun() ) // true

Extending a Java Base Class

class MyTask extends="java:java.util.TimerTask" {

    @override
    void function run() {
        println( "Hello from local TimerTask!" )
    }

}

task = new MyTask()
println( task instanceof "java.util.TimerTask" )

Implementing Comparable

class Ranked implements="java:java.lang.Comparable" {
    
  property name="rank" default=0;

    function init( rank ) {
        variables.rank = rank
        return this
    }

    int function compareTo( other ) {
        return variables.rank - other.getRank()
    }
}

a = new Ranked( 3 )
b = new Ranked( 7 )
println( a.compareTo( b ) ) // negative (3 < 7)

Shared Imports

Template classes inherit the import declarations from their enclosing script. Any import at the top of your .bxs file is available inside every template class defined in that file:

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()
    }
}

result = new Event( "BoxLang Launch" ).getInfo()
// "BoxLang Launch at Tue Jun 03 12:34: 56 UTC 2026"

Metadata

Template classes expose full metadata through the standard BoxLang APIs:

class Person {
    property name="firstName" default="John";
    property name="lastName" default="Doe";

    function fullName() {
        return this.getFirstName() & " " & this.getLastName()
    }
}

meta = getMetadata( new Person() )
println( meta.name )       // "Person"
println( meta.type )       // "Class"
println( meta.properties ) // Array with 2 entries

You can also look up metadata by name without instantiating first:

meta = getClassMetadata( "Person" )
println( meta.name ) // "Person"

Note: getClassMetadata() is the BoxLang rename of CFML's getComponentMetadata() -- same behavior, aligned naming convention.


Using Template Classes in .bxm Templates

Template classes can be defined inside <bx:script> islands within your .bxm markup templates:

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

You can even nest inner classes inside a template class within a script island:

<bx:script>
    class Parent {
        class Child {
            function greet() {
                return "hi"
            }
        }

        function getChild() {
            return new Child()
        }
    }

    result = new Parent().getChild().greet()
</bx:script>
<!-- Result: "hi" -->

Limitations

A few constraints to keep in mind:

No duplicate names. Class names must be unique within a file, case-insensitively. Person and PERSON in the same file is a compile error.

No import conflicts. A template class name cannot share a name with an imported alias -- and vice versa.

No class definitions inside functions. Template classes must live at the top level of the script or template. Defining one inside a function body throws a compile error.

Script-only. Classes cannot be defined in tag-based BoxLang source outside of a <bx:script> block.


Try It Now

Template classes work on try.boxlang.io today -- that's actually one of the motivating use cases. Since the online REPL compiles a single file at a time, template classes let you build real OOP examples without any file system at all. Paste any of the snippets above and run them instantly.

The feature is available now on the 1.14 snapshot builds. Give it a spin and let us know what you build.


What's Next

Template classes are one half of the inline class story in BoxLang 1.14. The other half is inner classes -- named classes defined inside .bx class bodies, accessible via the $ separator syntax for external use. Read about them here: Introducing Inner Classes in BoxLang.

Full documentation is available at boxlang.ortusbooks.com/boxlang-language/classes/template-classes.


Resources


Have questions or feedback? Join the conversation on the Ortus Community. Follow us on X and LinkedIn for the latest BoxLang news.

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
BoxLang 1.14.0 : Navigate Anything: JSONPath Comes to BoxLang's DataNavigator

BoxLang 1.14.0 : Navigate Anything: JSONPath Comes to BoxLang's DataNavigator

Every application eventually has to deal with deeply nested data. JSON API responses with payloads six levels deep. Configuration files where the key you need is buried inside an array of objects, one of which has a null for the field you thought was required. Module metadata structures that nobody wrote a schema for. Runtime introspection data shaped like a tree that grew without a plan.

Luis Majano
Luis Majano
June 03, 2026