Java 11 New String Methods

1. Introduction

If you have worked with Java for any length of time, you know that String is the single most used class in the language. Every validation check, every log message, every API response, every database query — they all involve strings. Despite this, Java’s String class went largely unchanged from Java 1.0 through Java 8. Developers had to reach for Apache Commons Lang, Guava, or hand-rolled utility methods just to do basic things like check if a string was blank or repeat a character.

Java 11 finally addressed these long-standing gaps by adding six new methods to the String class. These methods are small individually, but collectively they eliminate a surprising amount of boilerplate code and third-party dependencies. If you have ever written str != null && !str.trim().isEmpty(), you will appreciate what Java 11 brings to the table.

Here is what Java 11 added to String:

Method What It Does What It Replaces
isBlank() Returns true if the string is empty or contains only whitespace str.trim().isEmpty() or Apache Commons StringUtils.isBlank()
strip() Removes leading and trailing whitespace (Unicode-aware) trim() (ASCII-only)
stripLeading() Removes leading whitespace only Manual regex or substring logic
stripTrailing() Removes trailing whitespace only Manual regex or substring logic
lines() Splits string into a Stream<String> by line terminators split("\\n") or BufferedReader.lines()
repeat(int) Repeats the string n times String.join() hacks or StringUtils.repeat()

In this tutorial, we will explore each method in depth with practical, compilable examples that show exactly when and why you would use them in real-world Java applications.

Prerequisites: Java 11 or later. All code examples compile and run on Java 11+.

2. isBlank()

The isBlank() method returns true if the string is empty ("") or contains only whitespace characters. This is the method Java developers have been asking for since approximately forever.

The Problem It Solves

Before Java 11, checking whether a string was “blank” (empty or whitespace-only) required either a multi-step check or a third-party library:

// Before Java 11: verbose and error-prone
if (name != null && !name.trim().isEmpty()) {
    // name has actual content
}

// Or using Apache Commons Lang
if (StringUtils.isNotBlank(name)) {
    // name has actual content
}

// Java 11: clean and built-in
if (!name.isBlank()) {
    // name has actual content
}

isBlank() vs isEmpty()

This is the most common question developers ask. The difference is simple but critical:

Method Returns true when "" " " "\t\n" "hello"
isEmpty() Length is 0 true false false false
isBlank() Length is 0 or all characters are whitespace true true true false

Think of it this way: isEmpty() checks if the box is empty. isBlank() checks if the box is empty or filled with packing peanuts (whitespace) — either way, there is nothing useful inside.

public class IsBlankDemo {
    public static void main(String[] args) {
        // Basic comparisons
        String empty = "";
        String spaces = "   ";
        String tab = "\t";
        String newline = "\n";
        String mixed = " \t \n ";
        String content = "  hello  ";

        System.out.println("String        | isEmpty | isBlank");
        System.out.println("------------- | ------- | -------");
        printComparison("\"\"", empty);           // true  | true
        printComparison("\"   \"", spaces);       // false | true
        printComparison("\"\\t\"", tab);          // false | true
        printComparison("\"\\n\"", newline);      // false | true
        printComparison("\" \\t \\n \"", mixed);  // false | true
        printComparison("\"  hello  \"", content);// false | false
    }

    static void printComparison(String label, String value) {
        System.out.printf("%-14s| %-8s| %s%n",
            label, value.isEmpty(), value.isBlank());
    }
}

// Output:
// String        | isEmpty | isBlank
// ------------- | ------- | -------
// ""            | true    | true
// "   "         | false   | true
// "\t"          | false   | true
// "\n"          | false   | true
// " \t \n "     | false   | true
// "  hello  "   | false   | false

Practical Use: Form Validation

isBlank() shines in form validation where users might submit fields filled with spaces:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

public class FormValidator {

    public static List validate(Map formData) {
        List errors = new ArrayList<>();

        String username = formData.getOrDefault("username", "");
        String email = formData.getOrDefault("email", "");
        String password = formData.getOrDefault("password", "");
        String bio = formData.getOrDefault("bio", "");

        // isBlank() catches spaces-only submissions that isEmpty() would miss
        if (username.isBlank()) {
            errors.add("Username is required");
        } else if (username.length() < 3) {
            errors.add("Username must be at least 3 characters");
        }

        if (email.isBlank()) {
            errors.add("Email is required");
        } else if (!email.contains("@")) {
            errors.add("Email must contain @");
        }

        if (password.isBlank()) {
            errors.add("Password is required");
        }

        // Bio is optional -- only validate length if user typed something
        if (!bio.isBlank() && bio.strip().length() > 500) {
            errors.add("Bio must be 500 characters or less");
        }

        return errors;
    }

