Template Engines - EJS, Freemarker, and Thymeleaf
Template engines help generate dynamic HTML by combining templates with data. This guide covers three popular template engines.
EJS (Embedded JavaScript)
EJS is a simple templating language that lets you generate HTML markup with plain JavaScript.
Syntax
<% %> <!-- Executed but not output -->
<%= %> <!-- Output the value (HTML escaped) -->
Variables
<h1><%= title %></h1>
Loops
<h1><%= title %></h1>
<ul>
<% for(var i=0; i<supplies.length; i++) { %>
<li><%= supplies[i] %></li>
<% } %>
</ul>
Links
<%= link_to(name, url) %>
<!-- Example -->
<% for(var i=0; i<supplies.length; i++) { %>
<li><%= link_to(supplies[i], 'supplies/' + supplies[i]) %></li>
<% } %>
Images
<%= img_tag('images/logo.png') %>
Include
<% include ../partials/nav.ejs %>
Freemarker
Freemarker is a template engine for Java applications.
Basic Syntax
${variable} <!-- Interpolation -->
<#directiveName parameters> <!-- Predefined directive -->
<@mydirective parameters> <!-- User-defined directive -->
[#-- this is a comment --] <!-- Comment -->
Variables
<#assign seq = ["foo", "bar", "baz"]>
<#assign x++>
<#assign x="Hello ${user}!">
${x}
Conditionals
<#if x == 1>
x is 1
<#elseif x == 2>
x is 2
<#else>
x is something else
</#if>
Loops
<#list 1..3 as n>
${n}
</#list>
<#list users as user>
<tr>
<td>${user.firstname}</td>
<td>${user.lastname}</td>
</tr>
</#list>
Arrays
<!-- Declaration -->
["foo", "bar", 123.45]
<!-- Access -->
products[5]
<!-- Sub-array -->
products[20..29]
<!-- Concatenation -->
users + ["guest"]
<!-- Methods -->
${testSequence?size}
${testSequence?join(", ")}
Maps
<!-- Declaration -->
{"name":"green mouse", "price":150}
<!-- Access -->
user.name
user["name"]
<!-- Iteration -->
<#list user?keys as prop>
${prop} = ${user.get(prop)}
</#list>
<!-- Concatenation -->
passwords + { "joe": "secret42" }
Strings
"Foo" or 'Foo'
"It's \"quoted\""
${r"${raw}"} <!-- Raw string -->
<!-- Operations -->
name[0] <!-- Character -->
name[0..4] <!-- Substring (inclusive) -->
name[0..<5] <!-- Substring (exclusive) -->
name[5..] <!-- From index to end -->
<!-- Concatenation -->
<#assign s = "Hello " + user + "!">
<!-- Methods -->
${testString?upper_case}
${testString?cap_first}
Numbers
${1.999?int} <!-- 1 -->
${-1.999?int} <!-- -1 -->
Type Casting
${3 + "5"} <!-- "35" (string concatenation) -->
123?string <!-- Number to string -->
${married?string("yes", "no")} <!-- Boolean to string -->
Functions
<#function avg x y>
<#return (x + y) / 2>
</#function>
${avg(10, 20)}
Macros
<#macro greet name>
Hello, ${name}!
</#macro>
<@greet name="John"/>
<!-- With default values -->
<#macro defaultHead title="Default Title">
<title>${title}</title>
</#macro>
<!-- Nested content -->
<#macro box>
<div class="box">
<#nested>
</div>
</#macro>
<@box>
<p>Content inside box</p>
</@box>
Exception Handling
<#attempt>
Optional: ${thisMayFail}
<#recover>
Oops! Something went wrong.
</#attempt>
<!-- Missing values -->
${mouse!"No mouse."} <!-- Default if null -->
<#if mouse??> <!-- Check if exists -->
Mouse found
</#if>
Import and Include
<#import "/mylib.ftl" as my>
<#include "/footer/${company}.html">
Operators
<!-- Arithmetic -->
(x * 1.5 + 10) / 2 - y % 100
<!-- Comparison -->
x == y, x != y, x < y, x > y, x >= y, x <= y
x lt y, x lte y, x gt y, x gte y
<!-- Logical -->
!registered && (firstVisit || fromEurope)
<!-- Assignment -->
=, +=, -=, *=, /=, %=, ++, --
Compute Audience
For JavaScript or URL values (non-human readable):
<a href="/product?id=${product.id?c}">Details</a>
${someBoolean?c}
Thymeleaf
Thymeleaf is a modern server-side Java template engine for Spring applications.
Setup
<html xmlns:th="http://thymeleaf.org">
Display Values
<h4 th:text="${user.name}"></h4>
<p th:if="${param.error}">
Bad Credentials: ${param.error}
</p>
Conditionals
<div th:if="${condition}">
Shown if condition is true
</div>
<div th:unless="${condition}">
Shown if condition is false
</div>
<div th:switch="${user.role}">
<p th:case="'admin'">Admin User</p>
<p th:case="'user'">Regular User</p>
<p th:case="*">Unknown Role</p>
</div>
Loops
<tr th:each="user : ${users}">
<td th:text="${user.name}">Name</td>
<td th:text="${user.email}">Email</td>
</tr>
Attributes
<a th:href="@{/users/{id}(id=${user.id})}">View User</a>
<img th:src="@{/images/logo.png}">
<input th:value="${user.name}">
Comparison
| Feature | EJS | Freemarker | Thymeleaf |
|---|---|---|---|
| Platform | Node.js | Java | Java/Spring |
| Syntax | <%= %> |
${} |
th:* |
| Natural Templates | No | No | Yes |
| Logic in Template | Yes | Yes | Limited |
| Learning Curve | Low | Medium | Medium |
Each template engine has its strengths:
- EJS - Simple JavaScript templating for Node.js
- Freemarker - Powerful features for Java applications
- Thymeleaf - Natural templates that work as valid HTML
When to Use Which
Choosing the right template engine depends on your project’s technology stack, team expertise, and specific requirements. Here are some practical guidelines:
Use EJS when:
- You are building a Node.js application and want minimal learning overhead
- Your team is comfortable with HTML and JavaScript
- You need simple server-side rendering without complex template inheritance
- You are prototyping quickly and want to embed JavaScript logic directly in templates
Use Freemarker when:
- You are working on a Java application (Spring, Struts, or standalone)
- You need powerful macro support and template inheritance for large-scale projects
- Your templates require complex data manipulation and formatting
- You want strict separation between presentation logic and business logic
Use Thymeleaf when:
- You are using Spring Boot and want seamless integration
- Your team includes designers who need to preview templates in a browser
- You value natural templates that are valid HTML
- Form handling and validation display are important features for your application
Performance Considerations
Template engines generally have minimal performance impact compared to database queries and network calls. However, for high-traffic applications, keep these tips in mind:
- Template caching: All three engines support compiled template caching. Make sure caching is enabled in production.
- Minimize logic in templates: Complex calculations should happen in the controller or service layer, not in templates.
- Fragment caching: For expensive partial renders (like navigation menus with database queries), consider caching the rendered fragment output.
- Pre-compilation: Freemarker and Thymeleaf support template pre-compilation at startup, reducing first-request latency.
Choose the template engine that best fits your technology stack and project requirements.
Comments