This tutorial shows you how to use the StringTemplate
feature introduced in Java 21, including the STR
, FMT
, and RAW
processors..
When writing code, it's quite common to construct a string by combining expressions. In Java, there are several ways to do it. You can use string concatenations, StringBuilder
, String::format
or String::formatted
, and MessageFormat
. However, they all have drawbacks as explained in the code below.
int a = 100;
int b = 2;
// String concatenation may produce hard-to-read code, especially if there are many concatenations
// It's also has worse performance.
String str1 = a + " + " + b + " = " + (a + b);
System.out.println(str1);
// StringBuilder is too verbose.
String str2 = new StringBuilder()
.append(a)
.append(" + ")
.append(b)
.append(" = ")
.append(a + b)
.toString();
System.out.println(str2);
// String::format and String::formatted may cause arity and type mismatches.
String str3a = String.format("%1$d + %2$d = %3$d", a, b, a + b);
String str3b = "%1$d + %2$d = %3$d".formatted(a, b, a + b);
System.out.println(str3a);
System.out.println(str3b);
// MessageFormat requires too much ceremony
MessageFormat mf = new MessageFormat("{0} + {1} = {2}");
Object[] args = {a, b, a + b};
String str4 = mf.format(args);
System.out.println(str4);
You may already know that some other programming languages support defining string templates that contain embedded variables or expressions. Finally, Java 21 introduced a similar feature. It's called StringTemplate
and is available in the java.lang
package.
Template Expression
A string template expression consists of three parts.
- The processor name (
STR
,FMT
, orRAW
) - A dot character (U+002E)
- The string template which can contain multiple embedded expressions.
The first part indicates the processor name. Java provides some built-in processors which include STR
, FMT
, and RAW
. The differences between them are going to be explained later. The name is followed by a dot character.
A string template starts and ends with "
(double quotes). Alternatively, it can use """
(triple double quotes) to allow defining a template in multi-line like a text block. It can contain one or more embedded expressions. An embedded expression starts with \
(backslash), followed by an {
(opening curly brace), the expression, and a }
(closing curly brace). The expression itself can be a variable or a more complex expression. Writing the expression is the same as it would be written outside the template expression. If there are multiple embedded expressions, it will be executed in order from left to right.
Using STR
Template Processor
Java's StringTemplate
class provides a template processor instance called STR
, which can perform string interpolation of a given string template. It actually uses the StringTemplate.interpolate
method. However, for a better readability. it's recommended to use the STR
rather than invoking the interpolate
method.
Access a Variable
The STR
processor is defined in the java.lang.StringTemplate
class. However, it's not required to import it since Java is able to recognize it without the need to add an explicit import.
In the example below, there is a simple expression STR."Created by \{name}"
. The embedded expression is a variable which has already been defined before. If the embedded expression is invalid (e.g. if you use an undefined variable), you will get a compile-time error.
String name = "Woolha.com";
String text = STR."Created by \{name}";
System.out.println(text);
Output:
Created by Woolha.com
Below is another example which contains multiple embedded expressions that use and modify the same variable. The execution order is from left to right. As a result, the execution of an embedded expression may affect the next ones.
int count = 1;
String data = STR."\{count++}, \{count++}, \{count++}, \{count++}";
System.out.println(data);
Output:
1, 2, 3, 4
Invoke a Method
The embedded expression can also contain a call to a method in order to use the returned value. For example, we have the following method.
private String convertToUpperCase(String value) {
return value.toUpperCase();
}
Below is an example of an embedded expression that calls the method above. As you can see, the method is invoked with an argument whose type is a String. Double quotes character is supported and it must be written as-is without being escaped. In other words, you can write an embedded expression exactly as you would have written it outside the template expression.
String text = STR."The value is \{convertToUpperCase("foo")}";
System.out.println(text);
Output:
The value is FOO
The called method must have a return value. It's not allowed to call a void
method in the embedded expression. Below is an invalid example that calls the System.out.print
method.
String text = STR."The value is \{System.out.print("foo")}";
System.out.println(text);
Output:
error: 'void' type not allowed here
String text = STR."The value is \{System.out.print("foo")}";
Access a Class Property
It's also possible to access any property of a class as long as the access modifier allows it. For example, the Name
class below has two private fields firstName
and lastName
along with the public getter methods.
public class Name {
private String firstName;
private String lastName;
public Name(String firstName, String lastName) {
this.firstName = firstName;
this.lastName = lastName;
}
public String getFirstName() {
return this.firstName;
}
public String getLastName() {
return this.lastName;
}
}
If there is an instance of Name
, it's possible to access the getFirstName
and getLastName
properties since they're public. You cannot access the firstName
and lastName
fields directly since they're private.
Name name = new Name("Ivan", "Cute");
String text = STR."My full name is \{name.getFirstName()} \{name.getLastName()}";
System.out.println(text);
Output:
My full name is Ivan Cute
Nested Embedded Expression
An embedded expression may contain another embedded expression, as shown in the example below.
String[] types = { "grass", "water", "fire" };
String s = STR."\{types[0]}, \{STR."\{types[1]}, \{types[2]}"}";
System.out.println(s);
Output:
grass, water, fire
Multi-line Embedded Expression
Sometimes, the embedded expression can be quite long to be written in one line. In that case, it's better to spread it over multiple lines. If you do that, Java will interpolate the expression and the result will be put in the same line as the start of the embedded expression without adding newlines.
String text = STR. "Order will be expired at \{ZonedDateTime.now()
.truncatedTo(ChronoUnit.DAYS)
.plusDays(1)
}";
Output:
Order will be expired at 2023-08-19T00:00+07:00[Indian/Christmas]
Multi-line Template Expression
The template itself can be spreaded over multiple lines if necessary. The syntax is similar to Java's text blocks which uses triple double quotes at the start and end.
String name = "Cutie";
double amount = 100;
String html = STR."""
<div class="content>
<p>Dear \{name}</p>
<p>Your order of $\{amount} has been paid</p>
</div>
""";
System.out.println(html);
Output:
<div class="content>
<p>Dear Cutie</p>
<p>Your order of $100.0 has been paid</p>
</div>
Using FMT
Template Processor
There is another processor called FMT
, which constructs a string by combining Java's Formatter
and StringTemplate
. It uses the value from the embedded expression placed right after the format specifier.
The processor is defined in the FormatProcessor
class of the java.util
package. To use the FMT
processor, it's necessary to add the following import.
import static java.util.FormatProcessor.FMT;
For example, we have the following record type.
record Player(int idNumber, String name, double score) {}
There are some instances of the record and we want to print each instance in a different row, with the values of each column neatly aligned. We can use Formatter
's capability to pad and format the values of each field. Each value to be formatted can be written as an embedded expression following the format specifier.
Player[] players = new Player[] {
new Player(1, "Foo", 8123.112),
new Player(20, "Bar", 211.56),
new Player(300, "Baz", 22.5671),
};
String formattedPlayers = FMT."""
No Name Score
%-5d\{players[0].idNumber} %-5s\{players[0].name} %7.2f\{players[0].score}
%-5d\{players[1].idNumber} %-5s\{players[1].name} %7.2f\{players[1].score}
%-5d\{players[2].idNumber} %-5s\{players[2].name} %7.2f\{players[2].score}
\{" ".repeat(9)} Avg %7.2f\{players[0].score + players[1].score + players[2].score}
""";
System.out.println(formattedPlayers);
Output:
No Name Score
1 Foo 8123.11
20 Bar 211.56
300 Baz 22.57
Avg 8357.24
Using RAW
Template Processor
There is another processor instance called RAW
, which is used to create a template with deferred processing. That means the defined template is not interpolated automatically. To perform the interpolation, you have to call Processor.process(StringTemplate)
or StringTemplate.process(Processor)
.
You have to add the following import to use RAW
.
import static java.lang.StringTemplate.RAW;
Below is an example. First, we create a template expression using the RAW
processor, which returns a StringTemplate
. Then, to perform the interpolation, call the process
method of a Processor
by passing the StringTemplate
. The Processor
itself can be either STR
or FMT
. Another alternative is by using the process
method of the StringTemplate
with the Processor
to use passed as the argument.
String name = "Woolha.com";
StringTemplate template = RAW."Created by \{name}";
String text = STR.process(template);
System.out.println(text);
String text2 = template.process(STR);
System.out.println(text2);
Output:
Created by Woolha.com
Created by Woolha.com
Summary
The StringTemplate
class in Java provides a convenient way to define a string template that's easy to use and read. A template can have one or more embedded expressions which can be as simple as a variable or a complex expression. Java provides built-in processors which include STR
, FMT
, and RAW
.