JEP 430: String Templates (Preview) in Java

Introduction

String Templates (JEP 430) is a preview feature in Java that introduces template expressions to the Java language. This feature enhances string literals and text blocks with embedded expressions, making string composition more readable, secure, and convenient.

Table of Contents

  1. Basic String Templates
  2. Template Processors
  3. Custom Template Processors
  4. SQL Template Processing
  5. JSON Template Processing
  6. HTML Template Processing
  7. Security Considerations
  8. Performance Optimization

Basic String Templates

Traditional vs String Templates

public class BasicStringTemplates {
// OLD WAY: String concatenation
public static String oldWay(String name, int age, String city) {
return "Hello, my name is " + name + ", I'm " + age + 
" years old, and I live in " + city + ".";
}
// OLD WAY: StringBuilder
public static String oldWayStringBuilder(String name, int age, String city) {
return new StringBuilder()
.append("Hello, my name is ")
.append(name)
.append(", I'm ")
.append(age)
.append(" years old, and I live in ")
.append(city)
.append(".")
.toString();
}
// OLD WAY: String.format
public static String oldWayFormat(String name, int age, String city) {
return String.format("Hello, my name is %s, I'm %d years old, and I live in %s.", 
name, age, city);
}
// NEW WAY: String Templates
public static String newWay(String name, int age, String city) {
return STR."""
Hello, my name is \{name}, I'm \{age} years old, 
and I live in \{city}.
""";
}
// Using with text blocks
public static String multiLineTemplate(String name, int age, String city) {
return STR."""
User Profile:
============
Name:  \{name}
Age:   \{age}
City:  \{city}
Generated on: \{java.time.LocalDateTime.now()}
""";
}
// Expressions in templates
public static String withExpressions(int a, int b) {
return STR."""
Calculation:
\{a} + \{b} = \{a + b}
\{a} * \{b} = \{a * b}
\{a} / \{b} = \{b != 0 ? (double)a / b : "undefined"}
""";
}
// Method calls in templates
public static String withMethodCalls(String input) {
return STR."""
Original: \{input}
Upper case: \{input.toUpperCase()}
Lower case: \{input.toLowerCase()}
Length: \{input.length()}
Reversed: \{new StringBuilder(input).reverse()}
""";
}
public static void main(String[] args) {
String name = "Alice";
int age = 30;
String city = "New York";
System.out.println("=== Traditional Methods ===");
System.out.println(oldWay(name, age, city));
System.out.println(oldWayStringBuilder(name, age, city));
System.out.println(oldWayFormat(name, age, city));
System.out.println("\n=== String Templates ===");
System.out.println(newWay(name, age, city));
System.out.println(multiLineTemplate(name, age, city));
System.out.println(withExpressions(10, 5));
System.out.println(withMethodCalls("Hello World"));
}
}

Template Syntax and Features

public class TemplateSyntax {
// Basic template syntax
public static void demonstrateSyntax() {
String name = "John";
int score = 95;
double price = 19.99;
boolean isActive = true;
// Simple variable interpolation
String message1 = STR."Welcome \{name}!";
System.out.println(message1);
// Multiple expressions
String message2 = STR."User: \{name}, Score: \{score}, Active: \{isActive}";
System.out.println(message2);
// Complex expressions
String message3 = STR."""
Performance Report:
-----------------
User:       \{name}
Score:      \{score}/100
Grade:      \{score >= 90 ? "A" : score >= 80 ? "B" : "C"}
Price:      $\{price}
Discount:   $\{price * 0.1}
Final:      $\{price * 0.9}
""";
System.out.println(message3);
// Using with collections
List<String> fruits = List.of("Apple", "Banana", "Orange");
String listMessage = STR."""
Available Fruits:
\{String.join(", ", fruits)}
Total: \{fruits.size()} items
""";
System.out.println(listMessage);
}
// Nested templates
public static String nestedTemplates(String user, List<String> permissions) {
String permissionList = STR."""
Permissions: \{String.join(", ", permissions)}
Count: \{permissions.size()}
""";
return STR."""
User: \{user}
\{permissionList}
Status: \{permissions.isEmpty() ? "No permissions" : "Active"}
""";
}
// Template expressions with side effects
public static String withSideEffects() {
int counter = 0;
String result = STR."""
Counter before: \{counter}
Incremented: \{++counter}
Counter after: \{counter}
""";
return result;
}
// Using with enums and constants
enum Status { PENDING, APPROVED, REJECTED }
public static String withEnums(String user, Status status) {
return STR."""
User:    \{user}
Status:  \{status}
Is Final: \{status == Status.APPROVED || status == Status.REJECTED}
""";
}
// Date and time formatting
public static String withDateTime() {
var now = java.time.LocalDateTime.now();
var formatter = java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
return STR."""
Current Time:
Raw:      \{now}
Formatted: \{formatter.format(now)}
Date:      \{now.toLocalDate()}
Time:      \{now.toLocalTime()}
""";
}
// Exception handling in templates
public static String safeTemplate(String input) {
return STR."""
Input:    \{input}
Length:   \{input != null ? input.length() : "N/A"}
Upper:    \{input != null ? input.toUpperCase() : "N/A"}
""";
}
public static void main(String[] args) {
demonstrateSyntax();
System.out.println("\n=== Nested Templates ===");
System.out.println(nestedTemplates("admin", List.of("read", "write", "delete")));
System.out.println("\n=== Side Effects ===");
System.out.println(withSideEffects());
System.out.println("\n=== Enums ===");
System.out.println(withEnums("john_doe", Status.APPROVED));
System.out.println("\n=== Date/Time ===");
System.out.println(withDateTime());
System.out.println("\n=== Safe Templates ===");
System.out.println(safeTemplate("hello"));
System.out.println(safeTemplate(null));
}
}

