☕ Java

String Methods

The String class provides over 60 methods covering character inspection, searching, comparison, transformation, splitting, joining, formatting, and encoding. Knowing these methods well eliminates the need to write manual character-by-character loops for common string operations and prevents the common mistake of reimplementing logic that the library already provides efficiently. This entry covers every major method category with precise semantics, edge cases, performance characteristics, and practical examples — from the fundamental length() and charAt() through to the modern Java 11+ additions like strip(), isBlank(), lines(), and repeat().

Inspection and Access Methods

The inspection methods let you examine a String's content: its length, individual characters, whether it is empty or blank, and whether it starts or ends with specific sequences. These are the most fundamental String operations and appear in virtually every Java program. length() returns the number of char values in the string. For strings containing only Basic Multilingual Plane characters (the vast majority), this equals the number of visible characters. For strings containing supplementary characters (emoji, some Asian characters), length() may exceed the visible character count because supplementary characters require two char values (a surrogate pair). The codePointCount() method gives the true number of Unicode characters. charAt(index) returns the char at the given index, throwing StringIndexOutOfBoundsException for out-of-range indices. For robust character processing that handles supplementary characters correctly, use codePointAt() instead. isEmpty() tests whether length() == 0 — it does not check for whitespace. isBlank() (Java 11+) tests whether the string contains only whitespace after Unicode whitespace stripping — it is empty() for the purposes of "contains no meaningful content."
Java
// ── length() ──────────────────────────────────────────────────────────
"Hello".length()    // 5
"".length()         // 0
"Hello 👋".length() // 8 (surrogate pair for 👋)

// ── isEmpty() vs isBlank() ────────────────────────────────────────────
"".isEmpty()        // true
" ".isEmpty()       // false — has a space character
"".isBlank()        // true
" ".isBlank()       // true — only whitespace
"  	
  ".isBlank()// true — all whitespace
"a".isBlank()       // false

// ── charAt() ─────────────────────────────────────────────────────────
String s = "Hello";
s.charAt(0)  // 'H'
s.charAt(4)  // 'o'
// s.charAt(5)  // StringIndexOutOfBoundsException

// ── Iterating characters ──────────────────────────────────────────────
String word = "Hello";
// Classic style:
for (int i = 0; i < word.length(); i++) {
    char c = word.charAt(i);
}
// Stream of chars (as ints — use (char) cast):
word.chars().forEach(c -> System.out.print((char) c));

// ── startsWith() and endsWith() ──────────────────────────────────────
"Hello World".startsWith("Hello")  // true
"Hello World".startsWith("World")  // false
"Hello World".startsWith("World", 6) // true — start checking from index 6
"Hello World".endsWith("World")    // true
"Hello World".endsWith("Hello")    // false

// ── contains() ───────────────────────────────────────────────────────
"Hello World".contains("lo Wo")  // true
"Hello World".contains("xyz")    // false
"Hello World".contains("")       // true — empty string is always contained

// ── matches() — regex test ────────────────────────────────────────────
"hello123".matches("[a-z]+\d+")  // true — all lowercase then digits
"Hello123".matches("[a-z]+\d+")  // false — H is uppercase
"12345".matches("\d+")           // true — all digits

// ── compareTo() and compareToIgnoreCase() ─────────────────────────────
"apple".compareTo("banana")   // negative — apple < banana
"banana".compareTo("apple")   // positive — banana > apple
"apple".compareTo("apple")    // 0 — equal
"Apple".compareTo("apple")    // negative — 'A' (65) < 'a' (97)
"Apple".compareToIgnoreCase("apple")  // 0 — equal ignoring case

Search Methods — Finding and Locating

