String is the most used class in Java test code: locators, expected text, URLs, test data. This guide explains how strings are stored (the string pool), why they are immutable, when to use StringBuilder, and every String method you'll reach for.
Strings & String Pool
In Simple Terms
When you write 'Java' on the whiteboard, and your friend also writes 'Java', instead of two separate writings, everyone just points to the SAME 'Java' on the board. That's the String Pool — Java reuses the same string to save memory!
Creating Strings
// Method 1: String Literal → goes to String Pool
String s1 = "Hello";
String s2 = "Hello"; // s2 reuses s1's object in pool
System.out.println(s1 == s2); // true (same object!)
// Method 2: new keyword → ALWAYS creates new Heap object
String s3 = new String("Hello");
String s4 = new String("Hello");
System.out.println(s3 == s4); // false (different objects)
System.out.println(s3.equals(s4)); // true (same value)
// intern() → moves new String to pool / returns pool ref
String s5 = s3.intern();
System.out.println(s1 == s5); // true (now in pool!)
Deep Dive: Why Strings are Immutable
Strings in Java are IMMUTABLE — once created, they cannot be changed. Here's why this is a deliberate design:
- Security: String is used for class names, file paths, network URLs. If mutable, hackers could change them after validation.
- String Pool works only because of immutability — if strings could change, sharing them would be dangerous.
- Thread Safety: Immutable objects are inherently thread-safe, no synchronization needed.
- HashCode caching: String caches its hashCode() result — only works because value never changes.
String s = "Hello";
s = s + " World";
// s does NOT modify the original!
// Creates NEW object: "Hello World" on Heap
// Original "Hello" is now eligible for GC
// Memory view:
// Pool: ["Hello"] ["Hello World"] [" World"]
// s now points to "Hello World"
StringBuilder vs StringBuffer vs String
| Feature | String vs Builder |
|---|---|
| Mutability | String: Immutable | StringBuilder: Mutable |
| Thread Safety | String: Yes | StringBuilder: No | StringBuffer: Yes |
| Performance | String: Slow for concatenation | Builder: Fast |
| When to use | Use String for fixed values, Builder in loops |
// BAD: Creates 1000 String objects in loop!
String result = "";
for (int i = 0; i < 1000; i++) {
result += i; // Each += creates a new String object
}
// GOOD: StringBuilder reuses same buffer
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
sb.append(i); // Modifies same internal char array
}
String result = sb.toString(); // Only 1 String object created!
Interview Questions
Why is String immutable in Java?
Security (strings used in classpath, URLs, DB connections), String Pool efficiency (sharing requires immutability), thread safety (no sync needed for immutable objects), and hashCode caching (computed once since value never changes).
What is String interning?
String.intern() checks if an equal string exists in the pool. If yes, returns the pool reference. If no, adds it to the pool and returns that reference. Useful to reduce memory when working with many equal strings.
Difference between == and .equals() for Strings?
== compares REFERENCES (memory addresses) — are they the same object? .equals() compares VALUES (character content) — do they contain the same text? Always use .equals() for string value comparison.
All String Methods — Complete Reference
String s = "Hello, World!";
// ── LENGTH & ACCESS ──
s.length() // 13
s.charAt(0) // 'H'
s.isEmpty() // false
s.isBlank() // false (Java 11: blank if only whitespace)
// ── SEARCH ──
s.indexOf('o') // 4 (first occurrence)
s.indexOf('o', 5) // 8 (from index 5)
s.lastIndexOf('o') // 8
s.contains("World") // true
s.startsWith("Hello") // true
s.startsWith("World", 7) // true (from index 7)
s.endsWith("!") // true
s.matches("Hello.*") // true
// ── COMPARISON ──
s.equals("Hello, World!") // true
s.equalsIgnoreCase("hello, world!") // true
s.compareTo("Hello, World!") // 0
s.compareToIgnoreCase("abc") // positive (H > a in ignore case)
// ── EXTRACTION ──
s.substring(7) // "World!"
s.substring(7, 12) // "World"
s.toCharArray() // char[] {'H','e','l','l','o',...}
s.chars() // IntStream of char values
s.codePointAt(0) // 72 (Unicode code point for 'H')
// ── TRANSFORM ──
s.toLowerCase() // "hello, world!"
s.toUpperCase() // "HELLO, WORLD!"
s.trim() // remove leading/trailing whitespace (not unicode)
s.strip() // Java 11: unicode-aware trim
s.stripLeading() // Java 11: remove leading whitespace
s.stripTrailing() // Java 11: remove trailing whitespace
s.replace('l', 'r') // "Herro, Worrd!" (char replacement)
s.replace("World", "Java") // "Hello, Java!" (string replacement)
s.replaceAll("[aeiou]", "*") // regex replace
s.replaceFirst("\\d", "#") // replace first match
// ── SPLIT & JOIN ──
"a,b,c".split(",") // ["a", "b", "c"]
String.join("-", "a","b","c") // "a-b-c"
String.join(", ", List.of("x","y")) // "x, y"
// ── FORMATTING ──
String.format("%s is %d years old", "Alice", 30) // "Alice is 30 years old"
String.format("%.2f", 3.14159) // "3.14"
String.format("%05d", 42) // "00042" (zero-padded)
String.format("%-10s|", "hi") // "hi |" (left-aligned)
String.format("%,d", 1000000) // "1,000,000" (comma separator)
// Java 15+ formatted() method:
"Value: %d".formatted(42) // "Value: 42"
// ── JAVA 11+ METHODS ──
" ".isBlank() // true (only whitespace)
"line1\nline2".lines() // Stream<String>: ["line1", "line2"]
"ha".repeat(3) // "hahaha"
" hello ".strip() // "hello"
// ── JAVA 12+ ──
"hello".indent(4) // " hello\n"
"<tag>content</tag>".transform(s -> s.toUpperCase()) // chain transform
// ── CONVERSION ──
String.valueOf(42) // "42"
String.valueOf(true) // "true"
String.valueOf('A') // "A"
String.valueOf(3.14) // "3.14"
Integer.parseInt("42") // 42
Integer.valueOf("42") // Integer object
// ── INTERN ──
String s1 = new String("hello").intern(); // puts in pool, returns pool ref
String s2 = "hello";
System.out.println(s1 == s2); // true after intern