Introducing Local Template Classes in BoxLang

Introducing Template Classes in BoxLang

We’re excited to introduce Template classes — a powerful new feature that lets you define named classes inline within your .bxs scripts or .bxm templates without needing a separate file. This also means you’ll be able to use them on try.boxlang.io which will make it much more powerful.
Inner classes are here now as well, and detailed in this post. Introducing Inner Classes in BoxLang

This feature exists currently on the 1.14 snapshot builds

What Are Template Classes?

A Template class is a named class defined directly inside a script or template. It’s scoped to that compilation unit and compiled as part of the enclosing file. No more creating a separate .bx file just for a small helper class!

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

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

Defining a Template class basically works the exact same way as importing an external class like so:

import foo.bar.Greeter;

result = new Greeter().greet( "World" );

Template classes are NOT accessible outside of the template. They exist only in their little world. Of course, once you create an instance of one, you can pass it wherever you want.


Where Do Template Classes Work?

Template classes can be defined in:

  • BoxLang scripts (.bxs files)
  • BoxLang templates (.bxm files) inside <bx:script> islands

Template classes are NOT a feature of the CFML parser, so this is a BoxLang-only feature!

<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();
    // "(3,4)"
</bx:script>

Remember, classes CANNOT be defined in tag-based code in BoxLang source. It’s script-only, baby!


Hoisting

Template classes are hoisted — you can use them before their textual definition in the file. The compiler pre-processes all Template classes before executing the script body.

// Works! The class is usable before it's defined.
result = new Greeter().greet( "World" );

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

Properties and Constructors

Template classes support properties with defaults, and constructor (init) functions just like top-level classes:

class Counter {
    property numeric 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 (using generated getter)

Remember, accessors is set to true by default in BoxLang classes!


Multiple Template Classes

You can define as many Template classes as you need in a single file:

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

Using Imports

Template classes can reference any imports from the enclosing script:

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( "Party" ).getInfo();
// "Party at Sun May 18 10:30:00 CDT 2026"

Inheritance — Extending Other Template Classes

Template classes can extend other Template classes defined in the same file, including multi-level inheritance and super calls:

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"

There is no limit to the level of inheritance you can use, just like normal classes.


Static Members

Template classes support static initializers, static variables, and static methods:

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

Abstract and Final Classes

Template classes support abstract and final modifiers:

// Abstract class — 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 class — cannot be extended
final class Immutable {
    function getValue() {
        return "fixed";
    }
}

// This would throw an error:
// class Child extends="Immutable" { ... }

Java Interop — Implementing Interfaces and Extending Java Classes

Template classes can implement Java interfaces and extend Java classes just like normal classes:

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
class MyTask extends="java:java.util.TimerTask" {
    @overrideJava
    void function run() {
        println( "Hello from local TimerTask!" );
    }
}

task = new MyTask();
assert task instanceof "java.util.TimerTask";
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 number (3 < 7)

Metadata / Reflection

You can inspect Template class metadata using getMetaData(), getClassMetadata(), or the $bx.meta accessor:

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 class name without an instance:

class Widget {
    property name="label" default="default";
    function getLabel() { return variables.label; }
}

meta = getClassMetadata( "Widget" );
println( meta.name ); // "Widget"

Note, the getClassMetadata() BIF in BoxLang is the same as the getComponentMetadat() BIF in CFML, but we renamed it to match our naming conventions. You can only reference the Template class statically by name inside the specific file it’s defined in.


Limitations

  1. No duplicate names — Each Template class name must be unique within the file (case-insensitive):
// ERROR: Duplicate local class name [Person]
class Person { }
class Person { }
  1. No name conflicts with imports — A Template class cannot share a name with an imported class:
// ERROR: Local class [Widget] conflicts with an import of the same name
import java:java.util.HashMap as Widget;
class Widget { }

Summary

Template classes give you the full power of BoxLang’s OOP system — properties, inheritance, static members, abstract/final modifiers, Java interop — all without leaving your script file. They’re perfect for:

  • Quick utility/helper classes that don’t deserve their own file
  • Script-scoped data models and DTOs
  • Implementing Java interfaces inline (Runnable, Comparable, etc.)
  • Prototyping and REPL usage

Give them a try and let us know what you think!

https://ortussolutions.atlassian.net/browse/BL-2135

This feature exists currently on the 1.14 snapshot builds

Read to learn more-- read here about inner classes in your bx files.