Skip to content

jte: Java Template Engine

jtejte (Java Template Engine) is a secure and lightweight template engine for Java and Kotlin. jte is designed to introduce as few new keywords as possible and builds upon existing language features, making it straightforward to reason about what a template does. The IntelliJ plugin offers full completion and refactoring support for Java parts and jte keywords.

Build Status Coverage Status License Maven Central

jte 3 is here!

Check out the release notes for exciting new features, improved performance, and streamlined dependencies.

Features

  • Intuitive and easy syntax, you'll rarely need to check these docs.
  • Write plain Java or Kotlin for expressions. You don't need to learn yet another expression language
  • Context-sensitive HTML escaping at compile time
  • IntelliJ plugin with completion and refactoring support
  • Hot reloading of templates during development
  • Blazing fast execution (see benchmarks)

Getting started

jte is available on Maven Central:

in your pom.xml
1
2
3
4
5
<dependency>
    <groupId>gg.jte</groupId>
    <artifactId>jte</artifactId>
    <version>3.1.9</version>
</dependency>
in your build.gradle
implementation("gg.jte:jte:3.1.9")

No further dependencies are required! Check out the syntax documentation and have fun with jte.

IntelliJ Plugin

jte gives you the same productive, typesafe experience you're used to from writing Java or Kotlin. Here is a quick demo of the IntelliJ jte plugin:

jte plugin in IntelliJ

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>
@import org.example.Page

@param page: Page

<head>
    @if(page.description != null)
        <meta name="description" content="${page.description}">
    @endif
    <title>${page.title}</title>
</head>
<body>
    <h1>${page.title}</h1>
    <p>Welcome to my example page!</p>
</body>

So what is going on here?

  • @import directly translates to Java/Kotlin imports, in this case, so that org.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 parentheses (page.getDescription() != null) is plain Java code.
  • ${} writes to the underlying template output, as known from various other template engines.

To render this template, an instance of gg.jte.TemplateEngine is required. Typically, you create it once per application (it is safe to share the engine between threads):

1
2
3
4
5
6
7
8
import gg.jte.CodeResolver;
import gg.jte.TemplateEngine;
import gg.jte.resolve.DirectoryCodeResolver;

...

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
1
2
3
4
5
6
7
8
import gg.jte.CodeResolver
import gg.jte.TemplateEngine
import gg.jte.resolve.DirectoryCodeResolver

...

val codeResolver = DirectoryCodeResolver(Path.of("kte")) // This is the directory where your .kte files are located.
val 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, ContentType.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 gg.jte.TemplateEngine ready, templates are rendered like this:

1
2
3
4
5
6
7
8
import gg.jte.TemplateOutput;
import gg.jte.output.StringOutput;

...

TemplateOutput output = new StringOutput();
templateEngine.render("example.jte", page, output);
System.out.println(output);
1
2
3
4
5
6
7
8
import gg.jte.TemplateOutput
import gg.jte.output.StringOutput

...

val output = StringOutput()
templateEngine.render("example.kte", page, output)
println(output)

TemplateOutput implementations

Besides gg.jet.output.StringOutput, you can use several other gg.jte.TemplateOutput implementations or create your own if required.

  • gg.jte.output.StringOutput - writes to a String
  • gg.jte.output.FileOutput - writes to the given java.io.File
  • gg.jte.output.PrintWriterOutput - writes to a PrintWriter, for instance, the writer provided by HttpServletRequest
  • gg.jte.output.WriterOutput - writes to a java.io.Writer

If you had more than one page like example.jte, you would have to duplicate a lot of shared template code. Let's extract the shared code into a reusable template.

Let's move stuff from our example page to layout.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>
@import org.example.Page
@import gg.jte.Content

@param page: Page
@param content: Content

<head>
    @if(page.description != null)
    <meta name="description" content="${page.description}">
    @endif
    <title>${page.title}</title>
</head>
<body>
    <h1>${page.title}</h1>
    ${content}
</body>

The @param Content content is a content block that callers of the template can provide. ${content} renders this content block. Let's refactor example.jte to use the new template:

1
2
3
4
5
6
7
@import org.example.Page

@param Page page

@template.layout(page = page, content = @`
    <p>Welcome to my example page!</p>
`)
1
2
3
4
5
6
7
@import org.example.Page

@param page: Page

@template.layout(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 gg.jte.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 AMD Ryzen 5950x (single thread):

alt Template Benchmark

High concurrency

This is the same benchmark as above, but the amount of threads was set to @Threads(16), to fully utilize all cores. jte has pretty much zero serialization bottlenecks and runs very concurrent on servers with many CPU cores:

alt Template Benchmark

Framework integration

Websites rendered with jte