jstachio
JStachio Version: 1.3.4

User Guide

JStachio: A type-safe Java Mustache templating engine.

Templates are compiled into readable Java source code and value bindings are statically checked using the Java Annotation processing framework.

Contents

Getting Started

Follow these steps:
  1. Setup your build correctly
  2. See or copy Java Code example.
  3. Read about mustache syntax if you are not familiar with it.
  4. Explore configuring to make JStachio your way.
If you are using Spring it is probably easiest to see the example projects and or copy it.

Description

JStachio is a templating engine for the Java programming language. It's syntax is spec compliant Mustache which is a highly popular extremly easy to learn templating language that has implementations in multiple programming languages. Mustache embraces simplicity and touts itself as a "logicless" templating language.

There are several Mustache implementations for Java besides JStachio but JStachio's key difference is that it does type checking as well as generates readable Java code. Because JStachio generates Java code it happens to be one of the fastest templating engines for Java.

JStachio's philosophy is that the template is a contract and that templates should maintain as minimal logic as possible while being safe from accidental insecure output.

Use Cases

There are three main use cases that this templating engine is designed around with the cross cutting feature of type safety and performance:
  1. Templating web applications for traditional SEO friendly HTML server side rendering.
  2. Templating for code generating.
  3. General purpose inline templating instead of using StringBuilder and or a more powerful JEP 430 aka stringtemplates (still in preview)

Project Information

Source Control
https://github.com/jstachio/jstachio
Team
Issues
https://github.com/jstachio/jstachio/issues
Community
https://github.com/jstachio/jstachio/discussions
User Guide
This document
Javadoc
This document (modules listing at bottom)
Mustache Spec
v1.3.0
Mustache Manual
https://github.com/mustache/spec/tree/v1.3.0
The project follows semantic versioning.

Requirements

  1. Java 17 or greater
  2. A build system that supports running the Java compiler annotation processor
The only module needed during runtime is java.base

Limitations

Dynamic Templates

Because mustache templates need to be analyzed at compile time JStachio does not support dynamic templates. Dynamic meaning that the template markup cannot be changed (e.g. concatenating a string to be used as a template) at runtime without calling the Java compiler (which would generally be a bad idea for hosted platforms). Consequently JStachio is generally (ignoring extensions) not a good fit for user created templates (e.g. templates coming from a database).

However JStachio has very good lambda section support that can largely reproduce a lot of dynamic functionality. Furthermore a custom lambda could be made to render dynamically constructed templates using a reflection based mustach engine such as JMustache for the places where that is needed.

In the future a reflection based runtime version of JStachio that follows JStachio strict rules will be available.

Not Reactive

JStachio does not generate reactive code nor can handle reactive data types. Instead it generates code that writes to an Appendable.

The current best workaround is to buffer as discussed in the FAQ using BufferedEncodedOutput.

Its important to understand that "reactive" does not guarantee better performance particularly in regards to template engines. There are only handful of templating engines that handle backpressure as well as reactive data types and from benchmarking they appear to be slower than JStachio writing to a buffer. Furthermore if the model does not have reactive datatypes for its iterables (e.g. Flow.Publisher) it is unlikely to perform better as the model is already in memory.

Mustache Syntax and Semantics

The format of the templates should by default be Mustache specifically v1.3.0 syntax . The syntax and general semantics is informally explained by the latest mustache manual and formally explained by the spec.

⚠ WARNING "https://jgonggrijp.gitlab.io/wontache/mustache.5.html" is the latest manual and NOT the top google result: "https://mustache.github.io/mustache.5.html". This is important because the newer manual covers newer mustache features that JStachio implements.

Most of the documentation of what mustache spec options are implemented and how are discussed in the @JStache annotation.

Map like objects

Mustache was originally designed for languages like Javascript that have late binding where every object is like a Map<String, ?>. JStachio on the otherhand like Java is statically typed and even if a field or method is present on the instance of an object does not mean it is available to JStachio.

While JStachio supports Map types it is not possible to go deeper into the map and thus the contents of a Map are checked last even if it is a directly on the top of the context stack.

In short JStachio does not support the casting and reflection required to do that however there is some additional support to help wth JSON like data.

Whitespace handling