The search methods find occurrences of characters and substrings within a string. indexOf() and lastIndexOf() return the position of the first and last occurrence respectively, or -1 if not found. Both have overloads to start searching from a specific index, enabling sequential searches and iteration over all occurrences. The from-index parameter for lastIndexOf() searches backward starting from that position. These methods perform naive substring search with O(n×m) worst-case complexity, where n is the string length and m is the pattern length. For large strings with many potential matches, this can be slow. When searching a large string for many different patterns, or when performance is critical, consider using a regex Pattern compiled once and reused, or a more sophisticated string search algorithm via external libraries. Regular expression support in String is accessed through matches(), replaceAll(), replaceFirst(), and split(). These methods compile the pattern on each call. For performance when the same pattern is used repeatedly, compile it once with Pattern.compile() and reuse the Pattern object.
Java
// ── indexOf() — first occurrence ────────────────────────────────────
"Hello World".indexOf('o')      // 4 — first 'o'
"Hello World".indexOf('o', 5)   // 7 — first 'o' starting from index 5
"Hello World".indexOf("World")  // 6 — start of first "World"
"Hello World".indexOf("xyz")    // -1 — not found
"Hello World".indexOf("")       // 0 — empty string found at start

// ── lastIndexOf() — last occurrence ──────────────────────────────────
"Hello World".lastIndexOf('o')     // 7 — last 'o'
"Hello World".lastIndexOf('o', 6)  // 4 — last 'o' at or before index 6
"abcabc".lastIndexOf("bc")         // 4 — last occurrence of "bc"
"Hello World".lastIndexOf("xyz")   // -1 — not found

// ── Finding ALL occurrences ───────────────────────────────────────────
public static List<Integer> findAll(String text, String pattern) {
    List<Integer> positions = new ArrayList<>();
    int idx = 0;
    while ((idx = text.indexOf(pattern, idx)) != -1) {
        positions.add(idx);
        idx += pattern.length();  // advance past current match
    }
    return positions;
}

findAll("abcabcabc", "bc")  // [1, 4, 7]
findAll("aaaa", "aa")       // [0, 2] — non-overlapping

// ── indexOf vs contains ───────────────────────────────────────────────
// contains() is implemented as: indexOf(s) >= 0
// Use contains() when you only need to know IF it's there
// Use indexOf() when you need to know WHERE it is

"Hello World".contains("World")   // true — simpler when position not needed
int pos = "Hello World".indexOf("World");  // 6 — when position matters

// ── Regular expression search ─────────────────────────────────────────
"hello123world".matches(".*\d+.*")    // true — contains digits
"hello".matches(".*\d+.*")           // false

// For repeated searches, compile the pattern once:
Pattern digitPattern = Pattern.compile("\d+");
Matcher m = digitPattern.matcher("hello 42 world 99");
while (m.find()) {
    System.out.println("Found: " + m.group() + " at " + m.start());
}
// Found: 42 at 6
// Found: 99 at 15

Transformation Methods

Transformation methods return new String objects with the content modified in the specified way. Every transformation creates a new object — the original is never changed. This is the most important fact to remember when calling any method that sounds like it modifies the string. The whitespace-handling methods have important semantics differences. trim() removes characters with code point <= U+0020 (space), which covers ASCII control characters and the space character but not Unicode whitespace like non-breaking space (U+00A0), thin space (U+2009), or zero-width space (U+200B). strip() (Java 11+) uses the Unicode definition of whitespace and correctly handles all Unicode space characters. In modern Java, strip() should be preferred over trim(). replace() has two forms: replace(char, char) replaces all occurrences of one character with another; replace(CharSequence, CharSequence) replaces all occurrences of one substring with another. Both replace ALL occurrences. For replacing only the first occurrence, use replaceFirst(). For regex-based replacement, use replaceAll(regex, replacement) or replaceFirst(regex, replacement). Note that replaceAll() and replaceFirst() take a regex as the first argument — special regex characters like . * + ? ( ) [ ] { } ^ $ | must be escaped if you want literal replacement.
Java
// ── Case conversion ──────────────────────────────────────────────────
"Hello World".toUpperCase()         // "HELLO WORLD"
"Hello World".toLowerCase()         // "hello world"
"Hello World".toUpperCase(Locale.TURKISH)  // locale-aware (i→İ in Turkish)
"HELLO".toLowerCase(Locale.ROOT)    // "hello" — locale-independent (safe for IDs)

