1. Introduction
A String in Java is immutable. Once a String object is created, its sequence of characters cannot be changed. Methods that appear to modify a string—such as toUpperCase(), replace(), trim(), and substring()—return a result without changing the original object.
Immutability is an important part of Java's design. It helps make strings safe to share, reliable as HashMap keys, suitable for the String Pool, and predictable when passed to methods. It also has a performance trade-off: repeatedly building text with + inside a loop can create many temporary objects.
Premium Video
This video is available to Premium members or users who have purchased the course.
Sign in to UnlockThis article explains the concept with practical examples using product names, coupon codes, customer details, and order summaries.
2. Learning Objectives
By the end of this lesson, you will understand:
- What immutability means for a Java
String. - Why calling a String method does not change the original object.
- The difference between reference reassignment and object mutation.
- How Java's
Stringdesign supports immutability. - Why immutability is useful for threads, hash-based collections, pooling, APIs, and security.
- Why repeated concatenation in a loop can be inefficient.
- When to use
StringBuilderinstead of repeated String concatenation.
3. What Immutability Means
An object is immutable when its observable state cannot change after construction. For a String, that state is the sequence of characters it represents.
For example, calling toUpperCase() creates or returns an uppercase result. It does not rewrite the characters in the original object.
Example 1: Calling a method without saving its result
Output
wireless mouseThe call to toUpperCase() produces the uppercase result, but the program discards that returned value. The variable productName still refers to the original string.
Example 2: Saving the returned String
Output
Original: wireless mouseUppercase: WIRELESS MOUSE
There are now two variable references with different roles:
productNamerefers to the original lowercase string.upperNamerefers to the uppercase result.
The original string remains unchanged.
4. String Methods Return Results Instead of Mutating the Original
The same principle applies to methods such as:
replace()trim()andstrip()concat()substring()toLowerCase()toUpperCase()
The result must be captured if the program needs to use it.
Output
SAVE 20 NOWThe replacement result was discarded. To keep the cleaned coupon code, assign the result:
Output
SAVE20NOWThe assignment changes which object the variable refers to. It does not mutate the original String object.
5. How Java's String Design Supports Immutability
The Java String class is deliberately designed to prevent ordinary code from changing a String object after it has been created.
5.1 The class is final
The String class is declared final, so another class cannot extend it. This prevents a subclass from introducing a different or mutable interpretation of String behavior.
5.2 Internal storage is private and final
The character storage is hidden inside the class. Private storage prevents outside code from directly changing the internal data. The storage reference is final, so it cannot simply be reassigned after construction.
The implementation has evolved over Java versions:
- Before Java 9, String storage used a
char[]. - From Java 9 onward, the implementation uses a
byte[]plus a coder field as part of the Compact Strings optimization.
This implementation change does not make String mutable. The internal representation remains inaccessible to ordinary external code.
5.3 String methods do not mutate existing contents
String methods read the existing contents and return a result. When a transformed result is needed, the implementation creates or obtains an appropriate String result rather than changing the original String's characters in place.
Together, these design choices preserve the immutability guarantee.
6. Reference Reassignment Versus Object Mutation
This distinction is central to understanding String behavior.
- Reference reassignment: a variable is made to refer to a different object.
- Object mutation: the state inside an existing object is changed.
Java allows a String variable to be reassigned. It does not allow the String object's characters to be mutated through the normal String API.
Output
Hello, customer!The operations happen conceptually in this order:
greetinginitially refers to the String"Hello".- The
+expression produces the combined text"Hello, customer!". - The assignment makes
greetingrefer to the combined result. - The original
"Hello"object, if no longer referenced elsewhere, can eventually be reclaimed by the garbage collector.
The variable's reference changed. The original object's characters did not.
Two variables referring to the same String
Output
original: WIRELESS MOUSEalias: wireless mouse
Both variables initially refer to the same String object. When original is reassigned, alias remains associated with the original lowercase String.
7. A Common Bug: Ignoring the Returned Value
A frequent beginner mistake is expecting replace() to edit a String in place.
Incorrect version
Output
SAVE 20 NOWCorrect version
Output
SAVE20NOWA useful debugging habit is to inspect any String method call whose returned value is ignored. If the intention is to keep a transformed string, store the returned result.
8. Benefits of String Immutability
8.1 Safe sharing between threads
Multiple threads can read the same String without coordinating with one another to protect its character contents. Since the String cannot be changed after construction, readers cannot observe another thread rewriting its contents.
For example, an online store may use the same product name while rendering a page, creating a recommendation, and writing a log entry. Immutability makes sharing the String straightforward.
This does not automatically make every object or operation surrounding a String thread-safe. It means the String's own contents remain stable.
8.2 Reliable HashMap keys
Hash-based collections use a key's hash code to locate entries. If a key's equality-relevant state changed after insertion, the collection might search in a different bucket from the one used when the entry was stored.
A String's characters cannot change, so its equality and hash code remain stable.
Output
25The lookup succeeds because the key's character sequence and hash behavior remain stable.
8.3 Enables the String Pool
Java can reuse String literals with the same contents through the String Pool. This sharing is safe because one part of a program cannot change the shared String and unexpectedly affect other references.
Output
truetrue
For these identical literals, both variables refer to the pooled String. The == operator checks reference identity, while equals() checks content equality. In general application code, use equals() when comparing String contents.
8.4 Predictable method parameters
A method that receives a String cannot change the caller's String object's contents. This makes String parameters convenient to share without defensive copying.
Output
Welcome, ASHAStored name: Asha
The method creates and uses an uppercase result without changing the String held by customerName.
8.5 Security advantages
Programs often validate strings used as file paths, URLs, class names, or other sensitive inputs. Immutability ensures that the characters in a String cannot be changed through another reference between validation and later use.
This reduces one category of time-of-check to time-of-use risk. It does not replace correct validation, authorization, canonicalization, or other security controls; it ensures that the String object's contents stay stable.
9. The Performance Cost of Repeated Concatenation
Immutability has a trade-off when assembling text piece by piece. A concatenation expression produces a result String. In a loop, repeatedly adding to the growing result can repeatedly copy previously accumulated text and create temporary objects.
Output
Product1, Product2, Product3, Product4, Product5, The program works, but each iteration builds a new accumulated result. As the text grows, earlier characters may be copied again.
For a sequence of many pieces, repeated concatenation in a loop can require quadratic total copying in the overall output length under the straightforward repeated-copying model. Compiler optimizations can change details for particular expressions, but a loop that repeatedly reassigns a growing String is still a common reason to use StringBuilder.
10. Use StringBuilder for Repeated Text Assembly
StringBuilder is designed for building text through repeated changes. Append pieces to the builder and call toString() when a final immutable String is needed.
Output
Product1, Product2, Product3, Product4, Product5The builder accumulates the text, and toString() produces the final String. This approach avoids repeatedly rebuilding the entire accumulated result in the loop.
Choosing between String and StringBuilder
| Situation | Recommended approach | Reason |
|---|---|---|
| A few fixed pieces of text | + concatenation | Simple and readable |
| Transforming existing text | String methods | They return transformed Strings |
| Building text repeatedly in a loop | StringBuilder | Efficient mutable accumulation |
| Sharing finished text between methods | String | Immutable and predictable |
11. Complete Example: Normalizing a Customer Email
This example combines immutability, method return values, and the difference between original and normalized data.
Output
Raw email: [ Alice@Example.COM ]Normalized email: [alice@example.com]
The trim() call returns a trimmed String, and toLowerCase() returns the lowercase result. The original rawEmail remains unchanged, including its spaces and original letter case.
The expression is concise, but it performs two transformations. For one email, that is normally not a concern. For very large workloads, measure performance before optimizing, and consider allocations only when they are relevant to the application.
12. Memory View of the Normalization Example
Conceptually, the program has these values:
The diagram shows the logical flow of results, not a guarantee that every implementation must retain every intermediate object indefinitely. Unreferenced temporary objects may be reclaimed, and implementations may optimize some operations. The important guarantee is that the original String's contents do not change.
13. Common Mistakes and Better Practices
| Mistake | Why it is incorrect | Better practice |
|---|---|---|
Calling name.toUpperCase() without saving the result | The returned value is discarded | Assign the result or use it directly |
Assuming replace() mutates the String | String is immutable | Store the returned String |
Thinking s = s + "!" mutates the original object | The variable is reassigned to a result | Distinguish references from object state |
Using == to compare String contents | == checks whether references are identical | Use equals() for content comparison |
| Repeatedly concatenating a large result in a loop | Can create many intermediate results and copy growing text | Use StringBuilder |
| Assuming immutability makes an entire program thread-safe | Other shared state and operations may still be mutable | Evaluate thread safety for the complete design |
| Assuming String immutability alone guarantees security | Validation and authorization still matter | Validate inputs and apply proper security checks |
14. Best Practices
- Capture transformed results. String methods return results; they do not edit the source String.
- Use clear variable names. Names such as
rawEmailandnormalizedEmailmake the distinction between source and transformed values explicit. - Prefer
equals()for content comparisons. Use==only when reference identity is specifically what the program needs to check. - Use
StringBuilderfor repeated assembly. It is a suitable default for constructing large strings incrementally in a loop. - Avoid premature optimization. A small number of concatenations is normally fine. Optimize when the workload and measurements justify it.
- Keep security checks comprehensive. Immutability stabilizes String contents, but it does not replace correct input validation or authorization.
- Remember the scope of thread safety. String contents are immutable, but mutable collections and other surrounding state still require appropriate coordination.
15. Quick Revision Notes
- A Java
Stringis immutable: its character sequence cannot be changed after creation. - Methods such as
replace(),trim(),substring(), andtoUpperCase()return results rather than mutating the original String. - Ignoring a method's returned String means the transformed result is discarded.
- Reassigning a variable changes its reference; it does not mutate the object previously referenced.
- String immutability supports safe sharing, stable hash-based keys, String Pool reuse, predictable parameters, and security properties.
- Repeated concatenation of a growing String in a loop can be inefficient.
StringBuilderis useful for assembling text incrementally.- Immutability is a property of the String object, not a guarantee that every program using it is thread-safe or secure.
16. Conclusion
String immutability is one of the core ideas behind Java's String API. Once a String object is created, its contents remain fixed. String methods return results, and a variable can be reassigned to refer to a new result without altering the old object.
This design supports reliable sharing, stable hash keys, String Pool reuse, predictable method behavior, and important security properties. Its main performance trade-off appears when a program repeatedly rebuilds growing text. For that situation, StringBuilder provides a practical way to assemble text efficiently and convert it to an immutable String when the result is ready.