Thymeleaf

Tutorial: Using Thymeleaf (ja)Japanese translation by: Mitsuyuki Shiiba (@bufferings),
Masatoshi Tada (@suke_masa)
Document version: 20230730 - 30 July 2023
Project version: 3.1.2.RELEASE
Project web site: https://www.thymeleaf.org

Page 1 of 80

1 Thymeleaf
1.1 Thymeleaf
Thymeleaf

Java

HTML XML JavaScript CSS

Thymeleaf

Thymeleaf

Thymeleaf–

HTML5 –

1.2 Thymeleaf
Thymeleaf 6
HTML
XML
TEXT
JAVASCRIPT
CSS
RAW
2

HTML XML

HTML

HTML5 HTML 4

XML

XML

3

TEXT JAVASCRIPT CSS

XHTML

RAW

HTML
–

–

DTD XML

HTML XML

TEXT

TEXT

DOCTYPE
Thymeleaf

JAVASCRIPT
JavaScript

Thymeleaf

CSS

JavaScriptHTML

JAVASCRIPT

JavaScript

TEXT

CSS

JAVASCRIPT

CSS

TEXT

URL

RAW

HTML

Thymeleaf

1.3
Thymeleaf

Thymeleaf––

Thymeleaf

Thymeleaf

thymeleaf-spring3 thymeleaf-spring4
OGNL
SpringSpringSpringELHTML

Spring MVC

Spring Framework
SpringJSP<form:inputText name="userName" value="${user.name}" />…Thymeleaf

Page 2 of 80

<input type="text" name="userName" value="James Carrot" th:value="${user.name}" />value”James Carrot”${user.name}

Page 3 of 80

2 The Good Thymes Virtual Grocery
Good Thymes Virtual Grocery (GTVG)
javax.*
jakarta.*2

gtvg-javax.
gtvg-jakarta.

2.1
Thymeleaf
Thymeleaf
Products

OrdersCustomers

Products

Comments

Example application modelServicepublic class ProductService {...
public List<Product> findAll() {return ProductRepository.getInstance().findAll();}public Product findById(Integer id) {return ProductRepository.getInstance().findById(id);}}URL

Thymeleaf

Page 4 of 80

/**IWebApplication*
Jakarta*/public void init(final FilterConfig filterConfig) throws ServletException {this.application =JakartaServletWebApplication.buildApplication(filterConfig.getServletContext());//
this.templateEngine = buildTemplateEngine(this.application);
}
/*
*
*
*/
private boolean process(HttpServletRequest request, HttpServletResponse response)throws ServletException {try {final IWebExchange webExchange =this.application.buildExchange(request, response);
final IWebRequest webRequest = webExchange.getRequest();//URL
if (request.getRequestURI().startsWith("/css") ||request.getRequestURI().startsWith("/images") ||request.getRequestURI().startsWith("/favicon")) {return false;
}
/*
*
/URL
*
*
false
*
/
*/
final IGTVGController controller =ControllerMappings.resolveControllerForRequest(webRequest);
if (controller == null) {return false;
}
/*
*
*/
response.setContentType("text/html;charset=UTF-8");
response.setHeader("Pragma", "no-cache");response.setHeader("Cache-Control", "no-cache");response.setDateHeader("Expires", 0);/***/
final Writer writer = response.getWriter();
/*
*
*
*/
controller.process(webExchange, this.templateEngine, writer);return true;} catch (Exception e) {try {response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);} catch (final IOException ignored) {//}throw new ServletException(e);}}IGTVGController

Page 5 of 80

public interface IGTVGController {public void process(final IWebExchange webExchange,
final ITemplateEngine templateEngine,
final Writer writer)
throws Exception;}
IGTVGController

ITemplateEngine

Example application home page2.2
init(…)
this.templateEngine = buildTemplateEngine(this.application);org.thymeleaf.TemplateEngine
private static ITemplateEngine buildTemplateEngine(final IWebApplication application) {//ServletContext
final WebApplicationTemplateResolver templateResolver =new WebApplicationTemplateResolver(application);// HTML//
templateResolver.setTemplateMode(TemplateMode.HTML);// "home" "/WEB-INF/templates/home.html"
templateResolver.setPrefix("/WEB-INF/templates/");templateResolver.setSuffix(".html");
//
TTL 1
//
LRU
templateResolver.setCacheTTLMs(Long.valueOf(3600000L));
//
true
//
false
templateResolver.setCacheable(true);
final TemplateEngine templateEngine = new TemplateEngine();templateEngine.setTemplateResolver(templateResolver);return templateEngine;}
TemplateEngine

Page 6 of 80

final WebApplicationTemplateResolver templateResolver =new WebApplicationTemplateResolver(application);
Thymeleaf API

org.thymeleaf.templateresolver.ITemplateResolver

public interface ITemplateResolver {
...
/*
*
*
*
*
null
*/
public TemplateResolution resolveTemplate(final IEngineConfiguration configuration,
final String ownerTemplate, final String template,final Map<String, Object> templateResolutionAttributes);}
GTVG

org.thymeleaf.templateresolver.WebApplicationTemplateResolver

IWebApplication
IWebApplication Thymeleaf
API [javax|jakarta].servlet.ServletContext

templateResolver.setTemplateMode(TemplateMode.HTML);
HTML WebApplicationTemplateResolver
templateResolver.setPrefix("/WEB-INF/templates/");templateResolver.setSuffix(".html");prefix suffix“product/list”
servletContext.getResourceAsStream("/WEB-INF/templates/product/list.html")
cacheTTLMs
templateResolver.setCacheTTLMs(3600000L);
TTL

ICacheManager

org.thymeleaf.ITemplateEngine

StandardCacheManager

1

Thymeleaf

org.thymeleaf.TemplateEngine

templateEngine = new TemplateEngine();templateEngine.setTemplateResolver(templateResolver);TemplateEngine
Thymeleaf

Page 7 of 80

3
3.1/WEB-INF/templates/home.html<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><title>Good Thymes Virtual Grocery</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><link rel="stylesheet" type="text/css" media="all"href="../../css/gtvg.css" th:href="@{/css/gtvg.css}" /></head><body><p th:text="#{home.welcome}">Welcome to our grocery store!</p></body></html>
HTML5

HTML

HTML5

th:text
HTML5

th:*

<html>

xmlns:thHTML5<html xmlns:th="http://www.thymeleaf.org">…

IDE th:*HTML5

Thymeleaf

data-

:

-

<!DOCTYPE html><html><head><title>Good Thymes Virtual Grocery</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><link rel="stylesheet" type="text/css" media="all"href="../../css/gtvg.css" data-th-href="@{/css/gtvg.css}" /></head><body><p data-th-text="#{home.welcome}">Welcome to our grocery store!</p></body></html>
HTML5

data-

HTML5

th:*
data-

th:*

Thymeleaf

XML TEXT …

HTML

th:text.properties
i18n
Thymeleaf#{...}<p th:text="#{home.welcome}">Welcome to our grocery store!</p>

Page 8 of 80

Thymeleaf

2”Welcome to our grocery store!“th:text#{home.welcome}Thymeleaf

home.welcomeorg.thymeleaf.messageresolver.IMessageResolver

th:text.properties

org.thymeleaf.messageresolver.StandardMessageResolver/WEB-INF/templates/home.html/WEB-INF/templates/home_en.properties/WEB-INF/templates/home_es.properties/WEB-INF/templates/home_pt_BR.properties/WEB-INF/templates/home.properties
home_es.properties
home.welcome=¡Bienvenido a nuestra tienda de comestibles!
Thymeleaf

HomeController

IGTVGController

public class HomeController implements IGTVGController {public void process(final IWebExchange webExchange,
final ITemplateEngine templateEngine,
final Writer writer)
throws Exception {WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
templateEngine.process("home", ctx, writer);}}Thymeleaf

org.thymeleaf.context.IContext

public interface IContext {public Locale getLocale();public boolean containsVariable(final String name);public Set<String> getVariableNames();public Object getVariable(final String name);}org.thymeleaf.context.IWebContext
public interface IWebContext extends IContext {public IWebExchange getExchange();}Thymeleaf
org.thymeleaf.context.Context implements IContext
org.thymeleaf.context.WebContext implements IWebContext
WebContext

WebApplicationTemplateResolver

IWebContext

WebContext ctx = new WebContext(webExchange, webExchange.getLocale());

Page 9 of 80

WebContext

IWebExchange

IWebExchange

WebContext${x} Thymeleafx${param.x} x${session.x} x${application.x} x

templateEngine.process("home", ctx, writer);<!DOCTYPE html><html><head><title>Good Thymes Virtual Grocery</title><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" /></head><body><p>¡Bienvenido a nuestra tienda de comestibles!</p></body></html>3.2…
home.welcome=Welcome to our <b>fantastic</b> grocery store!

<p>Welcome to our &lt;b&gt;fantastic&lt;/b&gt; grocery store!</p><b>th:text

Thymeleaf HTML

th:utext<p th:utext="#{home.welcome}">Welcome to our grocery store!</p><p>Welcome to our <b>fantastic</b> grocery store!</p>

Welcome to our fantastic grocery store!
Today is: 12 july 2010

Page 10 of 80

public void process(final IWebExchange webExchange,
final ITemplateEngine templateEngine,
final Writer writer)
throws Exception {SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar cal = Calendar.getInstance();
WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));
templateEngine.process("home", ctx, writer);}Stringtoday<body><p th:utext="#{home.welcome}">Welcome to our grocery store!</p><p>Today is: <span th:text="${today}">13 February 2011</span></p></body>th:text#{...}

OGNL(Object-Graph Navigation${...}

Language)${today}

today${user.name}user

getName()…

Page 11 of 80

4Thymeleaf

Thymeleaf2<p th:utext="#{home.welcome}">Welcome to our grocery store!</p><p>Today is: <span th:text="${today}">13 february 2011</span></p>${...}*{...}#{...}
URL@{...}~{...}'one text' , 'Another one!' ,…
0 , 34 , 3.0 , 12.3 ,…
true , false
Null

null
one , sometext , main ,…

