In Java, String handling looks simple at first glance: just type "Hello World" and you’re done. But behind the scenes, strings are objects (not primitives), they’re immutable (you can’t actually change them in place), and they have special comparison rules. Understanding these fundamentals is crucial before applying the “Design Recipe” to real string-processing tasks.
In many languages, you might think of a string as a simple sequence of text stored directly in a variable. In Java, though, String variables actually hold references to String objects. This is different from primitives like int or boolean, which store numeric or true/false values directly.
It’s possible for multiple variables to share the exact same string object if they both refer to it. Other times, they might each refer to a different string object with the same text.
The new expression forces creation of a separate object in the general storage area (often called the heap). That’s why greeting2 references a distinct “Hello” object.
Subsection5.1.3Immutability: No In-Place Modification
Java Strings are immutable: once created, the sequence of characters in that string never changes. Instead of modifying the same object, methods like toUpperCase() or concat(...) create new String objects.
String word = "Hello";
word.toUpperCase();
System.out.println(word); // Prints "Hello", unchanged
word = word.toUpperCase();
System.out.println(word); // Prints "HELLO"
In the first call, the uppercase version of "Hello" is created, but we never stored it back into word. So word remains the original "Hello". Only on the second call do we assign word to the newly created string "HELLO".
This can be surprising if you’re coming from a language where "strings" can be changed in-place. In Java, always remember to capture the result of any "modifying" operation if you want to keep it.
Subsection5.1.4Comparing Strings: == vs. .equals(...)
Because String variables hold references to objects, using the == operator checks if two references point to the exact same string object. Meanwhile, .equals(...) compares the text content—i.e., do these objects have identical characters?
String a = "test";
String b = "test";
String c = new String("test");
System.out.println(a == b); // Possibly true if both share the same literal
System.out.println(a == c); // false, c is a distinct new String
System.out.println(a.equals(c)); // true, all have the same text "test"
We strongly recommend always using .equals(...) (or .equalsIgnoreCase(...) to ignore case) for string comparisons in Java. Relying on == can cause silent bugs if the compiler/runtime happens not to reuse the same literal object or if new was used.
The string pool is an internal optimization in Java: whenever the compiler sees a literal like "test" in your code, it can store just one copy of that text in a special region. Multiple variables using that same literal may point to this single pooled copy, which sometimes makes == appear to work. However, this behavior is only guaranteed for identical string literals in the same program.
On the other hand, new String("test")always creates a new object that is not in the pool. Therefore, a == b can be false even if a and b have the same text. This is why .equals(...) is so important for correct content-based checks.
Keeping these points in mind prevents confusion when writing or analyzing code. For example, if you want to “make your text uppercase,” in Java you do something like:
rather than assuming you changed the original string. In upcoming sections, you’ll see how these fundamentals shape the way we search, slice, or combine strings while applying the rest of the Design Recipe (e.g., writing tests, refining method outlines, etc.).
In the code below, we want to print HELLO and WELCOME. Currently, it prints Hello and Welcome because the calls to toUpperCase() are not stored back into the variables. Fix the snippet so that it correctly prints uppercase versions of each.
Java strings might look straightforward, yet they’re built upon distinct principles: they store characters in objects (not primitives), are immutable, and require .equals() for content comparison. Mastering these points is essential before tackling advanced string operations in the next sections. If you remember that String is about references, new objects, and in-place immutability, you’ll avoid common pitfalls when building real-world string-handling code.
Next, we’ll apply the Design Recipe in a step-by-step fashion to various string tasks—like slicing, searching, or replacing—knowing full well how Java String objects behave under the hood.