One of the most important characteristics and spec compliance of modern Mustache (v1.3) is whitespace handling which unfortunately is not discussed much even in the current manual. JStachio aims to follow the whitespace handling of the Mustache spec.

A general rule of this whitespace handling is a concept called "standalone" section-like tags. When a single tag (a tag being everything but a variable and raw content) is on a line with only whitespace and a following newline when rendering the whitespace, tag, and newline are removed.

For the following example let us assue sections is a boolean that is true:

    {{#sections}}
    1
    {{/sections}}
    {{#sections}}
    2
    {{/sections}}
The result will be:

    1
    2

Furthermore if a parent or partial tag(s) are standalone the amount of whitespace (other than newline) preceding them is used as indentation for the included template.

Virtual keys for iterable, enum and more

One of the biggest limitations in plain Mustache is that it is not easy to do logic on iterables (aka list-like) based on index like start and end logic. JStachio adds virtual keys (the keys do not actually exist on the objects) that is compatible with both handlebars.js (handlebars.java as well) and JMustache index keys for iterable sections.

Iterable virtual keys

While inside an iterable context (list) JStachio binds the following virtual keys:
Iterable Virtual Keys
KeyDescription
"@first" A boolean that is true when on the first item.
"@last" A boolean that is trune when on the last time.
"@index" A integer zero based index. The first item would be 0.
JStachio also binds all the JMustache virtual keys as well that begin with "-" instead of "@".

Enum virtual keys

JStachio also has virtual keys for enum types where the name is a boolean if it matches. An example of that is in the JStache javadoc.

Context virtual keys

JStachio provides special global virtual keys all starting "@".
Context Virtual Keys
KeyDescription
"@root" The original root model object. This can be useful if you find yourself stuck deep in the context stack.
"@template" An instance of the generated TemplateInfo class.
"@context" A special JSON/Map like type for cross cutting variables. See io.jstach.jstachio.context for more info.

Fragments

JStachio allows referencing a subset of a template called "fragments". The subset of a template can be any type of section block and the first one found from top to bottom reading with the matching name is picked. The subset of a resource template is referenced with the URI fragment notation.

If the fragment start tag is "standalone" and all the content inside the fragment start tag starts with the same whitespace (or more) as the fragment start tag then that starting whitespace will be stripped from each line of the content. This is to allow a partial references to dictate the indentation based on spec whitespace handling.

Fragment section blocks can be of these type:
  • {{#fragment}}...{{/fragment}}
  • {{$fragment}}...{{/fragment}}
  • {{<fragment}}...{{/fragment}}
  • {{^fragment}}...{{/fragment}}
The semantics of the section block are ignored as well as the rest of the template as it is like a preprocessor.

Note that the section block type does not dictate the separator of the template path and fragment. It is always a URI fragment "#" regardless of the type of section block (e.g. path = "somefile$fragment" is always wrong even if the fragment is in a block segment ({{$fragment}}).

An example of fragments is in the JStache javadoc.

Java Code

Simply annotate a class with JStache like below:

/*
 * Annotate the root model with an inline mustache template
 */
@JStache(template = """
        {{#people}}
        {{message}} {{name}}! You are {{#ageInfo}}{{age}}{{/ageInfo}} years old!
        {{#-last}}
        That is all for now!
        {{/-last}}
        {{/people}}
        """)
public record HelloWorld(String message, List<Person> people) implements AgeLambdaSupport {}

public record Person(String name, LocalDate birthday) {}

public record AgeInfo(long age, String date) {}

public interface AgeLambdaSupport {
    @JStacheLambda
    default AgeInfo ageInfo(Person person) {
        long age = ChronoUnit.YEARS.between(person.birthday(), LocalDate.now());
        String date = person.birthday().format(DateTimeFormatter.ISO_DATE);
        return new AgeInfo(age, date);
    }
}
The above will generate a HelloWorldRenderer class from the inline template. JStachio also supports external templates as well.

While you may use the generated classes directly to render HelloWorld instances in some cases it is easier and better to use JStachio to render directly from the model without referencing generated code.

Below is an example of that:

@Test
public void testPerson() throws Exception {
    Person rick = new Person("Rick", LocalDate.now().minusYears(70));
    Person morty = new Person("Morty", LocalDate.now().minusYears(14));
    Person beth = new Person("Beth", LocalDate.now().minusYears(35));
    Person jerry = new Person("Jerry", LocalDate.now().minusYears(35));
    var hello = new HelloWorld("Hello alien", List.of(rick, morty, beth, jerry));
    // render without reflective lookup
    String actual = HelloWorldRenderer.of().execute(hello);
    // or use JStachio reflective lookup which will also apply filters and other advise
    actual = JStachio.render(hello);
    String expected = """
            Hello alien Rick! You are 70 years old!
            Hello alien Morty! You are 14 years old!
            Hello alien Beth! You are 35 years old!
            Hello alien Jerry! You are 35 years old!
            That is all for now!
                            """;
    assertEquals(expected, actual);

}

Modules

JStachio is fully modularized consequently it is has many jars. The listing of modules at the end of the guide explain what each module does as well as its Maven GAV.

If your application or library is modularized (module-info.java) and you would like to use the runtime module or extensions there are some additional steps that maybe needed due to encapsulation requirements. In some cases the easiest solution is to allow the jstachio runtime module reflective access to the packages containing JStache models. Other solutions are discussed on the JStacheCatalog annotation.

Code Generation Modes

JStachio provides two major modes for how code is generated.
  1. JStachio enchanced mode: JStacheType.JSTACHIO
  2. Zero dependency mode: JStacheType.STACHE
If you do not care about dependencies or footprint JStacheType.JSTACHIO is the preferred mode and is the default.

On the other hand Zero dependency mode will generate code that will only have references to itself and the JDK base module (java.base). This is often desirable if you want to use JStachio for a code generation library (e.g. another annotation processor).

How it works

When the compiler compiles your annotated code JStachio's annotation processor will run. An annotation processor has access to the symbolic tree of the source code being compiled. The classes that are annotated with JStachio's annotations are analyzed to find a template and various other configuration. Once the template is found it is parsed while referring to the symbolic tree of the class annotated with JStache (the model). From that it deduces how to generate Java code that will navigate the model and output text based on the template.

More explanation is available on JStache javadoc.

Installation

JStachio uses the Java annotation processor facility to generate code. You will need to set that up otherwise code will not be generated.

Maven

Maven configuration has two choices. annotationProcessorPaths or classpath.

Option 1 annotationProcessorPaths


<properties>
    <io.jstach.version>1.3.4</io.jstach.version>
</properties>
...
<dependencies>
    <dependency>
        <groupId>io.jstach</groupId>
        <artifactId>jstachio</artifactId>
        <version>${io.jstach.version}</version>
    </dependency>
</dependencies>
...
<build>
    <plugins>
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            <version>3.8.1</version>
            <configuration>
                <source>17</source> <!-- 17 is the minimum -->
                <target>17</target> <!-- 17 is the minimum -->
                
                <!-- You may not need annotationProcessorPaths if jstachio-apt is added as an option dep -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.jstach</groupId>
                        <artifactId>jstachio-apt</artifactId>
                        <version>${io.jstach.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

Option 2 classpath

JStachio annotation processor MAY also work without being registered in annotationProcessorPaths even for modular libraries and applications provided there is not already an annotationProcessorPaths set. This can be desirable as it allows less explicit maven configuration and normal dependency management (as wells avoids this bug: MCOMPILER-391).

To make it work you would add jstachio-apt as an optional and provided dependency:


<dependencies>
    <dependency>
        <groupId>io.jstach</groupId>
        <artifactId>jstachio-apt</artifactId>
        <version>${io.jstach.version}</version>
        <optional>true</optional>
        <scope>provided</scope>
    </dependency>
<dependencies>

This works because maven will put jstachio-apt on the classpath instead of the module path during compilation provided you DO NOT put requires io.jstach.apt in your module-info.java which you should never do in general.

Be aware that the above option may have problems if you have multiple annotation processors as some processors rely on specific order. Furthermore if using Eclipse always set scope provided even if you are only using JStachio for unit testing. (note you still need to add either io.jstach:jstache or io.jstach:jstachio as a dependency.)

Maven Zero dependency configuration

If all of your JStache are configured for zero dependency via JStacheConfig.type() == STACHE you can instead rely on only one compile time dependency (replace dependencies section with following):

<dependencies>
    <dependency>
        <groupId>io.jstach</groupId>
        <artifactId>jstachio-annotation</artifactId>
        <version>${io.jstach.version}</version>
        <optional>true</optional>
        <scope>provided</scope>
    </dependency>
<dependencies>

You will still need to add the APT module as an annotation processor which is explained in the previous section. If configured correctly the APT module will not be a true dependency.

Gradle


dependencies {
    
    implementation 'io.jstach:jstachio:1.3.4'
 
    annotationProcessor 'io.jstach:jstachio-apt:1.3.4'
}

Gradle Zero dependency configuration

If all of your JStache are configured for zero dependency via JStacheConfig.type() == STACHE you can instead configure Gradle like:

dependencies {
    
    compileOnly 'io.jstach:jstachio-annotation:1.3.4'
 
    annotationProcessor 'io.jstach:jstachio-apt:1.3.4'
}

Gradle cache support

Gradle has powerful support for cache and incremental builds. If cache is turned on it is recommended you add a fileTree to the annotation processor of where the templates are stored. Otherwise if a template is changed a renderer will not be updated.

Below is an example of configuring Gradle to know about template changes:


dependencies {
    // other deps
    annotationProcessor 'io.jstach:jstachio-apt:1.3.4'
    annotationProcessor fileTree(layout.projectDirectory.dir('src/main/resources/views')) { include '**/*' }
}

Gradle incremental support

See JStache.INCREMENTAL_OPTION as there are major caveats when using this and it is recommend that this flag be disabled in CI/CD pipelines!

JStachio also supports experimental incremental annotation compiling support. Service provider files and catalogs of templates will not be generated in this mode!


compileJava {
   options.compilerArgs += [
      '-Ajstache.incremental=true',
   ]
}

Configuration

Compile time Configuration

JStache, JStacheConfig, and JStacheFlags are heavily documented on static configuration of JStachio via annotations as well as in some cases compiler command line arguments.

JStachio static config allows you to configure:

  1. Where external templates are stored and referred.
  2. How code is generated including what interfaces or classes are extended
  3. What types are allowed to be outputted without compiler error
  4. How types are formatted
  5. Escaping
  6. Null checking
  7. Which @Nullable annotation to use
  8. ... and much more

The most notable configuration is that you can configure whether or not zero dependency code is generated via JStacheConfig.type() as well as location paths of template files via JStachePath and what interfaces generated code extends via JStacheInterfaces.

Compile time Configuration Resolution

JStachio annotations configuration resolution is discussed in JStacheConfig and in general is where the most innner enclosing annotations have precedence. Thus to allow cascading of configuration the default return values of annotation methods are often not the actual default. These default return values to allow cascading of config are called "UNSPECIFIED" and basically signify to rely on the outer enclosing config if present.

Compile time Configuration Example


@JStacheConfig(
  using = MyGlobalConfig.class,                                            // 1
  naming = @JStacheName(suffix = "Template"),                              // 2
  pathing = @JStachePath(prefix = "templates/", suffix = ".mustache"),     // 3
  contentType = Html.class,                                                // 4
  formatter = SpecFormatter.class,                                         // 5
  interfacing = @JStacheInterfaces(                                        // 6
      templateAnnotations=Component.class,                                 // 7
      templateImplements=MyTemplateMixin.class),                           // 8
  charset = "UTF-8"                                                        // 9
  )
// class, or package-info.java or module-info.java                         // 10
  1. Import config from another class that has JStacheConfig annotation
  2. Set generated classes to have the suffix of "Template" instead of the default of "Renderer"
  3. Template references will be expanded such that a template referred to as "Me" will be expanded to templates/Me.mustache
  4. Use HTML escaping and set our media type to text/html
  5. Use the SpecFormatter which makes null an empty string
  6. Use JStacheInterfaces to decorate generated code
  7. Generated templates will have the third party "@Component" annotation put on the type
  8. Generated templates will implement MyTemplateMixin
  9. The charsets of the templates is UTF-8
  10. The configuration can be placed on enclosing types of class, package, and module where the inner enclosed takes precedence.

Annotation processor options

Some configuration is available as annotation processor options. Annotation processor options are compiler arguments starting with -A. Current available options are: compiler flags are also available as annotation options. The flags are prefixed with "jstache.". For example JStacheFlags.Flag.DEBUG would be:

-Ajstache.debug=true/false.

Runtime Configuration

Certain configuration such as logging and whether or not to use reflection is only avaiable during runtime when using the JStachio runtime module and the default JStachio. This is documented in JStachioConfig.

Extensions and Integrations

Using the JStachio runtime module additional extensions are available. They are in the opt directory of the project.

Many extensions just require the extension jars be in the classpath and are loaded via the ServiceLoader automatically (unless disabled or using DI framework).

Hot Reload with JMustache

See io.jstach.opt.jmustache and JMustacheRenderer

As of 1.3.0 JMustache extension has been deprecated! We are currently exploring other hot reload options that you can see in issue #187 Better Hot Reload.

JMustache extension allows you to change templates without recompiling the application.

One major caveat is that JMustache currently does not support template inheritance (aka parents and blocks) so if your jstachio templates use template inheritance JMustache will probably not work.

Spring Framework

See io.jstach.opt.spring

JStachio normally uses the ServiceLoader for loading runtime components. This extension will use Spring DI to find components as well as provides integration with Spring Web.

Spring Boot Starter

See io.jstach.opt.spring.boot.webmvc for MVC support with Spring Boot. By including that dependency Spring will automatically configure JStachio.

There is an example webmvc Spring Application that uses this module to autoconfigure JStachio.

Spring Web support

See JStachioHttpMessageConverter

For Spring MVC the integration allows you to return models and they will automatically be rendered to the response.


    @JStache
    public record HelloModel(String message){}
    
    @GetMapping(value = "/")
    @ResponseBody
    public HelloModel hello() {
        return new HelloModel("Spring Boot is now JStachioed!");
    }

Web MVC integration

See io.jstach.opt.spring.webmvc

JStachioModelView allows you to construct servlet based Spring Views for traditional Web MVC Spring applications. This integration is tied to the servlet API and thus will need it as a dependency.

Web Flux integration

See io.jstach.opt.spring.webflux

JStachioEncoder allows reactive containers such as Flux/Mono to contain JStache models.

Spring Web MVC Example Application

See io.jstach.opt.spring.example module and github project

There is an example modularized Spring Boot Web MVC application. While the code is Javadoc and the source is linked (if you click on the classes the source code is shown) it might be easier to look directly on github (link to project).

Spring Webflux Example Application

See io.jstach.opt.spring.webflux.example module and github project

There is an example modularized Spring Boot Webflux reative application. While the code is Javadoc and the source is linked (if you click on the classes the source code is shown) it might be easier to look directly on github (link to project).

Jooby 3.x

JStachio has support for the Jooby web framework version 3.x. The documentation on how to set this up is in Jooby's documentation.

Jooby has support for efficiently handling pre-encoded templates as well as buffer reuse.

Dropwizard 4.x

See io.jstach.opt.dropwizard

JStachio has support for Dropwizard version 4.x.

Dropwizard Example Application

See io.jstach.opt.dropwizard.example module and github project

There is an example modularized Dropwizard application. While the code is Javadoc and the source is linked (if you click on the classes the source code is shown) it might be easier to look directly on github (link to project).

FAQ

Why can't JStachio find my templates?

There are many problems that can happen but usually it boils down to three issues:
  1. The annotation processor did not run.
  2. The annotation processor did run but cannot find external template resources.
  3. The runtime cannot reflectively find a template for a model.

The first thing to check is to make sure the template classes are being generated. Usually the generated classes after build will reside in target/generated-sources/annotations.

If compilation is failing because the annotation processor cannot find your templates make sure that the external templates (mustache files) are put in src/main/resources. It is not recommended and generally will not work if you put template resources in src/main/java. The above can be disregarded if the template is inlined (e.g. a string).

If the templates are generated but JStachio.render is not working a reflection exception will be thrown. This may happen if the generated class is not available because of incremental compiling and particularly a problem with Maven. Try a full rebuild (e.g. mvn clean package).

If you are using Eclipse and see compiler errors in the Eclipse UI there is a possibility that the current working directory and or source paths could not be resolved. JStachio does its best to resolve source path but not all configuration will work. As a painful last resort you can use Eclipse project settings and go to: Java Compiler -> Annotation Processing -> Processor options. Set "jstache.resourcesPath" to the absolute path of your templates. See JStache.RESOURCES_PATH_OPTION for more information.

Finally if your application is a modular application (module-info.java) then you will need to either allow reflective access to JStachio (e.g open somepackage to io.jstach.jstachio) or register a Service Located TemplateProvider. The solution is discussed in JStacheCatalog annotation.

How do I do XYZ complicated logic in my template?

Mustache is inherently simple and is called logicless for a reason. If you need complicated behavior then it is recommended to put that behavior in either a lambda or method call.

Because JStachio always requires an annotated class that serves as the model it can be argued that the model associated with the template is more of a view model then a domain model (or DTO). Therefore it is recommended that you take advantage of that top level class and add methods needed to help render.

If you have common logic you can use normal Java practices to share the logic such as interfaces (default methods) or abstract classes and have the model implement or extend (respectively). You can even enforce that models always implement an interface with JStacheInterfaces.

How do I disable escaping?

Set JStacheConfig.contentType() to PlainText.

//package-info.java
@JStacheConfig(contentType=io.jstache.jstachio.escapers.PlainText.class)
package com.mycompany;

If you are setup for zero dependency mode escaping is disabled by default as escaping would require a dependency.

How do I do layout?

Modern Mustache has the concept of parent partials and blocks that is often called template inheritance. JStachio supports this.

Below is an example:

layout.mustache

{{! layout.mustache }}
<html>
  <head>
    <title>{{$title}}My site{{/title}}</title>
  </head>
  <body>
  {{$body}}Replace Me{{/body}}
  </body>
</html>

page.mustache

{{! page.mustache }}
{{< layout}}
  {{$title}}My Page!{{/title}}
  {{$body}}
  This is a page
  {{/body}}
{{/layout}}

I want to change templates without restarting my app. How do I do that?

See the JMustache Extension that uses reflection. In the future JStachio will have its own reflection based runtime for template development but for now we are leveraging the fact that JMustache is almost equivalent to JStachio once configured correctly.

How do I make the JStachio runtime use zero reflection?

The JStachio runtime mainly provides a way to find a template based on a model. To do this it will try the ServiceLoader first and then regular reflection. Thus to avoid this a list of model to templates needs to be registered.

See JStacheCatalog.

My MVC library uses Map<String,Object> for models what should I do?

As mentioned throughout this doc JStachio is not ideal for Map<String,Object>.

If this is a greenfield project (ie starting from scratch) it is recommend that you have your controllers return the model objects that are annotated with @JStache and wherever the framework does return type conversion or request body conversion you call JStachio render/execute functions. The spring integration shows an example of this technique.

If your library only allows Map<String,Object> (or similar analog aka request attributes) or you have existing code you can make a special entry in the Map<String,Object> with the model and then wherever normal template engines are executed you pull the entry out and call JStachio render/execute functions.

As for existing request attributes or entries in Map<String,Object> that you depend on you can simply have that as a field or method on your model object. When accessing those attributes it is best to use dotted path notation.


@JStache(template = "{{attributes.something}}")
public record Page(Map<String, Object> attributes){}

public Page someController(Map<String,Object> requestAttributes) {
  return new Page(requestAttributes);
}


If you do not want repeatedly add the request attributes see FAQ on decorating model.

If you are still having trouble integrating please file an issue.

I don't use Spring but some other Dependency Injection library how do I integrate?

See JStacheInterfaces and io.jstach.jstachio.spi. First start by looking at the Spring implementation: io.jstach.opt.spring. Most dependency injection frameworks have a way to discover all classes of some type or have some annotation. JStacheInterfaces allows you to generate Templates/Renderers that will have annotations or implement an interface that you can use to discover the generated code.

Once that is done you can then add the list of found templates by implementing your own JStachio by implementing a JStachioTemplateFinder and then extending AbstractJStachio or using JStachioFactory.builder().

How do I do I18N and Localization?

There are a lot of techniques to handle internationalization (I18N). JStachio currently does not ship with an opinionated way to handle it. If you would like JStachio with one please comment or thumbs-up: #104.

Regardless there are three major techniques all with various pros and cons as well as can be mixed:

  1. Load the model with already translated messages
  2. Use JStachio Lambdas: JStacheLambda
  3. Use a partial for each locale

The first technique is one of the easiest and safest. Because JStachio always requires an annotated class that serves as the model it can be argued that the model associated with the template is more of a view model then a domain model (or DTO). Therefore view oriented helper methods can be added that can do the translation.

The second alternative is to use Lambdas. There is an unsupported example of doing that in JStachio's test code here: https://github.com/jstachio/jstachio/tree/main/test/examples/src/main/java/io/jstach/examples/i18n

The last technique works wells for dynamic mustache implementations like JMustache and mustache.java but not so well for JStachio. To have a partial for each locale in JStachio would require writting section conditions to determine the correct partial to load. This is because JStachio cannot dynamically load a template and needs to analyze it at compile time. Even then every locale's template would need to be compiled. In the future JStachio may add better support for this but it is unlikely because it is still generally not recommended to handle i18n this way.

How can I add cross cutting model attributes like CSRF?

A common problem in web development is needing model attributes that are pseudo global or request based. In traditional MVC style web applications the model is usually a mutable Map<String,Object> so attributes like CSRF are added after the controller has filled the model but before rendering. However this presents a problem to JStachio as JStachio prefers you do not use Map<String,Object> as models (you can but not as a root model and you loose type safety). There are three general strategies:
  1. Use ContextJStachio
  2. Make the model mutable for those attributes and set the attributes by intercepting before rendering happens. (the push way)
  3. Use a Service Locator / Thread Local like pattern with an interface with pull method that all models get. (the pull way)

The Spring integration provides some guidance on doing the first strategy via JStachioModelViewConfigurer.

The second technique is to make request information available through the callstack by usually using ThreadLocals and then having an interface with a method that pulls the info from the ThreadLocal variable. This is a more advance technique but does allow keeping your models immutable (e.g. using record).

How do I integrate with reactive frameworks such as Reactor?

As noted in the limitations JStachio does not generate reactive code. The short answer is to handle the backpressure by simply buffering.

For example a simple workaround it just to buffer the entire output and hand the output as a String or ByteBuffer off to the reactive framework. The idea being that memory is cheap and the model has to be fully loaded anyway since JStachio does not support iterating over reactive data types. For buffered output strategies see BufferedEncodedOutput.

Another workaround if the output can be large is to render in parts and to compose those parts with reactive operators such as flatMap and map. For example lets say we are generating a page with a list of rows. We would first render a header as String and then reactively iterate over the rows rendering them to String and then finally render a footer.

The Spring webflux example project shows an example of using the buffering technique.

Can I make JStachio only check mustache and not generate code?

Currently no. But if you would like that please comment or thumbs-up: #103

Can I make whitespace explicit instead of "standalone" rules?

Currently no. JStachio follows the spec whitespace rules. However in theory it is always possible to achieve all the same output that no whitespace handling does.

If you would like an option to disable JStachio whitespace handling like some mustache implementations offer please file an issue.

There is a typo in this documentation how can I fix it?

If you would like to make corrections please file an issue or even better fork, edit, PR this file:
doc/src/main/javadoc/overview.html

Where is the Javadoc?

Shockingly this document is the Javadoc! To be precise it is the aggregate javadoc overview.html. The modules javadocs should be at the bottom of this document and the search bar at the top can be used to find documented classes.
Modules
Module
Description
JStachio compile time annotations: io.jstach:jstachio-annotation.
JStachio Core Runtime API: io.jstach:jstachio.
JStachio Dropwizard integration: io.jstach:jstachio-dropwizard.
Dropwizard example app using JStachio: io.jstach:jstachio-dropwizard-example.
JMustache extension to enable dynamic development of templates: io.jstach:jstachio-jmustache.
JStachio Spring integration module: io.jstach:jstachio-spring.
Spring Boot Web MVC starter for JStachio: io.jstach:jstachio-spring-boot-starter-webmvc.
Spring Web MVC example app for JStachio: io.jstach:jstachio-spring-example.
JStachio Spring Webflux integration module: io.jstach:jstachio-spring-webflux.
Spring Webflux example app for JStachio: io.jstach:jstachio-spring-webflux-example.
JStachio Spring webmvc module: io.jstach:jstachio-spring-webmvc.