+|The name is ${name}|+, -, *, /, %-and , or! , not> , < , >= , <= ( gt , lt , ge , le )== , != ( eq , ne )If-then (if) ? (then)If-then-else (if) ? (then) : (else)(value) ?: (defaultvalue)_'User is of type ' + (${user.isAdmin()} ? 'Administrator' : (${user.type} ?: 'Unknown'))

4.1#{...}<p th:utext="#{home.welcome}">Welcome to our grocery store!</p>…

Page 12 of 80

home.welcome=¡Bienvenido a nuestra tienda de comestibles!

<p>¡Bienvenido a nuestra tienda de comestibles, John Apricot!</p>home.welcome=¡Bienvenido a nuestra tienda de comestibles, {0}!
java.text.MessageFormat
HTTP

API

java.text.*user<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>th:utext

user.name<p th:utext="#{${welcomeMsgKey}(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>

4.2
OGNL(Object-Graph Navigation Language)${...}OGNL

OGNL

Spring MVC

OGNL SpringEL

OGNL

OGNL
<p>Today is: <span th:text="${today}">13 february 2011</span>.</p>…
ctx.getVariable("today");
OGNL<p th:utext="#{home.welcome(${session.user.name})}">
Welcome to our grocery store, Sebastian Pepper!
</p>…
((User) ctx.getVariable("session").get("user")).getName();
OGNL

1Page 13 of 80

/*
*
.
*
*/
${person.father.name}
/*
*
[]
*
*/
${person['father']['name']}/*** get(...)*/${countriesByCode.ES}${personsByName['Stephen Zucchini'].age}
/*
*
*
*/
${personsArray[0].name}/***/${person.createCompleteName()}${person.createCompleteNameWithSeparator('-')}

OGNL

OGNL

#

#ctx
#vars
#localeEstablished locale country: <span th:text="${#locale.country}">US</span>.
Appendix A#execInfo#messages #{…}
#uris URL/URI#conversions#dates java.util.Date#temporals : JDK8
#calendars #dates

java.time API
java.util.Calendar#numbers#strings String

contains startsWith prepending/appending#objects#bools#arrays#lists#sets#maps#aggregates#ids

idAppendix B

HomeController

Page 14 of 80

SimpleDateFormat dateFormat = new SimpleDateFormat("dd MMMM yyyy");
Calendar cal = Calendar.getInstance();
WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("today", dateFormat.format(cal.getTime()));
templateEngine.process("home", ctx, writer);…
WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("today", Calendar.getInstance());
templateEngine.process("home", ctx, writer);…
<p>
Today is: <span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 May 2011</span>
</p>

4.3
${...}*{...}

th:object

userprofile.html<div th:object="${session.user}"><p>Name: <span th:text="*{firstName}">Sebastian</span>.</p><p>Surname: <span th:text="*{lastName}">Pepper</span>.</p><p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p></div><div><p>Name: <span th:text="${session.user.firstName}">Sebastian</span>.</p><p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p><p>Nationality: <span th:text="${session.user.nationality}">Saturn</span>.</p></div><div th:object="${session.user}"><p>Name: <span th:text="*{firstName}">Sebastian</span>.</p><p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p><p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p></div>#object<div th:object="${session.user}"><p>Name: <span th:text="${#object.firstName}">Sebastian</span>.</p><p>Surname: <span th:text="${session.user.lastName}">Pepper</span>.</p><p>Nationality: <span th:text="*{nationality}">Saturn</span>.</p></div><div><p>Name: <span th:text="*{session.user.name}">Sebastian</span>.</p><p>Surname: <span th:text="*{session.user.surname}">Pepper</span>.</p><p>Nationality: <span th:text="*{session.user.nationality}">Saturn</span>.</p></div>

4.4

URL
URL

Thymeleaf@@{...}

Page 15 of 80

URL
URL http://www.thymeleaf.org
URL
URL user/login.html
URL /itemdetails?id=3
URL ~/billing/processInvoice= application

URL

URL //code.jquery.com/jquery-2.0.3.min.js
URL

ITemplateEngine

1

org.thymeleaf.linkbuilder.ILinkBuilder

org.thymeleaf.linkbuilder.StandardLinkBuilder

API

API
th:href<!-- 'http://localhost:8080/gtvg/order/details?orderId=3'--><!---><a href="details.html"
th:href="@{http://localhost:8080/gtvg/order/details(orderId=${o.id})}">view</a><!-- '/gtvg/order/details?orderId=3'--><a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a><!-- '/gtvg/order/3/details'--><a href="details.html" th:href="@{/order/{orderId}/details(orderId=${o.id})}">view</a>th:href

URL

URL

orderId=${o.id}

<a>

href
URL

@{/order/process(execId=${execId},execType='FAST')}
URL@{/order/{orderId}/details(orderId=${orderId})}
URL//order/details";jsessionid=..."

URL

URL

Thymeleaf

URL

API response.encodeURL(...)
th:hrefhref#{...}

URL<a th:href="@{${url}(orderId=${o.id})}">view</a><a th:href="@{'/details/'+${user.login}(orderId=${o.id})}">view</a>

URL<p>Please select an option</p><ol><li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li><li><a href="order/list.html" th:href="@{/order/list}">Order List</a></li><li><a href="subscribe.html" th:href="@{/subscribe}">Subscribe to our Newsletter</a></li><li><a href="userprofile.html" th:href="@{/userprofile}">See User Profile</a></li>
</ol>

URL
URL

URL

@{~/path/to/something}

4.5

th:insert

th:replace<div th:insert="~{commons :: main}">...</div>

Page 16 of 80

<div th:with="frag=~{footer :: #main/text()}"><p th:insert="${frag}"></div>

4.6\'<p>
Now you are looking at a <span th:text="'working web application'">template file</span>.
</p><p>The year is <span th:text="2013">1492</span>.</p><p>In two years, it will be <span th:text="2013 + 2">1494</span>.</p>true false<div th:if="${user.isAdmin()} == false"> ...
== false

Thymeleaf

OGNL/SpringEL<div th:if="${user.isAdmin() == false}"> ...

null
null
<div th:if="${variable.something} == null"> ...

null
'...'

A-Z a-z

0-9

[ ]

.

-

_

<div th:class="'content'">...</div><div th:class="content">...</div>

4.7+

Page 17 of 80

<span th:text="'The name of the user is ' + ${user.name}">4.8'...' + '...'|<span th:text="|Welcome to our application, ${user.name}!|"><span th:text="'Welcome to our application, ' + ${user.name} + '!'"><span th:text="${onevar} + ' ' + |${twovar}, ${threevar}|">

/

|...|

${...} *{...} #{...}

'...'

/

4.9
+ - * / %<div th:with="isEven=(${prodStat.count} % 2 == 0)">
OGNL

Thymeleaf

OGNL<div th:with="isEven=${prodStat.count % 2 == 0}">
div /

mod %

4.10
> < >= <=

XML

== !=

< >

&lt; &gt;<div th:if="${prodStat.count} &gt; 1"><span th:text="'Execution mode is ' + ( (${execMode} == 'dev')? 'Development' : 'Production')">
gt >

lt <

ge >=

le <=

not !

eq ==

neq / ne !=4.11
2
th:class<tr th:class="${row.even}? 'even' : 'odd'">
...
</tr>
3

condition thenelse${...} *{...}#{...}

URL @{...}'...'<tr th:class="${row.even}? (${row.first}? 'first' : 'even') : 'odd'">
...
</tr>
else

false

null

Page 18 of 80

<tr th:class="${row.even}? 'alt'">
...
</tr>

4.12
then

Groovy

2

null

null

2

<div th:object="${session.user}">...
<p>Age: <span th:text="*{age}?: '(no age specified)'">27</span>.</p></div>*{age} null?:<p>Age: <span th:text="*{age != null}? *{age} : '(no age specified)'">27</span>.</p>

<p>
Name:
<span th:text="*{firstName}?: (*{admin}? 'Admin' : #{default.username})">Sebastian</span>
</p>

4.13
_
th:text<span th:text="${user.name} ?: 'no user authenticated'">...</span>…‘no user authenticated’<span th:text="${user.name} ?: _">no user authenticated</span>

4.14

/

Thymeleaf${...}*{...}<td th:text="${{user.lastAccessDate}}">...</td>${{...}} Thymeleaf user.lastAccessDate
user.lastAccessDate java.util.Calendar

IStandardConversionService

IStandardConversionService

StandardConversionService

thymeleaf-spring3 thymeleaf-spring4${{...}}

StringThymeleaf*{{...}}.toString()

SpringCalendar -> String

StringConversion Service

Spring

4.15
Thymeleaf

Page 19 of 80

__${expression}__i18n Messages_fr.properties

OGNL

article.text=@myapp.translator.Translator@translateToFrench({0})… Messages_es.properties
article.text=@myapp.translator.Translator@translateToSpanish({0})
Thymeleaf<p th:text="${__#{article.text('textVar')}__}">Some text here...</p><p th:text="${@myapp.translator.Translator@translateToFrench(textVar)}">Some text here...</p>
__

\_\_

Page 20 of 80

55.1/WEB-INF/templates/subscribe.html<form action="subscribe.html"><fieldset><input type="text" name="email" /><input type="submit" value="Subscribe!" /></fieldset></form>Thymeleaf

URL

actionvalue

th:attr<form action="subscribe.html" th:attr="action=@{/subscribe}"><fieldset><input type="text" name="email" /><input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/></fieldset></form>th:attr<form action="/gtvg/subscribe"><fieldset><input type="text" name="email" /><input type="submit" value="¡Suscríbe!"/></fieldset></form>/gtvg/subscribe URL
XML

1

2

th:attr<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /><img src="/gtgv/images/gtvglogo.png" title="Logo de Good Thymes" alt="Logo de Good Thymes" />5.2<input type="submit" value="Subscribe!" th:attr="value=#{subscribe.submit}"/>Thymeleaf

th:attr

value

th:*

th:attrth:value<input type="submit" value="Subscribe!" th:value="#{subscribe.submit}"/>form

action<form action="subscribe.html" th:action="@{/subscribe}">Page 21 of 80

th:hrefhome.html<li><a href="product/list.html" th:href="@{/product/list}">Product List</a></li>
Thymeleaf

