eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Introduction

In this tutorial, we’ll look at compile-time templates. We’ll see what these are and look at a few libraries we can use to implement them.

2. What are Compile-Time Templates?

There are many occasions when writing our applications when we need to use a templating library of one form or another.

Often, when we do this, we need to load and parse the template at runtime, then provide the data to bind to it to generate the final result. This approach adds overhead and introduces some additional risk. For example, a malformed template or unexpected data may go unnoticed until we attempt to use it.

However, there are several libraries we can use that do this slightly differently. Instead of loading and parsing the templates at runtime, we can compile them to Java classes at build time and then treat them as normal Java from this point forward. This means that we’ll get a build failure if the template is malformed, and we’ll often have compiler safety for actually using the templates.

In addition to this, some of these libraries can work entirely reflection-free. This allows them to be used in an environment where reflection isn’t entirely available. For example, we can use these within a GraalVM setup to build and run our application as a native image.

3. JStachio

JStachio is a small library that implements the Mustache templating language. However, it does this at compile time and not at runtime. It also uses no runtime reflection, so it’s suitable for use with something like GraalVM.

3.1. Dependencies

In order to use JStachio, we need to configure it in our build. This comes as a compiler annotation processor that automatically converts our templates into Java code. To use this, we need to include the latest version in our build.

If we’re using Maven, we can include this in our pom.xml file:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>io.jstach</groupId>
                <artifactId>jstachio-apt</artifactId>
                <version>1.3.7</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

We also have a runtime dependency that we need to include to be able to use JStachio templates:

<dependency>
    <groupId>io.jstach</groupId>
    <artifactId>jstachio</artifactId>
    <version>1.3.7</version>
</dependency>

At this point, we’re ready to start using it in our application.

3.2. Writing Templates

Once we set up JStachio, we can start writing templates. These exist in two parts – the template itself and a Java class representing the data needed for the template.

We write our template in the Mustache template language:

<html>
  <body>
    Hello, {{name}}!
  </body>
</html>

We then need a Java class that represents the template:

@JStache(path = "templates/jstachio.mustache")
public record JStachioModel(String name) { }

The JStache annotation on this class indicates the template that should be used. The Java compiler will then detect this and generate a JStachioModelRenderer class in the same package.

3.3. Rendering Templates

Once we’ve built our template into Java code, we can use it to render into our output. This is done using the of() method, providing the input model and a StringBuilder to write the output to:

JStachioModel model = new JStachioModel("Baeldung");

StringBuilder sb = new StringBuilder();
JStachioModelRenderer.of().execute(model, sb);

Our generated JStachioModelRenderer requires an instance of our model class, JStachioModel, to ensure correctness at compile time.

3.4. Using with Spring

In addition to using JStachio manually, it also provides a Spring Boot starter that allows us to use it as a view technology from our controllers.

All we need to do is add the appropriate dependency to our build:

<dependency>
    <groupId>io.jstach</groupId>
    <artifactId>jstachio-spring-boot-starter-webmvc</artifactId>
    <version>1.3.7</version>
</dependency>

Having done this, we can have our Spring controllers return an instance of JStachioModelView that itself wraps our model class:

@GetMapping("/jstachio")
public View get() {
    return JStachioModelView.of(new JStachioModel("Baeldung"));
}

This is enough to have JStachio render the appropriate template with the provided data:

$ http localhost:8080/jstachio
HTTP/1.1 200
Content-Length: 55
Content-Type: text/html;charset=UTF-8

<html>
  <body>
    Hello, Baeldung!
  </body>
</html>

4. Rocker

Rocker is another library we can use for our templates. This uses its own custom template language instead of Mustache, but still compiles to Java code at build time. However, this one does use some reflection, so it may be less suitable in some situations.

4.1. Dependencies

In order to use Rocker, we need to configure it in our build. This comes as a custom Maven plugin that automatically converts our templates into Java code. To use this, we need to include the latest version in our build, which is currently 2.4.0:

<plugin>
    <groupId>com.fizzed</groupId>
    <artifactId>rocker-maven-plugin</artifactId>
    <version>2.4.0</version>
    <executions>
        <execution>
            <id>generate-rocker-templates</id>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
            <configuration>
                <templateDirectory>src/main/resources/templates/rocker</templateDirectory>
                <outputDirectory>target/rocker</outputDirectory>
            </configuration>
        </execution>
    </executions>
</plugin>

This searches for templates under src/main/resources/templates/rocker, finding anything with the file extension .rocker.html. It then generates Java code into target/rocker. It adds this as a build source by default, guaranteeing that the code compiles into our application.

We also need a runtime dependency for this to work:

<dependency>
    <groupId>com.fizzed</groupId>
    <artifactId>rocker-runtime</artifactId>
    <version>2.4.0</version>
</dependency>

At this point, we’re ready to start using it in our application.

4.2. Writing Templates