    public static void main(String[] args) {
        // Simulating a form where a user typed only spaces in the username field
        var formData = Map.of(
            "username", "   ",      // spaces only -- isBlank() catches this!
            "email", "john@example.com",
            "password", "secret123",
            "bio", ""
        );

        List errors = validate(formData);
        if (errors.isEmpty()) {
            System.out.println("Form is valid!");
        } else {
            errors.forEach(e -> System.out.println("ERROR: " + e));
        }
    }
}

// Output:
// ERROR: Username is required

Unicode Awareness

isBlank() recognizes all Unicode whitespace characters, not just ASCII space (\u0020). This matters when processing text from international sources:

public class IsBlankUnicode {
    public static void main(String[] args) {
        // Various Unicode whitespace characters
        String noBreakSpace = "\u00A0";          // Non-breaking space
        String emSpace = "\u2003";               // Em space (typography)
        String ideographicSpace = "\u3000";      // CJK ideographic space (Chinese/Japanese)
        String zeroWidthSpace = "\u200B";        // Zero-width space

        System.out.println("Non-breaking space isBlank: " + noBreakSpace.isBlank());
        System.out.println("Em space isBlank: " + emSpace.isBlank());
        System.out.println("Ideographic space isBlank: " + ideographicSpace.isBlank());
        System.out.println("Zero-width space isBlank: " + zeroWidthSpace.isBlank());
    }
}

// Output:
// Non-breaking space isBlank: true
// Em space isBlank: true
// Ideographic space isBlank: true
// Zero-width space isBlank: false   <-- not classified as whitespace by Character.isWhitespace()

Key takeaway: isBlank() uses Character.isWhitespace() internally, which covers most Unicode whitespace categories but not all code points (zero-width space is excluded). For the vast majority of real-world applications, this is exactly what you want.

3. strip(), stripLeading(), and stripTrailing()

Java has had trim() since version 1.0, so why did Java 11 add strip()? The answer is Unicode awareness. The trim() method only removes characters with a code point less than or equal to U+0020 (the ASCII space). It completely ignores Unicode whitespace characters like non-breaking spaces, em spaces, and ideographic spaces.

trim() vs strip() -- The Critical Difference

Feature trim() strip()
Available since Java 1.0 Java 11
Whitespace definition Characters <= '\u0020' Character.isWhitespace()
Handles Unicode whitespace No Yes
Non-breaking space (\u00A0) Not removed Removed
Em space (\u2003) Not removed Removed
Ideographic space (\u3000) Not removed Removed
public class StripVsTrim {
    public static void main(String[] args) {
        // ASCII whitespace -- both trim() and strip() handle this
        String asciiSpaces = "   hello   ";
        System.out.println("trim():  [" + asciiSpaces.trim() + "]");   // [hello]
        System.out.println("strip(): [" + asciiSpaces.strip() + "]");  // [hello]

        // Unicode whitespace -- only strip() handles this
        String unicodeSpaces = "\u2003 hello \u2003";  // Em spaces
        System.out.println("\ntrim():  [" + unicodeSpaces.trim() + "]");   // [ hello  ] -- em spaces remain!
        System.out.println("strip(): [" + unicodeSpaces.strip() + "]");    // [hello]

        // Non-breaking space from HTML  
        String nbspString = "\u00A0\u00A0data\u00A0\u00A0";
        System.out.println("\ntrim():  [" + nbspString.trim() + "]");   // [  data  ] -- nbsp not removed!
        System.out.println("strip(): [" + nbspString.strip() + "]");    // [data]

        // CJK ideographic space (common in Chinese/Japanese text)
        String cjkString = "\u3000Tokyo\u3000";
        System.out.println("\ntrim():  [" + cjkString.trim() + "]");    // [ Tokyo ] -- not removed!
        System.out.println("strip(): [" + cjkString.strip() + "]");     // [Tokyo]
    }
}

stripLeading() and stripTrailing()

Before Java 11, removing whitespace from only one side of a string required regex or manual character counting. Now it is a single method call:

public class StripDirectional {
    public static void main(String[] args) {
        String text = "   Hello, World!   ";

        System.out.println("Original:      [" + text + "]");
        System.out.println("strip():       [" + text.strip() + "]");
        System.out.println("stripLeading():[" + text.stripLeading() + "]");
        System.out.println("stripTrailing():[" + text.stripTrailing() + "]");
    }
}

// Output:
// Original:      [   Hello, World!   ]
// strip():       [Hello, World!]
// stripLeading():[Hello, World!   ]
// stripTrailing():[   Hello, World!]

