How do I use String.indent() to format output text?

The String.indent(int n) method in Java is a useful tool for formatting multi-line strings by adjusting the amount of leading space (indentation) on each line. Introduced in Java 12, it performs the following for the specified n indentation:

  1. Positive n: Adds n spaces to the beginning of each line in the string.
  2. Negative n: Removes up to n leading spaces from each line.
  3. Zero n: Leaves the string unchanged (but trims blank lines at the start/end).

This is particularly useful for formatting text in structured views, like logs, JSON, XML, or pretty-printed output.

Example: Using String.indent to Format Output

Here is how you can use the String.indent() method:

package org.kodejava.lang;

public class StringIndentExample {
    public static void main(String[] args) {
        // Original multi-line string (no indentation)
        String text = "Line 1\nLine 2\nLine 3";

        // Adding an indent of 4 spaces
        String indented = text.indent(4);
        System.out.println("Indented by 4 spaces:\n" + indented);

        // Removing 2 leading spaces (negative indent)
        String negativeIndent = indented.indent(-2);
        System.out.println("Indented with -2 (removing spaces):\n" + negativeIndent);

        // Applying indent to empty lines
        String withEmptyLines = "Line 1\n\nLine 3";
        String indentedWithEmptyLines = withEmptyLines.indent(4);
        System.out.println("Handling empty lines:\n" + indentedWithEmptyLines);
    }
}

Explanation of Code:

  1. Adding Indentation: The first call to .indent(4) adds 4 spaces to each line.
  2. Removing Indentation: .indent(-2) deducts 2 spaces from the start of each line (if spaces exist).
  3. Empty Lines: When dealing with blank lines, indent maintains the level of indentation for such lines, adding or removing spaces as needed.

Output:

Indented by 4 spaces:
    Line 1
    Line 2
    Line 3

Indented with -2 (removing spaces):
  Line 1
  Line 2
  Line 3

Handling empty lines:
    Line 1

    Line 3

Notes:

  • Empty lines and their indentation are preserved.
  • Lines with no leading spaces are unaffected by negative indentation.
  • Leading and trailing full blank lines are removed.

When to Use:

  • To format multiline strings cleanly.
  • To produce human-readable or formatted output in tools or commands (e.g., logging, text processing).

How do I use String.strip(), isBlank() and lines() methods?

The String class in Java provides the methods strip(), isBlank(), and lines(), which were introduced in Java 11. These methods are useful for managing whitespaces, checking for blank strings, and processing multi-line strings.

1. strip()

The strip() method removes leading and trailing whitespaces from a string. Unlike trim(), it uses Unicode-aware whitespace handling, making it more robust for international characters.

Example:

public class StringStripExample {
    public static void main(String[] args) {
        String str = " \u2009Hello World  "; // Unicode whitespace
        System.out.println(str.strip());      // Outputs: "Hello World"
        System.out.println(str.stripLeading()); // Removes leading spaces: "Hello World  "
        System.out.println(str.stripTrailing()); // Removes trailing spaces: " \u2009Hello World"
    }
}

Key Point:

  • strip() differs from trim() in that it removes all Unicode whitespace, not just ASCII spaces.

2. isBlank()

The isBlank() method checks whether a string is empty or contains only whitespaces. This includes Unicode whitespace and helps to quickly validate string content.

Example:

public class StringIsBlankExample {
    public static void main(String[] args) {
        String empty = "   "; // Contains whitespaces
        System.out.println(empty.isBlank()); // Outputs: true

        String nonBlank = "Hello";
        System.out.println(nonBlank.isBlank()); // Outputs: false

        String unicodeSpace = "\u2009"; // Unicode whitespace
        System.out.println(unicodeSpace.isBlank()); // Outputs: true
    }
}

Key Point:

  • isBlank() is stronger than isEmpty() because it treats strings with only whitespace as blank, whereas isEmpty() considers only an empty string ("").

3. lines()

The lines() method breaks a multi-line string into a stream of lines, using the platform’s line terminator (e.g., \n or \r\n) to split the string.

Example:

public class StringLinesExample {
    public static void main(String[] args) {
        String multiLineString = "Hello\nWorld\nJava 11";

        // Use 'lines()' to split the multi-line string
        multiLineString.lines().forEach(System.out::println);

        // Output:
        // Hello
        // World
        // Java 11
    }
}

