Immutable Strings & StringBuilder
Why String is immutable, the string pool, == vs equals, and building text the right way.
Strings are immutable
A String in Java is immutable: once a String object exists, its characters never change. Every method that *looks* like it edits a string — concat, replace, toUpperCase, trim, substring, even + — actually returns a brand-new String and leaves the original untouched.
String s = "hello";
s.toUpperCase(); // returns "HELLO" — but we threw it away
System.out.println(s); // hello (s is unchanged)
s = s.toUpperCase(); // now we keep the new object
System.out.println(s); // HELLO
Immutability is why strings are safe to share, safe to use as HashMap keys, and safe across threads.
The string pool and interning
String literals are *interned*: the JVM keeps one shared pool of literal strings, so two identical literals are the same object. But a string built at runtime (with new or by concatenating variables) is a fresh object on the heap.
String a = "java";
String b = "java";
String c = new String("java");
a == b // true — both refer to the one pooled literal
a == c // false — c is a distinct new object
a.equals(c) // true — same characters
== compares references; .equals compares contents
This is the single most common string bug in Java. == asks *"are these the same object?"*; .equals asks *"do these have the same characters?"*. For strings you almost always want .equals.
Building strings in a loop: use StringBuilder
Because every + on strings creates a new object, concatenating inside a loop is quadratic — it copies all the characters built so far on every iteration. StringBuilder holds a mutable char buffer and appends in place, then you call toString() once at the end.
Worked example. Join the numbers 0..4 with dashes.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
if (i > 0) sb.append('-');
sb.append(i);
}
String result = sb.toString(); // "0-1-2-3-4"
The loop appends into one growing buffer instead of allocating a new String each pass. StringBuilder is the unsynchronized (faster, single-thread) sibling of the older StringBuffer.