Practical Use: Cleaning User Input

Here is a real-world scenario where strip() and its variants clean up messy input from different sources:

import java.util.List;

public class InputCleaner {

    /**
     * Cleans a CSV value that may have been copied from a spreadsheet.
     * Spreadsheets often add non-breaking spaces or other Unicode whitespace.
     */
    public static String cleanCsvField(String raw) {
        if (raw == null) return "";
        return raw.strip();  // Unicode-aware -- handles nbsp from Excel copy-paste
    }

    /**
     * Formats log lines by preserving leading indentation structure
     * but removing trailing whitespace (common in log files).
     */
    public static String cleanLogLine(String line) {
        return line.stripTrailing();
    }

    /**
     * Normalizes code indentation by removing leading whitespace
     * for a code formatter.
     */
    public static String removeIndentation(String codeLine) {
        return codeLine.stripLeading();
    }

    public static void main(String[] args) {
        // CSV field with non-breaking spaces from Excel
        String excelField = "\u00A0\u00A0John Smith\u00A0";
        System.out.println("Cleaned CSV: [" + cleanCsvField(excelField) + "]");

        // Log lines with trailing whitespace
        List logLines = List.of(
            "2024-01-15 INFO  Starting server...   ",
            "  2024-01-15 DEBUG   Connection pool initialized   ",
            "    2024-01-15 ERROR   Failed to bind port   "
        );
        System.out.println("\nCleaned log lines:");
        logLines.stream()
                .map(InputCleaner::cleanLogLine)
                .forEach(line -> System.out.println("[" + line + "]"));

        // Code with mixed indentation
        String code = "        return result;    ";
        System.out.println("\nStripped leading: [" + removeIndentation(code) + "]");
    }
}

// Output:
// Cleaned CSV: [John Smith]
//
// Cleaned log lines:
// [2024-01-15 INFO  Starting server...]
// [  2024-01-15 DEBUG   Connection pool initialized]
// [    2024-01-15 ERROR   Failed to bind port]
//
// Stripped leading: [return result;    ]

Rule of thumb: Always prefer strip() over trim() in new code. There is no performance penalty, and it handles edge cases that trim() silently ignores. The only reason to keep using trim() is backward compatibility with code that explicitly depends on its ASCII-only behavior.

4. lines()

The lines() method splits a string into a Stream<String> using line terminators (\n, \r, or \r\n). This is a huge improvement over the old approaches of splitting by regex or wrapping strings in a BufferedReader.

Why Not Just Use split()?

There are three good reasons to prefer lines() over split("\\n"):

  • Cross-platform line endings -- lines() handles \n (Unix), \r\n (Windows), and \r (old Mac) automatically. With split(), you need split("\\r?\\n|\\r").
  • Lazy evaluation -- lines() returns a Stream, so it processes lines on demand. If you only need the first 10 lines of a 10,000-line string, it does not split the entire string.
  • No trailing empty strings -- lines() does not produce a trailing empty string if the text ends with a newline. With split(), you need the -1 limit parameter to control this behavior, and even then the behavior is different.
public class LinesDemo {
    public static void main(String[] args) {
        String multiline = "Line 1\nLine 2\nLine 3\n";

        // Old way: split -- note the trailing empty string issue
        String[] splitResult = multiline.split("\n");
        System.out.println("split() count: " + splitResult.length);   // 3

        // split with -1 limit to include trailing empties
        String[] splitAll = multiline.split("\n", -1);
        System.out.println("split(-1) count: " + splitAll.length);    // 4 (includes trailing "")

        // Java 11: lines() -- clean, no trailing empty string
        long linesCount = multiline.lines().count();
        System.out.println("lines() count: " + linesCount);           // 3

        // lines() handles all line ending styles
        String mixedEndings = "Windows\r\nUnix\nOld Mac\rEnd";
        mixedEndings.lines().forEach(System.out::println);
    }
}

// Output:
// split() count: 3
// split(-1) count: 4
// lines() count: 3
// Windows
// Unix
// Old Mac
// End

Practical Use: Log File Processing

