All Posts

June 10, 2025

4 min read
JavaProgrammingStringsPerformanceProblem Solving

Java Strings: Complete Guide to String Manipulation and Best Practices

Welcome back to the Java Fundamentals series! 👋

What is a String in Java?

A String in Java is an immutable sequence of characters that represents textual data. Here are the key characteristics:

  • Immutable: Once created, the content cannot be modified
  • Object type: Strings are objects of the java.lang.String class
  • Reference type: String variables store references to String objects
  • UTF-16 encoding: Internally uses UTF-16 character encoding
  • Thread-safe: Due to immutability, strings are inherently thread-safe

Key Point: Any operation like concat, replace, or substring returns a new String object rather than modifying the existing one.

String Declaration and Initialization

There are two main ways to create strings in Java:

// String literal (recommended)
String s1 = "Hello";

// Using new keyword (creates new object)
String s2 = new String("World");

String Pool vs Heap Memory

Understanding where strings are stored is crucial for performance:

  • String literals → Stored in String Pool (part of heap memory)
  • new String() → Creates a new object directly in heap memory
String s1 = "Java";        // Stored in String Pool
String s2 = "Java";        // References same object in String Pool
String s3 = new String("Java");  // New object in heap

System.out.println(s1 == s2);  // true (same reference)
System.out.println(s1 == s3);  // false (different objects)

Memory Efficiency: The JVM reuses string literals for better memory utilization through the String Pool mechanism.

Essential String Methods for Problem Solving

Character and Length Operations

MethodDescriptionExample
charAt(int index)Returns character at given index"Java".charAt(1)'a'
length()Returns length of string"Hello".length()5

Substring and Search Operations

MethodDescriptionExample
substring(int start, int end)Extracts part of string [start, end-1]"Hello".substring(1, 4)"ell"
indexOf(char/String)Returns index of first occurrence"Hello".indexOf('l')2
lastIndexOf(char/String)Returns last occurrence index"Hello".lastIndexOf('l')3

Comparison and Validation Methods

MethodDescriptionExample
equals(String)Content comparison (case-sensitive)"Java".equals("java")false
equalsIgnoreCase(String)Content comparison (case-insensitive)"Java".equalsIgnoreCase("java")true
contains(CharSequence)Returns true if substring exists"Hello World".contains("World")true
startsWith(String)Checks prefix"Hello".startsWith("He")true
endsWith(String)Checks suffix"Hello".endsWith("lo")true

Transformation Methods

MethodDescriptionExample
toUpperCase() / toLowerCase()Case conversion"Hello".toUpperCase()"HELLO"
replace(old, new)Replace all occurrences"Hello".replace("l", "x")"Hexxo"
split(regex)Splits string by delimiter into array"a,b,c".split(",")["a", "b", "c"]
trim()Removes leading & trailing whitespaces" Hello ".trim()"Hello"

String Comparison: The Right Way

Always use .equals() for content comparison, never ==

This is one of the most common mistakes in Java programming:

OperatorComparesUse Case
==Reference (object identity)Check if two variables point to same object
.equals()ContentCheck if two strings have same text content

Example: Why == Can Be Misleading

String s1 = "abc";                    // String pool
String s2 = new String("abc");        // Heap memory

System.out.println(s1 == s2);         // false (different objects)
System.out.println(s1.equals(s2));   // true (same content)

Best Practices for String Comparison

// ✅ Good: Always use .equals()
if (userInput.equals("yes")) {
    // Handle yes response
}

// ✅ Even better: Null-safe comparison
if ("yes".equals(userInput)) {
    // Avoids NullPointerException if userInput is null
}

// ❌ Bad: Using == for content comparison
if (userInput == "yes") {
    // This might not work as expected!
}

StringBuilder & StringBuffer: When and Why?

Since Strings are immutable, every modification creates a new String object. For heavy string manipulations, this becomes inefficient.

Performance Comparison

ClassThread-Safe?PerformanceBest Use Case
StringBuilder❌ No⚡ FastestSingle-threaded string building
StringBuffer✅ Yes🐌 SlowerMulti-threaded environments
String✅ Yes🐌 Slowest for modificationsImmutable text

StringBuilder Example

StringBuilder sb = new StringBuilder("Hello");
sb.append(" World");           // "Hello World"
sb.insert(5, ",");            // "Hello, World"
sb.replace(7, 12, "Java");    // "Hello, Java"
System.out.println(sb.toString());  // "Hello, Java"

Why Not Just Use String?

Consider this inefficient approach:

// ❌ Inefficient: Creates multiple String objects
String result = "";
for (int i = 0; i < 1000; i++) {
    result += "a";  // Creates new String object each iteration!
}

vs the efficient approach:

// ✅ Efficient: Modifies same StringBuilder object
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("a");  // Modifies existing buffer
}
String result = sb.toString();

Key StringBuilder Methods

Essential methods for StringBuilder manipulation:

MethodDescriptionExample
append()Add data to endsb.append("text")
insert()Insert at given indexsb.insert(5, "new")
replace()Replace substringsb.replace(0, 3, "Hi")
delete()Remove substringsb.delete(2, 5)
reverse()Reverse entire contentsb.reverse()
toString()Convert back to Stringsb.toString()

Real-World Example: Building SQL Query

StringBuilder query = new StringBuilder("SELECT * FROM users");

if (name != null) {
    query.append(" WHERE name = '").append(name).append("'");
}

if (age > 0) {
    query.append(query.indexOf("WHERE") > 0 ? " AND" : " WHERE");
    query.append(" age = ").append(age);
}

String finalQuery = query.toString();

Remember: StringBuilder is mutable — all changes happen to the same object, making it memory efficient.

Common String Patterns for Problem Solving

Here are some frequently used string manipulation patterns:

1. Checking for Palindromes

public boolean isPalindrome(String s) {
    String cleaned = s.toLowerCase().replaceAll("[^a-z0-9]", "");
    StringBuilder sb = new StringBuilder(cleaned);
    return cleaned.equals(sb.reverse().toString());
}

2. Counting Character Frequency

public Map<Character, Integer> countChars(String s) {
    Map<Character, Integer> freq = new HashMap<>();
    for (char c : s.toCharArray()) {
        freq.put(c, freq.getOrDefault(c, 0) + 1);
    }
    return freq;
}

3. String Rotation Check

public boolean isRotation(String s1, String s2) {
    return s1.length() == s2.length() && 
           (s1 + s1).contains(s2);
}

Performance Tips and Best Practices

1. Choose the Right Tool

  • String: For immutable text and simple operations
  • StringBuilder: For single-threaded string building
  • StringBuffer: Only when thread safety is required

2. Avoid String Concatenation in Loops

// ❌ Avoid
String result = "";
for (String item : items) {
    result += item;  // Creates new String each time
}

// ✅ Prefer
StringBuilder sb = new StringBuilder();
for (String item : items) {
    sb.append(item);
}
String result = sb.toString();

3. Use String Literals When Possible

// ✅ Good: Uses String Pool
String greeting = "Hello";

// ❌ Unnecessary: Creates new object
String greeting = new String("Hello");

4. Null Safety

// ✅ Null-safe comparison
if ("expected".equals(userInput)) { /* safe */ }

// ✅ Or check for null first
if (userInput != null && userInput.equals("expected")) { /* safe */ }

Conclusion

  • Strings are immutable → Any modification creates a new object
  • Use .equals() for content comparison, never ==
  • StringBuilder/StringBuffer → For heavy string manipulations
  • String Pool → Helps with memory efficiency for literals
  • Performance matters → Choose the right tool for the job

Happy coding! 💻