// ── Whitespace removal ────────────────────────────────────────────────
"  Hello  ".trim()          // "Hello" — ASCII whitespace only
"  Hello  ".strip()         // "Hello" — Unicode whitespace (Java 11+)
"  Hello  ".stripLeading()  // "Hello  " — remove leading only
"  Hello  ".stripTrailing() // "  Hello" — remove trailing only

// Non-ASCII whitespace: trim() vs strip()
" Hello ".trim()   // " Hello " — NO removal (U+00A0 > U+0020)
" Hello ".strip()  // "Hello" — Unicode-aware removal

// ── Replace ───────────────────────────────────────────────────────────
"Hello World".replace('l', 'r')          // "Herro Worrd"char replacement
"Hello World".replace("World", "Java")   // "Hello Java" — substring replacement
"aabbcc".replace("b", "X")              // "aaXXcc" — ALL occurrences
"aabbcc".replaceFirst("b", "X")         // "aaXbcc" — first only

// replaceAll uses REGEX — must escape special chars:
"price: $42.99".replaceAll("\$", "£")  // "price: £42.99" ($ escaped)
"a.b.c".replaceAll("\.", "-")          // "a-b-c" (. escaped)
"hello123".replaceAll("\d+", "NUM")    // "helloNUM" (regex digit class)

// For literal replacement with no regex: use replace() not replaceAll()
"a.b.c".replace(".", "-")              // "a-b-c" — literal, no escaping needed

// ── intern() and valueOf() ────────────────────────────────────────────
String s = String.valueOf(42);          // "42"
String s2 = String.valueOf(3.14);       // "3.14"
String s3 = String.valueOf(true);       // "true"
String s4 = String.valueOf((Object)null); // "null" — not NPE

// ── repeat() (Java 11+) ───────────────────────────────────────────────
"ab".repeat(4)   // "abababab"
"-".repeat(20)   // "--------------------"
"".repeat(100)   // "" — empty string repeated

// ── concat() vs + ─────────────────────────────────────────────────────
"Hello".concat(" World")  // "Hello World" — creates new String
// Equivalent to "Hello" + " World"
// concat() is slightly faster for two strings; + is cleaner for multiple

Split, Join, and Format Methods

The split(), join(), and format() methods handle structured string manipulation — breaking strings into arrays, assembling arrays into strings, and formatting values into strings. These three capabilities cover the bulk of string processing needed in web applications, data processing, and user interfaces. split() divides a string into an array of substrings using a regex delimiter. It has an optional limit parameter: a positive limit caps the number of splits, keeping the remainder of the string in the last element; a negative limit keeps trailing empty strings; zero (the default) discards trailing empty strings. The regex nature of the delimiter means special characters must be escaped — split(".") splits into individual characters (. matches any char in regex), not by literal dot. Use split("\."); or Pattern.quote(".") for literal dot splitting. String.format() produces formatted strings using printf-style format specifiers. It is powerful but has two caveats: it uses the default locale for number formatting (which can produce unexpected results for decimal separators in European locales) and it is slower than StringBuilder for simple concatenation. For performance-critical logging, avoid format() in hot paths; instead use parameterised logging (log.debug("value={}", x)) which defers string construction until the log level is enabled.
Java
// ── split() ──────────────────────────────────────────────────────────
"a,b,c,d".split(",")           // ["a", "b", "c", "d"]
"a,,b,,c".split(",")           // ["a", "", "b", "", "c"] — empty strings kept
"a,,b,,".split(",")            // ["a", "", "b"] — trailing empties DROPPED
"a,,b,,".split(",", -1)        // ["a", "", "b", "", ""] — keep trailing empties
"a,b,c,d".split(",", 2)        // ["a", "b,c,d"] — limit 2: only first split