One of the best use cases for lines() is processing multiline text like log entries, CSV data, or configuration files:

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class LogProcessor {

    public static void main(String[] args) {
        String logContent = """
                2024-01-15 08:30:00 INFO  UserService - User login: john@example.com
                2024-01-15 08:30:05 DEBUG ConnectionPool - Acquired connection #42
                2024-01-15 08:30:10 ERROR PaymentService - Payment failed: insufficient funds
                2024-01-15 08:30:15 WARN  SecurityFilter - Rate limit approached: 192.168.1.100
                2024-01-15 08:30:20 INFO  UserService - User logout: john@example.com
                2024-01-15 08:30:25 ERROR PaymentService - Payment timeout after 30s
                2024-01-15 08:31:00 INFO  HealthCheck - System status: OK
                """;

        // Count lines by log level
        Map levelCounts = logContent.lines()
                .filter(line -> !line.isBlank())
                .collect(Collectors.groupingBy(
                    line -> line.split("\\s+")[2],  // Extract log level (3rd token)
                    Collectors.counting()
                ));
        System.out.println("Log level counts: " + levelCounts);

        // Extract only ERROR lines
        System.out.println("\nError lines:");
        logContent.lines()
                .filter(line -> line.contains("ERROR"))
                .forEach(line -> System.out.println("  " + line.strip()));

        // Get unique services mentioned
        List services = logContent.lines()
                .filter(line -> !line.isBlank())
                .map(line -> line.split("\\s+")[3])   // 4th token is the service name
                .distinct()
                .collect(Collectors.toList());
        System.out.println("\nServices: " + services);

        // Get first 3 lines (lazy -- does not process the rest)
        System.out.println("\nFirst 3 lines:");
        logContent.lines()
                .limit(3)
                .forEach(System.out::println);
    }
}

// Output:
// Log level counts: {WARN=1, ERROR=2, DEBUG=1, INFO=3}
//
// Error lines:
//   2024-01-15 08:30:10 ERROR PaymentService - Payment failed: insufficient funds
//   2024-01-15 08:30:25 ERROR PaymentService - Payment timeout after 30s
//
// Services: [UserService, ConnectionPool, PaymentService, SecurityFilter, HealthCheck]
//
// First 3 lines:
// 2024-01-15 08:30:00 INFO  UserService - User login: john@example.com
// 2024-01-15 08:30:05 DEBUG ConnectionPool - Acquired connection #42
// 2024-01-15 08:30:10 ERROR PaymentService - Payment failed: insufficient funds

Practical Use: Parsing Structured Text

The lines() method combined with stream operations makes parsing structured text (like CSV or configuration) clean and expressive:

import java.util.List;
import java.util.stream.Collectors;

public class CsvParser {

    record Employee(String name, String department, double salary) {}

    public static void main(String[] args) {
        String csvData = """
                Name,Department,Salary
                Alice Johnson,Engineering,95000
                Bob Smith,Marketing,72000
                Carol Williams,Engineering,105000
                David Brown,Marketing,68000
                Eve Davis,Engineering,110000
                """;

        // Parse CSV into Employee records (skip header)
        List employees = csvData.lines()
                .skip(1)                              // Skip header row
                .filter(line -> !line.isBlank())       // Skip blank lines
                .map(line -> line.split(","))
                .map(parts -> new Employee(
                    parts[0].strip(),
                    parts[1].strip(),
                    Double.parseDouble(parts[2].strip())
                ))
                .collect(Collectors.toList());

        // Average salary by department
        employees.stream()
                .collect(Collectors.groupingBy(
                    Employee::department,
                    Collectors.averagingDouble(Employee::salary)
                ))
                .forEach((dept, avg) ->
                    System.out.printf("%s: $%,.0f average%n", dept, avg));

        // Top earner
        employees.stream()
                .max((a, b) -> Double.compare(a.salary(), b.salary()))
                .ifPresent(e ->
                    System.out.printf("\nTop earner: %s ($%,.0f)%n", e.name(), e.salary()));
    }
}

// Output:
// Engineering: $103,333 average
// Marketing: $70,000 average
//
// Top earner: Eve Davis ($110,000)

5. repeat(int)

The repeat(int count) method returns a string whose value is the concatenation of the original string repeated count times. If count is 0, it returns an empty string. If count is 1, it returns the original string. If count is negative, it throws IllegalArgumentException.

Before Java 11

Repeating a string was surprisingly awkward before Java 11. Here are the workarounds developers used:

// Before Java 11: various hacky approaches

// Approach 1: StringBuilder loop
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
    sb.append("ha");
}
String laughOld = sb.toString();  // "hahahahaha"

// Approach 2: String.join hack (Java 8)
String stars = String.join("", java.util.Collections.nCopies(5, "*"));

// Approach 3: char array fill (only works for single characters)
char[] dashes = new char[50];
java.util.Arrays.fill(dashes, '-');
String line = new String(dashes);

// Java 11: simple, readable, one line
String laugh = "ha".repeat(5);       // "hahahahaha"
String border = "-".repeat(50);      // "--------------------------------------------------"
String indent = "  ".repeat(3);      // "      " (6 spaces)