HTML5th:abbrth:accept

th:accept-charset

th:accesskey

th:actionth:align

th:altth:archiveth:audioth:autocomplete

th:axisth:backgroundth:bgcolor

th:border

th:cellpadding

th:cellspacing

th:challenge

th:charset

th:citeth:class

th:classid

th:codebase

th:codetype

th:colsth:colspanth:compact

th:content

th:contenteditable

th:contextmenu

th:datath:datetime

th:dirth:draggable

th:dropzone

th:enctype

th:forth:form

th:formaction

th:formenctype

th:formmethod

th:formtarget

th:fragment

th:frameth:frameborderth:headers

th:height

th:highth:href

th:hreflang

th:hspaceth:http-equiv

th:iconth:id

th:inline

th:keytype

th:kindth:labelth:lang

th:listth:longdesc

th:lowth:manifest

th:marginheightth:marginwidthth:maxth:maxlengthth:media

th:method

th:minth:nameth:onabortth:onafterprint

th:onbeforeprint

th:onbeforeunload

th:onblurth:oncanplay

th:oncanplaythroughth:onchange

th:onclick

th:oncontextmenu

th:ondblclick

th:ondragth:ondragend

th:ondragenter

th:ondragleaveth:ondragoverth:ondragstart

th:ondropth:ondurationchange

th:onemptied

th:onended

th:onerror

th:onfocus

th:onformchange

th:onforminput

th:onhashchange

th:oninput

th:oninvalid

th:onkeydown

th:onkeypress

th:onkeyup

th:onloadth:onloadeddata

th:onloadedmetadata

th:onloadstart

th:onmessage

th:onmousedown

th:onmousemove

th:onmouseout

th:onmouseoverth:onmouseup

th:onmousewheelth:onoffline

th:ononline

th:onpause

th:onplayth:onplaying

th:onpopstate

th:onprogress

th:onratechange

th:onreadystatechange

th:onredo

th:onresetth:onresize

th:onscroll

th:onseeked

th:onseeking

th:onselectth:onshow

th:onstalled

th:onstorage

th:onsubmit

th:onsuspend

th:ontimeupdate

th:onundo

th:onunload

th:onvolumechange

th:onwaiting

th:optimum

th:patternth:placeholder

th:posterth:preload

th:radiogroupth:relth:revth:rows

Page 22 of 80

th:relth:revth:rows

th:rowspanth:rulesth:sandbox

th:scheme

th:scope

th:scrollingth:sizeth:sizesth:spanth:spellcheckth:srcth:srclang

th:standby

th:start

th:step

th:style

th:summary

th:tabindex

th:target

th:title

th:typeth:usemap

th:valueth:valuetypeth:vspaceth:widthth:wrap

th:xmlbase

th:xmllang

th:xmlspace

5.3
2

th:alt-title th:lang-xmllang2th:alt-title alt title
th:lang-xmllang lang xml:lang
GTVG<img src="../../images/gtvglogo.png"
th:attr="src=@{/images/gtvglogo.png},title=#{logo},alt=#{logo}" /><img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:title="#{logo}" th:alt="#{logo}" /><img src="../../images/gtvglogo.png"
th:src="@{/images/gtvglogo.png}" th:alt-title="#{logo}" />5.4
Thymeleaf

th:attrappend th:attrprepend

CSS

CSS

<input type="button" value="Do it!" class="btn" th:attrappend="class=${' ' + cssStyle}" />cssStyle"warning"<input type="button" value="Do it!" class="btn warning" />2

CSS

th:classappend th:styleappend

style<tr th:each="prod : ${prods}" class="row" th:classappend="${prodStat.odd}? 'odd'">
th:each5.5
HTML”true”

XHTML

1checked<input type="checkbox" name="option2" checked /> <!-- HTML --><input type="checkbox" name="option1" checked="checked" /> <!-- XHTML -->
true

false

Page 23 of 80

<input type="checkbox" name="active" th:checked="${user.active}" />th:async

th:autofocus

th:autoplay

th:checked

th:controls

th:declareth:default

th:deferth:disabled

th:formnovalidate

th:hidden

th:ismap

th:loopth:multiple

th:novalidate

th:nowrap

th:openth:pubdate

th:readonly

th:required

th:reversed

th:scoped

th:seamless

th:selected

5.6
Thymeleafth:*<span th:whatever="${user.name}">...</span><span whatever="John Apricot">...</span>

5.7 HTML5
HTML5<table><tr data-th-each="user : ${users}"><td data-th-text="${user.login}">...</td><td data-th-text="${user.name}">...</td></tr></table>data-{prefix}-{name}

th:*

HTML5

Thymeleaf

W3C Custom Elements specification{prefix}-{name}W3C Web Components specth:blockth-block:th:*Page 24 of 80

6

6.1
1

/WEB-INF/templates/product/list.html

<tr>

1

–

–

Thymeleaf
th:eachth:eachpublic void process(final IWebExchange webExchange,
final ITemplateEngine templateEngine,
final Writer writer)
throws Exception {final ProductService productService = new ProductService();
final List<Product> allProducts = productService.findAll();
final WebContext ctx = new WebContext(webExchange, webExchange.getLocale());
ctx.setVariable("prods", allProducts);
templateEngine.process("product/list", ctx, writer);}th:each<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><title>Good Thymes Virtual Grocery</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><link rel="stylesheet" type="text/css" media="all"href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" /></head><body><h1>Product list</h1><table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th></tr><tr th:each="prod : ${prods}"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td></tr></table><p><a href="../home.html" th:href="@{/}">Return to home</a></p></body></html>prod : ${prods}${prods}

prod${prods}
prod
prod

<tr>

<td>

Page 25 of 80

Thymeleaf

java.util.List

th:eachjava.util.Iterable
java.util.Enumeration
java.util.Iterator
java.util.Map

java.util.Map.Entryjava.util.stream.Stream

6.2
Thymeleaf

th:eachth:each
index

0

count

1

size
current
even/odd
first
last

<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th></tr><tr th:each="prod,iterStat : ${prods}" th:class="${iterStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td></tr></table>th:eachiterStatth:eachPage 26 of 80

<!DOCTYPE html><html><head><title>Good Thymes Virtual Grocery</title><meta content="text/html; charset=UTF-8" http-equiv="Content-Type"/><link rel="stylesheet" type="text/css" media="all" href="/gtvg/css/gtvg.css" /></head><body><h1>Product list</h1><table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th></tr><tr class="odd"><td>Fresh Sweet Basil</td><td>4.99</td><td>yes</td>
</tr>
<tr>
<td>Italian Tomato</td><td>1.25</td><td>no</td></tr><tr class="odd"><td>Yellow Bell Pepper</td><td>2.50</td><td>yes</td>
</tr>
<tr>
<td>Old Cheddar</td><td>18.75</td><td>yes</td></tr></table><p><a href="/gtvg/" shape="rect">Return to home</a></p></body></html>odd CSSThymeleaf

Stat<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th></tr><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td></tr></table>6.3

Thymeleaf

ILazyContextVariable–

LazyContextVariable–

Page 27 of 80

context.setVariable("users",new LazyContextVariable<List<User>>() {@Override
protected List<User> loadValue() {return databaseRepository.findAllUsers();
}
});

<ul>
<li th:each="u : ${users}" th:text="${u.name}">user name</li></ul>condition false

loadValue()<ul th:if="${condition}"><li th:each="u : ${users}" th:text="${u.name}">user name</li></ul>Page 28 of 80

77.1“if” ”unless”

th:if<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th></tr><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td><td><span th:text="${#lists.size(prod.comments)}">2</span> comment/s<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:if="${not #lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
</table><a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:if="${not #lists.isEmpty(prod.comments)}">view</a>
id prodId/product/comments

URL

Page 29 of 80

<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td><td>4.99</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Italian Tomato</td><td>1.25</td><td>no</td><td><span>2</span> comment/s<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td><td>2.50</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Old Cheddar</td><td>18.75</td><td>yes</td><td><span>1</span> comment/s<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>

th:if

true

null
boolean true
00“false”

”off”

null

th:if false

”no”

th:if

th:unless

OGNL

not

<a href="comments.html"
th:href="@{/comments(prodId=${prod.id})}"th:unless="${#lists.isEmpty(prod.comments)}">view</a>

7.2
Java

switchth:switch / th:case<div th:switch="${user.role}"><p th:case="'admin'">User is an administrator</p><p th:case="#{roles.manager}">User is a manager</p></div>
th:casetrueth:casefalse

th:case="*"Page 30 of 80

<div th:switch="${user.role}"><p th:case="'admin'">User is an administrator</p><p th:case="#{roles.manager}">User is a manager</p><p th:case="*">User is some other thing</p></div>

Page 31 of 80

88.1

Thymeleaf

th:fragment/WEB-INF/templates/footer.html<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><body><div th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</div></body></html>copy

th:insert

th:replace<body>...
<div th:insert="~{footer :: copy}"></div></body>th:insert~{...}

3"~{templatename::selector}" templatename

selector~{footer :: copy}~{templatename::fragmentname}

AttoParser

XPath

CSS

Appendix C"~{templatename}" templatename

th:insert / th:replace~{::selector}""~{this::selector}"

selector

selectorroot
templatename selector<div th:insert="~{ footer :: (${user.isAdmin}? #{footer.admin} : #{footer.normaluser}) }"></div>th:*th:insert / th:replace

Thymeleaf

th:fragment

Page 32 of 80

Thymeleaf

th:fragment...
<div id="copy-section">&copy; 2011 The Good Thymes Virtual Grocery</div>...
id

CSS<body>...
<div th:insert="~{footer :: #copy-section}"></div></body>th:insert th:replace
th:insert th:replace
th:insert
th:replaceHTML<footer th:fragment="copy">&copy; 2011 The Good Thymes Virtual Grocery</footer>
…

<div>

2

<body>...
<div th:insert="~{footer :: copy}"></div><div th:replace="~{footer :: copy}"></div></body>…
<body>...
<div><footer>&copy; 2011 The Good Thymes Virtual Grocery</footer></div><footer>&copy; 2011 The Good Thymes Virtual Grocery</footer></body>8.2
th:fragment<div th:fragment="frag (onevar,twovar)"><p th:text="${onevar} + ' - ' + ${twovar}">...</p></div>
th:insert th:replace

