Architecture

1. Pipeline

A template string flows through four stages:

Template string -> Lexer -> Tokens -> Parser -> AST -> Executor -> Output
  • Lexer (parse/Lexer.java) — scans the template into tokens, tracking action delimiters and trim markers.

  • Parser (parse/Parser.java) — builds an AST of Node types (actions, pipelines, if/range/with, defined templates).

  • Executor (exec/Executor.java) — walks the AST against the data value, resolving fields, invoking functions, and writing output. A fresh executor is created per execute call, so a parsed template can be executed concurrently.

2. Modules and the function SPI

gotmpl4j-core knows only Go’s built-in functions (Functions.GO_BUILTINS). Everything else is contributed by FunctionProvider implementations discovered through ServiceLoader:

Provider Priority Source module

Go built-ins

gotmpl4j-core

Sprig

100

gotmpl4j-sprig

Helm

200

(external — jhelm)

Higher priority wins on name collisions. To add your own functions, implement FunctionProvider, register it in META-INF/services, and put it on the classpath — or pass functions directly through GoTemplate.builder().withFunctions(…​).

The core module must not depend on Sprig, Helm, or any host application; that boundary is what lets the engine be embedded standalone.

3. HTML auto-escaping

The html/ package is a port of Go’s html/template contextual escaper: a state machine that tracks the HTML/attribute/URL/JS/CSS context at each interpolation point and inserts the appropriate escaper. It is opt-in via GoTemplate.builder().htmlEscaping().

4. Caching

TemplateCache stores compiled ASTs so repeated renders of the same template skip the lex/parse stages. The Spring starter wires it in by default; it uses double-checked locking and is safe for concurrent use.

5. API stability

From 1.0, the supported public API — everything covered by semantic versioning — is driving the engine through GoTemplate (parse / execute / render / compiled / builder), the Function / FunctionProvider SPI, the MissingKeyMode option, and the GoTemplateException hierarchy. The Spring Boot starter’s documented properties and beans are covered too.

GoTemplate.getRootNodes() and getFunctions() — and the org.alexmond.gotmpl4j.parse.Node types they expose — are an advanced integrator surface, not general application API. They return the engine’s live, mutable internals so that engines built on top of gotmpl4j (such as Helm’s tpl / include, implemented in jhelm) can share named templates across instances. Because they leak internals, they are not covered by the 1.0 stability guarantee and may change in a minor release; a first-class replacement is tracked in issue #48. Application code should not depend on them.