Practical Use: Formatted Output and Reports

repeat() is incredibly useful for building formatted console output, ASCII art, and text reports:

import java.util.List;

public class ReportFormatter {

    record Product(String name, int quantity, double price) {
        double total() { return quantity * price; }
    }

    public static void main(String[] args) {
        List products = List.of(
            new Product("Laptop", 3, 999.99),
            new Product("Mouse", 15, 29.99),
            new Product("Keyboard", 10, 79.99),
            new Product("Monitor", 5, 449.99)
        );

        int width = 60;
        String border = "=".repeat(width);
        String thinBorder = "-".repeat(width);

        // Header
        System.out.println(border);
        System.out.println(centerText("INVENTORY REPORT", width));
        System.out.println(border);

        // Column headers
        System.out.printf("%-20s %8s %10s %12s%n",
                "Product", "Qty", "Price", "Total");
        System.out.println(thinBorder);

        // Data rows
        double grandTotal = 0;
        for (Product p : products) {
            System.out.printf("%-20s %8d %10.2f %12.2f%n",
                    p.name(), p.quantity(), p.price(), p.total());
            grandTotal += p.total();
        }

        // Footer
        System.out.println(thinBorder);
        System.out.printf("%-20s %8s %10s %12.2f%n",
                "GRAND TOTAL", "", "", grandTotal);
        System.out.println(border);
    }

    static String centerText(String text, int width) {
        int padding = (width - text.length()) / 2;
        return " ".repeat(Math.max(0, padding)) + text;
    }
}

// Output:
// ============================================================
//                       INVENTORY REPORT
// ============================================================
// Product                   Qty      Price        Total
// ------------------------------------------------------------
// Laptop                      3     999.99      2999.97
// Mouse                      15      29.99       449.85
// Keyboard                   10      79.99       799.90
// Monitor                     5     449.99      2249.95
// ------------------------------------------------------------
// GRAND TOTAL                                    6499.67
// ============================================================

Practical Use: Building Tree Structures

repeat() combined with depth tracking makes it easy to display hierarchical data:

import java.util.List;

public class TreePrinter {

    record TreeNode(String name, List children) {
        TreeNode(String name) { this(name, List.of()); }
    }

    public static void printTree(TreeNode node, int depth) {
        String indent = "  ".repeat(depth);
        String prefix = depth == 0 ? "" : "|" + "--".repeat(1) + " ";
        System.out.println(indent + prefix + node.name());

        for (TreeNode child : node.children()) {
            printTree(child, depth + 1);
        }
    }

    public static void main(String[] args) {
        TreeNode root = new TreeNode("src", List.of(
            new TreeNode("main", List.of(
                new TreeNode("java", List.of(
                    new TreeNode("com.example", List.of(
                        new TreeNode("Application.java"),
                        new TreeNode("config", List.of(
                            new TreeNode("AppConfig.java"),
                            new TreeNode("SecurityConfig.java")
                        )),
                        new TreeNode("service", List.of(
                            new TreeNode("UserService.java"),
                            new TreeNode("OrderService.java")
                        ))
                    ))
                )),
                new TreeNode("resources", List.of(
                    new TreeNode("application.properties")
                ))
            )),
            new TreeNode("test", List.of(
                new TreeNode("java")
            ))
        ));

        printTree(root, 0);
    }
}

// Output:
// src
//   |-- main
//     |-- java
//       |-- com.example
//         |-- Application.java
//         |-- config
//           |-- AppConfig.java
//           |-- SecurityConfig.java
//         |-- service
//           |-- UserService.java
//           |-- OrderService.java
//     |-- resources
//       |-- application.properties
//   |-- test
//     |-- java

Edge Cases

public class RepeatEdgeCases {
    public static void main(String[] args) {
        // repeat(0) returns empty string
        System.out.println("[" + "hello".repeat(0) + "]");  // []

        // repeat(1) returns the original string
        System.out.println("[" + "hello".repeat(1) + "]");  // [hello]

        // Empty string repeated is still empty
        System.out.println("[" + "".repeat(100) + "]");     // []

        // Negative count throws IllegalArgumentException
        try {
            "hello".repeat(-1);
        } catch (IllegalArgumentException e) {
            System.out.println("Negative count: " + e.getMessage());
        }

        // Very large repeat can cause OutOfMemoryError
        // "x".repeat(Integer.MAX_VALUE);  // OutOfMemoryError
    }
}

6. String Improvements for Null Safety

While not new methods on the String class itself, Java 11 also improved how strings work with null values in the broader ecosystem. Understanding these patterns helps you write safer string-handling code:

import java.util.Objects;

