JStachio Version: 1.2.1
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:- Setup your build correctly
- See or copy Java Code example.
- Read about mustache syntax if you are not familiar with it.
- Explore configuring to make JStachio your way.
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:- Templating web applications for traditional SEO friendly HTML server side rendering.
- Templating for code generating.
- 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
Requirements
- Java 17 or greater
- A build system that supports running the Java compiler annotation processor
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 aMap<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 assuesections
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 and enum
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.-first
and@first
is boolean that is true when you are on the first item-last
and@last
is a boolean that is true when you are on the last item in the iterable-index
is a one based index. The first item would be1
and not0
@index
is zero based index (handlebars). The first item would be0
.
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 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}}
An example of fragments is in the JStache javadoc.
Java Code
Simply annotate a class withJStache
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.
@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.- JStachio enchanced mode:
JStacheType.JSTACHIO
- Zero dependency mode:
JStacheType.STACHE
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.2.1</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 inannotationProcessorPaths
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. (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 viaJStacheConfig.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>
Gradle
dependencies {
implementation 'io.jstach:jstachio:1.2.1'
annotationProcessor 'io.jstach:jstachio-apt:1.2.1'
}
Gradle Zero dependency configuration
If all of your JStache are configured for zero dependency viaJStacheConfig.type() == STACHE
you can instead configure Gradle like:
dependencies {
compileOnly 'io.jstach:jstachio-annotation:1.2.1'
annotationProcessor 'io.jstach:jstachio-apt:1.2.1'
}
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:
- Where external templates are stored and referred.
- How code is generated including what interfaces or classes are extended
- What types are allowed to be outputted without compiler error
- How types are formatted
- Escaping
- Null checking
- Which @Nullable annotation to use
- ... 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 inJStacheConfig
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
- Import config from another class that has JStacheConfig annotation
- Set generated classes to have the suffix of "Template" instead of the default of "Renderer"
- Template references will be expanded such that a template referred to as "Me" will be expanded to
templates/Me.mustache
- Use HTML escaping and set our media type to text/html
- Use the SpecFormatter which makes
null
an empty string - Use JStacheInterfaces to decorate generated code
- Generated templates will have the third party "@Component" annotation put on the type
- Generated templates will implement
MyTemplateMixin
- The charsets of the templates is UTF-8
- The configuration can be placed on enclosing types of class, package, and module where the inner enclosed takes precedence.
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 inJStachioConfig
.
Extensions and Integrations
Using theJStachio
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
Seeio.jstach.opt.jmustache
and JMustacheRenderer
The most notable extension is the JMustache extension as it 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
Seeio.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
Seeio.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
SeeJStachioHttpMessageConverter
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
Seeio.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
Seeio.jstach.opt.spring.webflux
JStachioEncoder
allows
reactive containers such as Flux/Mono to contain JStache models.
Spring Web MVC Example Application
Seeio.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
Seeio.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
Seeio.jstach.opt.dropwizard
JStachio has support for Dropwizard version 4.x.
Dropwizard Example Application
Seeio.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:- The annotation processor did not run.
- The annotation processor did run but cannot find external template resources.
- 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 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 withJStacheInterfaces
.
How do I disable escaping?
SetJStacheConfig.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 @
and wherever the framework does return type conversion or request body conversion you call
JStache
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?
SeeJStacheInterfaces
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:
- Load the model with already translated messages
- Use JStachio Lambdas:
JStacheLambda
- 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 mutableMap<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 two general strategies:
- Make the model mutable for those attributes and set the attributes by intercepting before rendering happens. (the push way)
- 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.
Can I make JStachio only check mustache and not generate code?
Currently no. But if you would like that please comment or thumbs-up: #103Can 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 javadocoverview.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.