After completing the setup, we’re ready to use it. Unlike JStachio, Rocker uses the template as the only real input, but it can refer to other Java classes that we’ve written for our data.

We write our templates in a custom markup language that allows us to specify the input binds and how to use them:

@import com.baeldung.templates.RockerModel

@args(RockerModel model)

<html>
    <head>
    </head>
    <body>
        <h1>Demo</h1>
        <p>Hello @model.name()!</p>
    </body>
</html>

In this case, we’re importing a class com.baeldung.templates.RockerModel that must already exist, we’re specifying that this is an argument to our template, and then we’re referencing it when rendering our template.

Our model class is anything that exists on the classpath when the template is compiled:

public record RockerModel(String name) { }

4.3. Rendering Templates

Once compiled, we can then use our template to produce our output. Instead of directly referencing our compiled templates, Rocker provides us a means to reference them by the template name:

BindableRockerModel template = Rocker.template("RockerDemo.rocker.html");
template.bind("model", new RockerModel("Baeldung"));
RockerOutput output = template.render();

ContentType contentType = output.getContentType();
Charset charset = output.getCharset();
String html = output.toString();

This automatically finds the class generated for our RockerDemo.rocker.html template and renders it with the provided model object. Our output variable then gives us access to the rendered template, as well as some extra information such as the content type and character set.

This does have the disadvantage that the compiler won’t catch if our template is missing or the model object is incorrect. However, it lets us write application code before generating the classes for our templates.

4.4. Using Rocker with Spring

Rocker doesn’t directly support Spring, so in order to use it from our controllers, we’ll need to write a custom View implementation:

public class RockerView implements View {
    private final String viewName;

    public RockerView(String viewName) {
        this.viewName = viewName;
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
        BindableRockerModel template = Rocker.template(viewName);
        for (Map.Entry<String, ?> entry : model.entrySet()) {
            try {
                template.bind(entry.getKey(), entry.getValue());
            } catch (TemplateBindException e) {
                // Ignore
            }
        }
        RockerOutput output = template.render();

        response.setContentType(MediaType.TEXT_HTML_VALUE);
        response.getWriter().write(output.toString());
    }
}

This takes our view name when constructed and then attempts to bind everything from our provided model map. We have to handle the TemplateBindException in case there are entries here that Rocker doesn’t expect.

We can then use this directly in our controller methods:

@GetMapping("/rocker")
public ModelAndView get() {
    ModelAndView modelAndView = new ModelAndView(new RockerView("RockerDemo.rocker.html"));
    modelAndView.addObject("model", new RockerModel("Baeldung"));

    return modelAndView;
}

This then renders our Rocker template to the response when this controller is used:

$ http localhost:8080/rocker
HTTP/1.1 200
Content-Length: 108
Content-Type: text/html;charset=UTF-8

<html>
    <head>
    </head>
    <body>
        <h1>Demo</h1>
        <p>Hello Baeldung!</p>
    </body>
</html>

5. JTE

Our next library to look at is JTE. This works in a very similar manner to Rocker, with a custom template language and a build plugin that compiles these templates into Java code. However, JTE doesn’t use reflection and is able to be used with GraalVM.

5.1. Dependencies

In order to use JTE, we need to configure it in our build. This comes as a custom Maven plugin that automatically converts our templates into Java code. To use this, we need to include the latest version in our build:

<plugin>
    <groupId>gg.jte</groupId>
    <artifactId>jte-maven-plugin</artifactId>
    <version>3.2.1</version>
    <configuration>
        <sourceDirectory>${basedir}/src/main/resources/templates/jte</sourceDirectory>
        <targetDirectory>${basedir}/target/jte</targetDirectory>
        <contentType>Html</contentType>
    </configuration>
    <executions>
        <execution>
            <phase>generate-sources</phase>
            <goals>
                <goal>generate</goal>
            </goals>
        </execution>
    </executions>
</plugin>

This searches for templates under src/main/resources/templates/jte, finding anything with the file extension .jte. It then generates Java code into target/jte. It will also automatically add this as a build source so the code gets compiled into our application.

We also need a runtime dependency for this to work:

<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte</artifactId>
    <version>3.2.1</version>
</dependency>

At this point, we’re ready to start using it in our application.

5.2. Writing Templates

Once everything is set up, we’re ready to start using it. JTE works in a very similar way to Rocker, in that our template is the main input, but can refer to other Java classes for data.

Our templates are written in a custom markup language that looks similar to that used by Rocker:

@import com.baeldung.templates.JteModel

@param JteModel model
<html>
    <head>
    </head>
    <body>
        <h1>Demo</h1>
        <p>Hello ${model.name()}!</p>
    </body>
</html>

As with Rocker, we’re importing a Java class that must already exist and then using it as a parameter to our template.

Any class present on the classpath when the template is compiled can be used as a model class:

public record JteModel(String name) { }

5.3. Rendering Templates

Once compiled, we can then use our template to produce our output. As with Rocker, in JTE we don’t reference our generated classes directly. Instead, we use an instance of TemplateEngine that handles everything for us:

