Java Strings
A thorough guide to the String class in Java: immutability, the string pool, the full method API, mutable alternatives with StringBuilder and StringBuffer, text blocks, and stream operations on character data.
The String Class in Java
In Java, text is represented by the String class, which lives in the java.lang package and is therefore available without any import. String is not a primitive type: it is a full class, and every string value is an object. However, Java gives String special treatment that no other class receives, including dedicated literal syntax and compiler-level optimisations. This makes String behave in ways that are occasionally surprising until you understand what is happening under the hood.
What makes String special
Several language features exist exclusively for String, making it unlike any other class.
- Literal syntax: you can write "hello" directly in source code without calling new.
- The + operator is overloaded for string concatenation. This is the only operator overloading in Java.
- String is immutable: once created, its character sequence never changes.
- The JVM maintains a string pool to deduplicate identical literals automatically.
- String implements Comparable
, CharSequence, and Serializable.
Creating Strings: Literals vs. the new Keyword
There are two ways to create a String object, and they behave differently in a way that matters when you compare strings. Understanding the difference is essential for avoiding one of the most common bugs in Java.
Literal vs. new String()
The two creation methods result in different object placement, which affects reference equality.
- String literal:String s = "hello";, the JVM checks the string pool first. If a String object with that value already exists there, the same object is reused. No new object is created.
- new String():String s = new String("hello");, always creates a brand-new String object on the heap, even if an identical value already exists in the pool. You almost never need this form.
- intern():Calling s.intern() on a heap String returns the pool version of that string, or adds it to the pool if not already present. Rarely needed in normal application code.
Literal vs. new String()
JavaHow the two creation methods differ in memory placement and reference equality.
String Immutability
Once a String object is created, its character sequence cannot be modified. Every method that appears to modify a String, such as toUpperCase() or replace(), actually creates and returns a new String object. The original is untouched. Immutability is not a limitation: it is a deliberate design choice that makes strings safe to share across threads without synchronisation, and safe to use as keys in hash maps.
Consequences of immutability
Immutability affects both correctness and performance in ways you should anticipate.
- Thread safety: multiple threads can read the same String concurrently with no synchronisation needed.
- Safe hash keys: a String's hash code never changes, so it works reliably as a HashMap or HashSet key.
- Performance cost: building a string through many concatenations inside a loop creates a new object on every iteration. Use StringBuilder instead.
- The variable can be reassigned (s = "new value"), but the original object is not changed. The variable simply points to a different object.
String Immutability
JavaMethods return new Strings; the original is never modified.
String Pool
The string pool (also called the intern pool) is a special region of the Java heap where the JVM stores one canonical copy of each unique string literal. When the compiler sees the literal "Java" in ten different places in your code, it arranges for all ten references to point to the same single object in the pool. This saves memory and makes reference comparison of literals predictable.
How the pool works
The JVM checks the pool before allocating a new object for a literal.
- At class load time, each string literal is checked against the pool.
- If a matching string exists in the pool, the literal reference points to it.
- If not, a new String object is created in the pool and the reference points to that.
- Strings created with new String() bypass the pool and go directly to the heap.
- Since Java 7, the string pool lives in the main heap (not PermGen), so it can be garbage collected.
String Concatenation with + and concat()
Java provides two main ways to join strings. The + operator is the most convenient and handles non-string operands by calling String.valueOf() on them automatically. The concat() method works only on String arguments and throws a NullPointerException if called on a null reference. Both create a new String. For repeated concatenation in a loop, use StringBuilder instead.
How the compiler handles + concatenation
The + operator is syntactic sugar. The compiler rewrites it under the hood.
- For compile-time constants (two string literals), the compiler concatenates them directly in the bytecode: "Hello" + " World" becomes "Hello World" with no runtime cost.
- For runtime concatenation, Java 9+ uses invokedynamic with StringConcatFactory, which is efficient for single concatenations.
- A loop like result += item repeated n times still creates n intermediate String objects. StringBuilder avoids this entirely.
String Concatenation
JavaThe + operator with mixed types, concat(), and why StringBuilder is needed in loops.
String Length and Accessing Characters
Two fundamental operations on any string are checking its size and reading individual characters. Java provides these through length() and charAt(). Note that String uses a method length() while arrays use a field length (no parentheses). This inconsistency is a common source of compiler errors for beginners.
length() and charAt()
JavaReading the size of a string and iterating over its characters by index.
String Comparison: equals(), equalsIgnoreCase(), compareTo()
Comparing strings correctly is critical. Java provides three distinct methods for different comparison needs. The rule is straightforward: use == only for primitive types. For Strings and all other objects, use a method.
The three comparison methods
Each answers a different question about two strings.
- equals(other):Returns true if both strings have exactly the same sequence of characters, including case. "Java".equals("Java") is true; "Java".equals("java") is false.
- equalsIgnoreCase(other):Same as equals() but ignores uppercase and lowercase differences. "Java".equalsIgnoreCase("java") is true.
- compareTo(other):Returns a negative int if this string comes before other lexicographically, zero if equal, or a positive int if it comes after. Used for sorting.
== vs. equals() on Strings
This is the most common String mistake in Java. The distinction is important enough to deserve its own section.
- == compares references (memory addresses), not content. Two different String objects containing the same text are not == to each other.
- String literals may appear == due to string pool deduplication, but this is an implementation detail you should never rely on.
- Always use equals() (or equalsIgnoreCase(), or compareTo()) to compare String content.
- To guard against NullPointerException, put the known non-null string on the left: "expected".equals(userInput) rather than userInput.equals("expected").
String Comparison Methods
Javaequals(), equalsIgnoreCase(), compareTo(), and the == trap demonstrated together.
String Methods
The String class provides a comprehensive API. The methods below are the ones you will reach for most often in day-to-day Java. All of them return a new String or a primitive; none modifies the original.
Extraction methods
Pulling a portion of a string or locating a character within it.
- substring(start):Returns everything from index start to the end. "Hello".substring(2) gives "llo".
- substring(start, end):Returns characters from start (inclusive) to end (exclusive). "Hello".substring(1, 4) gives "ell".
- indexOf(str):Returns the index of the first occurrence of str, or -1 if not found.
- lastIndexOf(str):Returns the index of the last occurrence of str, or -1 if not found.
Testing methods
Checking properties of a string without extracting content.
- contains(sequence):Returns true if the string includes the given character sequence anywhere.
- startsWith(prefix):Returns true if the string begins with the given prefix.
- endsWith(suffix):Returns true if the string ends with the given suffix.
- isEmpty():Returns true if length() == 0.
- isBlank() (Java 11+):Returns true if the string is empty or contains only whitespace characters. isEmpty() would return false for a string of spaces; isBlank() returns true.
Transformation methods
Each returns a new String with the requested modification applied.
- replace(old, new):Replaces all occurrences of a char or CharSequence.
- replaceAll(regex, replacement):Replaces all substrings matching a regular expression.
- trim():Removes leading and trailing ASCII whitespace (characters <= U+0020).
- strip() (Java 11+):Removes leading and trailing whitespace using Unicode definitions. Handles non-ASCII whitespace that trim() misses.
- toUpperCase() / toLowerCase():Converts all characters to upper or lower case.
- split(regex):Splits the string into an array of substrings at each match of the regex.
- toCharArray():Returns a new char[] containing all characters in the string.
- join(delimiter, elements):Static method. Joins multiple strings with a delimiter: String.join(", ", "a", "b", "c") gives "a, b, c".
- formatted(args) (Java 15+):Instance method equivalent of String.format(). "Score: %d".formatted(95) gives "Score: 95".
Core String Methods
JavaExtraction, testing, and transformation methods applied to practical examples.
String.valueOf() and toString()
Converting non-string values to String is a routine operation. String.valueOf() is the robust static method for this purpose, as it handles null gracefully by returning the string "null" rather than throwing an exception. Every class also inherits toString() from Object, and you should override it in your own classes to provide a meaningful text representation, because that is what the debugger, logging frameworks, and println() all call internally.
String.valueOf() and toString()
JavaConverting primitives and objects to String, including null-safe handling.
StringBuilder Class
StringBuilder is a mutable sequence of characters. It provides the same output as repeated String concatenation but does so without creating a new object on every operation. Internally it maintains a resizable char[] array and only converts to a String when you call toString(). Whenever you find yourself building a string inside a loop, StringBuilder is the right tool.
Key StringBuilder methods
StringBuilder has a rich API that goes well beyond simple appending.
- append(value):Adds value to the end. Accepts any type. Returns the StringBuilder for chaining.
- insert(index, value):Inserts value at the given position.
- delete(start, end):Removes characters from start (inclusive) to end (exclusive).
- replace(start, end, str):Replaces the specified range with str.
- reverse():Reverses the character sequence in place.
- charAt(index) / setCharAt(index, char):Reads or writes a single character.
- length():Returns the current length of the character sequence.
- toString():Converts the builder to an immutable String.
StringBuilder
JavaBuilding a formatted report line efficiently, plus common mutation operations.
StringBuffer Class
StringBuffer is the thread-safe counterpart of StringBuilder. It has an identical API but every method is synchronized, meaning only one thread can execute any of its methods at a time. This makes it safe to share between threads but slower than StringBuilder due to locking overhead. It has been in Java since version 1.0; StringBuilder was added in Java 5 as the faster single-threaded alternative.
When to use StringBuffer
StringBuffer is rarely the correct choice in modern Java code.
- Use StringBuffer only when a mutable string builder is genuinely shared and mutated by multiple threads simultaneously.
- In practice, most string concatenation happens in a single thread, making StringBuilder the right default.
- If you are building a string in one thread and then passing it (as a String, via toString()) to other threads, StringBuilder is fine: the synchronisation is not needed.
- Legacy codebases written before Java 5 use StringBuffer throughout. You may encounter it when maintaining older code.
StringBuilder vs. StringBuffer vs. String
The three classes serve different purposes. Choosing the right one is a matter of understanding your mutation and concurrency requirements.
Comparison at a glance
Each class occupies a different point in the trade-off between immutability, speed, and thread safety.
- String:Immutable. Thread-safe (nothing to synchronise). Best for values that are set once and read many times. Poor choice for building strings through many concatenations.
- StringBuilder:Mutable. Not thread-safe. Fast. The correct choice for building strings in a single-threaded context, which covers the vast majority of cases.
- StringBuffer:Mutable. Thread-safe (synchronised). Slower than StringBuilder. Use only when the builder is shared and mutated across threads, which is unusual.
Performance Comparison
JavaDemonstrating why StringBuilder is dramatically faster than String concatenation in a loop.
String Formatting with printf and format()
Formatted output in Java uses a format string containing placeholders that describe how each value should be presented. The two methods share the same format string syntax: String.format() returns a new String, and System.out.printf() prints immediately. Both are covered in detail in the Input and Output tutorial. The most important specifiers in practice are below for quick reference.
String Formatting
JavaBuilding formatted strings for a structured data display.
Text Blocks (Java 15+)
A text block is a multi-line string literal introduced as a permanent feature in Java 15. It opens with three double-quote characters followed immediately by a newline, and closes with three double-quote characters on their own line (or on the last content line). Text blocks preserve newlines literally, removing the need for \n escape sequences, and they handle indentation intelligently by stripping the common leading whitespace from all lines.
Text block rules and features
Text blocks are not just cosmetic. They interact with indentation in a specific, deliberate way.
- The opening triple-quote must be followed by a newline. You cannot put content on the same line as the opening delimiter.
- Incidental leading whitespace is stripped: the JVM calculates the minimum indentation across all content lines and removes that many spaces from each line.
- The position of the closing triple-quote determines how much trailing whitespace is stripped from the last line.
- Text blocks support the same escape sequences as regular strings. Use \""" to embed triple-quotes inside a text block.
- A trailing \ at the end of a line suppresses the newline for that line, useful for long lines you want to break for readability.
Text Blocks
JavaA JSON snippet, an HTML fragment, and a SQL query expressed as text blocks without escape clutter.
String.chars() and Stream Operations
Java 8 added the chars() method to String, which returns an IntStream of the Unicode code points of each character. This lets you apply the full power of the Stream API to the characters of a string: filtering, mapping, reducing, collecting. The stream elements are int values rather than char, so cast with (char) when you need the actual character.
String.chars() and Stream Operations
JavaCounting vowels, filtering digits, reversing, and collecting unique characters using streams.
Quiz - Test Your Knowledge
Ten questions covering immutability, the string pool, comparison, String methods, StringBuilder, StringBuffer, text blocks, and stream operations. Read each option carefully before answering.
Knowledge Check
1. What does String immutability mean in Java?
2. Which method should you use to compare the content of two String objects?
3. What does substring(3, 7) return for the String "Hello, World!"?
4. Which class is preferred for building Strings in a single-threaded environment with many concatenations?
5. What is the String Pool in Java?
6. What is the difference between trim() and strip() in Java 11+?
7. What does String.join("-", "a", "b", "c") produce?
8. What is the main advantage of a Text Block (Java 15+) over a regular String literal?
9. Which method converts a String into a stream of character codes for stream operations?
10. What does StringBuffer have that StringBuilder does not?