Home

Awesome

Maven Central Github

<img src="etc/social-media.svg" alt="jstachio">

A type-safe Java Mustache templating engine.

Templates are compiled into readable Java source code and value bindings are statically checked.

Documentation

The doc is also on javadoc.io but is not aggregated like the above. The aggregated javadoc is the preferred documentation and the rest of this readme is mainly for propaganda marketing purposes.

For previous releases:

https://jstach.io/doc/jstachio/VERSION/apidocs

Where VERSION is the version you want.

Why choose JStachio

Covered in why_jstachio_is_better.md.

Features

Performance

It is not a goal of this project to be the fastest java templating engine!

<sub><sup>(however it is currently the fastest that I know when this readme was last updated)</sup></sub>

Not that peformance matters much with templating languges (it is rarely the bottleneck) but JStachio is very fast:

https://github.com/agentgt/template-benchmark

String Output

Template Comparison

UTF-8 byte Output with extended characters

Template Comparison

Quick Example


@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);
    }

}

@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));
    String actual = JStachio.render(new HelloWorld("Hello alien", List.of(rick, morty, beth, jerry)));
    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);

}

Installation

Maven

<properties>
    <io.jstach.version>0.6.0-SNAPSHOT</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 -->
                <annotationProcessorPaths>
                    <path>
                        <groupId>io.jstach</groupId>
                        <artifactId>jstachio-apt</artifactId>
                        <version>${io.jstach.version}</version>
                    </path>
                    <!-- other annotation processors -->
                </annotationProcessorPaths>
            </configuration>
        </plugin>
    </plugins>
</build>

N.B. The annotations jar (jstachio-annotation) is pulled in transitively

Gradle

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

Examples

user.mustache

{{#name}}
<p>Name: {{.}}, Name Length is {{length}}</p>
{{/name}}

<p>Age: {{  age  }}</p>

<p>Achievements:</p>

<ul>
{{#array}}
  <li>{{.}}</li>
{{/array}}
</ul>

{{^array}}
<p>No achievements</p>
{{/array}}

<p>Items:</p>

<ol>
{{#list1}}
  <li>{{value}}</li>
{{/list1}}
</ol>

User.java

Following class can be used to provide actual data to fill into above template.

@JStache(
    // points to src/main/resources/user.mustache file
    path = "user.mustache",
   
    // or alternatively you can inline the template
    template = "", 

    )
public record User(String name, int age, String[] array, List<Item<String>> list) {

   public static class Item<T> {
        private final T value;
        public Item(T value) {
            this.value = value;
        }
        T value() {
            return value;
        }
    }
}

Rendering

New class UserRenderer will be mechanically generated with the above code. This class can be used to render template filled with actual data. To render template following code can be used:

class Main {
    public static void main(String[] args) throws IOException {
        User user = new User("John Doe", 21, new String[] {"Knowns nothing"}, list);
        StringBuilder appendable = new StringBuilder();
        JStachio.render(user, appendable);
    }
}

The result of running this code will be

<p>Name: John Doe, Name Length is 8</p>

<p>Age: 21</p>

<p>Achievements:</p>

<ul>
  <li>Knowns nothing</li>
</ul>

<p>Items:</p>

<ol>
  <li>helmet</li>
  <li>shower</li>
</ol>

Referencing non existent fields, or fields with non renderable type, all result in compile-time errors. These errors are reported at your project's compile-time alone with other possible errors in java sources.

target/classes/user.mustache:5: error: Field not found in current context: 'age1'
  <p>Age: {{  age1  }} ({{birthdate}}) </p>
                  ^
  symbol: mustache directive
  location: mustache template
target/classes/user.mustache:5: error: Unable to render field: type error: Can't render data.birthdate expression of java.util.Date type
  <p>Age: {{  age  }} ({{birthdate}}) </p>
                                    ^
  symbol: mustache directive
  location: mustache template

See test/examples project for more examples.

Java specific extensions

Enum matching support

Basically enums have boolean keys that are the enums name (Enum.name()) that can be used as conditional sections.

Assume light is an enum like:

public enum Light {
  RED,
  GREEN,
  YELLOW
}

You can conditinally select on the enum like a pattern match:

{{#light.RED}}
STOP
{{/light.RED}}
{{#light.GREEN}}
GO
{{/light.GREEN}}
{{#light.YELLOW}}
Proceeed with caution
{{/light.YELLOW}}

Index support

JStachio is compatible with both handlebars and JMustache index keys for iterable sections.

Lambda support

JStachio supports lambda section calls in a similar manner to JMustache. Just tag your methods with @JStacheLambda and the returned models will be used to render the contents of the lambda section. The top of the context stack can be passed to the lambda.

JStachio unlike the spec does not support returning dynamic templates that are then rendered against the context stack. However dynamic output can be achieved by the caller changing the contents of the lambda section as the contents of the section act as an inline template.

Design

The idea is to create templating engine combining mustache logicless philosophy with Java's single responsibility and static-typing. Full compile-time check of syntax and data-binding is the main requirement.

Currently Java-code is generated for templates. Generated Java-code should never fail to compile. If it is impossible to generate valid Java-code from some template, friendly compile-time error pointing to template file should be generated. Users should never be exposed to generated Java-code.

Original mustache uses Javascript-objects to define rendering context. Fields of selected Javascript-objects are binded with template fields.

Static mustache uses Java-objects to define rendering context. Binding of template fields is defined and checked at compile-time. Missing fields are compile-time error.

License

JStachio is under BSD 3-clause license.