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>| Syntax | Meaning |
|---|---|
{{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.