Template Processors

Built-in Template Processors

import java.util.*;
import java.text.NumberFormat;
public class BuiltInProcessors {
// STR processor - basic string interpolation
public static void demonstrateSTR() {
String name = "Alice";
int age = 25;
double salary = 75000.50;
// Basic usage
String basic = STR."Name: \{name}, Age: \{age}";
System.out.println("STR Basic: " + basic);
// Multi-line
String multiLine = STR."""
Employee Details:
----------------
Name:   \{name}
Age:    \{age}
Salary: $\{salary}
""";
System.out.println("STR Multi-line:\n" + multiLine);
// With expressions
String withExpr = STR."""
Next Year:
Age:    \{age + 1}
Salary: $\{salary * 1.1}
""";
System.out.println("STR With Expressions:\n" + withExpr);
}
// FMT processor - formatted output
public static void demonstrateFMT() {
String product = "Laptop";
double price = 1299.99;
int quantity = 3;
double discount = 0.15;
// Number formatting
String formatted = FMT."""
Product:     %-10s\{product}
Price:       $%8.2f\{price}
Quantity:    %5d\{quantity}
Discount:    %7.1f%%\{discount * 100}
Total:       $%8.2f\{price * quantity * (1 - discount)}
""";
System.out.println("FMT Formatted:\n" + formatted);
// Date formatting
var now = java.time.LocalDateTime.now();
String dateFormatted = FMT."""
Current Time:
Date: %tF\{now}
Time: %tT\{now}
Full: %tc\{now}
""";
System.out.println("FMT Date Formatting:\n" + dateFormatted);
}
// RAW processor - get Template object
public static void demonstrateRAW() {
String name = "Bob";
int score = 85;
// Get template without processing
StringTemplate template = RAW."""
Player: \{name}
Score:  \{score}
Grade:  \{score >= 90 ? "A" : score >= 80 ? "B" : "C"}
""";
System.out.println("RAW Template: " + template);
System.out.println("Fragments: " + template.fragments());
System.out.println("Values: " + template.values());
// Process with different processors
String strResult = STR.process(template);
System.out.println("STR Result:\n" + strResult);
String fmtResult = FMT."""
Player: %-10s\{name}
Score:  %5d\{score}
""";
System.out.println("FMT Result:\n" + fmtResult);
}
// Combining processors
public static void combinedExample() {
List<Employee> employees = Arrays.asList(
new Employee("John", "Developer", 80000),
new Employee("Sarah", "Manager", 95000),
new Employee("Mike", "Analyst", 70000)
);
String report = STR."""
Employee Report
===============
\{employees.stream()
.map(emp -> FMT."""
%-10s\{emp.name()} | %-12s\{emp.position()} | $%8.2f\{emp.salary()}
""")
.collect(java.util.stream.Collectors.joining())}
Total Employees: \{employees.size()}
Average Salary: $\{employees.stream().mapToDouble(Employee::salary).average().orElse(0)}
""";
System.out.println("Combined STR and FMT:\n" + report);
}
record Employee(String name, String position, double salary) {}
public static void main(String[] args) {
System.out.println("=== STR Processor ===");
demonstrateSTR();
System.out.println("\n=== FMT Processor ===");
demonstrateFMT();
System.out.println("\n=== RAW Processor ===");
demonstrateRAW();
System.out.println("\n=== Combined Example ===");
combinedExample();
}
}

Custom Template Processors

Creating Custom Processors

