Understanding Strings in Java
Explore how Java represents and manages strings as immutable objects, including efficient storage, memory usage, and key operations such as concatenation, formatting, and comparison. Understand correct string manipulation techniques to avoid common bugs and improve application text handling.
Text processing is fundamental to almost every application we build. Whether we are logging a username, processing a payment ID, or displaying a chat message, we rely on text. In Java, text is represented by the String class. While strings may look like simple primitive values, they are actually complex objects with unique behaviors regarding memory and modification.
In this lesson, we will explore how Java stores strings efficiently, why they cannot be changed once created, and how to manipulate them effectively.
The String object and creation
Unlike int or boolean, a String is a reference type, meaning it is an object. However, because strings are so common, Java provides a special shorthand syntax for creating them.
There are two ways to create a string:
String literal: We enclose text in double quotes (
"Hello"). Java optimizes this by storing the string in a special memory area called the String Pool. If we use the same literal twice, Java reuses the existing instance to save memory.newkeyword: We can explicitly create a new object usingnew String("Hello"). This forces Java to create a distinct object on the heap, bypassing the pool’s optimization. We rarely do this in practice, but understanding the difference is vital for debugging.
Lines 5–6: We create two string variables using literals. Because the text is identical,
s1ands2point to the exact same memory address in the String Pool.Line 10: We use the
newkeyword. This forces the JVM to create a brand new object in standard heap memory, even though the content ("Java") is the same.Lines 13–14: We print the result of reference comparisons.
s1 == s2is true because they share the same address.s1 == s3is false becauses3is a completely separate object.
This diagram illustrates how literals share references in the String Pool, whereas objects created with new occupy distinct memory locations on the heap.
Understanding immutability
One of the most defining characteristics of the String class is immutability. Once a String object is created, its internal state (the sequence of characters) cannot be changed.
When we appear to modify a string, such as by adding text to it, Java does not change the original object. Instead, it creates a new String object containing the result and updates our variable to point to this new object. The old object remains in memory (until garbage collected) or stays in the String Pool.
Line 3: We create a string
greetingwith the value"Hello".Line 6: We call
.concat(). This creates a new string"Hello, World"in memory, but we do not assign it to anything. The originalgreetingvariable still points to"Hello".Line 7: We print
greeting, confirming it has not changed.Line 10: We call
.concat()again, but this time we assign the result back togreeting. The variable now points to the new string object.
Concatenation and composition
The most common string operation is combining text, known as concatenation. We typically use the + operator. Java handles + smartly: if we add a non-string value (like a number) to a string, Java automatically converts the value to a string before combining them.
While + is convenient, remember that every concatenation creates a new string object. For combining many strings in a loop, this is inefficient (we will discuss StringBuilder for those cases later).
Line 8: We build a single string
profile. Java evaluates this left-to-right. It combinesfirstName, a space, andlastName. When it encountersyear(an integer), it converts1815into"1815"and appends it to the string.Line 10: We print the final formatted result.
Formatting strings
While the + operator works well for simple combinations, it becomes messy and hard to read when we have multiple variables or need specific formatting (like controlling decimal places).
Java provides String.format() and the modern .formatted() method (introduced in Java 15) to solve this. These allow us to use a template with placeholders like %s (for strings), %d (for integers), and %.2f (for floating-point numbers).
Line 8: We use
String.formatwith a template string.%sis replaced byproduct.%.2froundspriceto two decimal places (1000.00).%dis replaced bystock.Line 11: We use the newer
.formatted()instance method, which achieves the same result but reads more naturally (left-to-right).Lines 13–14: Both outputs are identical:
"Item: Laptop | Price: $1000.00 | Stock: 5".
Comparing strings correctly
This is the most common bug for beginners: comparing strings using ==.
==compares references: It checks if two variables point to the exact same memory address..equals()compares content: It checks if two strings contain the same sequence of characters.
Because strings can exist in the pool or on the heap, two strings can contain identical text but live at different addresses. Always use .equals() to check if text matches.
Line 3:
literalis created in the String Pool.Line 4:
inputis forced onto the heap usingnew. They have different memory addresses.Line 7:
literal == inputreturnsfalsebecause the addresses differ. Access is incorrectly denied.Line 13:
literal.equals(input)returnstruebecause the characters "password" match exactly.
Useful string operations
Java provides a rich API for manipulating text. Let’s look at the operations we will use most often.
Searching
We search for text using indexOf() (finds the first occurrence) and lastIndexOf() (finds the last occurrence). These return -1 if the text is not found.
Line 5: Finds the first
"Java"starting at index 0.Line 6: Finds the last
"Java"starting at index 17.Line 7: Searches for
"Python". Since it is not in the string, it returns-1.
Extracting text
We often need to extract a portion of a larger string. We use substring(int beginIndex, int endIndex).
beginIndex: The starting position (inclusive).endIndex: The ending position (exclusive).
Indices in Java are zero-based. In the string "Java", ‘J’ is at index 0, ‘a’ at 1, ‘v’ at 2, and ‘a’ at 3.
Line 6:
indexOf("@")scans the string and returns the integer index of the first@symbol (which is 7).Line 10:
substring(0, atIndex)grabs characters starting at index 0 up to index 7. The character at index 7 (‘@’) is not included.Line 12: The output is: support.
Cleaning input
When dealing with user input, we often get accidental spaces at the start or end of a string (e.g., " user "). Java provides methods to remove this whitespace cleanly.
trim(): The classic method. It removes ASCII whitespace (spaces, tabs, newlines) from both ends.strip(): The modern (Java 11+) method. It is “Unicode-aware,” meaning it removes all types of whitespace, including special invisible characters from other languages. In modern Java, we preferstrip(), thoughtrim()is still widely used.
Line 3: The string contains two spaces before and two spaces after "admin".
Line 6:
trim()creates a new string with surrounding spaces removed. It is sufficient for standard English text.Line 9:
strip()does the same but is smarter about Unicode whitespace (like non-breaking spaces).Lines 11–13: Both methods result in the clean string
"admin", verifying the spaces are gone.
Checking content and accessing characters
We can inspect strings to see if they contain specific patterns or are empty. We can also grab individual characters using charAt().
Line 6:
.endsWith(".java")returnstruebecause the string matches that suffix. We can also use.startsWith()or.contains().Line 7:
.isEmpty()returnstrueonly if the string length is 0. We negate it with!to ensure text exists.Line 8:
.length()returns the total count of characters (11).Line 14:
.charAt(0)retrieves the character at index 0 (‘R’).
We now understand that strings in Java are immutable objects that require careful handling. Every time we modify a string, we create a new one. We also learned how to use formatting templates to make our strings readable, how to clean user input using strip(), and the golden rule of string comparison: never use == for content checking, always use .equals().