jte is a fast and lightweight template engine for Java. All jte templates are compiled to Java class files, meaning jte adds essentially zero overhead to your application. jte is designed to introduce as few new keywords as possible and builds upon existing Java features, so that it is very easy to reason about what a template does. The IntelliJ plugin offers full completion and refactoring support for Java parts as well as for jte keywords. Supports Java 11 or higher.
Features
- Compile time checked and context-sensitive
- IntelliJ plugin offering completion and refactoring support
- Hot reloading of templates during development
- Intuitive, easy to learn syntax
- Blazing fast execution (~100k renders/s on a MacBook Pro 2015, ~2M renders/s on AMD Ryzen 5950x)
- Very small footprint (just one external dependency for HTML escaping)
TLDR
jte is a lot of fun to work with! Have a look how it feels like in IntelliJ with the jte plugin installed:
5 minutes example
Here is a small jte template example.jte
:
@import org.example.Page
@param Page page
<head>
@if(page.getDescription() != null)
<meta name="description" content="${page.getDescription()}">
@endif
<title>${page.getTitle()}</title>
</head>
<body>
<h1>${page.getTitle()}</h1>
<p>Welcome to my example page!</p>
</body>
So what is going on here?
@import
directly translates to Java imports, in this case so thatorg.example.Page
is known to the template.@param Page page
is the parameter that needs to be passed to this template.@if
/@endif
is an if-block. The stuff inside the braces (page.getDescription() != null
) is plain Java code. @JSP users: Yes, there is@elseif()
and@else
in jte❤️ .${}
writes to the underlying template output, as known from various other template engines.
To render this template, an instance of TemplateEngine
is required. Typically you create it once per application (it is safe to share the engine between threads):
CodeResolver codeResolver = new DirectoryCodeResolver(Path.of("jte")); // This is the directory where your .jte files are located.
TemplateEngine templateEngine = TemplateEngine.create(codeResolver, ContentType.Html); // Two choices: Plain or Html
The content type passed to the engine determines how user output will be escaped. If you render HTML files, Html
is highly recommended. This enables the engine to analyze HTML templates at compile time and perform context sensitive output escaping of user data, to prevent you from XSS attacks.
With the TemplateEngine
ready, templates are rendered like this:
TemplateOutput output = new StringOutput();
templateEngine.render("example.jte", page, output);
System.out.println(output);
Besides
StringOutput
, there are several otherTemplateOutput
implementations you can use, or create your own if required.
example.jte
works, but imagine you have more than one page. You would have to duplicate a lot of shared template code. Let's extract the shared code into a tag. Tags are template snippets that can be called by other templates.
All tags must be created in a directory called
tag
in your template root directory.
Let's move stuff from our example page to tag/page.jte
:
@import org.example.Page
@import gg.jte.Content
@param Page page
@param Content content
<head>
@if(page.getDescription() != null)
<meta name="description" content="${page.getDescription()}">
@endif
<title>${page.getTitle()}</title>
</head>
<body>
<h1>${page.getTitle()}</h1>
${content}
</body>
The @param Content content
is a content block that can be provided by callers of the template. ${content}
renders this content block. Let's refactor example.jte
to use the new tag:
@import org.example.Page
@param Page page
@tag.page(page = page, content = @`
<p>Welcome to my example page!</p>
`)
The shorthand to create content blocks within jte templates is an @
followed by two backticks. For advanced stuff, you can even create Java methods that return custom Content
implementation and call it from your template code!
Check out the syntax documentation, for a more comprehensive introduction.
Performance
By design, jte provides very fast output. This is a fork of mbosecke/template-benchmark with jte included, running on a MacBook Pro 2015 (single thread):
Note that the above is with ContentType.Plain
, so no output escaping is done. This is basically what the other engines in the benchmark are set-up with. Well, except Thymeleaf I think. Since jte 0.8.0, you will want to render HTML pages with ContentType.Html
, so that output is automatically escaped by the engine, depending on where in the HTML data is written to. With ContentType.Html
, jte is still extremly fast, thanks to owasp-java-encoder:
High concurrency
This is a fork of mbosecke/template-benchmark with jte included, running on an AMD Ryzen 5950x:
For this benchmark, the amount of threads was manually set @Threads(32)
, to fully utilize all cores. jte has pretty much zero serialization bottlenecks and runs very concurrent on servers with a lot of CPU cores.
Getting started
jte is available on Maven Central:
Maven
<dependency>
<groupId>gg.jte</groupId>
<artifactId>jte</artifactId>
<version>1.4.0</version>
</dependency>
Gradle
implementation group: 'gg.jte', name: 'jte', version: '1.4.0'
No further dependencies required! Check out the syntax documentation and start hacking :-)