TemplateEngine templateEngine = TemplateEngine.createPrecompiled(ContentType.Html);

JteModel model = new JteModel("Baeldung");
StringOutput output = new StringOutput();
templateEngine.render("JteDemo.jte", model, output);

This automatically finds the class generated for our JteDemo.jte template, and renders it with the provided model object.

5.4. Using with Spring

As with JStachio, JTE provides a Spring Boot starter that we can include in our application to use these templates:

<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte-spring-boot-starter-3</artifactId>
    <version>3.2.1</version>
</dependency>

We also need to add some configuration to our application.properties to tell it how the templates are being built:

gg.jte.usePrecompiledTemplates=true

Once done, we can write a controller that returns a view name and binds our model attributes:

@GetMapping("/jte")
public String view(Model model) {
    model.addAttribute("model", new JteModel("Baeldung"));
    return "JteDemo";
}

Here, we only need to specify the name “JteDemo” since the JTE view resolver automatically adds a suffix of “.jte” for us.

This is enough to have JTE render the appropriate template with the provided data:

$ http localhost:8080/jte
HTTP/1.1 200
Content-Length: 103
Content-Type: text/html;charset=UTF-8

<html>
    <head>
    </head>
    <body>
        <h1>Demo</h1>
        <p>Hello Baeldung!</p>
    </body>
</html>

6. ManTL

Our final library is ManTL, or the Manifold Template Library. This is a template library built on top of the Manifold compiler plugin system.

6.1. Dependencies

In order to use ManTL, we need to configure it in our build. This comes as a compiler annotation processor that automatically converts our templates into Java code. To use this, we need to include the latest version in our build.

If we’re using Maven, we can include this dependency in our pom.xml file:

<plugin>
    <artifactId>maven-compiler-plugin</artifactId>
    <configuration>
        <compilerArgs>
            <arg>-Xplugin:Manifold</arg>
        </compilerArgs>
        <annotationProcessorPaths>
            <path>
                <groupId>systems.manifold</groupId>
                <artifactId>manifold-templates</artifactId>
                <version>2025.1.31</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

We also have a runtime dependency that we need to include to be able to use ManTL templates:

<dependency>
    <groupId>systems.manifold</groupId>
    <artifactId>manifold-templates-rt</artifactId>
    <version>2025.1.31</version>
</dependency>

At this point, we’re ready to start using it.

6.2. Writing Templates

Once we finish setting up, we can start writing templates. These are written as .html.mtl files under the src/main/resources directory, with the path under this being the fully qualified class name. For example, src/main/resources/templates/mantl.ManTLDemo.html.mtl equates to a class of templates.mantl.ManTLDemo.

We write our templates in a custom markup language that looks similar to that used by Rocker and JTE:

<%@ import com.baeldung.templates.ManTLModel %>

<%@ params(ManTLModel model) %>
Hello ${model.name()}!

The <%@ params() %> directive indicates the parameters that will need to be passed in to the template when rendering it, similar to parameters to a Java method call.

As with the other templates, these can also refer to Java classes that we’ve written to provide data for us to render:

public record ManTLModel(String name) { }

6.3. Rendering Templates

Once we’ve written our template, we can use it directly to render the output:

String output = templates.mantl.ManTLDemo.render(new ManTLModel("Baeldung"));

This takes our model object as a parameter and produces the fully rendered output ready for us to use.

6.4. Using with Spring

The same as with Rocker, ManTL doesn’t provide any direct Spring integration, and so we’ll need to write our own View implementation use it. However, the way that we call ManTL templates is different, so we can’t easily just provide a set of model objects. Instead, we’ll write our view to take a lambda that returns a string, and then we can handle this all in our own code:

public class StringView implements View {
    private final Supplier<String> output;

    public StringView(Supplier<String> output) {
        this.output = output;
    }

    @Override
    public void render(Map<String, ?> model, HttpServletRequest request,
      HttpServletResponse response) throws Exception {
        response.setContentType(MediaType.TEXT_HTML_VALUE);
        response.getWriter().write(output.get());
    }
}

We can then use this in our controller, calling our template in the lambda we pass in:

@GetMapping("/mantl")
public View get() {
    return new StringView(() -> templates.mantl.ManTLDemo.render(new ManTLModel("Baeldung")));
}

And now our controller will render and return our template:

$ http localhost:8080/mantl
HTTP/1.1 200
Content-Length: 17
Content-Type: text/html;charset=UTF-8

Hello Baeldung!

7. Summary

In this article, we’ve looked at a few different libraries that we can use as compile-time templates. We’ve seen what they are, how to set them up and use them, and how to integrate them with our Spring controllers. Next time you need to do some templating in your application, why not give one of them a try?

As usual, all of the examples from this article are available over on GitHub.

Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)
guest
0 Comments
Oldest
Newest
Inline Feedbacks
View all comments