2

Page 33 of 80

<div th:replace="~{ ::frag (${value1},${value2}) }">...</div><div th:replace="~{ ::frag (onevar=${value1},twovar=${value2}) }">...</div><div th:replace="~{ ::frag (twovar=${value2},onevar=${value1}) }">...</div><div th:fragment="frag">...
</div><div th:replace="~{::frag (onevar=${value1},twovar=${value2})}">
th:replace th:with<div th:replace="~{::frag}" th:with="onevar=${value1},twovar=${value2}">––th:asserttrue

th:assert<div th:assert="${onevar},(${twovar} != 43)">...</div><header th:fragment="contentheader(title)" th:assert="${!#strings.isEmpty(title)}">...</header>

8.3

title

links

<head th:fragment="common_header(title,links)"><title th:replace="${title}">The awesome application</title><!-- Common styles and scripts --><link rel="stylesheet" type="text/css" media="all" th:href="@{/css/awesomeapp.css}"><link rel="shortcut icon" th:href="@{/images/favicon.ico}"><script type="text/javascript" th:src="@{/sh/scripts/codebase.js}"></script><!--/* Per-page placeholder for additional links */--><th:block th:replace="${links}" /></head>Page 34 of 80

...
<head th:replace="~{ base :: common_header(~{::title},~{::link}) }"><title>Awesome - Main</title><link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"><link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"></head>...
…

<title> <link>

title

links

...
<head><title>Awesome - Main</title><!-- Common styles and scripts --><link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css"><link rel="shortcut icon" href="/awe/images/favicon.ico"><script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script><link rel="stylesheet" href="/awe/css/bootstrap.min.css"><link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css"></head>...

( ~{} )
<head th:replace="~{ base :: common_header(~{::title},~{}) }"><title>Awesome - Main</title></head>
...
2

links

<th:block th:replace="${links}" />...
<head><title>Awesome - Main</title><!-- Common styles and scripts --><link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css"><link rel="shortcut icon" href="/awe/images/favicon.ico"><script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script></head>...

common_header...
<head th:replace="~{base :: common_header(_,~{::link})}"><title>Awesome - Main</title><link rel="stylesheet" th:href="@{/css/bootstrap.min.css}"><link rel="stylesheet" th:href="@{/themes/smoothness/jquery-ui.css}"></head>...
title

common_header_

title =:

<title th:replace="${title}">The awesome application</title>

Page 35 of 80

...
<head><title>The awesome application</title><!-- Common styles and scripts --><link rel="stylesheet" type="text/css" media="all" href="/awe/css/awesomeapp.css"><link rel="shortcut icon" href="/awe/images/favicon.ico"><script type="text/javascript" src="/awe/sh/scripts/codebase.js"></script><link rel="stylesheet" href="/awe/css/bootstrap.min.css"><link rel="stylesheet" href="/awe/themes/smoothness/jquery-ui.css"></head>...

common :: adminhead...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : ~{}">...</div>...

...
<div th:insert="${user.isAdmin()} ? ~{common :: adminhead} : _">Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>...
– checkExistence–...
<!-- "common :: salutation"--><!-- <div>--><div th:insert="~{common :: salutation} ?: _">Welcome [[${user.name}]], click <a th:href="@{/support}">here</a> for help-desk support.
</div>...

8.4<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th></tr><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td><td><span th:text="${#lists.size(prod.comments)}">2</span> comment/s<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
</table>Thymeleaf
1…

2

Page 36 of 80

<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th></tr><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td><td><span th:text="${#lists.size(prod.comments)}">2</span> comment/s<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd"><td>Blue Lettuce</td><td>9.55</td><td>no</td><td><span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td><td>1.99</td><td>yes</td><td><span>3</span> comment/s<a href="comments.html">view</a>
</td>
</tr>
</table>3…Thymeleaf

Page 37 of 80

<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td><td>4.99</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Italian Tomato</td><td>1.25</td><td>no</td><td><span>2</span> comment/s<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td><td>2.50</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Old Cheddar</td><td>18.75</td><td>yes</td><td><span>1</span> comment/s<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
<tr class="odd"><td>Blue Lettuce</td><td>9.55</td><td>no</td><td><span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td><td>1.99</td><td>yes</td><td><span>3</span> comment/s<a href="comments.html">view</a>
</td>
</tr>
</table>2

Thymeleaf
2

th:remove

2

3

2

<tr>

Page 38 of 80

<table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th></tr><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td><td><span th:text="${#lists.size(prod.comments)}">2</span> comment/s<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd" th:remove="all"><td>Blue Lettuce</td><td>9.55</td><td>no</td><td><span>0</span> comment/s
</td>
</tr>
<tr th:remove="all"><td>Mild Cinnamon</td><td>1.99</td><td>yes</td><td><span>3</span> comment/s<a href="comments.html">view</a>
</td>
</tr>
</table><table><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th>
</tr>
<tr>
<td>Fresh Sweet Basil</td><td>4.99</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Italian Tomato</td><td>1.25</td><td>no</td><td><span>2</span> comment/s<a href="/gtvg/product/comments?prodId=2">view</a>
</td>
</tr>
<tr>
<td>Yellow Bell Pepper</td><td>2.50</td><td>yes</td><td><span>0</span> comment/s
</td>
</tr>
<tr class="odd"><td>Old Cheddar</td><td>18.75</td><td>yes</td><td><span>1</span> comment/s<a href="/gtvg/product/comments?prodId=4">view</a>
</td>
</tr>
</table>allth:remove

5

Page 39 of 80

all
body
tag
all-but-firstnone
all-but-firstth:remove="all"<table><thead><tr><th>NAME</th><th>PRICE</th><th>IN STOCK</th><th>COMMENTS</th></tr></thead><tbody th:remove="all-but-first"><tr th:each="prod : ${prods}" th:class="${prodStat.odd}? 'odd'"><td th:text="${prod.name}">Onions</td><td th:text="${prod.price}">2.41</td><td th:text="${prod.inStock}? #{true} : #{false}">yes</td><td><span th:text="${#lists.size(prod.comments)}">2</span> comment/s<a href="comments.html"
th:href="@{/product/comments(prodId=${prod.id})}"th:unless="${#lists.isEmpty(prod.comments)}">view</a>
</td>
</tr>
<tr class="odd"><td>Blue Lettuce</td><td>9.55</td><td>no</td><td><span>0</span> comment/s
</td>
</tr>
<tr>
<td>Mild Cinnamon</td><td>1.99</td><td>yes</td><td><span>3</span> comment/s<a href="comments.html">view</a>
</td>
</tr>
</tbody></table>th:remove

all tag body all-but-firstnone

Thymeleaf<a href="/something" th:remove="${condition}? tag : none">Link text not to be removed</a>th:remove null none<a href="/something" th:remove="${condition}? tag">Link text not to be removed</a>${condition} false

null

8.5
1

title content

th:fragment th:replace

Page 40 of 80

<!DOCTYPE html><html th:fragment="layout (title, content)" xmlns:th="http://www.thymeleaf.org"><head><title th:replace="${title}">Layout Title</title></head><body><h1>Layout H1</h1><div th:replace="${content}"><p>Layout content</p></div><footer>
Layout footer</footer></body></html>layouttitle content<!DOCTYPE html><html th:replace="~{layoutFile :: layout(~{::title}, ~{::section})}"><head><title>Page Title</title></head><body><section><p>Page content</p><div>Included on page</div></section></body></html>html

layouttitle content

title content

header footer

Page 41 of 80

9Thymeleaf
prod<tr th:each="prod : ${prods}">
...
</tr>
prod

<tr>
th:each

<td>

th:*

th:each<tr>

Thymeleaf

th:with

<div th:with="firstPer=${persons[0]}"><p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p></div>
th:withfirstPer<div><div th:with="firstPer=${persons[0]},secondPer=${persons[1]}"><p>
The name of the first person is <span th:text="${firstPer.name}">Julius Caesar</span>.
</p><p>
But the name of the second person is<span th:text="${secondPer.name}">Marcus Antonius</span>.
</p></div>
th:with
<div th:with="company=${user.company + ' Co.'},account=${accounts[company]}">...</div><p>
Today is:<span th:text="${#calendars.format(today,'dd MMMM yyyy')}">13 february 2011</span></p>"dd MMMM yyyy"

home_en.properties

date.format=MMMM dd'','' yyyy
home_es.properties
date.format=dd ''de'' MMMM'','' yyyy
th:withth:text<p th:with="df=#{date.format}">Today is: <span th:text="${#calendars.format(today,df)}">13 February 2011</span></p>
th:with th:text

span

<p>
Today is:<span th:with="df=#{date.format}"th:text="${#calendars.format(today,df)}">13 February 2011</span></p>

Page 42 of 80

Page 43 of 80

10
th:*
<ul>
<li th:each="item : ${items}" th:text="${item.description}">Item description here...</li></ul>th:eachth:text

HTML/XML

Thymeleaf

1th:insert
th:replace2th:each3

th:if
th:unlessth:switch
th:case4th:object
th:with5

th:attrth:attrprepend
th:attrappend

6th:valueth:href
th:src...

7

th:text
th:utext

8

th:fragment

9th:remove<ul><li th:text="${item.description}" th:each="item : ${items}">Item description here...</li></ul>Page 44 of 80

11
11.1.

HTML/XML

HTML/XML<!-- ... --> Thymeleaf

Thymeleaf<!---><div th:text="${...}">...
</div>

11.2. Thymeleaf
Thymeleaf<!--/*

Thymeleaf*/-->

Thymeleaf <!--/* */-->

Thymeleaf<!--/*--><div></div><!--*/--><tr><table><tr th:each="x : ${xs}">
...
</tr>
<!--/*-->
<tr>
...
</tr>
<tr>
...
</tr>
<!--*/--></table>11.3. Thymeleaf
Thymeleaf<span>hello!</span><!--/*/<div th:text="${...}">...
</div>/*/--><span>goodbye!</span>
Thymeleaf<!--/*/ /*/-->