// GOTCHA: split uses REGEX — escape special chars
"a.b.c".split("\.")           // ["a", "b", "c"] — escaped dot
"a.b.c".split(".")             // [] — "." matches any char, splits everything

// Splitting on whitespace — convenient patterns:
"  hello   world  ".split("\s+")      // ["", "hello", "world"]
"  hello   world  ".strip().split("\s+") // ["hello", "world"] — strip first

// ── String.join() ────────────────────────────────────────────────────
String.join(", ", "Alice", "Bob", "Carol")   // "Alice, Bob, Carol"
String.join("-", "2024", "03", "15")         // "2024-03-15"

// With collection:
List<String> names = List.of("Alice", "Bob", "Carol");
String.join(", ", names)                      // "Alice, Bob, Carol"

// ── StringJoiner — join with prefix/suffix ────────────────────────────
StringJoiner sj = new StringJoiner(", ", "[", "]");
names.forEach(sj::add);
sj.toString()   // "[Alice, Bob, Carol]"

// ── String.format() ──────────────────────────────────────────────────
String.format("Hello, %s!", "World")                  // "Hello, World!"
String.format("Pi = %.2f", Math.PI)                   // "Pi = 3.14"
String.format("%d items at $%.2f each", 5, 9.99)      // "5 items at $9.99 each"
String.format("%-15s %5d %8.2f", "Widget", 42, 19.99) // left-padded table row
String.format("%05d", 42)                              // "00042" — zero-padded

// Locale-aware formatting:
String.format(Locale.GERMANY, "%.2f", 1234.56)  // "1.234,56" (German format)
String.format(Locale.US,      "%.2f", 1234.56)  // "1234.56"  (US format)

// ── formatted() — Java 15+ instance method ───────────────────────────
"Hello, %s! You have %d messages.".formatted("Alice", 3)
// "Hello, Alice! You have 3 messages."

// ── toCharArray() and chars() ─────────────────────────────────────────
char[] chars = "Hello".toCharArray();   // ['H','e','l','l','o']
chars[0] = 'h';                          // modifiable copy
new String(chars);                       // "hello" — reconstruct from modified array

// Count vowels using chars stream:
long vowelCount = "Hello World".chars()
    .filter(c -> "aeiouAEIOU".indexOf(c) >= 0)
    .count();
System.out.println(vowelCount);   // 3

Substring, Comparison, and Conversion Methods

The substring() methods extract portions of a string. substring(beginIndex) returns all characters from beginIndex to the end. substring(beginIndex, endIndex) returns characters in the range [beginIndex, endIndex) — beginIndex inclusive, endIndex exclusive. Passing endIndex equal to length() is equivalent to the single-argument form. Both throw StringIndexOutOfBoundsException for out-of-range indices. The toCharArray() method returns a new char array containing the string's characters — a mutable copy that can be freely modified. This is useful for algorithms that need to manipulate characters in place, such as sorting characters, performing in-place transformations, or passing to APIs that require char arrays. Java 12 added indent() for adding indentation to multi-line strings, and transform() for applying a Function to a string in a fluent pipeline. Java 15 added the formatted() convenience method and text blocks (triple-quoted strings) which provide multi-line string literals with automatic whitespace stripping. These modern additions reduce boilerplate for common string patterns.
Java
// ── substring() ─────────────────────────────────────────────────────
String s = "Hello World";
s.substring(6)        // "World" — from index 6 to end
s.substring(0, 5)     // "Hello" — indices [0, 5)
s.substring(6, 11)    // "World" — indices [6, 11)
s.substring(0)        // "Hello World" — full string
s.substring(s.length()) // "" — empty string (valid, not an exception)

