JJTemplate
RU GitHub ↗
Java 11+ · v1.0.0 · JSON-compatible output

Templates that stay
close to the data.

JJT means Java JSON Template: a compact template language that compiles JSON-shaped documents into optimized expression trees for fast, repeatable rendering.

Compile onceReuse the optimized template
Typed valuesNot string-only substitution
ExtensibleAdd namespaced Java functions
invoice.jjt
{
  "definitions": [
    { "name": "Ada" },
    { "price": 420 },
    { "quantity": 3 },
    {
      "total": "{{ math:mul .price, .quantity }}"
    }
  ],
  "template": {
    "customer": "{{ .name | string:trim }}",
    "total": "{{ .total }}",
    "tier": "{{ .total | gt 1000 ? 'priority' : 'standard' }}"
  }
}
result { "customer": "Ada", "total": 1260, "tier": "priority" }
01

Quick start

Add the library, compile a JSON-shaped script, then render it with a context map.

implementation("io.github.sibmaks.jjtemplate:jjtemplate:1.0.0")
<dependency>
  <groupId>io.github.sibmaks.jjtemplate</groupId>
  <artifactId>jjtemplate</artifactId>
  <version>1.0.0</version>
  <type>pom</type>
</dependency>
  1. 1
    Describe the script

    Pass definitions and a JSON-compatible template to TemplateScript.

  2. 2
    Compile once

    Let the compiler fold constants and prepare the expression tree.

  3. 3
    Render with context

    Supply request-specific values without recompiling the template.

Java
var script = TemplateScript.builder()
        .template(
                Map.of(
                        "message", "{{ string:concat 'Hello, ', .name }}",
                        "active", "{{ .enabled }}"
                )
        )
        .build();

var compiler = TemplateCompiler.getInstance();
var compiled = compiler.compile(script);

// Reuse compiled for every context.
var result = compiled.render(
        Map.of(
                "name", "Alice",
                "enabled", true
        )
);
02

The small syntax

JJT keeps JSON structure visible. Four interpolation forms cover substitution, conditional insertion, and spreading.

.user.name

Variable access

Read root or nested values from the render context and definitions.

{{ expression }}

Substitution

Insert a typed result directly: boolean stays boolean, number stays number.

{{? expression }}

Conditional insert

Skip an object field or array item when the expression evaluates to null.

{{. expression }}

Spread

Expand an object into an object or collection values into an array.

.value | string:trim

Pipe

Pass the left-hand value into a function and compose transformations.

.ready ? 'yes' : 'no'

Ternary

Choose a value inline; branches may contain calls, pipes, and nested expressions.

.repository?.foo('bar')

Safe member access

Return null when a property or matching method is absent; combine it with default for a fallback.

Keep expression keys explicit.

Definition keys for switch and range use the same {{ ... }} delimiters as other expressions. This keeps the source unambiguous.

03

Learn by transformation

Each example pairs a JJT document with the JSON value produced by the runtime.

template.jjt

        
output.json

        
04

Function reference

Functions are grouped by namespace. Global logic functions deliberately omit a namespace.

cast

str · int · float · boolean

Convert values with cast:str, cast:int, cast:float, or cast:boolean.

string

concat · join · format

Compose and format strings, optionally with a locale.

string

lower · upper · trim

Normalize case and remove leading or trailing whitespace.

string

len · empty · contains

Inspect string length, emptiness, and contained substrings.

string

split · substr · indexOf

Split, slice, and locate content, including negative substring indexes.

string

replace · replaceAll

Replace literal targets or regular-expression matches.

list

new · concat

Create a list or concatenate arrays and collections.

list

len · head · tail · join

Inspect, select, and join list values.

map

new · collapse

Create key-value maps or collapse object properties.

map

len · empty · contains

Inspect map size, emptiness, and key presence.

math

sum · sub · mul · div

Perform decimal-safe arithmetic, negation, and scale adjustment.

date/time

now · parse · format

Create, parse, and format date and date-time values.

locale

locale:new · numberFormat:new

Build locale-aware number and currency formatters.

global

eq · neq · lt · le · gt · ge

Compare values without a namespace.

global

not · and · or · xor

Compose boolean logic; and and or short-circuit.

global

default

Return a fallback only when the primary value is null.

05

Bring your own function

Implement the public TemplateFunction contract, register the instance, and call it by namespace and name.

ReverseTemplateFunction.java
public final class ReverseTemplateFunction
        implements TemplateFunction<String> {

    public String invoke(List<Object> args) {
        if (args.size() != 1) {
            throw fail("exactly 1 argument required");
        }
        return reverse(args.get(0));
    }

    public String invoke(List<Object> args, Object pipeArg) {
        if (!args.isEmpty()) {
            throw fail("no arguments expected");
        }
        return reverse(pipeArg);
    }

    public String getNamespace() { return "custom"; }
    public String getName() { return "reverse"; }
    public boolean isDynamic() { return false; }

    private String reverse(Object value) {
        return value == null ? null
                : new StringBuilder(value.toString()).reverse().toString();
    }
}
01

Register

var evaluation = TemplateEvaluationOptions.builder()
    .functions(List.of(new ReverseTemplateFunction()))
    .build();

var options = TemplateCompileOptions.builder()
    .evaluationOptions(evaluation)
    .build();

var compiler = TemplateCompiler.getInstance(options);
02

Call

"{{ custom:reverse .value }}"
"{{ .value | custom:reverse }}"
TIP

Make optimization honest

Return false from isDynamic() only for deterministic, side-effect-free functions. JJTemplate may fold calls whose inputs are constant.

06

Production guidance

A few deliberate choices keep templates fast, testable, and unsurprising.

01

Compile outside the hot path

Build CompiledTemplate when configuration changes, then reuse it across render calls with request-specific contexts.

02

Keep functions pure

Custom functions should be thread-safe and side-effect free. Mark a function static only when identical inputs always produce identical outputs.

03

Own a namespace

Use a stable namespace such as billing:tax. Names must be unique within a namespace, including built-ins.

04

Use lazy evaluation narrowly

Override isLazy() only for short-circuit behavior. Access the supplied arguments on demand instead of iterating or copying them.

05

Test template → output pairs

Keep representative .jjt, context, and expected JSON fixtures together. Assert semantic values, not formatting.

06

Keep JSON visible

Prefer small definitions and pipes over deeply nested expressions. The source should still read like the output it creates.

07

From source to value

JJTemplate separates parsing, optimization, and evaluation so runtime work stays focused.

01Lexer

Tokenizes JJT expressions.

02Parser

Builds expression syntax trees.

03Compiler

Creates executable nodes.

04Optimizer

Folds and simplifies once.

05Runtime

Renders JSON-compatible values.

1.0 policy

Compatibility is part of the release gate.

Supported lexer, parser, compiler API, runtime, exception, and function packages are checked against the published baseline during verification.

Read the policy →

Ready to shape some JSON?

Keep the template readable.
Let the compiler do the work.