import java.util.*;
import java.util.function.Function;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
public class CustomProcessors {
// Simple custom processor that uppercases all values
public static final StringTemplate.Processor<String, RuntimeException> UPPER =
StringTemplate.Processor.of((StringTemplate st) -> {
List<Object> upperValues = st.values().stream()
.map(value -> value != null ? value.toString().toUpperCase() : "NULL")
.toList();
return StringTemplate.interpolate(st.fragments(), upperValues);
});
// Processor that adds logging
public static final StringTemplate.Processor<String, RuntimeException> LOGGING =
StringTemplate.Processor.of((StringTemplate st) -> {
String result = STR.process(st);
System.out.printf("[LOG] %s: Template processed: %s%n", 
LocalDateTime.now(), result);
return result;
});
// Processor that validates inputs
public static final StringTemplate.Processor<String, IllegalArgumentException> VALIDATING =
StringTemplate.Processor.of((StringTemplate st) -> {
// Validate that no values are null
for (int i = 0; i < st.values().size(); i++) {
if (st.values().get(i) == null) {
throw new IllegalArgumentException(
"Null value at position " + i + " in template");
}
}
return STR.process(st);
});
// HTML escaping processor
public static final StringTemplate.Processor<String, RuntimeException> HTML =
StringTemplate.Processor.of((StringTemplate st) -> {
List<Object> escapedValues = st.values().stream()
.map(value -> {
if (value == null) return "";
return value.toString()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;");
})
.toList();
return StringTemplate.interpolate(st.fragments(), escapedValues);
});
// JSON processor
public static final StringTemplate.Processor<String, RuntimeException> JSON =
StringTemplate.Processor.of((StringTemplate st) -> {
List<Object> jsonValues = st.values().stream()
.map(value -> {
if (value == null) return "null";
if (value instanceof String) return "\"" + escapeJsonString(value.toString()) + "\"";
if (value instanceof Number) return value;
if (value instanceof Boolean) return value;
return "\"" + escapeJsonString(value.toString()) + "\"";
})
.toList();
return StringTemplate.interpolate(st.fragments(), jsonValues);
});
private static String escapeJsonString(String str) {
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
// Demonstration methods
public static void demonstrateUpper() {
String name = "john doe";
int age = 30;
String result = UPPER."""
Name: \{name}
Age:  \{age}
City: \{null}
""";
System.out.println("UPPER Processor:\n" + result);
}
public static void demonstrateLogging() {
String user = "alice";
String action = "login";
String result = LOGGING."""
User '\{user}' performed action '\{action}' 
at \{LocalDateTime.now()}
""";
System.out.println("Result: " + result);
}
public static void demonstrateValidating() {
try {
String valid = VALIDATING."""
Name: \{ "John" }
Age:  \{ 25 }
""";
System.out.println("Valid: " + valid);
String invalid = VALIDATING."""
Name: \{ "John" }
Age:  \{ null }
""";
} catch (IllegalArgumentException e) {
System.out.println("Validation failed: " + e.getMessage());
}
}
public static void demonstrateHtml() {
String userInput = "<script>alert('xss')</script>";
String name = "John & Jane";
String result = HTML."""
<div class="user">
<h1>Welcome \{name}!</h1>
<p>Input: \{userInput}</p>
</div>
""";
System.out.println("HTML Processor:\n" + result);
}
public static void demonstrateJson() {
String name = "John";
int age = 30;
boolean active = true;
String email = "[email protected]";
String result = JSON."""
{
"name": \{name},
"age": \{age},
"active": \{active},
"email": \{email},
"created": "\{LocalDateTime.now()}"
}
""";
System.out.println("JSON Processor:\n" + result);
}
// Processor with configuration
public static class ConfigurableProcessor {
private final boolean trimSpaces;
private final boolean addTimestamp;
public ConfigurableProcessor(boolean trimSpaces, boolean addTimestamp) {
this.trimSpaces = trimSpaces;
this.addTimestamp = addTimestamp;
}
public StringTemplate.Processor<String, RuntimeException> processor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
String result = STR.process(st);
if (trimSpaces) {
result = result.trim().replaceAll("\\s+", " ");
}
if (addTimestamp) {
result = String.format("[%s] %s", 
LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME), 
result);
}
return result;
});
}
}
public static void demonstrateConfigurable() {
var processor = new ConfigurableProcessor(true, true).processor();
String result = processor."""
This    has   extra   spaces
and will be cleaned up.
""";
System.out.println("Configurable Processor:\n" + result);
}
public static void main(String[] args) {
System.out.println("=== UPPER Processor ===");
demonstrateUpper();
System.out.println("\n=== LOGGING Processor ===");
demonstrateLogging();
System.out.println("\n=== VALIDATING Processor ===");
demonstrateValidating();
System.out.println("\n=== HTML Processor ===");
demonstrateHtml();
System.out.println("\n=== JSON Processor ===");
demonstrateJson();
System.out.println("\n=== Configurable Processor ===");
demonstrateConfigurable();
}
}

SQL Template Processing

Safe SQL Query Building

import java.sql.*;
import java.util.*;
public class SQLTemplates {
// Safe SQL processor with parameter validation
public static final StringTemplate.Processor<PreparedStatement, SQLException> SQL =
StringTemplate.Processor.of((StringTemplate st, Connection conn) -> {
// Build the SQL query with placeholders
StringBuilder sqlBuilder = new StringBuilder();
List<Object> parameters = new ArrayList<>();
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
sqlBuilder.append(fragments.get(i));
if (i < values.size()) {
sqlBuilder.append("?");
parameters.add(values.get(i));
}
}
String sql = sqlBuilder.toString();
System.out.println("Generated SQL: " + sql);
System.out.println("Parameters: " + parameters);
// Create prepared statement
PreparedStatement ps = conn.prepareStatement(sql);
// Set parameters
for (int i = 0; i < parameters.size(); i++) {
Object param = parameters.get(i);
ps.setObject(i + 1, param);
}
return ps;
}, Connection.class);
// SQL processor for queries with return type
public static final StringTemplate.Processor<QueryWithParams, RuntimeException> SQL_QUERY =
StringTemplate.Processor.of((StringTemplate st) -> {
StringBuilder sqlBuilder = new StringBuilder();
List<Object> parameters = new ArrayList<>();
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
sqlBuilder.append(fragments.get(i));
if (i < values.size()) {
sqlBuilder.append("?");
parameters.add(values.get(i));
}
}
return new QueryWithParams(sqlBuilder.toString(), parameters);
});
record QueryWithParams(String sql, List<Object> parameters) {
public PreparedStatement prepare(Connection conn) throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
for (int i = 0; i < parameters.size(); i++) {
ps.setObject(i + 1, parameters.get(i));
}
return ps;
}
}
// Demonstration methods
public static void demonstrateBasicSQL() {
String table = "users";
String name = "John";
int age = 30;
try (Connection conn = getConnection()) {
PreparedStatement ps = SQL."""
SELECT * FROM \{table} 
WHERE name = \{name} 
AND age > \{age}
""".process(conn);
// Execute query...
System.out.println("SQL prepared successfully");
} catch (SQLException e) {
System.out.println("SQL error: " + e.getMessage());
}
}
public static void demonstrateDynamicQuery() {
String table = "products";
String category = "Electronics";
Double minPrice = 100.0;
Double maxPrice = 1000.0;
Boolean inStock = true;
try (Connection conn = getConnection()) {
QueryWithParams query = SQL_QUERY."""
SELECT * FROM \{table}
WHERE 1=1
\{category != null ? "AND category = ?" : ""} \{category}
\{minPrice != null ? "AND price >= ?" : ""} \{minPrice}
\{maxPrice != null ? "AND price <= ?" : ""} \{maxPrice}
\{inStock != null ? "AND in_stock = ?" : ""} \{inStock}
ORDER BY price DESC
""";
System.out.println("Dynamic Query: " + query.sql());
System.out.println("Parameters: " + query.parameters());
PreparedStatement ps = query.prepare(conn);
// Execute query...
} catch (SQLException e) {
System.out.println("SQL error: " + e.getMessage());
}
}
public static void demonstrateInsert() {
String table = "employees";
String name = "Alice Smith";
String position = "Developer";
double salary = 75000.0;
LocalDateTime hireDate = LocalDateTime.now();
try (Connection conn = getConnection()) {
PreparedStatement ps = SQL."""
INSERT INTO \{table} (name, position, salary, hire_date)
VALUES (\{name}, \{position}, \{salary}, \{hireDate})
""".process(conn);
// Execute update...
System.out.println("Insert prepared successfully");
} catch (SQLException e) {
System.out.println("SQL error: " + e.getMessage());
}
}
public static void demonstrateUpdate() {
String table = "users";
String newEmail = "[email protected]";
boolean isActive = true;
int userId = 123;
try (Connection conn = getConnection()) {
PreparedStatement ps = SQL."""
UPDATE \{table}
SET email = \{newEmail}, 
is_active = \{isActive},
updated_at = \{LocalDateTime.now()}
WHERE id = \{userId}
""".process(conn);
// Execute update...
System.out.println("Update prepared successfully");
} catch (SQLException e) {
System.out.println("SQL error: " + e.getMessage());
}
}
// Mock connection for demonstration
private static Connection getConnection() throws SQLException {
// In real application, this would get a real connection
System.out.println("Getting database connection...");
return null; // Simplified for example
}
public static void main(String[] args) {
System.out.println("=== Basic SQL Query ===");
demonstrateBasicSQL();
System.out.println("\n=== Dynamic SQL Query ===");
demonstrateDynamicQuery();
System.out.println("\n=== SQL Insert ===");
demonstrateInsert();
System.out.println("\n=== SQL Update ===");
demonstrateUpdate();
}
}