public class NullSafeStrings {
    public static void main(String[] args) {
        String nullStr = null;
        String emptyStr = "";
        String blankStr = "   ";
        String validStr = "Hello";

        // Pattern 1: Null-safe blank check
        System.out.println("--- Null-safe blank check ---");
        System.out.println(isNullOrBlank(nullStr));    // true
        System.out.println(isNullOrBlank(emptyStr));   // true
        System.out.println(isNullOrBlank(blankStr));   // true
        System.out.println(isNullOrBlank(validStr));   // false

        // Pattern 2: Objects.toString() with default
        System.out.println("\n--- Objects.toString() ---");
        System.out.println(Objects.toString(nullStr, "N/A"));   // N/A
        System.out.println(Objects.toString(validStr, "N/A"));  // Hello

        // Pattern 3: String.valueOf() -- never returns null
        System.out.println("\n--- String.valueOf() ---");
        System.out.println(String.valueOf(nullStr));    // "null" (the string literal)
        System.out.println(String.valueOf(12345));      // "12345"
        System.out.println(String.valueOf(true));       // "true"

        // Pattern 4: Combining new methods with null checks
        System.out.println("\n--- Combined pattern ---");
        String input = "   ";
        String result = (input != null && !input.isBlank())
                ? input.strip()
                : "default";
        System.out.println("Result: " + result);  // "default"
    }

    /**
     * Utility method: null-safe blank check.
     * Combines null check with Java 11's isBlank().
     */
    static boolean isNullOrBlank(String str) {
        return str == null || str.isBlank();
    }
}

7. Comparison Table -- All New String Methods

Here is a complete reference of every String method added in Java 11:

Method Signature Return Type Description Example
isBlank() isBlank() boolean Returns true if the string is empty or contains only whitespace (Unicode-aware) " ".isBlank() returns true
strip() strip() String Removes leading and trailing Unicode whitespace " hi ".strip() returns "hi"
stripLeading() stripLeading() String Removes only leading Unicode whitespace " hi ".stripLeading() returns "hi "
stripTrailing() stripTrailing() String Removes only trailing Unicode whitespace " hi ".stripTrailing() returns " hi"
lines() lines() Stream<String> Splits by line terminators (\n, \r\n, \r) into a lazy stream "a\nb".lines().count() returns 2
repeat(int) repeat(int count) String Repeats the string count times "ha".repeat(3) returns "hahaha"

8. Practical Examples -- Putting It All Together

Let us combine all the new methods in real-world scenarios that you will encounter in production Java applications.

Example 1: Configuration File Parser

This example parses a properties-style configuration, handling comments, blank lines, and messy whitespace:

import java.util.LinkedHashMap;
import java.util.Map;

public class ConfigParser {

    public static Map parse(String configContent) {
        Map config = new LinkedHashMap<>();

        configContent.lines()                              // Split into stream of lines
                .map(String::strip)                         // Remove leading/trailing whitespace
                .filter(line -> !line.isBlank())             // Skip blank lines
                .filter(line -> !line.startsWith("#"))       // Skip comments
                .filter(line -> line.contains("="))          // Must be key=value
                .forEach(line -> {
                    int eq = line.indexOf('=');
                    String key = line.substring(0, eq).strip();
                    String value = line.substring(eq + 1).strip();
                    config.put(key, value);
                });

        return config;
    }

    public static String toConfigString(Map config) {
        StringBuilder sb = new StringBuilder();
        sb.append("# Generated Configuration\n");
        sb.append("#").append("=".repeat(40)).append("\n\n");

        config.forEach((key, value) ->
            sb.append(key).append(" = ").append(value).append("\n"));

        return sb.toString();
    }

    public static void main(String[] args) {
        String configFile = """
                # Database Configuration
                # ======================

                db.host     =   localhost
                db.port     =   5432
                db.name     =   myapp

                # Connection Pool
                pool.size   =   10
                pool.timeout =  30000

                # Feature Flags
                feature.darkMode = true
                feature.beta     = false
                """;

        Map config = parse(configFile);

        // Display parsed configuration
        System.out.println("Parsed " + config.size() + " properties:");
        String separator = "-".repeat(45);
        System.out.println(separator);
        config.forEach((key, value) ->
            System.out.printf("  %-20s = %s%n", key, value));
        System.out.println(separator);

        // Regenerate config file
        System.out.println("\nRegenerated config:");
        System.out.println(toConfigString(config));
    }
}

