What Is It?
What Are Strings in Java?
A String in Java is an object that represents a sequence of characters. Unlike primitive data types such as int or char, strings in Java are objects of the java.lang.String class. This distinction is critical because it means strings have methods, can be passed as objects, and follow object-oriented rules including immutability.
Every Java program that prints output, reads user input, or processes text relies on strings. From storing a student's name to building SQL queries, from parsing JSON responses to formatting log messages, strings are the backbone of text processing in Java.
Creating Strings
Java provides two primary ways to create strings:
1. String Literal: String name = "Aarav"; — Created using double quotes. Java stores this in a special memory area called the String Pool. If another variable is assigned the same literal, Java reuses the existing object from the pool instead of creating a new one. This is memory-efficient.
2. Using the new keyword: String name = new String("Aarav"); — Explicitly creates a new String object on the heap, bypassing the String Pool. Even if an identical string exists in the pool, a new object is created. This is rarely needed in practice.
Unlike Python and JavaScript, Java does not support single quotes for strings. Single quotes in Java are reserved for the char data type: char ch = 'A';.
Why Does It Matter?
Why Are Strings Important in Java?
Strings are one of the most heavily tested topics in Java interviews and university exams. Here is why mastering them is non-negotiable for any serious Java developer:
1. Strings Are Everywhere in Java Applications
Every System.out.println() call involves a string. Every Scanner.nextLine() returns a string. Configuration files, database queries, API endpoints, file paths, error messages — all are strings. You cannot write a meaningful Java program without manipulating strings.
2. Immutability Is a Core Interview Concept
Java strings are immutable — once created, their content cannot be changed. This single property leads to dozens of interview questions about the String Pool, memory management, thread safety, and why StringBuilder exists. Understanding immutability is understanding Java's design philosophy.
3. The == vs .equals() Trap
One of the most common bugs in Java programs comes from using == instead of .equals() to compare strings. This distinction between reference equality and content equality is fundamental to Java and is tested in virtually every placement exam.
4. Performance-Critical String Operations
Concatenating strings in a loop using + creates a new object every iteration, leading to O(n^2) performance. Knowing when to use StringBuilder versus the + operator separates junior developers from those who understand Java internals.
5. Foundation for Collections, I/O, and Regex
Strings interact with almost every part of the Java ecosystem: they are keys in HashMaps, they are read from and written to files, they are matched against regular expressions, and they are serialized to JSON. Weak string skills create a bottleneck across all these areas.
Detailed Explanation
Detailed Explanation
1. String Immutability
In Java, strings are immutable. Once a String object is created, its character sequence cannot be modified. Every operation that appears to modify a string actually creates a new String object.
String s = "Hello";
s.toUpperCase(); // Returns "HELLO" but s is still "Hello"
s = s.toUpperCase(); // Now s points to the new "HELLO" objectWhy is this important? Immutability provides three key benefits: (1) Thread safety — multiple threads can share the same String without synchronization. (2) Security — strings used as class names, file paths, or network addresses cannot be altered after creation. (3) Hashcode caching — since the content never changes, the hashcode can be computed once and reused, making strings efficient as HashMap keys.
2. The String Pool
Java maintains a special memory area called the String Pool (or String Intern Pool) within the heap. When you create a string using a literal, Java first checks if an identical string already exists in the pool. If it does, the new variable simply references the existing object. If not, a new object is created in the pool.
String a = "Hello";
String b = "Hello";
System.out.println(a == b); // true — same object in the poolWhen you use new String("Hello"), Java creates a new object on the heap regardless of whether "Hello" exists in the pool. The pool object is still created (for the literal), but the variable points to the heap object.
String c = new String("Hello");
System.out.println(a == c); // false — different objects
System.out.println(a.equals(c)); // true — same contentThe intern() method forces a string into the pool: String d = c.intern(); After this, a == d would be true.
3. String Comparison: == vs .equals() vs .compareTo()
This is one of the most critical distinctions in Java:
== (Reference Equality): Checks whether two variables point to the exact same object in memory. It does NOT compare the content of the strings.
.equals() (Content Equality): Checks whether two strings contain the same sequence of characters. This is what you should use for string comparison in almost all cases.
.equalsIgnoreCase(): Same as .equals() but ignores case differences. "Hello".equalsIgnoreCase("hello") returns true.
.compareTo() (Lexicographic Comparison): Returns 0 if strings are equal, a negative value if the calling string comes before the argument, and a positive value if it comes after. Used for sorting.
"apple".compareTo("banana"); // negative (a comes before b)
"banana".compareTo("apple"); // positive (b comes after a)
"apple".compareTo("apple"); // 0 (equal)4. Essential String Methods
The String class provides over 60 methods. Here are the ones you must know:
Length and Character Access
length() returns the number of characters. charAt(int index) returns the character at the given index (0-based). toCharArray() converts the string to a character array.
Searching
indexOf(String str) returns the index of the first occurrence, or -1 if not found. lastIndexOf(String str) returns the index of the last occurrence. contains(CharSequence s) returns true if the string contains the given sequence.
Extraction
substring(int beginIndex) returns a substring from the given index to the end. substring(int beginIndex, int endIndex) returns a substring from beginIndex (inclusive) to endIndex (exclusive).
Case Conversion
toUpperCase() and toLowerCase() return new strings with all characters converted to the respective case.
Trimming and Replacing
trim() removes leading and trailing whitespace. replace(char oldChar, char newChar) replaces all occurrences of a character. replace(CharSequence target, CharSequence replacement) replaces all occurrences of a substring.
Splitting and Joining
split(String regex) splits the string into an array based on a delimiter pattern. String.join(CharSequence delimiter, CharSequence... elements) joins multiple strings with a delimiter (Java 8+).
Checking Content
startsWith(String prefix) and endsWith(String suffix) check the beginning and end of a string. isEmpty() returns true if the string has length 0. isBlank() returns true if the string is empty or contains only whitespace (Java 11+).
Conversion
String.valueOf(int i) converts a primitive to a string. toCharArray() converts to a char array. getBytes() converts to a byte array.
5. StringBuilder — Mutable Strings
Since strings are immutable, repeated concatenation in a loop creates many temporary objects, leading to poor performance. StringBuilder solves this by providing a mutable character sequence.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 5; i++) {
sb.append("Item " + i + ", ");
}
String result = sb.toString();Key StringBuilder methods: append() adds text at the end. insert(int offset, String str) inserts text at a position. delete(int start, int end) removes characters. reverse() reverses the sequence. toString() converts back to a String.
6. StringBuffer — Thread-Safe Mutable Strings
StringBuffer is identical to StringBuilder in functionality but all its methods are synchronized, making it thread-safe. The trade-off is slower performance due to synchronization overhead. Use StringBuilder in single-threaded contexts (which is most of the time) and StringBuffer only when multiple threads access the same mutable string.
7. String Formatting
Java provides two main ways to format strings:
String.format(): Returns a formatted string without printing it.
String msg = String.format("Name: %s, Age: %d, GPA: %.2f", "Priya", 20, 9.45);System.out.printf(): Prints a formatted string directly to the console.
System.out.printf("Score: %d out of %d%n", 85, 100);Common format specifiers: %s for strings, %d for integers, %f for floating-point numbers, %.2f for 2 decimal places, %n for a platform-independent newline.
8. String Concatenation Performance
The + operator for string concatenation is convenient but has a hidden cost. In a loop, each + creates a new String object, copies the old content plus the new content, and discards the old object. For n iterations, this results in O(n^2) time complexity.
// BAD: O(n^2) — creates n intermediate String objects
String result = "";
for (int i = 0; i < 10000; i++) {
result += i + ","; // new object every iteration
}
// GOOD: O(n) — single StringBuilder, no copying
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(",");
}
String result = sb.toString();Note: The Java compiler optimizes simple concatenation like "Hello" + name using StringBuilder internally. The problem arises only in loops.
Code Examples
public class StringCreation {
public static void main(String[] args) {
// String literal — stored in String Pool
String s1 = "Hello";
String s2 = "Hello";
// Using new — creates a new object on the heap
String s3 = new String("Hello");
System.out.println(s1 == s2); // true (same pool object)
System.out.println(s1 == s3); // false (different objects)
System.out.println(s1.equals(s3)); // true (same content)
// intern() — moves heap string to the pool
String s4 = s3.intern();
System.out.println(s1 == s4); // true (same pool object)
}
}s1 and s2 point to the same object (== returns true). new String() always creates a separate heap object, so s1 == s3 is false even though the content is identical. .equals() compares content and returns true. intern() returns the pool reference for a given string, so s1 == s4 is true.public class StringComparison {
public static void main(String[] args) {
String a = "Java";
String b = "Java";
String c = new String("Java");
String d = "java";
// == checks reference (same object?)
System.out.println(a == b); // true
System.out.println(a == c); // false
// .equals() checks content (same characters?)
System.out.println(a.equals(c)); // true
System.out.println(a.equals(d)); // false (case matters)
// .equalsIgnoreCase() ignores case
System.out.println(a.equalsIgnoreCase(d)); // true
// .compareTo() returns int for lexicographic order
System.out.println("apple".compareTo("banana")); // negative
System.out.println("banana".compareTo("apple")); // positive
System.out.println("apple".compareTo("apple")); // 0
}
}== checks if two references point to the same object in memory. For literals in the pool, this is true. For objects created with new, it is false. .equals() compares the actual character content and is the correct way to compare strings in almost all situations. .compareTo() returns 0 for equal strings, a negative number if the caller is lexicographically smaller, and a positive number if it is larger.public class StringMethods {
public static void main(String[] args) {
String s = " Modern Age Coders ";
// Length and trimming
System.out.println(s.length()); // 21
System.out.println(s.trim()); // "Modern Age Coders"
System.out.println(s.trim().length()); // 17
String name = "Kavitha Suresh";
// Character access
System.out.println(name.charAt(0)); // K
System.out.println(name.charAt(name.length() - 1)); // h
// Case conversion
System.out.println(name.toUpperCase()); // KAVITHA SURESH
System.out.println(name.toLowerCase()); // kavitha suresh
// Search methods
System.out.println(name.indexOf("a")); // 1
System.out.println(name.lastIndexOf("a")); // 9
System.out.println(name.contains("Suresh")); // true
System.out.println(name.indexOf("xyz")); // -1
// Substring
System.out.println(name.substring(0, 7)); // Kavitha
System.out.println(name.substring(8)); // Suresh
}
}length() returns character count including spaces. trim() removes leading and trailing whitespace. charAt() returns the character at a given zero-based index. indexOf() returns the first occurrence of a substring or -1 if not found. lastIndexOf() returns the last occurrence. contains() returns a boolean. substring(start, end) extracts characters from start (inclusive) to end (exclusive).public class StringOperations {
public static void main(String[] args) {
// split()
String csv = "Aarav,Priya,Rohan,Meera";
String[] names = csv.split(",");
for (String name : names) {
System.out.println(name);
}
// String.join() — Java 8+
String joined = String.join(" | ", names);
System.out.println(joined);
// replace()
String text = "Java is hard. Java is confusing.";
System.out.println(text.replace("hard", "fun"));
System.out.println(text.replace('a', 'o'));
// startsWith() and endsWith()
String file = "Report2026.pdf";
System.out.println(file.startsWith("Report")); // true
System.out.println(file.endsWith(".pdf")); // true
System.out.println(file.endsWith(".doc")); // false
}
}split() takes a regex pattern and returns a String array. String.join() is a static method that concatenates elements with a delimiter. replace() has two overloads: one for single characters and one for CharSequences. startsWith() and endsWith() check prefixes and suffixes and are commonly used for file type validation.public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Hello");
// append()
sb.append(" World");
System.out.println(sb); // Hello World
// insert()
sb.insert(5, ",");
System.out.println(sb); // Hello, World
// delete()
sb.delete(5, 7); // Remove ", "
System.out.println(sb); // HelloWorld
// reverse()
sb.reverse();
System.out.println(sb); // dlroWolleH
// replace()
sb.reverse(); // back to HelloWorld
sb.replace(0, 5, "Namaste");
System.out.println(sb); // NamasteWorld
// length and charAt
System.out.println(sb.length()); // 12
System.out.println(sb.charAt(0)); // N
// Convert to String
String result = sb.toString();
System.out.println(result); // NamasteWorld
}
}append() adds to the end. insert() adds at a specific position. delete(start, end) removes characters from start (inclusive) to end (exclusive). reverse() reverses in place. replace(start, end, str) replaces a range with a new string. Always call toString() when you need an immutable String.public class StringFormatting {
public static void main(String[] args) {
String name = "Deepak";
int age = 21;
double gpa = 8.756;
// String.format()
String info = String.format("Name: %s, Age: %d, GPA: %.2f", name, age, gpa);
System.out.println(info);
// printf() — prints directly
System.out.printf("%-15s %5d %8.2f%n", "Deepak", 21, 8.756);
System.out.printf("%-15s %5d %8.2f%n", "Sneha", 20, 9.120);
System.out.printf("%-15s %5d %8.2f%n", "Vikram", 22, 7.890);
// Padding with zeros
System.out.printf("Roll No: %05d%n", 42);
// valueOf()
int num = 100;
String str = String.valueOf(num);
System.out.println(str + " is a String: " + (str instanceof String));
}
}String.format() works like printf but returns a String instead of printing. %s is for strings, %d for integers, %.2f for two decimal places. %-15s left-aligns a string in a 15-character field. %05d zero-pads an integer to 5 digits. %n is the platform-independent newline. String.valueOf() converts any primitive to its String representation.public class ConcatPerformance {
public static void main(String[] args) {
int n = 50000;
// BAD: String concatenation in a loop — O(n^2)
long start1 = System.currentTimeMillis();
String bad = "";
for (int i = 0; i < n; i++) {
bad += i;
}
long time1 = System.currentTimeMillis() - start1;
System.out.println("String + loop: " + time1 + " ms");
// GOOD: StringBuilder — O(n)
long start2 = System.currentTimeMillis();
StringBuilder good = new StringBuilder();
for (int i = 0; i < n; i++) {
good.append(i);
}
String result = good.toString();
long time2 = System.currentTimeMillis() - start2;
System.out.println("StringBuilder: " + time2 + " ms");
System.out.println("Both produce same result: " + bad.equals(result));
}
}+ in a loop creates a new String object on every iteration, copying all previous characters plus the new ones. For n iterations, this results in approximately n*(n+1)/2 character copies, which is O(n^2). StringBuilder maintains a resizable internal buffer and only copies characters when the buffer needs to expand, resulting in O(n) performance. For 50,000 iterations, the difference can be hundreds of milliseconds versus single-digit milliseconds.public class StringCharOps {
public static void main(String[] args) {
String s = "Hello World 123";
// toCharArray()
char[] chars = s.toCharArray();
System.out.println("Length: " + chars.length);
System.out.println("First char: " + chars[0]);
// Count digits, letters, and spaces
int digits = 0, letters = 0, spaces = 0;
for (char c : chars) {
if (Character.isDigit(c)) digits++;
else if (Character.isLetter(c)) letters++;
else if (c == ' ') spaces++;
}
System.out.println("Digits: " + digits);
System.out.println("Letters: " + letters);
System.out.println("Spaces: " + spaces);
// valueOf() conversions
System.out.println(String.valueOf(42)); // "42"
System.out.println(String.valueOf(3.14)); // "3.14"
System.out.println(String.valueOf(true)); // "true"
System.out.println(String.valueOf('A')); // "A"
}
}toCharArray() converts a String into a char[], useful for character-by-character processing. The Character wrapper class provides static methods like isDigit(), isLetter(), isUpperCase(), and isLowerCase() for checking individual characters. String.valueOf() is overloaded to accept any primitive type and returns its string representation.Common Mistakes
Using == Instead of .equals() to Compare Strings
String a = new String("Hello");
String b = new String("Hello");
if (a == b) {
System.out.println("Equal");
} else {
System.out.println("Not equal"); // This runs!
}String a = new String("Hello");
String b = new String("Hello");
if (a.equals(b)) {
System.out.println("Equal"); // This runs correctly
}== compares object references (memory addresses), not content. Two String objects created with new are always different objects even if they contain the same characters. Always use .equals() for string content comparison. This is one of the most common bugs in Java and is asked in virtually every interview.Calling a Method on a Null String
String name = null;
System.out.println(name.length()); // NullPointerExceptionString name = null;
if (name != null) {
System.out.println(name.length());
} else {
System.out.println("Name is not set");
}
// Or use a constant on the left: "Hello".equals(name) — safe even if name is nullnull reference throws a NullPointerException. Always check for null before calling methods. A common defensive technique is to put the known non-null string on the left: "expected".equals(name) instead of name.equals("expected").Forgetting That String Methods Return New Strings
String name = " aarav ";
name.trim();
name.toUpperCase();
System.out.println(name); // Still " aarav "String name = " aarav ";
name = name.trim().toUpperCase();
System.out.println(name); // AARAVtrim() and toUpperCase() do not modify the original string. They return a new String object. You must assign the result back to a variable. Method chaining works: name.trim().toUpperCase() first trims, then converts the trimmed result to uppercase.String Concatenation with + in a Loop
String result = "";
for (int i = 0; i < 10000; i++) {
result += i + ",";
}
// Extremely slow — O(n^2) time, creates 10000 temporary objectsStringBuilder sb = new StringBuilder();
for (int i = 0; i < 10000; i++) {
sb.append(i).append(",");
}
String result = sb.toString();
// Fast — O(n) time, single mutable object+ operator in a loop causes Java to create a new String object on every iteration, copying the entire accumulated string plus the new part. For n iterations, the total character copies are approximately n^2/2. Use StringBuilder in loops to maintain a single mutable buffer that grows efficiently.Using length Instead of length() for Strings
String name = "Sneha";
System.out.println(name.length); // Compilation errorString name = "Sneha";
System.out.println(name.length()); // 5
// Note: arrays use .length (no parentheses)
int[] arr = {1, 2, 3};
System.out.println(arr.length); // 3length() (a method), while arrays use length (a field). This inconsistency is a frequent source of errors. Strings are objects, so they have a method. Arrays are special objects with a public length field. Collections use size(). Three different ways to get "length" in Java.IndexOutOfBoundsException with charAt() or substring()
String s = "Hello";
System.out.println(s.charAt(5)); // StringIndexOutOfBoundsExceptionString s = "Hello";
// Valid indices are 0 to s.length()-1
System.out.println(s.charAt(s.length() - 1)); // 'o'
// Always check bounds before accessing
int index = 5;
if (index >= 0 && index < s.length()) {
System.out.println(s.charAt(index));
} else {
System.out.println("Index out of range");
}StringIndexOutOfBoundsException. Unlike Python, Java does not support negative indexing. Always validate indices before using charAt() or substring().Summary
- Strings in Java are immutable objects of the java.lang.String class. Once created, their content cannot be changed. Every modification creates a new String object.
- String literals are stored in the String Pool for memory efficiency. The new keyword bypasses the pool and always creates a separate heap object.
- Use .equals() to compare string content, not ==. The == operator checks reference equality (same object), while .equals() checks content equality (same characters). This is the single most important rule for Java strings.
- Key String methods: length(), charAt(), substring(), indexOf(), lastIndexOf(), contains(), startsWith(), endsWith(), toUpperCase(), toLowerCase(), trim(), replace(), split(), and String.join().
- StringBuilder provides a mutable character sequence for efficient string building. Use it in loops instead of the + operator to avoid O(n^2) performance degradation.
- StringBuffer is the thread-safe version of StringBuilder. Use StringBuilder in single-threaded code (most cases) and StringBuffer only when thread safety is required.
- String.format() and System.out.printf() provide C-style formatting with %s (string), %d (integer), %f (float), and %.Nf (N decimal places).
- compareTo() enables lexicographic comparison: returns 0 for equal strings, negative if the caller is smaller, positive if larger. Used for sorting.
- toCharArray() converts a String to a char array for character-by-character processing. The Character class provides isDigit(), isLetter(), isUpperCase(), and other checking methods.
- The intern() method returns the String Pool reference for any string, enabling == comparison. Useful for performance optimization when comparing the same set of strings repeatedly.