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 Unlock

This 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 String design 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 StringBuilder instead 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 mouse

The 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:

  • productName refers to the original lowercase string.
  • upperName refers 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() and strip()
  • concat()
  • substring()
  • toLowerCase()
  • toUpperCase()

The result must be captured if the program needs to use it.

Output

 SAVE 20 NOW

The replacement result was discarded. To keep the cleaned coupon code, assign the result:

Output

 SAVE20NOW

The 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:

  1. greeting initially refers to the String "Hello".
  2. The + expression produces the combined text "Hello, customer!".
  3. The assignment makes greeting refer to the combined result.
  4. 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 NOW

Correct version

Output

 SAVE20NOW

A 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

 25

The 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, Product5

The 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

SituationRecommended approachReason
A few fixed pieces of text+ concatenationSimple and readable
Transforming existing textString methodsThey return transformed Strings
Building text repeatedly in a loopStringBuilderEfficient mutable accumulation
Sharing finished text between methodsStringImmutable 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

MistakeWhy it is incorrectBetter practice
Calling name.toUpperCase() without saving the resultThe returned value is discardedAssign the result or use it directly
Assuming replace() mutates the StringString is immutableStore the returned String
Thinking s = s + "!" mutates the original objectThe variable is reassigned to a resultDistinguish references from object state
Using == to compare String contents== checks whether references are identicalUse equals() for content comparison
Repeatedly concatenating a large result in a loopCan create many intermediate results and copy growing textUse StringBuilder
Assuming immutability makes an entire program thread-safeOther shared state and operations may still be mutableEvaluate thread safety for the complete design
Assuming String immutability alone guarantees securityValidation and authorization still matterValidate inputs and apply proper security checks

14. Best Practices

  1. Capture transformed results. String methods return results; they do not edit the source String.
  2. Use clear variable names. Names such as rawEmail and normalizedEmail make the distinction between source and transformed values explicit.
  3. Prefer equals() for content comparisons. Use == only when reference identity is specifically what the program needs to check.
  4. Use StringBuilder for repeated assembly. It is a suitable default for constructing large strings incrementally in a loop.
  5. Avoid premature optimization. A small number of concatenations is normally fine. Optimize when the workload and measurements justify it.
  6. Keep security checks comprehensive. Immutability stabilizes String contents, but it does not replace correct input validation or authorization.
  7. 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 String is immutable: its character sequence cannot be changed after creation.
  • Methods such as replace(), trim(), substring(), and toUpperCase() 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.
  • StringBuilder is 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.