Introducing Inner Classes in BoxLang

Introducing Inner Classes in BoxLang

Following up on our template classes feature, we’re thrilled to announce inner classes — named classes defined inside .bx class files. If Template classes let you define helpers inline in scripts, inner classes let you do the same thing inside your classes themselves.

This feature exists currently on the 1.14 snapshot builds.

What Are Inner Classes?

An inner class is a named class defined directly inside the body of another .bx class. It’s compiled as a sibling JVM class with a $-delimited name (e.g., Outer$Inner) and can be used by the outer class, other inner classes in the same file, or even accessed externally.

MyComponent.bx

class {

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

    function sayHello( name ) {
        return new Helper().greet( name );
    }

}

Think of them as co-located utility classes that live alongside the code that uses them — no extra files cluttering your project. Just like template classes, inner classes support full inheritance (from other inner classes, external BoxLang classes, or Java classes), nesting to unlimited depth, and all the OOP features you’d expect.

How Are Inner Classes Different from Template Classes?

Template classes live in scripts (.bxs) and templates (.bxm). Inner classes live inside .bx class files. Here’s what makes inner classes unique:

  • Defined in: Template classes live in scripts/templates; inner classes live in .bx class files
  • External access: Template classes are not accessible outside their file; inner classes are accessible anywhere via $ syntax
  • Outer static access: Inner classes can reference their outer class’s static members by name

Nesting — Classes Inside Classes Inside Classes

Inner classes can be nested to arbitrary depth:

Nested.bx

class {

    class First {

        class Second {
            function getDepth() {
                return "second level";
            }
        }

        function getSecond() {
            return new Second();
        }

    }

    function drill() {
        return new First().getSecond().getDepth();
    }

}

Each level compiles with an additional $ separator: Outer$First$Second.

Accessing the Outer Class’s Static Members

Inner classes can reference the outer class’s static members by name using either dot or double-colon syntax:

MyService.bx

class MyService {

    static {
        VERSION = "2.0.0";
    }

    class Reporter {
        function getVersion() {
            // Both syntaxes work
            return MyService::VERSION;
            // or: return MyService.VERSION;
        }
    }

    function getReporter() {
        return new Reporter();
    }

}

This gives inner classes a natural way to share configuration and constants with their enclosing class without passing them through constructors.

External Access via $ Syntax

Inner classes aren’t trapped inside their outer class. You can instantiate them directly from anywhere using the $ separator in the class path:

App.bxs

// Direct instantiation
widget = new com.myapp.Dashboard$Widget( "stats" );

// Or import first
import com.myapp.Dashboard$Widget;
myWidget = new Widget( "chart" );

// With an alias
import com.myapp.Dashboard$Widget as DashWidget;
myWidget = new DashWidget( "chart" );

You can also access their static members externally:

App.bxs

import com.myapp.Dashboard$Config;
maxRetries = Config::MAX_RETRIES;

For deeply nested inner classes, chain the $ separators:

App.bxs

result = new com.myapp.Outer$First$Second().doSomething();

Inner Classes with Inheritance

Inner classes can extend other inner classes defined in the same outer class:

Zoo.bx

class Zoo {

    class Animal {
        function speak() {
            return "...";
        }
    }

    class Dog extends="Animal" {
        function speak() {
            return "Woof!";
        }
    }

    function getDog() {
        return new Dog();
    }

}

This gives you polymorphism scoped to a single file — perfect for strategy patterns, state machines, or type hierarchies that only make sense in the context of their enclosing class.

Java Interop — Implementing Interfaces

Inner classes support implements and extends for Java types, just like any BoxLang class. This is where things get really powerful — you can define collaborating classes that implement Java interfaces right next to the code that uses them.

Here’s a real-world example: a class that implements java.lang.Iterable using an inner class that implements java.util.Iterator:

IterableList.bx

class implements="java:java.lang.Iterable" {

    property Array items;

    function init( array items = [] ) {
        variables.items = arguments.items;
        return this;
    }

    function get( numeric index ) {
        return variables.items[ index + 1 ];
    }

    function size() {
        return variables.items.len();
    }

    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 ];
        }

    }

}

Now your BoxLang class is a proper Java Iterable — it works in Java for-each loops, can be passed to any Java API expecting an Iterable, and the Iterator implementation is neatly tucked away as an inner class rather than polluting your project with a separate file.

Hoisting

Just like Template classes, inner classes are hoisted. You can reference them before their textual definition in the class body:

IterableList.bx

class {

    // Works! BoxIterator is defined below.
    function iterator() {
        return new BoxIterator( variables.items );
    }

    class BoxIterator implements="java:java.util.Iterator" {
        // ...
    }

}

Metadata and Introspection

Inner classes are fully introspectable via getClassMetadata() using the $ syntax:

App.bxs

meta = getClassMetadata( "com.myapp.Dashboard$Widget" );
println( meta.name );       // "com.myapp.Dashboard$Widget"
println( meta.type );       // "Class"
println( meta.properties ); // Array of property definitions

Rules and Constraints

  1. Inner classes are static — Inner classes retain no reference to an instance of the outer class. They are standalone classes that happen to be defined inside the outer class body. If you need data from the outer class, pass it explicitly (as in the Iterator example above).

  2. No name collisions with the outer class — An inner class cannot have the same name as the enclosing file’s class.

  3. Outer class name is reserved — When a class has inner classes, the outer class name becomes a reserved identifier within the class body. You cannot use it as a variable or argument name.

  4. Unique names per level — Each inner class name must be unique among its siblings (case-insensitive).

When to Use Inner Classes

Inner classes shine when you have:

  • Implementation details that only make sense in context of the outer class (iterators, builders, state objects)
  • Strategy or callback implementations for Java interfaces that are tightly coupled to the outer class
  • Type hierarchies scoped to a single component (AST nodes, event types, command objects)
  • Encapsulation of helper logic without file proliferation

Summary

Inner classes bring the full power of BoxLang’s OOP system inside your class files — nesting, inheritance, static members, Java interop — all without creating extra files. Combined with external $ access, they give you fine-grained modularity with the option of exposing inner types when needed.

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

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