Thymeleaf<span>hello!</span><div th:text="${...}">...
</div><span>goodbye!</span>

Page 45 of 80

11.4.
th:blockth:blockThymeleaf
Thymeleaf

th:block1<tr><table><th:block th:each="user : ${users}"><tr><td th:text="${user.login}">...</td><td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td></tr></th:block></table><table><!--/*/ <th:block th:each="user : ${users}"> /*/--><tr><td th:text="${user.login}">...</td><td th:text="${user.name}">...</td>
</tr>
<tr>
<td colspan="2" th:text="${user.address}">...</td></tr><!--/*/ </th:block> /*/--></table><table>

<div>

HTML

Page 46 of 80

12
12.1
HTML
<p>Hello, <span th:text="${session.user.name}">Sebastian</span>!</p>…
<p>Hello, [[${session.user.name}]]!</p>Thymeleaf[[...]] [(...)]

th:text th:utext

HTML[[...]] th:text[(...)] th:utextHTML

msg = 'This is <b>great!</b>'<p>The message is "[(${msg})]"</p><b><p>The message is "This is <b>great!</b>"</p><p>The message is "[[${msg}]]"</p>HTML<p>The message is "This is &lt;b&gt;great!&lt;/b&gt;"</p>––vsth:text
HTML…
Hello, Sebastian!
…

…

Hello, [[${session.user.name}]]!
…

[[...]] [(...)]

th:inline="none":<p th:inline="none">A double array looks like this: [[1, 2, 3], [4, 5]]!</p><p>A double array looks like this: [[1, 2, 3], [4, 5]]!</p>

12.2
Page 47 of 80

th:inline="text"
TEXT

12.3 JavaScript
JavaScriptHTML<script>

JavaScript

JAVASCRIPT
Thymeleaf

JavaScript

th:inline="javascript"<script th:inline="javascript">...
var username = [[${session.user.name}]];...
</script>:<script th:inline="javascript">...
var username = "Sebastian \"Fruity\" Applejuice";...
</script>
2
1

JavaScript

2

JavaScript${session.user.name}JavaScript[[${session.user.name}]]<script th:inline="javascript">...
var username = [(${session.user.name})];...
</script><script th:inline="javascript">...
var username = Sebastian "Fruity" Applejuice;...
</script>…

JavaScript

JavaScript
JavaScript

JavaScript
JavaScript<script th:inline="javascript">...
var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";...
</script>
Thymeleaf'Gertrud Kiwifruit'<script th:inline="javascript">...
var username = "Sebastian \"Fruity\" Applejuice";...
</script>

Page 48 of 80

<script th:inline="javascript">...
var username = /*[[${session.user.name}]]*/ "Gertrud Kiwifruit";...
</script>
JavaScript
JavaScript

JavaScript
JavaScript

Thymeleaf

JavaScript<script th:inline="javascript">...
var user = /*[[${session.user}]]*/ null;...
</script>${session.user}

User

Thymeleaf

JavaScript<script th:inline="javascript">...
var user = {"age":null,"firstName":"John","lastName":"Apricot","name":"John Apricot","nationality":"Antarctica"};...
</script>
JavaScriptorg.thymeleaf.standard.serializer.IStandardJavaScriptSerializer

JS

StandardDialect

Jackson library

12.4 CSS
Thymeleaf

CSS <style><style th:inline="css">...
</style>2

2

Stringclassname = 'main elems'align = 'center'<style th:inline="css">.[[${classname}]] {text-align: [[${align}]];}</style>Page 49 of 80

<style th:inline="css">.main\ elems {text-align: center;}</style>CSSJavaScript

CSS[[${classname}]]classname = 'mainelems' main\ elems

CSS
JavaScript

CSS<style>CSS<style th:inline="css">.main\ elems {text-align: /*[[${align}]]*/ left;}</style>Page 50 of 80

13
13.1
Thymeleaf

3

TEXT JAVASCRIPT CSSHTML XML

Dear [(${name})],Please find attached the results of the report you requested
with name "[(${report.name})]".
Sincerely,
The Reporter.
Thymeleaf