// Common pattern: extract before/after a delimiter
String email = "alice@example.com";
int atPos    = email.indexOf('@');
String local  = email.substring(0, atPos);  // "alice"
String domain = email.substring(atPos + 1); // "example.com"

// ── toCharArray() ────────────────────────────────────────────────────
char[] chars = "dcba".toCharArray();
Arrays.sort(chars);
String sorted = new String(chars);   // "abcd"

// Count character frequency:
String text = "hello world";
int[] freq = new int[26];
for (char c : text.toCharArray()) {
    if (c >= 'a' && c <= 'z') freq[c - 'a']++;
}

// ── getBytes() and charset handling ──────────────────────────────────
byte[] utf8Bytes  = "Hello".getBytes(StandardCharsets.UTF_8);
byte[] utf16Bytes = "Hello".getBytes(StandardCharsets.UTF_16);
String fromBytes  = new String(utf8Bytes, StandardCharsets.UTF_8);

// ── Text blocks (Java 15+) ────────────────────────────────────────────
String json = """
        {
            "name": "Alice",
            "age": 30,
            "active": true
        }
        """;
// Indentation is stripped to the level of the closing """
// Trailing newline is included

String sql = """
        SELECT id, name, email
        FROM users
        WHERE active = true
          AND age > 18
        ORDER BY name
        """;

// ── indent() (Java 12+) ───────────────────────────────────────────────
"Hello
World".indent(4)
// "    Hello
    World
"  — 4 spaces added to each line

// ── transform() (Java 12+) ───────────────────────────────────────────
String result = "  hello world  "
    .transform(String::strip)
    .transform(s2 -> s2.substring(0, 1).toUpperCase() + s2.substring(1));
System.out.println(result);  // "Hello world"

// ── lines() (Java 11+) ───────────────────────────────────────────────
"line1
line2
line3
line4"
    .lines()
    .forEach(System.out::println);
// line1
// line2
// line3
// line4

Related Topics in Strings

String Class
String is one of the most fundamental classes in Java — used in virtually every program, yet deeply misunderstood by many developers. A String represents an immutable sequence of Unicode characters. It is not a primitive type but a full class in java.lang, automatically imported into every Java file. Understanding String means understanding how it is stored in memory, why it is immutable, how the string pool works, what the difference between == and equals() means for strings, and how to use the class efficiently. This entry covers String's nature as a class, its internal representation, the critical distinction between reference equality and value equality, String's place in the type hierarchy, and the design decisions that make String behave the way it does.
String Pool
The string pool (also called the string intern pool or string constant pool) is a special memory region maintained by the JVM that stores a single copy of each unique string value. When two string literals have the same content, they refer to the same object in the pool rather than two separate objects. The pool is a flyweight pattern applied at the language level — it dramatically reduces memory consumption in applications that use many repeated string values, which is nearly every application. This entry covers how the pool works, where it lives in JVM memory, how to interact with it programmatically, the intern() method, performance implications, and when to use or avoid pool entries.
Immutable String
String immutability is the most important design decision in Java's String class. Once a String object is created, its character sequence can never change. No method on String modifies the string; every method that appears to modify returns a new String object containing the result. This design decision drives thread safety, enables the string pool, makes strings safe hash map keys, and simplifies reasoning about string values. Understanding why String is immutable, how immutability is enforced, and what the consequences of immutability are clarifies the behaviour of virtually every piece of Java code that handles strings.
Mutable String
Java provides two mutable string classes for scenarios where String's immutability would be inefficient: StringBuilder and StringBuffer. Both maintain an internal character buffer that can be modified in place — characters can be appended, inserted, deleted, and replaced without creating new objects. StringBuilder is the modern choice for single-threaded use; StringBuffer is the legacy thread-safe version with synchronised methods. This entry covers the internal buffer mechanics, the full API of both classes, performance characteristics, when to use each, thread safety implications, and the complete patterns for efficient string construction.