JSON Template Processing

JSON Generation with Templates

import java.util.*;
import java.time.LocalDateTime;
public class JSONTemplates {
// Enhanced JSON processor
public static final StringTemplate.Processor<String, RuntimeException> JSON =
StringTemplate.Processor.of((StringTemplate st) -> {
StringBuilder jsonBuilder = new StringBuilder();
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
jsonBuilder.append(fragments.get(i));
if (i < values.size()) {
jsonBuilder.append(escapeJsonValue(values.get(i)));
}
}
return jsonBuilder.toString();
});
private static String escapeJsonValue(Object value) {
if (value == null) return "null";
if (value instanceof String str) {
return "\"" + escapeJsonString(str) + "\"";
}
if (value instanceof Number) return value.toString();
if (value instanceof Boolean) return value.toString();
if (value instanceof Collection) return collectionToJson((Collection<?>) value);
if (value instanceof Map) return mapToJson((Map<?, ?>) value);
if (value instanceof LocalDateTime) {
return "\"" + value.toString() + "\"";
}
return "\"" + escapeJsonString(value.toString()) + "\"";
}
private static String escapeJsonString(String str) {
return str.replace("\\", "\\\\")
.replace("\"", "\\\"")
.replace("\b", "\\b")
.replace("\f", "\\f")
.replace("\n", "\\n")
.replace("\r", "\\r")
.replace("\t", "\\t");
}
private static String collectionToJson(Collection<?> collection) {
StringBuilder sb = new StringBuilder("[");
Iterator<?> iterator = collection.iterator();
while (iterator.hasNext()) {
sb.append(escapeJsonValue(iterator.next()));
if (iterator.hasNext()) sb.append(", ");
}
sb.append("]");
return sb.toString();
}
private static String mapToJson(Map<?, ?> map) {
StringBuilder sb = new StringBuilder("{");
Iterator<? extends Map.Entry<?, ?>> iterator = map.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<?, ?> entry = iterator.next();
sb.append(escapeJsonValue(entry.getKey().toString()))
.append(": ")
.append(escapeJsonValue(entry.getValue()));
if (iterator.hasNext()) sb.append(", ");
}
sb.append("}");
return sb.toString();
}
// Demonstration methods
public static void demonstrateBasicJson() {
String name = "John Doe";
int age = 30;
boolean active = true;
String email = "[email protected]";
String json = JSON."""
{
"name": \{name},
"age": \{age},
"active": \{active},
"email": \{email},
"created": "\{LocalDateTime.now()}"
}
""";
System.out.println("Basic JSON:\n" + json);
}
public static void demonstrateNestedJson() {
String userName = "Alice Smith";
List<String> roles = List.of("admin", "user", "editor");
Map<String, Object> preferences = Map.of(
"theme", "dark",
"notifications", true,
"language", "en"
);
String json = JSON."""
{
"user": {
"name": \{userName},
"roles": \{roles},
"preferences": \{preferences},
"metadata": {
"created": "\{LocalDateTime.now()}",
"version": \{1.0}
}
}
}
""";
System.out.println("Nested JSON:\n" + json);
}
public static void demonstrateArrayJson() {
List<Map<String, Object>> users = List.of(
Map.of("id", 1, "name", "John", "age", 25),
Map.of("id", 2, "name", "Jane", "age", 30),
Map.of("id", 3, "name", "Bob", "age", 35)
);
String json = JSON."""
{
"users": \{users},
"total": \{users.size()},
"averageAge": \{users.stream()
.mapToInt(user -> (Integer) user.get("age"))
.average()
.orElse(0)},
"generated": "\{LocalDateTime.now()}"
}
""";
System.out.println("Array JSON:\n" + json);
}
public static void demonstrateComplexObject() {
record Address(String street, String city, String zipCode) {}
record User(String name, int age, Address address, List<String> tags) {}
User user = new User(
"Carol Johnson",
28,
new Address("123 Main St", "New York", "10001"),
List.of("vip", "early-adopter", "premium")
);
String json = JSON."""
{
"user": {
"name": "\{user.name()}",
"age": \{user.age()},
"address": {
"street": "\{user.address().street()}",
"city": "\{user.address().city()}",
"zipCode": "\{user.address().zipCode()}"
},
"tags": \{user.tags()},
"profileComplete": true
}
}
""";
System.out.println("Complex Object JSON:\n" + json);
}
// JSON template for API responses
public static void demonstrateApiResponse() {
String requestId = "req-12345";
boolean success = true;
String message = "Data retrieved successfully";
Object data = Map.of(
"items", List.of("item1", "item2", "item3"),
"total", 3,
"page", 1
);
String response = JSON."""
{
"requestId": "\{requestId}",
"success": \{success},
"message": "\{message}",
"data": \{data},
"timestamp": "\{LocalDateTime.now()}",
"version": "1.0"
}
""";
System.out.println("API Response JSON:\n" + response);
}
public static void main(String[] args) {
System.out.println("=== Basic JSON ===");
demonstrateBasicJson();
System.out.println("\n=== Nested JSON ===");
demonstrateNestedJson();
System.out.println("\n=== Array JSON ===");
demonstrateArrayJson();
System.out.println("\n=== Complex Object JSON ===");
demonstrateComplexObject();
System.out.println("\n=== API Response JSON ===");
demonstrateApiResponse();
}
}