TEXT[# th:each="item : ${items}"]- [(${item})][/][#th:block th:each="item : ${items}"]- [#th:block th:utext="${item}" /][/th:block]<element ...>\[#element ...][#element ...][/element]XML[#element ... /]th:blockth:block[#th:block ...] ... [/th:block][# ...] ... [/][# th:each="item : ${items}"]- [# th:utext="${item}" /][/][# th:utext="${item}" /][# th:each="item : ${items}"]- [(${item})][/]– HTMLXML

TEXT
Dear [(${customer.name})],This is the list of our products:[# th:each="prod : ${products}"]- [(${prod.name})]. Price: [(${prod.price})] EUR/kg[/]
Thanks,The Thymeleaf Shop

Page 51 of 80

Dear Mary Ann Blueberry,This is the list of our products:- Apricots. Price: 1.12 EUR/kg- Bananas. Price: 1.78 EUR/kg- Apples. Price: 0.85 EUR/kg- Watermelon. Price: 1.91 EUR/kg
Thanks,The Thymeleaf Shop
JAVASCRIPT

HTML

greeter.js

HTML<script>.js
var greeter = function() {var username = [[${session.user.name}]];[# th:each="salut : ${salutations}"]
alert([[${salut}]] + " " + username);[/]};var greeter = function() {var username = "Bertrand \"Crunchy\" Pear";alert("Hello" + " " + username);alert("Ol\u00E1" + " " + username);alert("Hola" + " " + username);
};

HTML

text

HTML

th:inline="text"

Thymeleaf 3.0

HTML

TEXT

JavaScript

JAVASCRIPT
CSS

CSS
TEXT

OK

&gt;

[# th:if="${120&lt;user.age}"]
Congratulations!
[/]
&lt;

<user.age

13.2[#myorg:dosomething myorg:importantattr="211"]some text[/myorg:dosomething]

13.3
JAVASCRIPT CSS/*[+...+]*/

Thymeleaf

TEXT

Page 52 of 80

var x = 23;/*[+
var msg= "This is a working application";+]*/var f = function() {...

var x = 23;
var msg= "This is a working application";var f = function() {...

var x = 23;/*[+
var msg= "Hello, " + [[${session.user.name}]];+]*/var f = function() {
...

13.4
3

TEXT JAVASCRIPT CSS/*[- */ /* -]*/

Thymeleaf

var x = 23;/*[- */
var msg= "This is shown only when executed statically!";/* -]*/
var f = function() {
...
TEXT
...
/*[- Note the user is obtained from the session, which must exist -]*/
Welcome [(${session.user.name})]!
...

13.5 JavaScript CSS
JavaScript CSS

JavaScript/CSS...
var username = /*[[${session.user.name}]]*/ "Sebastian Lychee";...
…

JavaScript...
var username = "John Apricot";...

Page 53 of 80

/*[# th:if="${user.admin}"]*/
alert('Welcome admin');/*[/]*/– 100%JavaScript–[# th:if="${user.admin}"]alert('Welcome admin');[/]…
;Thymeleaf 3.0

JavaScript

CSS

Page 54 of 80

14
Thymeleaf
HTML

14.1
/WEB-INF/templates/order/list.html<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><title>Good Thymes Virtual Grocery</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><link rel="stylesheet" type="text/css" media="all"href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" /></head><body><h1>Order list</h1><table><tr><th>DATE</th><th>CUSTOMER</th><th>TOTAL</th><th></th></tr><tr th:each="o : ${orders}" th:class="${oStat.odd}? 'odd'"><td th:text="${#calendars.format(o.date,'dd/MMM/yyyy')}">13 jan 2011</td><td th:text="${o.customer.name}">Frederic Tomato</td><td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td><td><a href="details.html" th:href="@{/order/details(orderId=${o.id})}">view</a>
</td>
</tr>
</table><p><a href="../home.html" th:href="@{/}">Return to home</a></p></body></html>OGNL<td th:text="${#aggregates.sum(o.orderLines.{purchasePrice * amount})}">23.32</td>
OrderLinepurchasePrice amount

getPurchasePrice() getAmount()#aggregates.sum(...)
OGNL

14.2

Page 55 of 80

<!DOCTYPE html><html xmlns:th="http://www.thymeleaf.org"><head><title>Good Thymes Virtual Grocery</title><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><link rel="stylesheet" type="text/css" media="all"href="../../../css/gtvg.css" th:href="@{/css/gtvg.css}" /></head><body th:object="${order}"><h1>Order details</h1><div><p><b>Code:</b> <span th:text="*{id}">99</span></p><p><b>Date:</b><span th:text="*{#calendars.format(date,'dd MMM yyyy')}">13 jan 2011</span></p></div><h2>Customer</h2><div th:object="*{customer}"><p><b>Name:</b> <span th:text="*{name}">Frederic Tomato</span></p><p><b>Since:</b><span th:text="*{#calendars.format(customerSince,'dd MMM yyyy')}">1 jan 2011</span></p></div><h2>Products</h2><table><tr><th>PRODUCT</th><th>AMOUNT</th><th>PURCHASE PRICE</th></tr><tr th:each="ol,row : *{orderLines}" th:class="${row.odd}? 'odd'"><td th:text="${ol.product.name}">Strawberries</td><td th:text="${ol.amount}" class="number">3</td><td th:text="${ol.purchasePrice}" class="number">23.32</td></tr></table><div><b>TOTAL:</b><span th:text="*{#aggregates.sum(orderLines.{purchasePrice * amount})}">35.23</span></div><p><a href="list.html" th:href="@{/order/list}">Return to order list</a></p></body></html><body th:object="${order}">...
<div th:object="*{customer}"><p><b>Name:</b> <span th:text="*{name}">Frederic Tomato</span></p>...
</div>...
</body>*{name}<p><b>Name:</b> <span th:text="${order.customer.name}">Frederic Tomato</span></p>

Page 56 of 80

15
15.1
ITemplateResolver

WebApplicationTemplateResolver

Thymeleaf

ITemplateResolver

4

org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
return Thread.currentThread().getContextClassLoader().getResourceAsStream(template);org.thymeleaf.templateresolver.FileTemplateResolver
return new FileInputStream(new File(template));URL

org.thymeleaf.templateresolver.UrlTemplateResolver
return (new URL(template)).openStream();org.thymeleaf.templateresolver.StringTemplateResolver template

Stringreturn new StringReader(templateName);
ITemplateResolver

templateResolver.setPrefix("/WEB-INF/templates/");templateResolver.setSuffix(".html");/
templateResolver.addTemplateAlias("adminHome","profiles/admin/home");templateResolver.setTemplateAliases(aliasesMap);

templateResolver.setCharacterEncoding("UTF-8");//HTML
templateResolver.setTemplateMode("XML");//
true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/*");
TTL
//
TTL
templateResolver.setCacheTTLMs(60000L);

Thymeleaf + Spring

SpringResourceTemplateResolver

SpringSpringPage 57 of 80

2ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
WebApplicationTemplateResolver webApplicationTemplateResolver =new WebApplicationTemplateResolver(application);
webApplicationTemplateResolver.setOrder(Integer.valueOf(2));
templateEngine.addTemplateResolver(classLoaderTemplateResolver);templateEngine.addTemplateResolver(webApplicationTemplateResolver);Thymeleaf
ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();classLoaderTemplateResolver.setOrder(Integer.valueOf(1));// This classloader will not be even asked for any templates not matching these patterns
classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/layout/*.html");classLoaderTemplateResolver.getResolvablePatternSpec().addPattern("/menu/*.html");
WebApplicationTemplateResolver webApplicationTemplateResolver =new WebApplicationTemplateResolver(application);
webApplicationTemplateResolver.setOrder(Integer.valueOf(2));
ITemplateResolver

Thymeleaf

ITemplateResolver

checkExistence

ClassLoaderTemplateResolver classLoaderTemplateResolver = new ClassLoaderTemplateResolver();classLoaderTemplateResolver.setOrder(Integer.valueOf(1));
classLoaderTempalteResolver.setCheckExistence(true);
false

checkExistence

URL

–

15.2
org.thymeleaf.messageresolver.StandardMessageResolverStandardMessageResolver

Thymeleaf + Spring

IMessageResolver

IMessageResolver

SpringSpringMessageSource

StandardMessageResolver
home/WEB-INF/templates/home.html

gl_ES/WEB-INF/templates/home_gl_ES.properties/WEB-INF/templates/home_gl.properties/WEB-INF/templates/home.properties
StandardMessageResolver

JavaDoc// 1
templateEngine.setMessageResolver(messageResolver);// 1
templateEngine.addMessageResolver(messageResolver);2

3

Page 58 of 80

15.3
Thymeleaf${{...}}
StandardDialect

IStandardConversionService

IStandardConversionService customConversionService = ...
StandardDialect dialect = new StandardDialect();
dialect.setConversionService(customConversionService);
templateEngine.setDialect(dialect);

thymeleaf-spring3 thymeleaf-spring4
Conversion Service

SpringStandardDialect

IStandardConversionService

SpringThymeleaf

15.4
Thymeleaf
slf4j

slf4j

Thymeleaf

log4j
TRACE DEBUG INFO

TemplateEngine

3

org.thymeleaf.TemplateEngine.CONFIG
org.thymeleaf.TemplateEngine.TIMER
org.thymeleaf.TemplateEngine.cacheorg.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE
org.thymeleaf.TemplateEngine.cache.EXPRESSION_CACHE
Thymeleaf

log4j

log4j.logger.org.thymeleaf=DEBUG
log4j.logger.org.thymeleaf.TemplateEngine.CONFIG=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.TIMER=TRACE
log4j.logger.org.thymeleaf.TemplateEngine.cache.TEMPLATE_CACHE=TRACE

Page 59 of 80

16Thymeleaf –

–

–

–

–

–

Input/Output

IO
/
//
true
templateResolver.setCacheable(false);
templateResolver.getCacheablePatternSpec().addPattern("/users/*");
StandardCacheManager//
200StandardCacheManager cacheManager = new StandardCacheManager();cacheManager.setTemplateCacheMaxSize(100);...
templateEngine.setCacheManager(cacheManager);org.thymeleaf.cache.StandardCacheManager Javadoc API//
templateEngine.clearTemplateCache();//templateEngine.clearTemplateCacheFor("/users/userList");Page 60 of 80

17
17.1

Thymeleaf HTML XML.th.xml/templates+->/home.html+->/home.th.xml
home.html<!DOCTYPE html><html><body><table id="usersTable"><tr><td class="username">Jeremy Grapefruit</td><td class="usertype">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td><td class="usertype">Administrator</td></tr></table></body></html>Thymeleaf
HTMLhome.th.xml

Thymeleaf

home.html

Thymeleaf

Thymeleaf<?xml version="1.0"?><thlogic><attr sel="#usersTable" th:remove="all-but-first"><attr sel="/tr[0]" th:each="user : ${users}"><attr sel="td.username" th:text="${user.name}" /><attr sel="td.usertype" th:text="#{|user.type.${user.type}|}" /></attr></attr></thlogic><attr>

thlogic<attr><attr>selsel

Thymeleaf

AttoParser

sel="/tr[0]" sel="#usersTable/tr[0]"<td> sel="#usersTable/tr[0]//td.username"<!DOCTYPE html><html><body><table id="usersTable" th:remove="all-but-first"><tr th:each="user : ${users}"><td class="username" th:text="${user.name}">Jeremy Grapefruit</td><td class="usertype" th:text="#{|user.type.${user.type}|}">Normal User</td>
</tr>
<tr>
<td class="username">Alice Watermelon</td><td class="usertype">Administrator</td></tr></table></body></html>Thymeleaf

Page 61 of 80

–<table>id="usersTable"

–

HTML

17.2

ITemplateResolver
ITemplateResolver

StringTemplateResolver

useDecoupledLogic

StringTemplateResolver

final WebApplicationTemplateResolver templateResolver =new WebApplicationTemplateResolver(application);...
templateResolver.setUseDecoupledLogic(true);

Thymeleaf

v3.0

th:ref17.3 th:refth:refth:fragment<attr sel="whatever" .../><whatever>th:fragment="whatever"th:ref="whatever"HTML
th:refid

th:refid class

Thymeleaf( ~{...} )th:ref

17.4

AttoParser

17.5
Thymeleaf
org.thymeleaf.templateparser.markup.decoupled.IDecoupledTemplateLogicResolverStandardDecoupledTemplateLogicResolver

ITemplateResource#getBaseName().th.xml

Page 62 of 80

ITemplateResource#relative(String relativeLocation)
IDecoupledTemplateLogicResolver

TemplateEngine

final StandardDecoupledTemplateLogicResolver decoupledresolver =new StandardDecoupledTemplateLogicResolver();
decoupledResolver.setPrefix("../viewlogic/");...
templateEngine.setDecoupledTemplateLogicResolver(decoupledResolver);Page 63 of 80

18 Appendix A#ctx

org.thymeleaf.context.IContext#vars #root

org.thymeleaf.context.IWebContext

#ctx

/*
* ======================================================================* See javadoc API for class org.thymeleaf.context.IContext* ======================================================================*/${#ctx.locale}${#ctx.variableNames}/** ======================================================================* See javadoc API for class org.thymeleaf.context.IWebContext* ======================================================================*/${#ctx.request}${#ctx.response}${#ctx.session}${#ctx.servletContext}#localejava.util.Locale${#locale}/Thymeleaf

#

param

${param.foo} foo

String[]${param.foo[0]}/** ============================================================================* See javadoc API for class org.thymeleaf.context.WebRequestParamsVariablesMap* ============================================================================*/${param.foo}// 'foo'${param.size()}${param.isEmpty()}${param.containsKey('foo')}...

String[]session/** ======================================================================* See javadoc API for class org.thymeleaf.context.WebSessionVariablesMap* ======================================================================*/${session.foo}${session.size()}${session.isEmpty()}${session.containsKey('foo')}...
application// 'foo'/

Page 64 of 80/** =============================================================================* See javadoc API for class org.thymeleaf.context.WebServletContextVariablesMap* =============================================================================*/${application.foo}// 'foo'${application.size()}${application.isEmpty()}${application.containsKey('foo')}...

${myRequestAttribute}

Page 65 of 80

19 Appendix B#execInfo Thymeleaf/** ======================================================================* See javadoc API for class org.thymeleaf.expression.ExecutionInfo* ======================================================================
*/
/*
*
*
*
"B" "A"* "B"
B*/${#execInfo.templateName}${#execInfo.templateMode}/****"B" "A"* "A"
A*/${#execInfo.processedTemplateName}${#execInfo.processedTemplateMode}/**
List<String>*'processedTemplate'***/${#execInfo.templateNames}${#execInfo.templateModes}

"A"

"A"

List<TemplateMode>/**'templateNames' 'templateModes'**
List<TemplateData>*/${#execInfo.templateStack}#messages :#{...}

Page 66 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Messages* ======================================================================
*/
/*
*
/
/
*
/
/
*
'??msgKey??'*/${#messages.msg('msgKey')}${#messages.msg('msgKey', param1)}${#messages.msg('msgKey', param1, param2)}${#messages.msg('msgKey', param1, param2, param3)}${#messages.msgWithParams('msgKey', new Object[] {param1, param2, param3, param4})}${#messages.arrayMsg(messageKeyArray)}${#messages.listMsg(messageKeyList)}${#messages.setMsg(messageKeySet)}
/*
*
null
*
null
*/
${#messages.msgOrNull('msgKey')}${#messages.msgOrNull('msgKey', param1)}${#messages.msgOrNull('msgKey', param1, param2)}${#messages.msgOrNull('msgKey', param1, param2, param3)}${#messages.msgOrNullWithParams('msgKey', new Object[] {param1, param2, param3, param4})}${#messages.arrayMsgOrNull(messageKeyArray)}${#messages.listMsgOrNull(messageKeyList)}${#messages.setMsgOrNull(messageKeySet)}

URI/URL#uris Thymeleaf

URI/URL//** ======================================================================* See javadoc API for class org.thymeleaf.expression.Uris* ======================================================================*//** URI/URL/*/${#uris.escapePath(uri)}${#uris.escapePath(uri, encoding)}${#uris.unescapePath(uri)}${#uris.unescapePath(uri, encoding)}/** URI/URL'/'/*/${#uris.escapePathSegment(uri)}${#uris.escapePathSegment(uri, encoding)}${#uris.unescapePathSegment(uri)}${#uris.unescapePathSegment(uri, encoding)}
/*
*
#frag
/
*/
${#uris.escapeFragmentId(uri)}${#uris.escapeFragmentId(uri, encoding)}${#uris.unescapeFragmentId(uri)}${#uris.unescapeFragmentId(uri, encoding)}/**?var=value/*/${#uris.escapeQueryParam(uri)}${#uris.escapeQueryParam(uri, encoding)}${#uris.unescapeQueryParam(uri)}${#uris.unescapeQueryParam(uri, encoding)}#conversions

Page 67 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Conversions* ======================================================================*//** 'object'*/${#conversions.convert(object, 'java.util.TimeZone')}${#conversions.convert(object, targetClass)}#dates java.util.Date

Page 68 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Dates* ======================================================================
*/
/*
*
*
*/
${#dates.format(date)}${#dates.arrayFormat(datesArray)}${#dates.listFormat(datesList)}${#dates.setFormat(datesSet)}/** ISO8601**/${#dates.formatISO(date)}${#dates.arrayFormatISO(datesArray)}${#dates.listFormatISO(datesList)}${#dates.setFormatISO(datesSet)}
/*
*
*
*/
${#dates.format(date, 'dd/MMM/yyyy HH:mm')}${#dates.arrayFormat(datesArray, 'dd/MMM/yyyy HH:mm')}${#dates.listFormat(datesList, 'dd/MMM/yyyy HH:mm')}${#dates.setFormat(datesSet, 'dd/MMM/yyyy HH:mm')}
/*
*
*
*/
${#dates.day(date)}${#dates.month(date)}${#dates.monthName(date)}${#dates.monthNameShort(date)}${#dates.year(date)}${#dates.dayOfWeek(date)}${#dates.dayOfWeekName(date)}${#dates.dayOfWeekNameShort(date)}${#dates.hour(date)}${#dates.minute(date)}${#dates.second(date)}${#dates.millisecond(date)}// also arrayDay(...), listDay(...), etc.
// also arrayMonth(...), listMonth(...), etc.
// also arrayMonthName(...), listMonthName(...), etc.
// also arrayMonthNameShort(...), listMonthNameShort(...), etc.
// also arrayYear(...), listYear(...), etc.
// also arrayDayOfWeek(...), listDayOfWeek(...), etc.
// also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
// also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
// also arrayHour(...), listHour(...), etc.
// also arrayMinute(...), listMinute(...), etc.
// also arraySecond(...), listSecond(...), etc.
// also arrayMillisecond(...), listMillisecond(...), etc.

/**
java.util.Date*/${#dates.create(year,month,day)}${#dates.create(year,month,day,hour,minute)}${#dates.create(year,month,day,hour,minute,second)}${#dates.create(year,month,day,hour,minute,second,millisecond)}/**
java.util.Date*/${#dates.createNow()}${#dates.createNowForTimeZone()}/**java.util.Date
*
00:00
*/
${#dates.createToday()}${#dates.createTodayForTimeZone()}#calendars #dates

java.util.Calendar

Page 69 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Calendars* ======================================================================
*/
/*
*
*
*/
${#calendars.format(cal)}${#calendars.arrayFormat(calArray)}${#calendars.listFormat(calList)}${#calendars.setFormat(calSet)}/** ISO8601**/${#calendars.formatISO(cal)}${#calendars.arrayFormatISO(calArray)}${#calendars.listFormatISO(calList)}${#calendars.setFormatISO(calSet)}
/*
*
*
*/
${#calendars.format(cal, 'dd/MMM/yyyy HH:mm')}${#calendars.arrayFormat(calArray, 'dd/MMM/yyyy HH:mm')}${#calendars.listFormat(calList, 'dd/MMM/yyyy HH:mm')}${#calendars.setFormat(calSet, 'dd/MMM/yyyy HH:mm')}
/*
*
*
*/
${#calendars.day(date)}// also arrayDay(...), listDay(...), etc.
${#calendars.month(date)}// also arrayMonth(...), listMonth(...), etc.
${#calendars.monthName(date)}// also arrayMonthName(...), listMonthName(...), etc.
${#calendars.monthNameShort(date)}// also arrayMonthNameShort(...), listMonthNameShort(...), etc.
${#calendars.year(date)}// also arrayYear(...), listYear(...), etc.
${#calendars.dayOfWeek(date)}// also arrayDayOfWeek(...), listDayOfWeek(...), etc.
${#calendars.dayOfWeekName(date)}// also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
${#calendars.dayOfWeekNameShort(date)} // also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
${#calendars.hour(date)}// also arrayHour(...), listHour(...), etc.
${#calendars.minute(date)}// also arrayMinute(...), listMinute(...), etc.
${#calendars.second(date)}// also arraySecond(...), listSecond(...), etc.
${#calendars.millisecond(date)}// also arrayMillisecond(...), listMillisecond(...), etc.
/**
java.util.Calendar*/${#calendars.create(year,month,day)}${#calendars.create(year,month,day,hour,minute)}${#calendars.create(year,month,day,hour,minute,second)}${#calendars.create(year,month,day,hour,minute,second,millisecond)}${#calendars.createForTimeZone(year,month,day,timeZone)}${#calendars.createForTimeZone(year,month,day,hour,minute,timeZone)}${#calendars.createForTimeZone(year,month,day,hour,minute,second,timeZone)}${#calendars.createForTimeZone(year,month,day,hour,minute,second,millisecond,timeZone)}/**
java.util.Calendar*/${#calendars.createNow()}${#calendars.createNowForTimeZone()}/**java.util.Calendar
*
00:00
*/
${#calendars.createToday()}${#calendars.createTodayForTimeZone()}Temporals (java.time)#temporals JDK8

java.time API//*

Page 70 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Temporals* ======================================================================
*/
/*
*
*
*/
${#temporals.format(temporal)}${#temporals.arrayFormat(temporalsArray)}${#temporals.listFormat(temporalsList)}${#temporals.setFormat(temporalsSet)}
/*
*
*
*/
${#temporals.format(temporal, locale)}${#temporals.arrayFormat(temporalsArray, locale)}${#temporals.listFormat(temporalsList, locale)}${#temporals.setFormat(temporalsSet, locale)}/*** SHORT MEDIUM LONG FULL
java.time.format.FormatStyle**/${#temporals.format(temporal, 'dd/MMM/yyyy HH:mm')}${#temporals.format(temporal, 'dd/MMM/yyyy HH:mm', 'Europe/Paris')}${#temporals.arrayFormat(temporalsArray, 'dd/MMM/yyyy HH:mm')}${#temporals.listFormat(temporalsList, 'dd/MMM/yyyy HH:mm')}${#temporals.setFormat(temporalsSet, 'dd/MMM/yyyy HH:mm')}
/*
*
*
*/
${#temporals.format(temporal, 'dd/MMM/yyyy HH:mm', locale)}${#temporals.arrayFormat(temporalsArray, 'dd/MMM/yyyy HH:mm', locale)}${#temporals.listFormat(temporalsList, 'dd/MMM/yyyy HH:mm', locale)}${#temporals.setFormat(temporalsSet, 'dd/MMM/yyyy HH:mm', locale)}/** ISO-8601**/${#temporals.formatISO(temporal)}${#temporals.arrayFormatISO(temporalsArray)}${#temporals.listFormatISO(temporalsList)}${#temporals.setFormatISO(temporalsSet)}
/*
*
*
*/
${#temporals.day(temporal)}${#temporals.month(temporal)}${#temporals.monthName(temporal)}${#temporals.monthNameShort(temporal)}${#temporals.year(temporal)}${#temporals.dayOfWeek(temporal)}${#temporals.dayOfWeekName(temporal)}${#temporals.dayOfWeekNameShort(temporal)}${#temporals.hour(temporal)}${#temporals.minute(temporal)}${#temporals.second(temporal)}${#temporals.nanosecond(temporal)}// also arrayDay(...), listDay(...), etc.
// also arrayMonth(...), listMonth(...), etc.
// also arrayMonthName(...), listMonthName(...), etc.
// also arrayMonthNameShort(...), listMonthNameShort(...), etc.
// also arrayYear(...), listYear(...), etc.
// also arrayDayOfWeek(...), listDayOfWeek(...), etc.
// also arrayDayOfWeekName(...), listDayOfWeekName(...), etc.
// also arrayDayOfWeekNameShort(...), listDayOfWeekNameShort(...), etc.
// also arrayHour(...), listHour(...), etc.
// also arrayMinute(...), listMinute(...), etc.
// also arraySecond(...), listSecond(...), etc.
// also arrayNanosecond(...), listNanosecond(...), etc.

/**
temporal java.time.Temporal*/${#temporals.create(year,month,day)}${#temporals.create(year,month,day,hour,minute)}${#temporals.create(year,month,day,hour,minute,second)}${#temporals.create(year,month,day,hour,minute,second,nanosecond)}/**
temporal java.time.Temporal*/${#temporals.createNow()}${#temporals.createNowForTimeZone(zoneId)}${#temporals.createToday()}${#temporals.createTodayForTimeZone(zoneId)}/***/// return a instance of java.time.LocalDate// return a instance of java.time.LocalDateTime// return a instance of java.time.LocalDateTime// return a instance of java.time.LocalDateTime// return a instance of java.time.LocalDateTime// return a instance of java.time.ZonedDateTime// return a instance of java.time.LocalDate// return a instance of java.time.LocalDate

temporal java.time.Temporal

Page 71 of 80*/${#temporals.createDate(isoDate)}// return a instance of java.time.LocalDate${#temporals.createDateTime(isoDate)}// return a instance of java.time.LocalDateTime${#temporals.createDate(isoDate, pattern)}// return a instance of java.time.LocalDate${#temporals.createDateTime(isoDate, pattern)} // return a instance of java.time.LocalDateTime#numbers/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Numbers* ======================================================================*//** ==========================** ==========================
*/
/*
*
*
*/
${#numbers.formatInteger(num,3)}${#numbers.arrayFormatInteger(numArray,3)}${#numbers.listFormatInteger(numList,3)}${#numbers.setFormatInteger(numSet,3)}/*** 'POINT' 'COMMA' 'WHITESPACE' 'NONE''DEFAULT'**/${#numbers.formatInteger(num,3,'POINT')}${#numbers.arrayFormatInteger(numArray,3,'POINT')}${#numbers.listFormatInteger(numList,3,'POINT')}${#numbers.setFormatInteger(numSet,3,'POINT')}/** ==========================** ==========================
*/
/*
*
*
*/
${#numbers.formatDecimal(num,3,2)}${#numbers.arrayFormatDecimal(numArray,3,2)}${#numbers.listFormatDecimal(numList,3,2)}${#numbers.setFormatDecimal(numSet,3,2)}
/*
*
*
*/
${#numbers.formatDecimal(num,3,2,'COMMA')}${#numbers.arrayFormatDecimal(numArray,3,2,'COMMA')}${#numbers.listFormatDecimal(numList,3,2,'COMMA')}${#numbers.setFormatDecimal(numSet,3,2,'COMMA')}
/*
*
*
*/
${#numbers.formatDecimal(num,3,'POINT',2,'COMMA')}${#numbers.arrayFormatDecimal(numArray,3,'POINT',2,'COMMA')}${#numbers.listFormatDecimal(numList,3,'POINT',2,'COMMA')}${#numbers.setFormatDecimal(numSet,3,'POINT',2,'COMMA')}/** =====================** =====================*/${#numbers.formatCurrency(num)}

Page 72 of 80

${#numbers.formatCurrency(num)}${#numbers.arrayFormatCurrency(numArray)}${#numbers.listFormatCurrency(numList)}${#numbers.setFormatCurrency(numSet)}/** ======================** ======================*/${#numbers.formatPercent(num)}${#numbers.arrayFormatPercent(numArray)}${#numbers.listFormatPercent(numList)}${#numbers.setFormatPercent(numSet)}/***/${#numbers.formatPercent(num, 3, 2)}${#numbers.arrayFormatPercent(numArray, 3, 2)}${#numbers.listFormatPercent(numList, 3, 2)}${#numbers.setFormatPercent(numSet, 3, 2)}/** ===============** ===============
*/
/*
* x y
*/
${#numbers.sequence(from,to)}${#numbers.sequence(from,to,step)}#strings String/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Strings* ======================================================================*//** nulltoString()*/${#strings.toString(obj)}
/*
*
null
*
*/
${#strings.isEmpty(name)}${#strings.arrayIsEmpty(nameArr)}${#strings.listIsEmpty(nameList)}${#strings.setIsEmpty(nameSet)}// also array*, list* and set*trim()/** 'isEmpty()'false* true**/${#strings.defaultString(text,default)}${#strings.arrayDefaultString(textArr,default)}${#strings.listDefaultString(textList,default)}${#strings.setDefaultString(textSet,default)}
/*
*
*
*/
${#strings.contains(name,'ez')}${#strings.containsIgnoreCase(name,'ez')}// also array*, list* and set*// also array*, list* and set*

/*
*
*
*
*/
${#strings.startsWith(name,'Don')}// also array*, list* and set*

Page 73 of 80

${#strings.startsWith(name,'Don')}${#strings.endsWith(name,endingFragment)}// also array*, list* and set*// also array*, list* and set*

/*
*
*
*/
${#strings.indexOf(name,frag)}${#strings.substring(name,3,5)}${#strings.substringAfter(name,prefix)}${#strings.substringBefore(name,suffix)}${#strings.replace(name,'las','ler')}// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*

/*
*
*
*/
${#strings.prepend(str,prefix)}${#strings.append(str,suffix)}// also array*, list* and set*// also array*, list* and set*

/*
*
*
*/
${#strings.toUpperCase(name)}${#strings.toLowerCase(name)}// also array*, list* and set*// also array*, list* and set*/***/${#strings.arrayJoin(namesArray,',')}${#strings.listJoin(namesList,',')}${#strings.setJoin(namesSet,',')}${#strings.arraySplit(namesStr,',')}${#strings.listSplit(namesStr,',')}${#strings.setSplit(namesStr,',')}// returns String[]// returns List<String>// returns Set<String>

/*
*
*
*/
${#strings.trim(str)}// also array*, list* and set*

/*
*
*
*/
${#strings.length(str)}// also array*, list* and set*

/*
*
n
*
"..."
*
*/
${#strings.abbreviate(str,10)}// also array*, list* and set*/***/${#strings.capitalize(str)}${#strings.unCapitalize(str)}// also array*, list* and set*// also array*, list* and set*/***/${#strings.capitalizeWords(str)}${#strings.capitalizeWords(str,delimiters)}// also array*, list* and set*// also array*, list* and set*/***/${#strings.escapeXml(str)}${#strings.escapeJava(str)}${#strings.escapeJavaScript(str)}${#strings.unescapeJava(str)}${#strings.unescapeJavaScript(str)}// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*// also array*, list* and set*/*
* null*/${#strings.equals(first, second)}${#strings.equalsIgnoreCase(first, second)}${#strings.concat(values...)}${#strings.concatReplaceNulls(nullValue, values...)}/** random*/

Page 74 of 80

*/${#strings.randomAlphanumeric(count)}#objects/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Objects* ======================================================================*//** nullobj null**/${#objects.nullSafe(obj,default)}${#objects.arrayNullSafe(objArray,default)}${#objects.listNullSafe(objList,default)}${#objects.setNullSafe(objSet,default)}#bools/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Bools* ======================================================================*//** th:if**/${#bools.isTrue(obj)}${#bools.arrayIsTrue(objArray)}${#bools.listIsTrue(objList)}${#bools.setIsTrue(objSet)}
/*
*
*
*/
${#bools.isFalse(cond)}${#bools.arrayIsFalse(condArray)}${#bools.listIsFalse(condList)}${#bools.setIsFalse(condSet)}
/*
*
AND
*
*/
${#bools.arrayAnd(condArray)}${#bools.listAnd(condList)}${#bools.setAnd(condSet)}
/*
*
OR
*
*/
${#bools.arrayOr(condArray)}${#bools.listOr(condList)}${#bools.setOr(condSet)}#arrays

Page 75 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Arrays* ======================================================================
*/
/*
*
*
*/
${#arrays.toArray(object)}

Object[]/***/${#arrays.toStringArray(object)}${#arrays.toIntegerArray(object)}${#arrays.toLongArray(object)}${#arrays.toDoubleArray(object)}${#arrays.toFloatArray(object)}${#arrays.toBooleanArray(object)}/***/${#arrays.length(array)}/***/${#arrays.isEmpty(array)}
/*
* 1
*/
${#arrays.contains(array, element)}${#arrays.containsAll(array, elements)}#lists/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Lists* ======================================================================
*/
/*
*
*/
${#lists.toList(object)}/***/${#lists.size(list)}/***/${#lists.isEmpty(list)}
/*
* 1
*/
${#lists.contains(list, element)}${#lists.containsAll(list, elements)}/**
comparable*
comparator*/${#lists.sort(list)}${#lists.sort(list, comparator)}#sets

Page 76 of 80/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Sets* ======================================================================
*/
/*
*
*/
${#sets.toSet(object)}/***/${#sets.size(set)}/***/${#sets.isEmpty(set)}
/*
* 1
*/
${#sets.contains(set, element)}${#sets.containsAll(set, elements)}

#maps
/*
* ======================================================================* See javadoc API for class org.thymeleaf.expression.Maps* ======================================================================
*/
/*
*
*/
${#maps.size(map)}/***/${#maps.isEmpty(map)}/***/${#maps.containsKey(map, key)}${#maps.containsAllKeys(map, keys)}${#maps.containsValue(map, value)}${#maps.containsAllValues(map, value)}#aggregates/** ======================================================================* See javadoc API for class org.thymeleaf.expression.Aggregates* ======================================================================
*/
/*
*
null
*/
${#aggregates.sum(array)}${#aggregates.sum(collection)}
/*
*
null
*/
${#aggregates.avg(array)}${#aggregates.avg(collection)}

Page 77 of 80

ID
#ids

id

/*
* ======================================================================* See javadoc API for class org.thymeleaf.expression.Ids* ======================================================================
*/
/*
*
th:id
* id
*/
${#ids.seq('someId')}/**<label>th:for* #ids.seq(...)
id** <label> #ids.seq(...)* "next""seq"*/${#ids.next('someId')}${#ids.prev('someId')}"prev""seq"

Page 78 of 80

20 Appendix C
Thymeleaf

Thymeleaf

AttoParser

XPath CSS jQuery

AttoParser documentation

content<div><div th:insert="~{mytemplate :: //div[@class='content']}">...</div>

/x

x

//x

x

x[@z="v"] x

z”v”

x[@z1="v1" and @z2="v2"] xx[i] xz1 z2”v1” “v2”ix[@z="v"][i] x

z

x //x

x

”v”

i

th:ref[@class='oneclass']= equal!= not equal

XPath

@

class

^= starts with

"oneclass"$= ends with

jQueryXPathandth:fragmentx[@class^='section'] x

class

sectionx[z='v'] x[@z='v']
jQueryx[@z1='v1' and @z2='v2'] x[@z1='v1'][@z2='v2']x[z1='v1'][z2='v2']
jQueryx.oneclass x[class='oneclass'].oneclass [class='oneclass']
x#oneid x[id='oneid']#oneid [id='oneid']
x%oneref <x>

th:ref="oneref"

th:fragment="oneref"%oneref

th:ref="oneref"

th:fragment="oneref"

onerefa.external[@href^='https']<div th:insert="~{mytemplate :: //div[@class='content']}">...</div><div th:insert="~{mytemplate :: div.content}">...</div><div th:replace="~{mytemplate :: myfrag}">...</div>
th:fragment="myfrag"th:refHTML

myfrag<div th:replace="~{mytemplate :: .myfrag}">...</div>…

class="myfrag"th:fragment="myfrag"th:refPage 79 of 80

class
class

class

div.two <div class="one two three" />Page 80 of 80