// Output:
// Parsed 7 properties:
// ---------------------------------------------
//   db.host              = localhost
//   db.port              = 5432
//   db.name              = myapp
//   pool.size            = 10
//   pool.timeout         = 30000
//   feature.darkMode     = true
//   feature.beta         = false
// ---------------------------------------------
//
// Regenerated config:
// # Generated Configuration
// #========================================
//
// db.host = localhost
// db.port = 5432
// db.name = myapp
// pool.size = 10
// pool.timeout = 30000
// feature.darkMode = true
// feature.beta = false

Example 2: Text Report Generator

This example uses repeat(), strip(), and lines() together to generate a formatted text report:

import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class SalesReport {

    record Sale(String region, String product, double amount) {}

    public static void main(String[] args) {
        List sales = List.of(
            new Sale("North", "Laptop", 2999.97),
            new Sale("North", "Phone", 1599.98),
            new Sale("South", "Laptop", 999.99),
            new Sale("South", "Phone", 3199.96),
            new Sale("South", "Tablet", 1499.97),
            new Sale("East", "Laptop", 5999.94),
            new Sale("East", "Phone", 799.99),
            new Sale("West", "Tablet", 2999.94)
        );

        printReport(sales);
    }

    static void printReport(List sales) {
        int width = 55;
        String doubleLine = "=".repeat(width);
        String singleLine = "-".repeat(width);

        // Title
        System.out.println("\n" + doubleLine);
        System.out.println(center("QUARTERLY SALES REPORT", width));
        System.out.println(center("Q4 2024", width));
        System.out.println(doubleLine);

        // Group by region
        Map> byRegion = sales.stream()
                .collect(Collectors.groupingBy(Sale::region));

        double grandTotal = 0;

        for (var entry : byRegion.entrySet()) {
            String region = entry.getKey();
            List regionSales = entry.getValue();
            double regionTotal = regionSales.stream()
                    .mapToDouble(Sale::amount).sum();
            grandTotal += regionTotal;

            System.out.println("\n" + " ".repeat(2) + region + " Region");
            System.out.println(" ".repeat(2) + "-".repeat(region.length() + 7));

            for (Sale sale : regionSales) {
                System.out.printf("  %-20s $%,10.2f%n",
                        sale.product(), sale.amount());
            }
            System.out.printf("  %-20s $%,10.2f%n",
                    "Subtotal:", regionTotal);
        }

        System.out.println("\n" + doubleLine);
        System.out.printf("  %-20s $%,10.2f%n", "GRAND TOTAL:", grandTotal);
        System.out.println(doubleLine + "\n");
    }

    static String center(String text, int width) {
        int pad = (width - text.length()) / 2;
        return " ".repeat(Math.max(0, pad)) + text;
    }
}

Example 3: Input Sanitizer for Web Applications

This example demonstrates using the new String methods for a realistic web input sanitization pipeline:

import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.stream.Collectors;

public class InputSanitizer {

    record ValidationResult(boolean valid, String cleanValue, List errors) {}

    public static ValidationResult sanitizeAndValidate(String fieldName, String rawInput,
                                                        int minLength, int maxLength,
                                                        boolean required) {
        List errors = new java.util.ArrayList<>();

        // Step 1: Handle null
        if (rawInput == null) {
            if (required) {
                errors.add(fieldName + " is required");
            }
            return new ValidationResult(!required, "", errors);
        }

        // Step 2: strip() removes Unicode whitespace (better than trim())
        String cleaned = rawInput.strip();

        // Step 3: isBlank() detects whitespace-only submissions
        if (cleaned.isBlank()) {
            if (required) {
                errors.add(fieldName + " cannot be blank");
            }
            return new ValidationResult(!required, "", errors);
        }

        // Step 4: Normalize internal whitespace using lines() + joining
        // This handles embedded newlines, tabs, and multiple spaces
        cleaned = cleaned.lines()
                .map(String::strip)
                .filter(line -> !line.isBlank())
                .collect(Collectors.joining(" "));

        // Step 5: Length validation
        if (cleaned.length() < minLength) {
            errors.add(fieldName + " must be at least " + minLength + " characters");
        }
        if (cleaned.length() > maxLength) {
            errors.add(fieldName + " must be at most " + maxLength + " characters");
            cleaned = cleaned.substring(0, maxLength);
        }

        return new ValidationResult(errors.isEmpty(), cleaned, errors);
    }