HTML Template Processing

Safe HTML Generation

import java.util.*;
import java.time.LocalDateTime;
public class HTMLTemplates {
// HTML processor with automatic escaping
public static final StringTemplate.Processor<String, RuntimeException> HTML =
StringTemplate.Processor.of((StringTemplate st) -> {
StringBuilder htmlBuilder = new StringBuilder();
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
htmlBuilder.append(fragments.get(i));
if (i < values.size()) {
htmlBuilder.append(escapeHtml(values.get(i)));
}
}
return htmlBuilder.toString();
});
private static String escapeHtml(Object value) {
if (value == null) return "";
return value.toString()
.replace("&", "&amp;")
.replace("<", "&lt;")
.replace(">", "&gt;")
.replace("\"", "&quot;")
.replace("'", "&#39;")
.replace("\n", "<br>");
}
// Demonstration methods
public static void demonstrateBasicHtml() {
String title = "Welcome Page";
String username = "John Doe";
String userContent = "This is <b>my</b> content with 'quotes' and \"double quotes\"";
String html = HTML."""
<!DOCTYPE html>
<html>
<head>
<title>\{title}</title>
<style>
body { font-family: Arial, sans-serif; }
.user { color: blue; }
.content { background: #f0f0f0; padding: 10px; }
</style>
</head>
<body>
<h1>Welcome to \{title}</h1>
<div class="user">
Logged in as: <strong>\{username}</strong>
</div>
<div class="content">
User content: \{userContent}
</div>
<footer>
Generated on: \{LocalDateTime.now()}
</footer>
</body>
</html>
""";
System.out.println("Basic HTML:\n" + html);
}
public static void demonstrateUserProfile() {
String name = "Alice Smith";
String email = "[email protected]";
int age = 28;
List<String> skills = List.of("Java", "Python", "JavaScript", "SQL");
String bio = "I love programming and <script>alert('xss')</script> learning new technologies!";
String html = HTML."""
<div class="profile-card">
<div class="header">
<h2>\{name}</h2>
<p class="email">\{email}</p>
</div>
<div class="content">
<p><strong>Age:</strong> \{age}</p>
<p><strong>Bio:</strong> \{bio}</p>
<div class="skills">
<h3>Skills:</h3>
<ul>
\{skills.stream()
.map(skill -> "<li>" + escapeHtml(skill) + "</li>")
.reduce("", (a, b) -> a + b)}
</ul>
</div>
</div>
<div class="footer">
<small>Profile last updated: \{LocalDateTime.now()}</small>
</div>
</div>
""";
System.out.println("User Profile HTML:\n" + html);
}
public static void demonstrateProductTable() {
record Product(String name, double price, int stock, String category) {}
List<Product> products = List.of(
new Product("Laptop", 999.99, 15, "Electronics"),
new Product("Desk Chair", 199.50, 8, "Furniture"),
new Product("Coffee Mug", 12.99, 50, "Kitchen"),
new Product("Notebook", 5.99, 100, "Stationery")
);
String html = HTML."""
<table class="product-table">
<thead>
<tr>
<th>Product Name</th>
<th>Price</th>
<th>Stock</th>
<th>Category</th>
<th>Status</th>
</tr>
</thead>
<tbody>
\{products.stream()
.map(product -> """
<tr>
<td>\{product.name()}</td>
<td>$\{product.price()}</td>
<td>\{product.stock()}</td>
<td>\{product.category()}</td>
<td>\{product.stock() > 10 ? "In Stock" : "Low Stock"}</td>
</tr>
""")
.reduce("", (a, b) -> a + b)}
</tbody>
<tfoot>
<tr>
<td colspan="5">
Total Products: \{products.size()} | 
Generated: \{LocalDateTime.now()}
</td>
</tr>
</tfoot>
</table>
""";
System.out.println("Product Table HTML:\n" + html);
}
public static void demonstrateEmailTemplate() {
String recipientName = "Sarah Johnson";
String company = "Tech Corp";
String offerCode = "SAVE20";
LocalDateTime expiryDate = LocalDateTime.now().plusDays(7);
String email = HTML."""
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<style>
body { font-family: Arial, sans-serif; line-height: 1.6; }
.container { max-width: 600px; margin: 0 auto; padding: 20px; }
.header { background: #4CAF50; color: white; padding: 20px; text-align: center; }
.content { padding: 20px; background: #f9f9f9; }
.offer { background: #fff3cd; padding: 15px; border-left: 4px solid #ffc107; }
.footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Special Offer Just For You!</h1>
</div>
<div class="content">
<p>Dear \{recipientName},</p>
<p>We're excited to offer you a special discount from \{company}!</p>
<div class="offer">
<h2>Use code: <strong>\{offerCode}</strong></h2>
<p>Get 20% off your next purchase!</p>
<p><em>Offer expires: \{expiryDate}</em></p>
</div>
<p>Thank you for being a valued customer.</p>
<p>Best regards,<br>The \{company} Team</p>
</div>
<div class="footer">
<p>&copy; 2024 \{company}. All rights reserved.</p>
<p>This email was sent to you as a valued customer.</p>
</div>
</div>
</body>
</html>
""";
System.out.println("Email Template HTML:\n" + email);
}
// HTML processor for specific components
public static final StringTemplate.Processor<String, RuntimeException> COMPONENT =
StringTemplate.Processor.of((StringTemplate st) -> {
String component = HTML."""
<div class="component">
<div class="component-header">
<h3>Component</h3>
</div>
<div class="component-content">
\{STR.process(st)}
</div>
</div>
""";
return component;
});
public static void demonstrateComponents() {
String content = "This is component content with <script>alert('xss')</script>";
String component = COMPONENT."""
<p>\{content}</p>
<button onclick="handleClick()">Click Me</button>
""";
System.out.println("Component HTML:\n" + component);
}
public static void main(String[] args) {
System.out.println("=== Basic HTML ===");
demonstrateBasicHtml();
System.out.println("\n=== User Profile HTML ===");
demonstrateUserProfile();
System.out.println("\n=== Product Table HTML ===");
demonstrateProductTable();
System.out.println("\n=== Email Template HTML ===");
demonstrateEmailTemplate();
System.out.println("\n=== Component HTML ===");
demonstrateComponents();
}
}

