Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

HTML templates

A static file is always the same, but a page with the list of posts must change along with the data. For this you use a template — an HTML file with places for values. The program that fills a template is called a template engine. Javalin supports several template engines through separate modules; we take Mustache because it has the simplest syntax.

The dependency

Add the module to pom.xml, in the dependencies section:

<!-- pom.xml -->
<dependency>
    <groupId>io.javalin</groupId>
    <artifactId>javalin-rendering-mustache</artifactId>
    <version>${javalin.version}</version>
</dependency>

The module already contains Mustache itself. For other engines there are javalin-rendering-freemarker, javalin-rendering-thymeleaf, javalin-rendering-jte and others — they are added the same way.

The template

Templates live in the classpath. Create a file in the templates folder:

<!-- src/main/resources/templates/posts.mustache -->
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog</title>
</head>
<body>
<h1>Posts</h1>
{{#posts}}
    <article>
        <h2>{{title}}</h2>
        <p>{{body}}</p>
        <small>Author: {{author}}</small>
    </article>
{{/posts}}
{{^posts}}
    <p>No posts yet.</p>
{{/posts}}
</body>
</html>
SyntaxMeaning
{{title}}Insert a value, HTML special characters are escaped
{{#posts}} ... {{/posts}}Repeat the block for each item of the list
{{^posts}} ... {{/posts}}Show the block if the list is empty

Mustache can read record components: {{title}} calls the title() method of Post.

Enabling and calling it

Register the template engine in the configuration and add the page route to the apiBuilder block:

// src/main/java/com/example/blog/Main.java
config.fileRenderer(new JavalinMustache());   // io.javalin.rendering.template.JavalinMustache
 
config.routes.apiBuilder(() -> {
    // ... the /posts routes
    get("/blog", posts::page);
});

In the controller the page method passes a model — a map with data — to the template:

// src/main/java/com/example/blog/PostController.java
public void page(Context ctx) {
    ctx.render("templates/posts.mustache", Map.of("posts", store.findAll()));
}

ctx.render fills the template and sends HTML with the Content-Type: text/html header. Open http://localhost:8080/blog in a browser or call curl:

curl http://localhost:8080/blog
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Blog</title>
</head>
<body>
<h1>Posts</h1>
    <article>
        <h2>Hello, Javalin</h2>
        <p>The first post.</p>
        <small>Author: Anna</small>
    </article>
    <article>
        <h2>Routes</h2>
        <p>How routes work.</p>
        <small>Author: Oleg</small>
    </article>
</body>
</html>

Common mistake

Mustache looks for the file from the classpath root, so the path includes the folder. The call ctx.render("posts.mustache", ...) gives a 500 response, and the log says:

com.github.mustachejava.MustacheNotFoundException: Template posts.mustache not found

Give the path from src/main/resources: templates/posts.mustache.

Thanks for reading!

Donate to the site — we work hard for you.