Search⌘ K
AI Features

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:

  1. 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.

  2. new keyword: We can explicitly create a new object using new 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.

Java 25
public class StringCreation {
public static void main(String[] args) {
// Method 1: String Literal (Recommended)
// Stored in the String Pool
String s1 = "Java";
String s2 = "Java"; // Reuses the same object as s1
// Method 2: Using 'new' (Avoid unless necessary)
// Forces a new object on the Heap, outside the Pool
String s3 = new String("Java");
// Comparing references (addresses), NOT content
System.out.println("s1 == s2: " + (s1 == s2)); // true (Same pool object)
System.out.println("s1 == s3: " + (s1 == s3)); // false (Different objects)
}
}
  • Lines 5–6: We create two string variables using literals. Because the text is identical, s1 and s2 point to the exact same memory address in the String Pool.

  • Line 10: We use the new keyword. 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 == s2 is true because they share the same address. s1 == s3 is false because s3 is 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.

String literals in the Constant Pool vs. string objects in the Heap
String literals in the Constant Pool vs. string objects in 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.

Java 25
public class StringImmutability {
public static void main(String[] args) {
String greeting = "Hello";
// We attempt to modify the string
greeting.concat(", World");
System.out.println("After concat (ignored): " + greeting);
// We must reassign the variable to store the change
greeting = greeting.concat(", World");
System.out.println("After reassignment: " + greeting);
}
}
  • Line 3: We create a string greeting with 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 original greeting variable 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 to greeting. 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).

Java 25
public class StringConcat {
public static void main(String[] args) {
String firstName = "Ada";
String lastName = "Lovelace";
int year = 1815;
// Concatenating strings and numbers
String profile = firstName + " " + lastName + " (Born: " + year + ")";
System.out.println(profile);
}
}
  • Line 8: We build a single string profile. Java evaluates this left-to-right. It combines firstName, a space, and lastName. When it encounters year (an integer), it converts 1815 into "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).

Java 25
public class StringFormatting {
public static void main(String[] args) {
String product = "Laptop";
double price = 999.9985;
int stock = 5;
// Using String.format (Static method)
String label = String.format("Item: %s | Price: $%.2f | Stock: %d", product, price, stock);
// Using .formatted (Instance method - Java 15+)
String modernLabel = "Item: %s | Price: $%.2f | Stock: %d".formatted(product, price, stock);
System.out.println(label);
System.out.println(modernLabel);
}
}
  • Line 8: We use String.format with a template string. %s is replaced by product. %.2f rounds price to two decimal places (1000.00). %d is replaced by stock.

  • 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.

Java 25
public class StringComparison {
public static void main(String[] args) {
String literal = "password";
String input = new String("password"); // Simulates user input
// WRONG: Comparing memory addresses
if (literal == input) {
System.out.println("Access Granted (==)");
} else {
System.out.println("Access Denied (==)"); // This executes
}
// CORRECT: Comparing text content
if (literal.equals(input)) {
System.out.println("Access Granted (.equals)"); // This executes
}
}
}
  • Line 3: literal is created in the String Pool.

  • Line 4: input is forced onto the heap using new. They have different memory addresses.

  • Line 7: literal == input returns false because the addresses differ. Access is incorrectly denied.

  • Line 13: literal.equals(input) returns true because 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.

Java 25
public class StringSearch {
public static void main(String[] args) {
String sentence = "Java is fun, and Java is powerful.";
int firstJava = sentence.indexOf("Java");
int lastJava = sentence.lastIndexOf("Java");
int pythonIndex = sentence.indexOf("Python");
System.out.println("First 'Java' at: " + firstJava);
System.out.println("Last 'Java' at: " + lastJava);
System.out.println("'Python' found at: " + pythonIndex);
}
}
  • 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.

Java 25
public class StringExtract {
public static void main(String[] args) {
String email = "support@educative.io";
// Find the index of the '@' symbol
int atIndex = email.indexOf("@");
// Extract everything before '@'
// substring(0, 7) takes characters from index 0 up to (but not including) 7
String username = email.substring(0, atIndex);
System.out.println("Username: " + username);
}
}
  • 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 prefer strip(), though trim() is still widely used.

Java 25
public class StringClean {
public static void main(String[] args) {
String rawInput = " admin ";
// Classic trim (valid, but older style)
String trimmed = rawInput.trim();
// Modern strip (Java 11+ recommended)
String stripped = rawInput.strip();
System.out.println("Original: '" + rawInput + "'");
System.out.println("Trimmed: '" + trimmed + "'");
System.out.println("Stripped: '" + stripped + "'");
}
}
  • 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().

Java 25
public class StringCheck {
public static void main(String[] args) {
String filename = "Report.java";
// Check content
boolean isJavaFile = filename.endsWith(".java");
boolean hasText = !filename.isEmpty();
int length = filename.length();
System.out.println("Is Java File: " + isJavaFile);
System.out.println("Length: " + length);
// Access specific character
char firstLetter = filename.charAt(0);
System.out.println("First letter: " + firstLetter);
}
}
  • Line 6: .endsWith(".java") returns true because the string matches that suffix. We can also use .startsWith() or .contains().

  • Line 7: .isEmpty() returns true only 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().