Security Considerations

Security-Focused Template Processing

import java.util.*;
import java.util.regex.Pattern;
public class SecurityTemplates {
// Safe template processor with input validation
public static final StringTemplate.Processor<String, SecurityException> SAFE =
StringTemplate.Processor.of((StringTemplate st) -> {
// Validate all inputs
for (Object value : st.values()) {
if (value == null) continue;
String strValue = value.toString();
// Check for potential XSS
if (containsXSS(strValue)) {
throw new SecurityException("Potential XSS detected in template value: " + strValue);
}
// Check for SQL injection patterns
if (containsSQLInjection(strValue)) {
throw new SecurityException("Potential SQL injection detected: " + strValue);
}
// Check for excessive length
if (strValue.length() > 1000) {
throw new SecurityException("Input value too long: " + strValue.length());
}
}
return STR.process(st);
});
private static boolean containsXSS(String input) {
if (input == null) return false;
// Simple XSS pattern detection
Pattern xssPattern = Pattern.compile(
"<script|javascript:|on\\w+\\s*=|eval\\(|alert\\(|document\\.|window\\.", 
Pattern.CASE_INSENSITIVE
);
return xssPattern.matcher(input).find();
}
private static boolean containsSQLInjection(String input) {
if (input == null) return false;
// Simple SQL injection pattern detection
Pattern sqlPattern = Pattern.compile(
"('(''|[^'])*')|(;)|(\\b(ALTER|CREATE|DELETE|DROP|EXEC(UTE){0,1}|INSERT( +INTO){0,1}|MERGE|SELECT|UPDATE|UNION( +ALL){0,1})\\b)",
Pattern.CASE_INSENSITIVE
);
return sqlPattern.matcher(input).find();
}
// Sanitizing processor
public static final StringTemplate.Processor<String, RuntimeException> SANITIZED =
StringTemplate.Processor.of((StringTemplate st) -> {
List<Object> sanitizedValues = st.values().stream()
.map(value -> {
if (value == null) return "";
String str = value.toString();
// Remove potentially dangerous content
str = str.replaceAll("<script.*?</script>", "")
.replaceAll("javascript:", "")
.replaceAll("on\\w+\\s*=", "")
.replaceAll("eval\\(", "")
.replaceAll("alert\\(", "");
// Limit length
if (str.length() > 1000) {
str = str.substring(0, 1000) + "...";
}
return str;
})
.toList();
return StringTemplate.interpolate(st.fragments(), sanitizedValues);
});
// Logging processor for security auditing
public static final StringTemplate.Processor<String, RuntimeException> AUDITED =
StringTemplate.Processor.of((StringTemplate st) -> {
String result = STR.process(st);
// Log template usage for auditing
System.out.printf("[AUDIT] %s: Template processed by user: %s%n", 
LocalDateTime.now(), getCurrentUser());
System.out.printf("[AUDIT] Values used: %s%n", st.values());
System.out.printf("[AUDIT] Result length: %d%n", result.length());
return result;
});
private static String getCurrentUser() {
// In real application, get from security context
return System.getProperty("user.name", "unknown");
}
// Rate-limited processor
public static class RateLimitedProcessor {
private final Map<String, Integer> requestCounts = new HashMap<>();
private final int maxRequestsPerMinute;
public RateLimitedProcessor(int maxRequestsPerMinute) {
this.maxRequestsPerMinute = maxRequestsPerMinute;
}
public StringTemplate.Processor<String, SecurityException> processor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
String user = getCurrentUser();
String minuteKey = user + "_" + System.currentTimeMillis() / 60000;
int count = requestCounts.getOrDefault(minuteKey, 0);
if (count >= maxRequestsPerMinute) {
throw new SecurityException("Rate limit exceeded for user: " + user);
}
requestCounts.put(minuteKey, count + 1);
// Clean up old entries (simplified)
requestCounts.keySet().removeIf(key -> 
!key.endsWith(String.valueOf(System.currentTimeMillis() / 60000)));
return STR.process(st);
});
}
}
// Demonstration methods
public static void demonstrateSafeProcessor() {
try {
String safeInput = "Hello World";
String result1 = SAFE."Message: \{safeInput}";
System.out.println("Safe input: " + result1);
String dangerousInput = "<script>alert('xss')</script>";
String result2 = SAFE."Message: \{dangerousInput}";
System.out.println("Dangerous input: " + result2);
} catch (SecurityException e) {
System.out.println("Security exception: " + e.getMessage());
}
}
public static void demonstrateSanitizedProcessor() {
String dangerousInput = "Hello <script>alert('xss')</script> World";
String result = SANITIZED."Message: \{dangerousInput}";
System.out.println("Sanitized: " + result);
String longInput = "A".repeat(2000);
String result2 = SANITIZED."Message: \{longInput}";
System.out.println("Length limited: " + result2.length() + " chars");
}
public static void demonstrateAuditedProcessor() {
String result = AUDITED."User \{getCurrentUser()} accessed resource at \{LocalDateTime.now()}";
System.out.println("Audited result: " + result);
}
public static void demonstrateRateLimited() {
var rateLimited = new RateLimitedProcessor(5).processor();
for (int i = 0; i < 7; i++) {
try {
String result = rateLimited."Request #\{i + 1} at \{LocalDateTime.now()}";
System.out.println("Request " + (i + 1) + ": OK");
} catch (SecurityException e) {
System.out.println("Request " + (i + 1) + ": " + e.getMessage());
break;
}
}
}
// Context-aware security processor
public static class ContextAwareProcessor {
private final Set<String> allowedTags;
public ContextAwareProcessor(Set<String> allowedTags) {
this.allowedTags = allowedTags;
}
public StringTemplate.Processor<String, SecurityException> processor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
// Check if template contains only allowed tags
String templateText = String.join("", st.fragments());
for (String fragment : st.fragments()) {
if (!isFragmentSafe(fragment)) {
throw new SecurityException("Unsafe template fragment: " + fragment);
}
}
return STR.process(st);
});
}
private boolean isFragmentSafe(String fragment) {
// Implement fragment safety checks
// This is a simplified example
return !fragment.contains("${") && !fragment.contains("#{");
}
}
public static void main(String[] args) {
System.out.println("=== Safe Processor ===");
demonstrateSafeProcessor();
System.out.println("\n=== Sanitized Processor ===");
demonstrateSanitizedProcessor();
System.out.println("\n=== Audited Processor ===");
demonstrateAuditedProcessor();
System.out.println("\n=== Rate Limited Processor ===");
demonstrateRateLimited();
}
}

