Can Java 21 String templates replace the third-party Java templating engines?
What are String templates?
String templates are a new feature introduced in Java 21 with JEP 430. They are a way to combine literal text with expressions that are evaluated at runtime to produce the final string. This allows for more concise and readable code compared to traditional string concatenation or formatting methods.
This is a preview language feature.
What are traditional string concatenation or formatting methods?
- String concatenation using the plus operator. “Hello” + “World” = “HelloWorld”
- StringBuilder and StringBuffer.
- String.format and String.formatted
- java.text.messageFormat
How is String templates different than these traditional methods:
Here is a comparison of traditional string concatenation and string templates.
// t
String string1 = "The file " + filePath + " " + (file.exists() ? "does" : "does not") + " exist";
String string2 = STR."The file \{filePath} \{file.exists() ? "does" : "does not"} exist";
STR
(used in the 2nd line of code above) is a template processor defined in the Java Platform. It performs string interpolation by replacing each embedded expression in the template with the (stringified) value of that expression.
Here is a multiline example.
String name = "Joan Smith";
String phone = "555-123-4567";
String address = "1 Maple Drive, Anytown";
String json = STR."""
{
"name": "\{name}",
"phone": "\{phone}",
"address": "\{address}"
}
""";
Output:
{
"name": "Joan Smith",
"phone": "555-123-4567",
"address": "1 Maple Drive, Anytown"
}
As you can see, string templates are much more readable in comparison to string concatenations. There are more benefits which we have discussed below.
What are Java template engines?
Java template engines are software libraries that provide a way to generate dynamic text content (such as HTML, emails, or configuration files) from templates and data. They separate the presentation layer (templates) from the application logic, making code more organized, maintainable, and reusable.
Examples: Pebble, Thymeleaf, Jtwig, etc
Can we use string templates instead of Java template engines?
Since JEP 430, string templates are in the preview phase only, it only supports simple interpolation and simple IF conditions. Complex logic can not be embedded in string templates as of now.
However, string templates can replace Java template engines if development continues and new capabilities are added in the next releases.
What will be the benefits of using string templates over Java template engines?
(1) Speed. Pebble is the fastest template engine available. If we compare pebble (the fastest template engine) with string templates for a simple use case, string templates is much faster.
Here are the benchmarking results. The code is available below.
(2) In-built support. If string templates is released with enhanced capabilities, developers would prefer this over third-party template engines. Developers won’t have to worry about active development, or deprecation (like jtwig) of third-party libraries.
Conclusion:
It is too early to say whether string templates can replace the third-party Java template engines or not. A whole lot depends on the capabilities this feature will bring in the released state. However, for a simple use case string templates is much faster than template engines.
JMH code for benchmarking Pebble vs String templates.
package org.rana.example;
import com.mitchellbosecke.pebble.PebbleEngine;
import com.mitchellbosecke.pebble.template.PebbleTemplate;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
import java.util.concurrent.TimeUnit;
/**
* Author - Anurag Rana
*/
public class JMH_example1 {
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void stringTemplate(BenchmarkState state, Blackhole blackhole) throws IOException {
Map<String, Object> context = new HashMap<>();
context.put("message", "Template String compilation");
context.put("year", 2023);
context.put("items", Arrays.asList("Apples", "Bananas", "Milk", "Eggs", "Bread"));
int year = 2023;
String message = "Template String compilation";
List<String> items = Arrays.asList("Apples", "Bananas", "Milk", "Eggs", "Bread");
String tmp = STR."<p>\{ message }</p> ";
String html = STR."""
<!DOCTYPE html>
<html>
<head>
<title>My Single Page</title>
</head>
<body>
<h1>Welcome to my page!</h1>
<p>This is a simple example of a Pebble template without inheritance.</p>
<p>The current year is \{ year }.</p>
\{ message != null ? tmp: "" }
</body>
</html>
""";
blackhole.consume(html);
}
@Benchmark
@BenchmarkMode({Mode.Throughput, Mode.AverageTime})
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public void pebble(BenchmarkState state, Blackhole blackhole) throws IOException {
Map<String, Object> context = new HashMap<>();
context.put("message", "Template String compilation");
context.put("year", 2023);
context.put("items", Arrays.asList("Apples", "Bananas", "Milk", "Eggs", "Bread"));
Writer writer = new StringWriter();
state.compiledTemplate.evaluate(writer, context);
String output = writer.toString();
blackhole.consume(output);
}
@State(Scope.Benchmark)
public static class BenchmarkState {
PebbleEngine engine = new PebbleEngine.Builder().build();
PebbleTemplate compiledTemplate;
@Setup
public void setup() {
compiledTemplate = engine.getTemplate("template1.txt");
}
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(JMH_example1.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
Build the jar and run it with the below commands:
$ mvn clean compile package
$ java --enable-preview -jar target/benchmarks.jar JMH_example1
Bonus: