google-site-verification: google61fe8ba583a51912.html
Mastering Interpolated Strings with STR.”…” in Java

Java 21 introduces String Templates as a preview feature (JEP 430), bringing interpolated string capabilities to Java. This article provides a comprehensive guide to using the STR."..." processor and other string template features.

Understanding String Templates

String Templates combine literal text with embedded expressions and template processors to produce specialized results. The STR processor is the standard processor for string interpolation.

Basic String Interpolation

Step 1: Basic Syntax and Usage

public class BasicStringTemplates { public static void main(String[] args) { String name = "John"; int age = 30; double salary = 55000.75; // Traditional concatenation String traditional = "Name: " + name + ", Age: " + age + ", Salary: " + salary; // String template interpolation String interpolated = STR."Name: \{name}, Age: \{age}, Salary: \{salary}"; System.out.println("Traditional: " + traditional); System.out.println("Interpolated: " + interpolated); // Expressions within templates int x = 10, y = 20; String calculation = STR."\{x} + \{y} = \{x + y}"; System.out.println(calculation); // "10 + 20 = 30" // Method calls in templates String methodCall = STR."Current time: \{java.time.LocalTime.now()}"; System.out.println(methodCall); // Object method calls Person person = new Person("Alice", 25); String objectInfo = STR."Person: \{person.getName()}, Age: \{person.getAge()}"; System.out.println(objectInfo); } static class Person { private String name; private int age; public Person(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } } }

Step 2: Multi-line Templates

public class MultilineTemplates { public record User(String username, String email, String role, LocalDateTime createdAt) {} public static void main(String[] args) { User user = new User("johndoe", "[email protected]", "ADMIN", LocalDateTime.now()); // Multi-line string template String userProfile = STR.""" User Profile: ============ Username: \{user.username()} Email: \{user.email()} Role: \{user.role()} Created: \{user.createdAt().format( java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME)} Last Login: \{LocalDateTime.now().minusHours(2)} Status: Active """; System.out.println(userProfile); // SQL query template String tableName = "users"; int limit = 100; String sqlQuery = STR.""" SELECT id, username, email, created_at FROM \{tableName} WHERE status = 'ACTIVE' ORDER BY created_at DESC LIMIT \{limit} """; System.out.println("\nSQL Query:\n" + sqlQuery); // JSON template String jsonResponse = STR.""" { "status": "success", "data": { "user": { "username": "\{user.username()}", "email": "\{user.email()}", "role": "\{user.role()}" } }, "timestamp": "\{Instant.now()}" } """; System.out.println("\nJSON Response:\n" + jsonResponse); } }

Advanced Template Features

Step 3: Complex Expressions and Formatting

import java.math.BigDecimal; import java.text.NumberFormat; import java.util.*; public class AdvancedTemplates { public static void main(String[] args) { // Complex expressions List<Integer> numbers = List.of(1, 2, 3, 4, 5); String stats = STR.""" List Analysis: - List: \{numbers} - Size: \{numbers.size()} - Sum: \{numbers.stream().mapToInt(Integer::intValue).sum()} - Average: \{numbers.stream().mapToInt(Integer::intValue).average().orElse(0)} - Max: \{Collections.max(numbers)} - Min: \{Collections.min(numbers)} """; System.out.println(stats); // Conditional expressions int score = 85; String gradeResult = STR.""" Score: \{score} Grade: \{ score >= 90 ? "A" : score >= 80 ? "B" : score >= 70 ? "C" : score >= 60 ? "D" : "F" } Status: \{score >= 70 ? "PASS" : "FAIL"} """; System.out.println(gradeResult); // Method chain expressions String text = " Hello, World! "; String processed = STR.""" Original: '\{text}' Trimmed: '\{text.trim()}' Upper Case: '\{text.trim().toUpperCase()}' Lower Case: '\{text.trim().toLowerCase()}' Length: \{text.trim().length()} """; System.out.println(processed); // Date formatting LocalDate today = LocalDate.now(); String dateInfo = STR.""" Date Information: - ISO: \{today} - Formatted: \{today.format(DateTimeFormatter.ofPattern("MMM dd, yyyy"))} - Day of Week: \{today.getDayOfWeek()} - Month: \{today.getMonth()} - Year: \{today.getYear()} - Is Leap Year: \{today.isLeapYear()} """; System.out.println(dateInfo); } // Financial formatting example public static String generateInvoice(Invoice invoice) { NumberFormat currency = NumberFormat.getCurrencyInstance(Locale.US); return STR.""" INVOICE #\{invoice.invoiceNumber()} ======================== Customer: \{invoice.customerName()} Date: \{invoice.date()} Items: \{ invoice.items().stream() .map(item -> STR." • \{item.description()}: \{currency.format(item.amount())}") .collect(java.util.stream.Collectors.joining("\n")) } Subtotal: \{currency.format(invoice.subtotal())} Tax: \{currency.format(invoice.tax())} Total: \{currency.format(invoice.total())} Thank you for your business! """; } record Invoice(String invoiceNumber, String customerName, LocalDate date, List<LineItem> items, double subtotal, double tax, double total) {} record LineItem(String description, double amount) {} }

Custom Template Processors

Step 4: Beyond STR - Creating Custom Processors

import java.time.LocalDateTime; import java.util.Locale; import java.util.regex.Pattern; public class CustomTemplateProcessors { // Custom processor for SQL safe interpolation public static final StringTemplate.Processor<String, RuntimeException> SQL = StringTemplate.Processor.of((StringTemplate st) -> { StringBuilder sb = new StringBuilder(); 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); // Basic SQL injection prevention if (value instanceof String str) { // Escape single quotes for SQL String escaped = str.replace("'", "''"); sb.append("'").append(escaped).append("'"); } else if (value instanceof Number) { sb.append(value); } else if (value == null) { sb.append("NULL"); } else { sb.append("'").append(value.toString().replace("'", "''")).append("'"); } } } return sb.toString(); }); // JSON processor with proper escaping public static final StringTemplate.Processor<String, RuntimeException> JSON = StringTemplate.Processor.of((StringTemplate st) -> { StringBuilder sb = new StringBuilder(); 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); if (value instanceof String str) { // JSON escape String escaped = str.replace("\"", "\\\"") .replace("\\", "\\\\") .replace("\b", "\\b") .replace("\f", "\\f") .replace("\n", "\\n") .replace("\r", "\\r") .replace("\t", "\\t"); sb.append("\"").append(escaped).append("\""); } else if (value instanceof Number || value instanceof Boolean) { sb.append(value); } else if (value == null) { sb.append("null"); } else { sb.append("\"").append(value.toString() .replace("\"", "\\\"") .replace("\\", "\\\\")) .append("\""); } } } return sb.toString(); }); // HTML processor with basic XSS protection public static final StringTemplate.Processor<String, RuntimeException> HTML = StringTemplate.Processor.of((StringTemplate st) -> { StringBuilder sb = new StringBuilder(); 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); if (value instanceof String str) { // Basic HTML escaping String escaped = str.replace("&", "&amp;") .replace("<", "&lt;") .replace(">", "&gt;") .replace("\"", "&quot;") .replace("'", "&#x27;"); sb.append(escaped); } else { sb.append(value); } } } return sb.toString(); }); public static void main(String[] args) { // Using SQL processor String tableName = "users"; String username = "john'; DROP TABLE users; --"; int age = 30; String sql = SQL.""" SELECT * FROM \{tableName} WHERE username = \{username} AND age > \{age} """; System.out.println("SQL Query:\n" + sql); // Using JSON processor String name = "John\"Doe"; String email = "[email protected]"; boolean active = true; String json = JSON.""" { "name": "\{name}", "email": "\{email}", "active": \{active}, "createdAt": "\{LocalDateTime.now()}" } """; System.out.println("\nJSON:\n" + json); // Using HTML processor String userInput = "<script>alert('XSS')</script>"; String title = "User Profile"; String html = HTML.""" <div class="profile"> <h1>\{title}</h1> <p>User input: \{userInput}</p> <span>Generated at: \{LocalDateTime.now()}</span> </div> """; System.out.println("\nHTML:\n" + html); } }

Real-World Use Cases

Step 5: Practical Applications

import java.util.*; public class RealWorldTemplates { // Email template system public static class EmailTemplates { public static String welcomeEmail(String userName, String activationLink) { return STR.""" Subject: Welcome to Our Service, \{userName}! Dear \{userName}, Welcome to Our Service! We're excited to have you on board. To get started, please activate your account by clicking the link below: \{activationLink} This link will expire in 24 hours. If you have any questions, please don't hesitate to contact our support team. Best regards, The Our Service Team Generated: \{LocalDateTime.now()} """; } public static String passwordReset(String userName, String resetLink) { return STR.""" Subject: Password Reset Request Hello \{userName}, We received a request to reset your password. If you didn't make this request, please ignore this email. To reset your password, click the link below: \{resetLink} This link will expire in 1 hour. For security reasons, please do not share this link with anyone. Best regards, Security Team """; } } // Logging system with templates public static class TemplateLogger { private static final StringTemplate.Processor<String, RuntimeException> LOG = StringTemplate.Processor.of((StringTemplate st) -> { String timestamp = LocalDateTime.now().format( java.time.format.DateTimeFormatter.ISO_LOCAL_DATE_TIME); String message = STR.process(st); return STR."[\{timestamp}] \{message}"; }); public static void info(StringTemplate st) { System.out.println(LOG."INFO: \{st}"); } public static void error(StringTemplate st) { System.err.println(LOG."ERROR: \{st}"); } public static void debug(StringTemplate st) { System.out.println(LOG."DEBUG: \{st}"); } } // Configuration template system public static class ConfigTemplates { public static String generateDockerfile(String baseImage, int port, String maintainer, List<String> dependencies) { return STR.""" # Generated Dockerfile FROM \{baseImage} LABEL maintainer="\{maintainer}" LABEL version="1.0" LABEL description="Application container" # Install dependencies RUN apt-get update && apt-get install -y \ \{ String.join(" \\\n ", dependencies) } # Expose application port EXPOSE \{port} # Set working directory WORKDIR /app # Copy application files COPY . . # Start application CMD ["java", "-jar", "app.jar"] # Health check HEALTHCHECK --interval=30s --timeout=3s \\ CMD curl -f http://localhost:\{port}/health || exit 1 """; } public static String generateApplicationProperties(String dbUrl, String dbUsername, int serverPort, boolean debugMode) { return STR.""" # Application Properties # Generated: \{LocalDateTime.now()} # Database Configuration spring.datasource.url=\{dbUrl} spring.datasource.username=\{dbUsername} spring.datasource.password=$\{DB_PASSWORD} # Server Configuration server.port=\{serverPort} server.servlet.context-path=/ # Logging logging.level.com.example=\{debugMode ? "DEBUG" : "INFO"} logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %msg%n # Security security.jwt.secret=$\{JWT_SECRET} security.jwt.expiration=86400 # Feature Flags feature.email.notifications=true feature.analytics.enabled=\{debugMode ? "false" : "true"} """; } } public static void main(String[] args) { // Test email templates String welcomeEmail = EmailTemplates.welcomeEmail( "John Doe", "https://example.com/activate?token=abc123" ); System.out.println("Welcome Email:\n" + welcomeEmail); // Test logging String userId = "user123"; int actionCount = 5; TemplateLogger.info(STR."User \{userId} performed \{actionCount} actions"); TemplateLogger.error(STR."Database connection failed for user \{userId}"); // Test configuration generation List<String> dependencies = Arrays.asList( "openjdk-17-jdk", "curl", "vim", "git" ); String dockerfile = ConfigTemplates.generateDockerfile( "openjdk:17", 8080, "[email protected]", dependencies ); System.out.println("\nDockerfile:\n" + dockerfile); String appProps = ConfigTemplates.generateApplicationProperties( "jdbc:mysql://localhost:3306/mydb", "admin", 8080, true ); System.out.println("\nApplication Properties:\n" + appProps); } }

Performance Considerations

Step 6: Efficiency and Best Practices

public class PerformanceTemplates { // Benchmark comparison public static void performanceComparison() { String name = "John"; int age = 30; double salary = 55000.75; int iterations = 100_000; // Traditional concatenation long startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { String result = "Name: " + name + ", Age: " + age + ", Salary: " + salary; } long concatTime = System.nanoTime() - startTime; // StringBuilder startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { String result = new StringBuilder() .append("Name: ").append(name) .append(", Age: ").append(age) .append(", Salary: ").append(salary) .toString(); } long builderTime = System.nanoTime() - startTime; // String template startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { String result = STR."Name: \{name}, Age: \{age}, Salary: \{salary}"; } long templateTime = System.nanoTime() - startTime; // String.format startTime = System.nanoTime(); for (int i = 0; i < iterations; i++) { String result = String.format("Name: %s, Age: %d, Salary: %.2f", name, age, salary); } long formatTime = System.nanoTime() - startTime; System.out.println("Performance Comparison (" + iterations + " iterations):"); System.out.printf("Concatenation: %10d ns%n", concatTime); System.out.printf("StringBuilder: %10d ns%n", builderTime); System.out.printf("String Template:%10d ns%n", templateTime); System.out.printf("String.format: %10d ns%n", formatTime); } // Best practices for template usage public static class TemplateBestPractices { // Good: Simple expressions public static String createUserMessage(String name, int messages) { return STR."Hello \{name}, you have \{messages} new messages"; } // Avoid: Complex logic in templates public static String createComplexReport(List<Double> data) { // Instead of putting complex logic in template... double average = data.stream().mapToDouble(Double::doubleValue).average().orElse(0); double max = Collections.max(data); double min = Collections.min(data); // ...precompute values and use in template return STR.""" Data Report: - Count: \{data.size()} - Average: \{String.format("%.2f", average)} - Range: \{String.format("%.2f", min)} to \{String.format("%.2f", max)} """; } // Good: Use with records public record Product(String id, String name, double price, int stock) {} public static String productInfo(Product product) { return STR.""" Product: \{product.name()} ID: \{product.id()} Price: $\{String.format("%.2f", product.price())} Stock: \{product.stock()} """; } // Template reuse pattern private static final StringTemplate.Processor<String, RuntimeException> ERROR_TEMPLATE = StringTemplate.Processor.of(st -> { String timestamp = LocalDateTime.now().toString(); String message = STR.process(st); return STR."[\{timestamp}] ERROR: \{message}"; }); public static void logError(String errorType, String details, String userId) { String errorMessage = ERROR_TEMPLATE.""" Type: \{errorType} User: \{userId} Details: \{details} """; System.err.println(errorMessage); } } public static void main(String[] args) { // Run performance comparison performanceComparison(); // Demonstrate best practices String userMessage = TemplateBestPractices.createUserMessage("Alice", 5); System.out.println("\nUser Message: " + userMessage); List<Double> data = List.of(10.5, 20.3, 15.7, 8.2, 25.1); String report = TemplateBestPractices.createComplexReport(data); System.out.println("\nReport:\n" + report); TemplateBestPractices.Product product = new TemplateBestPractices.Product( "P123", "Laptop", 999.99, 10 ); String productInfo = TemplateBestPractices.productInfo(product); System.out.println("\nProduct Info:\n" + productInfo); TemplateBestPractices.logError("Database", "Connection timeout", "user456"); } }

Integration with Existing Code

Step 7: Migrating Existing Code to Templates

public class MigrationExamples { // Before: Traditional approaches public static class TraditionalApproach { public static String buildUserInfo(String firstName, String lastName, int age, String city) { return "User: " + firstName + " " + lastName + ", Age: " + age + ", City: " + city; } public static String buildSqlQuery(String table, String whereClause, int limit, String orderBy) { return "SELECT * FROM " + table + " WHERE " + whereClause + " ORDER BY " + orderBy + " LIMIT " + limit; } public static String buildHtmlElement(String tag, String content, String... attributes) { StringBuilder sb = new StringBuilder(); sb.append("<").append(tag); for (int i = 0; i < attributes.length; i += 2) { if (i + 1 < attributes.length) { sb.append(" ").append(attributes[i]) .append("=\"").append(attributes[i + 1]).append("\""); } } sb.append(">").append(content).append("</").append(tag).append(">"); return sb.toString(); } } // After: Using string templates public static class TemplateApproach { public static String buildUserInfo(String firstName, String lastName, int age, String city) { return STR."User: \{firstName} \{lastName}, Age: \{age}, City: \{city}"; } public static String buildSqlQuery(String table, String whereClause, int limit, String orderBy) { return STR."SELECT * FROM \{table} WHERE \{whereClause} ORDER BY \{orderBy} LIMIT \{limit}"; } public static String buildHtmlElement(String tag, String content, String... attributes) { StringBuilder attrs = new StringBuilder(); for (int i = 0; i < attributes.length; i += 2) { if (i + 1 < attributes.length) { attrs.append(" ") .append(attributes[i]) .append("=\"") .append(attributes[i + 1]) .append("\""); } } return STR."<\{tag}\{attrs}>\{content}</\{tag}>"; } } // Gradual migration example public static class HybridApproach { public static String generateReport(Map<String, Object> data) { // Use template for the main structure String header = STR.""" ========================== REPORT: \{data.get("reportName")} Generated: \{LocalDateTime.now()} ========================== """; // Use traditional approach for dynamic sections StringBuilder body = new StringBuilder(); for (Map.Entry<String, Object> entry : data.entrySet()) { if (!entry.getKey().equals("reportName")) { body.append(entry.getKey()) .append(": ") .append(entry.getValue()) .append("\n"); } } // Combine both approaches return STR.""" \{header} \{body.toString()} ========================== """; } } public static void main(String[] args) { // Compare approaches String traditional = TraditionalApproach.buildUserInfo("John", "Doe", 30, "New York"); String template = TemplateApproach.buildUserInfo("John", "Doe", 30, "New York"); System.out.println("Traditional: " + traditional); System.out.println("Template: " + template); // HTML generation comparison String htmlTraditional = TraditionalApproach.buildHtmlElement( "div", "Hello World", "class", "container", "id", "main" ); String htmlTemplate = TemplateApproach.buildHtmlElement( "div", "Hello World", "class", "container", "id", "main" ); System.out.println("\nHTML Traditional: " + htmlTraditional); System.out.println("HTML Template: " + htmlTemplate); // Hybrid approach Map<String, Object> reportData = Map.of( "reportName", "Monthly Sales", "totalSales", 150000.0, "unitsSold", 1250, "topProduct", "Widget X", "growth", "+15.5%" ); String hybridReport = HybridApproach.generateReport(reportData); System.out.println("\nHybrid Report:\n" + hybridReport); } }

Key Benefits and Best Practices

Benefits:

  1. Readability: Much cleaner than concatenation or StringBuilder
  2. Safety: Compile-time checking of expression syntax
  3. Performance: Generally faster than String.format()
  4. Flexibility: Custom processors for different contexts
  5. Maintainability: Easier to modify and understand

Best Practices:

  1. Use for simple expressions and avoid complex logic in templates
  2. Create custom processors for domain-specific formatting
  3. Precompute complex values before using in templates
  4. Use multi-line templates for complex structured text
  5. Consider performance for hot code paths
  6. Combine with records for type-safe data interpolation

String templates with STR."..." provide a powerful, readable, and efficient way to handle string interpolation in Java, significantly improving upon traditional concatenation approaches while maintaining type safety and performance.

Leave a Reply

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


Macro Nepal Helper