Performance Optimization

High-Performance Template Processing

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class PerformanceTemplates {
// Cached template processor
public static class CachedProcessor {
private final Map<StringTemplate, String> cache = new ConcurrentHashMap<>();
private final StringTemplate.Processor<String, RuntimeException> baseProcessor;
public CachedProcessor(StringTemplate.Processor<String, RuntimeException> baseProcessor) {
this.baseProcessor = baseProcessor;
}
public StringTemplate.Processor<String, RuntimeException> processor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
return cache.computeIfAbsent(st, key -> baseProcessor.process(key));
});
}
public void clearCache() {
cache.clear();
}
public int getCacheSize() {
return cache.size();
}
}
// High-performance string builder processor
public static final StringTemplate.Processor<String, RuntimeException> FAST =
StringTemplate.Processor.of((StringTemplate st) -> {
// Pre-calculate total length for efficient StringBuilder allocation
int totalLength = st.fragments().stream().mapToInt(String::length).sum() +
st.values().stream().mapToInt(v -> v != null ? v.toString().length() : 4).sum();
StringBuilder sb = new StringBuilder(totalLength);
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
sb.append(fragments.get(i));
if (i < values.size()) {
Object value = values.get(i);
sb.append(value != null ? value : "null");
}
}
return sb.toString();
});
// Bulk template processor
public static List<String> processBulk(
List<StringTemplate> templates,
StringTemplate.Processor<String, RuntimeException> processor) {
return templates.parallelStream()
.map(processor::process)
.toList();
}
// Template pre-compilation
public static class PrecompiledTemplate {
private final List<String> fragments;
private final List<Class<?>> valueTypes;
public PrecompiledTemplate(StringTemplate template) {
this.fragments = List.copyOf(template.fragments());
this.valueTypes = template.values().stream()
.map(Object::getClass)
.toList();
}
public String instantiate(Object... values) {
if (values.length != valueTypes.size()) {
throw new IllegalArgumentException("Wrong number of values");
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < fragments.size(); i++) {
sb.append(fragments.get(i));
if (i < values.length) {
sb.append(values[i]);
}
}
return sb.toString();
}
}
// Demonstration methods
public static void demonstrateCachedProcessor() {
var cachedSTR = new CachedProcessor(STR).processor();
String name = "John";
int age = 30;
// First call - processes and caches
long start1 = System.nanoTime();
String result1 = cachedSTR."Hello \{name}, you are \{age} years old";
long time1 = System.nanoTime() - start1;
// Second call - uses cache
long start2 = System.nanoTime();
String result2 = cachedSTR."Hello \{name}, you are \{age} years old";
long time2 = System.nanoTime() - start2;
System.out.println("First call: " + time1 + " ns");
System.out.println("Second call: " + time2 + " ns");
System.out.println("Speedup: " + (double) time1 / time2 + "x");
System.out.println("Cache size: " + cachedSTR.getCacheSize());
}
public static void demonstrateFastProcessor() {
String longText = "A".repeat(1000);
int iterations = 10000;
// Test STR processor
long strStart = System.nanoTime();
for (int i = 0; i < iterations; i++) {
STR."Text: \{longText} - Iteration: \{i}";
}
long strTime = System.nanoTime() - strStart;
// Test FAST processor
long fastStart = System.nanoTime();
for (int i = 0; i < iterations; i++) {
FAST."Text: \{longText} - Iteration: \{i}";
}
long fastTime = System.nanoTime() - fastStart;
System.out.println("STR processor time: " + strTime / 1_000_000 + " ms");
System.out.println("FAST processor time: " + fastTime / 1_000_000 + " ms");
System.out.println("Performance improvement: " + (double) strTime / fastTime + "x");
}
public static void demonstrateBulkProcessing() {
List<StringTemplate> templates = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
templates.add(RAW."User \{i}: Score \{i * 10}");
}
long start = System.nanoTime();
List<String> results = processBulk(templates, STR);
long time = System.nanoTime() - start;
System.out.println("Bulk processed " + results.size() + " templates in " + 
time / 1_000_000 + " ms");
}
public static void demonstratePrecompiled() {
StringTemplate template = RAW."Hello \{}, your balance is $\{}";
PrecompiledTemplate precompiled = new PrecompiledTemplate(template);
long start = System.nanoTime();
for (int i = 0; i < 10000; i++) {
precompiled.instantiate("User" + i, i * 100.0);
}
long time = System.nanoTime() - start;
System.out.println("Precompiled templates: " + time / 1_000_000 + " ms for 10000 iterations");
}
// Memory-efficient processor for large templates
public static final StringTemplate.Processor<String, RuntimeException> MEMORY_EFFICIENT =
StringTemplate.Processor.of((StringTemplate st) -> {
// For very large templates, process in chunks
if (estimateSize(st) > 1000000) { // 1MB threshold
return processLargeTemplate(st);
}
return STR.process(st);
});
private static long estimateSize(StringTemplate st) {
return st.fragments().stream().mapToLong(String::length).sum() +
st.values().stream().mapToLong(v -> v != null ? v.toString().length() : 0).sum();
}
private static String processLargeTemplate(StringTemplate st) {
// Process large template in chunks to avoid memory issues
StringBuilder result = new StringBuilder();
List<String> fragments = st.fragments();
List<Object> values = st.values();
for (int i = 0; i < fragments.size(); i++) {
result.append(fragments.get(i));
if (i < values.size()) {
Object value = values.get(i);
if (value != null) {
String strValue = value.toString();
// Process large values in chunks
if (strValue.length() > 100000) {
result.append("[LARGE_VALUE]");
} else {
result.append(strValue);
}
}
}
}
return result.toString();
}
public static void main(String[] args) {
System.out.println("=== Cached Processor ===");
demonstrateCachedProcessor();
System.out.println("\n=== Fast Processor ===");
demonstrateFastProcessor();
System.out.println("\n=== Bulk Processing ===");
demonstrateBulkProcessing();
System.out.println("\n=== Precompiled Templates ===");
demonstratePrecompiled();
}
}