Key Points:

  • lines() splits the string into lines and returns a Stream<String>.
  • It can be combined with stream operations like filter(), map(), and forEach().

Combining Methods for Common Use Cases

Here’s how you can combine them:

Trim and Check Blank:

public class StringExample {
    public static void main(String[] args) {
        String input = "   ";
        if (input.strip().isBlank()) {
            System.out.println("Input is blank or empty!");
        } else {
            System.out.println("Input: " + input.strip());
        }
    }
}

Processing Multi-Line Strings:

public class MultiLineExample {
    public static void main(String[] args) {
        String text = "  Line 1  \n  Line 2  \n  Line 3  ";

        text.lines()
            .map(String::strip) // Clean up each line
            .forEach(System.out::println);

        // Output:
        // Line 1
        // Line 2
        // Line 3
    }
}

Summary of Functionalities:

  • strip(): Removes leading/trailing Unicode whitespace.
  • isBlank(): Checks if a string is empty or only whitespace.
  • lines(): Processes multi-line strings by splitting them into lines.

How do I use records for immutable data carriers?

In Java, records are special classes introduced in Java 14 (as a preview feature) and became a standard feature in Java 16. They are specifically designed to model immutable data carriers with minimal boilerplate code. A record in Java automatically generates boilerplate code such as constructors, getters, toString, equals, and hashCode methods, making it a great choice for representing immutable data.

Here’s how you can use records as immutable data carriers:

1. Define a Record

To define a record, use the record keyword. A record automatically generates:

  • A constructor.
  • Accessors (getters) for all fields.
  • toString(), equals(), and hashCode() based on the fields.

Example:

public record User(String name, int age) {}