    public static void main(String[] args) {
        // Simulate messy form submissions
        Map formInputs = new LinkedHashMap<>();
        formInputs.put("username", "  john_doe  ");
        formInputs.put("email", "\u00A0\u00A0user@example.com\u00A0");  // Non-breaking spaces
        formInputs.put("bio", "  Hello!\n\n  I am a\n   developer.  \n\n ");
        formInputs.put("name", "   \t\n   ");     // Whitespace only
        formInputs.put("website", null);            // Missing field

        System.out.println("=".repeat(60));
        System.out.println("Input Sanitization Results");
        System.out.println("=".repeat(60));

        formInputs.forEach((field, raw) -> {
            boolean required = !field.equals("website");
            var result = sanitizeAndValidate(field, raw, 2, 200, required);
            System.out.printf("\nField: %s%n", field);
            System.out.printf("  Raw:    [%s]%n", raw);
            System.out.printf("  Clean:  [%s]%n", result.cleanValue());
            System.out.printf("  Valid:  %s%n", result.valid());
            if (!result.errors().isEmpty()) {
                result.errors().forEach(e -> System.out.printf("  Error:  %s%n", e));
            }
        });
    }
}

// Output:
// ============================================================
// Input Sanitization Results
// ============================================================
//
// Field: username
//   Raw:    [  john_doe  ]
//   Clean:  [john_doe]
//   Valid:  true
//
// Field: email
//   Raw:    [  user@example.com ]
//   Clean:  [user@example.com]
//   Valid:  true
//
// Field: bio
//   Raw:    [  Hello!
//
//   I am a
//    developer.
//
//  ]
//   Clean:  [Hello! I am a developer.]
//   Valid:  true
//
// Field: name
//   Raw:    [
//    ]
//   Clean:  []
//   Valid:  false
//   Error:  name cannot be blank
//
// Field: website
//   Raw:    [null]
//   Clean:  []
//   Valid:  true

9. Best Practices

Here are the key guidelines for using Java 11's new String methods effectively in production code:

Always Prefer strip() Over trim()

In new code, always use strip() instead of trim(). It handles all Unicode whitespace, has no performance penalty, and prevents subtle bugs when processing text from international sources, web forms (where browsers may insert &nbsp;), or files saved with different encodings.

Use isBlank() for Validation, isEmpty() for Protocol Checks

Use isBlank() when you need to know whether a string has meaningful content (form validation, business logic). Use isEmpty() when you need to know whether a string is literally empty (protocol handling, serialization, exact-length checks).

Use lines() Instead of split() for Multiline Text

Whenever you are splitting a string by line breaks, prefer lines() over split("\\n"). It handles all line ending formats, is lazy (better for large strings), and has cleaner semantics around trailing newlines.

Combine Methods in Pipelines

The new methods work beautifully together in stream pipelines:

// Common pattern: parse multiline input, clean each line, skip blanks
String rawInput = "  line one  \n\n  line two  \n   \n  line three  \n";

List cleanedLines = rawInput.lines()     // Split into stream
        .map(String::strip)                       // Clean each line
        .filter(line -> !line.isBlank())           // Remove blank lines
        .collect(Collectors.toList());

// Result: ["line one", "line two", "line three"]

Use repeat() for Formatting, Not Concatenation Loops

Replace any loop that builds a repeated string pattern with repeat(). It is more readable, and internally it uses an optimized algorithm (not simple concatenation).

Do/Don't Summary

Do Don't
input.isBlank() input.trim().isEmpty()
input.strip() input.trim() (in new code)
text.lines().filter(...) text.split("\\n") (for multiline processing)
"-".repeat(50) Loop with StringBuilder.append("-")
line.stripTrailing() line.replaceAll("\\s+$", "")
Null-check before calling isBlank() Calling isBlank() on a potentially null reference

10. Quick Reference

Method Signature Returns Key Behavior Replaces
isBlank() boolean isBlank() boolean True if empty or all whitespace (Unicode-aware) trim().isEmpty(), StringUtils.isBlank()
strip() String strip() String Removes leading + trailing Unicode whitespace trim()
stripLeading() String stripLeading() String Removes leading Unicode whitespace only Regex replaceAll("^\\s+", "")
stripTrailing() String stripTrailing() String Removes trailing Unicode whitespace only Regex replaceAll("\\s+$", "")
lines() Stream<String> lines() Stream<String> Lazy split by \n, \r\n, or \r split("\\r?\\n"), BufferedReader.lines()
repeat(int) String repeat(int count) String Repeats string count times. 0 returns "". Negative throws IllegalArgumentException StringUtils.repeat(), StringBuilder loop

These six methods may seem small individually, but they collectively eliminate a significant amount of boilerplate code. If you are still using Apache Commons Lang's StringUtils solely for isBlank() and repeat(), you can likely remove that dependency now. Java 11's built-in methods are well-tested, well-optimized, and available everywhere.




Subscribe To Our Newsletter
You will receive our latest post and tutorial.
Thank you for subscribing!

required
required


Leave a Reply

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