Extending functions

Template functions are pluggable. The Go builtins ship in gotmpl4j-core, Sprig adds ~220 more when gotmpl4j-sprig is on the classpath, and your application can contribute its own — through the same SPI the built-in libraries use. There are three ways in, from quickest to most reusable.

1. The Function interface

A function is a single method that receives its evaluated pipeline arguments and returns the value passed to the next stage (or rendered):

@FunctionalInterface
public interface Function {
    Object invoke(Object... args);
}

The last argument of a pipeline is the piped value, so {{ .name | greet "Hello" }} calls greet with ("Hello", <value of .name>).

2. 1. Inline functions on a single template

For one-off or application-local helpers, hand a name→Function map to the Builder:

GoTemplate tpl = GoTemplate.builder()
        .withFunctions(Map.of(
                "shout", (Function) args -> String.valueOf(args[0]).toUpperCase(Locale.ROOT)))
        .build();

tpl.parse("t", "{{ .name | shout }}!");   // -> "WORLD!"

These override any discovered function of the same name (they are applied last).

3. 2. A reusable FunctionProvider

To package a set of functions for reuse — and to have them auto-register on the classpath — implement FunctionProvider:

public class GreetingFunctions implements FunctionProvider {

    @Override
    public Map<String, Function> getFunctions(GoTemplate template) {
        return Map.of("greet", args -> args[1] + ", " + args[0] + "!");
    }

    @Override
    public int priority() {
        return 150;   // applied after Sprig (100), before Helm (200)
    }
}

getFunctions receives the owning GoTemplate, so a provider may capture it (for example to share named templates or compose with other functions).

3.1. Auto-discovery via ServiceLoader

Register the provider as a service and it is discovered automatically whenever the jar is on the classpath — no wiring:

src/main/resources/META-INF/services/org.alexmond.gotmpl4j.FunctionProvider
com.example.GreetingFunctions

Now new GoTemplate() already has greet. (Pass GoTemplate.builder().noAutoDiscovery() to opt out of classpath discovery and register providers explicitly.)

3.2. Explicit registration

Skip discovery and add a provider directly:

GoTemplate tpl = GoTemplate.builder()
        .withProvider(new GreetingFunctions())
        .build();

4. Priority and name collisions

When two providers register the same name, the higher priority() wins. The built-in bands are:

Provider Priority Source

Go builtins

0

gotmpl4j-core

Sprig

100

gotmpl4j-sprig

Helm

200

external (jhelm)

Inline withFunctions(…​) always wins, regardless of priority — it is layered on top last.

5. Signalling failure

Throw FunctionExecutionException (unchecked) to fail a render from inside a function; the engine surfaces it wrapped in a TemplateExecutionException (both under the GoTemplateException root — see Error handling):

Function div = args -> {
    long d = ((Number) args[1]).longValue();
    if (d == 0) {
        throw new FunctionExecutionException("div: division by zero");
    }
    return ((Number) args[0]).longValue() / d;
};

6. Spring Boot

The starter wires the SPI the Spring-idiomatic way (mirroring how Thymeleaf collects IDialect beans): every FunctionProvider bean in the context is registered with the engine, and a single Map<String, Function> bean — if one is present — contributes extra name→function entries that override the providers on top.

@Configuration
class TemplateFunctionsConfig {

    @Bean
    FunctionProvider greetingFunctions() {
        return new GreetingFunctions();          // a whole provider
    }

    @Bean
    Map<String, Function> extraTemplateFunctions() {   // one-off helpers, by name
        return Map.of("shout", args -> String.valueOf(args[0]).toUpperCase(Locale.ROOT));
    }
}

Both are then callable from any view the GoTemplateService renders.