Search⌘ K
AI Features

Understanding Strings in Java

Explore how Java represents and stores strings using the String Pool and heap memory. Understand string immutability, concatenation methods, proper formatting techniques, and the correct way to compare strings using .equals() instead of ==. This lesson helps you master efficient and error-free text handling in Java applications.

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.

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 and the golden rule of string comparison: never use == for content checking, always use .equals().