This creates an immutable User record class with:

  • Fields: name and age
  • Automatically provides:
    • Constructor: User(String name, int age)
    • name() and age() as accessors for the fields
    • A meaningful `toString(), method
    • Implementations of equals() and hashCode()

2. Using a Record

Once defined, you can use the record class as follows:

public class Main {
    public static void main(String[] args) {
        // Creating and using a User record
        User user = new User("Alice", 30);

        // Access fields (no need for `getName()` or `getAge()`)
        System.out.println(user.name());  // Alice
        System.out.println(user.age());  // 30

        // Automatic toString()
        System.out.println(user);        // User[name=Alice, age=30]

        // Automatic equals() and hashCode()
        User anotherUser = new User("Alice", 30);
        System.out.println(user.equals(anotherUser)); // true
    }
}

3. Immutability

Records are immutable by default:

  • The fields of a record are implicitly private final.
  • Once an object is created, its fields cannot be changed.
  • Records make it easier to declare immutable objects compared to manually writing getters and using final.

4. Customizing a Record

While records are concise, you can still customize them if needed:

  • Add extra methods.
  • Implement additional interfaces.
  • Preprocess fields in the constructor or validate input.

Example:

public record User(String name, int age) {
   public User {
       // Compact constructor for validation
       if (age < 0) {
           throw new IllegalArgumentException("Age cannot be negative");
       }
   }

   // Additional method
   public String greeting() {
       return "Hello, " + name + "!";
   }
}

Usage:

User user = new User("Alice", 30);
System.out.println(user.greeting()); // Hello, Alice!

5. Limitations of Records

While records are extremely powerful for data carrier use cases, they are not suitable for every situation:

  1. Records cannot extend other classes (but they can implement interfaces).
  2. Fields in records cannot be modified after object creation.
  3. Records are designed primarily for data aggregation and are not meant for behavior-heavy classes.

6. Common Use Cases

  • Representing DTOs (Data Transfer Objects).
  • Creating immutable models for APIs.
  • Storing simple structured data (e.g., key-value pairs, coordinates).

Summary

To use records for immutable data carriers:

  1. Define them with record. The syntax automatically generates boilerplate code.
  2. Use the generated constructors and field accessors (name() instead of getName()).
  3. Optionally, customize validation or add methods if you need additional behavior.

By leveraging records, you simplify your code, reduce boilerplate, and ensure your data class is immutable by design!

How do I use text blocks to write cleaner multi-line strings?

Text blocks in Java, introduced in Java 15, provide a way to declare multi-line strings in a cleaner and more readable format compared to traditional string concatenation or line breaks (\n). They are enclosed using triple double quotes (""") and support multi-line content without requiring explicit escape characters for formatting.

Key Features of Text Blocks

  1. Multi-line Flexibility: No need for manual concatenation or escape characters, as everything is written as-is.
    String message = """
            Hello,
            This is a multi-line message.
            Regards,
            AI Assistant
            """;
    
  2. Improved Readability: Code looks cleaner, especially for complex templates like JSON, XML, or SQL.

  3. Whitespace Control: Leading and trailing whitespace can be managed easily without affecting the structure.
  4. String Formatting: Text blocks can use the formatted() method for dynamic content injection, similar to String.format.

Examples of Usage for Clean Code

1. Working with JSON or HTML Templates

Instead of concatenating strings for JSON, text blocks help preserve structure:

String jsonTemplate = """
       {
           "username": "%s",
           "email": "%s"
       }
       """;
String json = jsonTemplate.formatted("foo", "[email protected]");
System.out.println(json);

2. Complex SQL Queries

Writing SQL in code often spreads across multiple lines. With text blocks:

String query = """
       SELECT id, username, email
       FROM users
       WHERE email = '%s'
       ORDER BY username;
       """.formatted("[email protected]");

This improves readability compared to a mix of + or \n.

3. HTML Documents

String html = """
       <html>
           <head>
               <title>%s</title>
           </head>
           <body>
               <h1>Welcome, %s!</h1>
           </body>
       </html>
       """.formatted("My Page", "Visitor");

Additional Tips

  1. Formatting Whitespace Correctly: Text blocks remove unnecessary leading indentation (common with code). However, if needed, you can align whitespaces manually:
    String alignedBlock = """
            A line of text
            More text with consistent indentation
            """;
    
  2. Escape Sequences: Although text in text blocks is written as-is, you can still use escape sequences where necessary:
    String code = """
           public static void main(String[] args) {
               System.out.println("Hello, World!");
           }
           """;
    
  3. Dynamic Injection: Combine text blocks with .formatted() for cleaner parameterized content:
    String greeting = """
            Dear %s,
    
            Thank you for your email (%s). 
            We will get back to you shortly.
            """.formatted("John", "[email protected]");
    

Benefits Over Traditional Strings

  • Enhanced readability for configurations or templates.
  • Less boilerplate—no need for multiple +, \n, or explicit escapes.
  • Ideal for structured data like SQL, HTML, JSON, etc.

How do I format strings with String::formatted?

The String::formatted method in Java is a concise way to format strings using placeholders, similar to the String::format method but with a cleaner syntax. It was introduced in Java 15, and it allows you to replace placeholders in a string with specified values.

The syntax of the formatted method is straightforward:

String formattedString = "Your name is %s and your age is %d".formatted("John", 25);

Here’s how it works:

  1. The placeholders in the string (such as %s, %d) follow the same format specifiers as used in String.format().
    • %s: Formats strings.
    • %d: Formats integers.
    • %f: Formats floating-point numbers.
    • And so on.
  2. The formatted() method takes the format arguments in the exact order of appearance of the placeholders.

Example Usage:

Here are a few examples illustrating the different use cases:

Example 1: Format a simple text

String result = "Hello, %s!".formatted("Alice");
System.out.println(result);
// Output: Hello, Alice!

Example 2: Combine multiple placeholders

String summary = "Product: %s, Quantity: %d, Price: $%.2f".formatted("Widget", 10, 9.99);
System.out.println(summary);
// Output: Product: Widget, Quantity: 10, Price: $9.99

Example 3: Use with text blocks

In text blocks, you can similarly use the formatted method to insert values dynamically:

String jsonTemplate = """
    {
        "name": "%s",
        "age": %d,
        "email": "%s"
    }
    """;

String json = jsonTemplate.formatted("John", 30, "[email protected]");
System.out.println(json);
// Output:
// {
//     "name": "John",
//     "age": 30,
//     "email": "[email protected]"
// }

Key Points:

  • The formatted method is directly callable on the string you want to format, making the code cleaner.
  • It has the same capabilities as String.format, so it supports all format specifiers.
  • formatted works particularly well with text blocks for clean and readable multi-line string formatting.

This method is helpful for writing concise and fluent code without the need to call String.format explicitly.