Best Practices Summary

public class StringTemplatesBestPractices {
/*
* STRING TEMPLATES BEST PRACTICES:
* 
* 1. Use STR for simple string interpolation
* 2. Use FMT for formatted output
* 3. Use RAW when you need the template object
* 4. Create custom processors for domain-specific needs
* 5. Always escape user input in HTML/JSON/SQL contexts
* 6. Validate inputs in security-sensitive applications
* 7. Consider performance for high-throughput scenarios
* 8. Use caching for frequently used templates
* 9. Prefer templates over concatenation for complex strings
* 10. Test template processors thoroughly
*/
// Example of well-structured template usage
public static class WellStructuredExample {
private static final StringTemplate.Processor<String, RuntimeException> SQL = 
createSafeSQLProcessor();
private static final StringTemplate.Processor<String, RuntimeException> HTML =
createSafeHTMLProcessor();
public String createUserWelcome(String username, String email, LocalDateTime joinDate) {
return STR."""
Welcome \{username}!
Thank you for joining our service. Your account details:
- Username: \{username}
- Email: \{email}
- Join Date: \{joinDate.format(DateTimeFormatter.ISO_LOCAL_DATE)}
We're excited to have you on board!
""";
}
public String createProductHTML(Product product) {
return HTML."""
<div class="product">
<h3>\{product.name()}</h3>
<p class="price">$\{product.price()}</p>
<p class="description">\{product.description()}</p>
\{product.inStock() ? 
"<button class='buy-now'>Buy Now</button>" : 
"<p class='out-of-stock'>Out of Stock</p>"}
</div>
""";
}
private static StringTemplate.Processor<String, RuntimeException> createSafeSQLProcessor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
// Implementation with proper escaping and validation
return STR.process(st); // Simplified
});
}
private static StringTemplate.Processor<String, RuntimeException> createSafeHTMLProcessor() {
return StringTemplate.Processor.of((StringTemplate st) -> {
// Implementation with HTML escaping
return STR.process(st); // Simplified
});
}
record Product(String name, double price, String description, boolean inStock) {}
}
public static void main(String[] args) {
var example = new WellStructuredExample();
String welcome = example.createUserWelcome(
"john_doe", 
"[email protected]", 
LocalDateTime.now()
);
System.out.println(welcome);
}
}

String Templates in Java provide a powerful, safe, and expressive way to work with string composition. By following these patterns and best practices, you can write more maintainable, secure, and efficient string processing code.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper