Topic 9: Strings in Java
📖 5 min read · 🎯 intermediate · 🧭 Prerequisites: inheritance-in-java, fragments-intents-intent-filters
Why this matters
Here's the thing — almost everything your Android app shows to the user is text. Button labels, error messages, names, addresses, search queries — all of it is a String. And when you're just starting out, it looks simple: just put some words in quotes and move on. But Java has three different tools for working with text — String, StringBuilder, and StringBuffer — and picking the wrong one can quietly slow your app down or give you results you didn't expect. Once you understand why they exist, a lot of Java code will start making more sense.
What You'll Learn
- Create
Stringobjects using literals and thenewkeyword - Use the most important built-in
Stringmethods (length,charAt,substring,replace,equals,toUpperCase,trim, and more) - Understand why
Stringobjects are immutable and what that means in practice - Choose between
StringBuilderandStringBufferfor mutable text manipulation
The Analogy
Think of a Java String as a laminated ID card — the text printed on it is fixed the moment it is made. If you need a corrected card, you don't erase the old one; you print a brand-new card and hand that out instead. The original card sits unchanged in a drawer. StringBuilder, by contrast, is a whiteboard: you can write, erase, and rewrite as many times as you like without ever touching a new board. StringBuffer is that same whiteboard, but mounted behind a locked cabinet — only one person can write at a time, making it safe for a busy shared office but a bit slower to access.
Chapter 1: Creating Strings
Java gives you two ways to bring a String into existence.
String literals — the most common form. The JVM stores literals in a special pool and may reuse the same object if the same text appears again, saving memory.
The new keyword — forces the JVM to allocate a brand-new object on the heap, bypassing the pool. Rarely needed, but worth knowing it exists.
public class StringDemo {
public static void main(String[] args) {
// Creating strings using string literals
String greeting = "Hello, Full Stack World!";
// Creating strings using the new keyword
String message = new String("Welcome to the adventure of Strings!");
// Display the strings
System.out.println(greeting);
System.out.println(message);
}
}
Output:
Hello, Full Stack World!
Welcome to the adventure of Strings!
Chapter 2: String Methods
String ships with a rich set of built-in methods. The eight you will reach for most often are:
length()— number of characters in the string- Concatenation (
+operator) — joins two strings into one new string charAt(int index)— returns the character at a zero-based positionsubstring(int start, int end)— extracts characters fromstart(inclusive) toend(exclusive)replace(CharSequence target, CharSequence replacement)— swaps every occurrence oftargetforreplacementequals(Object other)— compares content, not reference (always prefer over==)toUpperCase()/toLowerCase()— returns a case-converted copytrim()— removes leading and trailing whitespace
Here is all eight in a single demo:
public class StringMethodsDemo {
public static void main(String[] args) {
String str = " Full Stack World ";
// Length of the string
System.out.println("Length: " + str.length());
// Concatenation
String greeting = "Hello," + str.trim();
System.out.println("Greeting: " + greeting);
// Character access
char firstChar = str.charAt(1);
System.out.println("First character: " + firstChar);
// Substring
String world = str.substring(6, 11);
System.out.println("Substring: " + world);
// Replace
String replacedStr = str.replace("Stack", "Developer");
System.out.println("Replaced: " + replacedStr);
// Comparison
boolean equals = str.equals(" Full Stack World ");
System.out.println("Equals: " + equals);
// Case conversion
System.out.println("Upper case: " + str.toUpperCase());
System.out.println("Lower case: " + str.toLowerCase());
// Trim
System.out.println("Trimmed: " + str.trim());
}
}
Expected output:
Length: 18
Greeting: Hello,Full Stack World
First character: F
Substring: Stack
Replaced: Full Developer World
Equals: true
Upper case: FULL STACK WORLD
Lower case: full stack world
Trimmed: Full Stack World
Chapter 3: String Immutability
Once a String object is created, its content cannot change. Every method that appears to "modify" a string — concat, replace, toUpperCase, etc. — actually returns a new String object and leaves the original untouched.
public class StringImmutability {
public static void main(String[] args) {
String original = "Immutable";
String modified = original.concat(" String");
System.out.println("Original: " + original);
System.out.println("Modified: " + modified);
}
}
Output:
Original: Immutable
Modified: Immutable String
original still holds "Immutable". concat produced a brand-new object stored in modified.
Why immutability matters for Android:
- Security — a
Stringpassed to an authentication method cannot be altered mid-flight by another thread. - Thread safety — multiple threads can read the same
Stringwithout synchronization. - String pool reuse — the JVM can safely hand out the same object to multiple callers, reducing heap pressure.
The downside surfaces when you build strings in a loop. Each + inside a loop allocates a fresh object. For tight loops or frequent concatenation, reach for StringBuilder instead.
Chapter 4: StringBuilder and StringBuffer
Both classes represent mutable sequences of characters — you modify the same underlying buffer rather than creating new objects.
StringBuilder
StringBuilder is not synchronized. Only one thread should use a given instance at a time, but in return you get faster performance. This is the right choice in the vast majority of Android code running on a single thread.
public class StringBuilderDemo {
public static void main(String[] args) {
StringBuilder sb = new StringBuilder("Full Stack");
sb.append(" World");
System.out.println(sb.toString()); // Full Stack World
sb.insert(5, " Developer");
System.out.println(sb.toString()); // Full Developer Stack World
sb.replace(5, 14, " Engineer");
System.out.println(sb.toString()); // Full Engineer Stack World
sb.delete(5, 14);
System.out.println(sb.toString()); // Full Stack World
}
}
Key StringBuilder methods:
| Method | What it does |
|---|---|
append(String s) | Adds s to the end of the buffer |
insert(int offset, String s) | Inserts s at position offset |
replace(int start, int end, String s) | Replaces characters from start to end with s |
delete(int start, int end) | Removes characters from start to end |
toString() | Converts the buffer to an immutable String |
StringBuffer
StringBuffer is synchronized — every method is thread-safe. Use it only when multiple threads genuinely share the same buffer. The synchronization overhead makes it slower than StringBuilder for single-threaded use.
public class StringBufferDemo {
public static void main(String[] args) {
StringBuffer sb = new StringBuffer("Full Stack");
sb.append(" World");
System.out.println(sb.toString()); // Full Stack World
sb.insert(5, " Developer");
System.out.println(sb.toString()); // Full Developer Stack World
sb.replace(5, 14, " Engineer");
System.out.println(sb.toString()); // Full Engineer Stack World
sb.delete(5, 14);
System.out.println(sb.toString()); // Full Stack World
}
}
The API is identical to StringBuilder; the only difference is thread-safety behaviour under the hood.
Quick comparison
String → immutable, thread-safe, great for fixed text
StringBuilder → mutable, NOT thread-safe, best performance for single thread
StringBuffer → mutable, thread-safe, use only with shared mutable state across threads
flowchart TD
A[Need a string?] --> B{Will it change?}
B -- No --> C[String literal or new String]
B -- Yes --> D{Shared across threads?}
D -- No --> E[StringBuilder]
D -- Yes --> F[StringBuffer]
🧪 Try It Yourself
Task: Write a program that takes the sentence " Android Development is Powerful " and, using only built-in String methods, prints:
- The trimmed version
- The length of the trimmed version
- The sentence in all caps
- The word
"Powerful"replaced with"Awesome" - The character at index 7 of the trimmed string
Success criterion: Your console output should look like this:
Trimmed: Android Development is Powerful
Length: 35
Upper: ANDROID DEVELOPMENT IS POWERFUL
Replaced: Android Development is Awesome
Char at 7: D
Starter snippet:
public class StringExercise {
public static void main(String[] args) {
String sentence = " Android Development is Powerful ";
String trimmed = sentence.trim();
// 1. Print trimmed
// 2. Print trimmed.length()
// 3. Print trimmed.toUpperCase()
// 4. Print trimmed.replace("Powerful", "Awesome")
// 5. Print trimmed.charAt(7)
}
}
🔍 Checkpoint Quiz
Q1. Why are Java String objects immutable, and what is one practical benefit of that design?
Q2. Given the following code, what does it print?
String s = "Hello World";
System.out.println(s.substring(6, 11));
A) Hello
B) World
C) World!
D) o Wor
Q3. A colleague writes this loop to build a comma-separated list of 10,000 IDs:
String result = "";
for (int id : idList) {
result = result + id + ",";
}
What is the problem, and how would you fix it?
Q4. You are writing an Android background service where two worker threads append log lines to the same buffer. Which class should you use — StringBuilder or StringBuffer — and why?
A1. String objects are immutable because once created their internal character array cannot change. A key benefit: immutability makes String inherently thread-safe — multiple threads can read the same object without any synchronization.
A2. B) World — substring(6, 11) extracts characters at indices 6, 7, 8, 9, 10 (the end index is exclusive), which spells World.
A3. Each iteration of the loop creates a brand-new String object for result, throwing away the old one. For 10,000 iterations that means 10,000 allocations and copies, causing significant heap churn. Fix: use a StringBuilder and call append() inside the loop, then call toString() once at the end.
A4. StringBuffer — because it is synchronized. When two threads share the same mutable buffer, unsynchronized writes (as with StringBuilder) can interleave and corrupt the data. StringBuffer's method-level locks prevent that.
🪞 Recap
- Java
Stringis an immutable sequence of characters; every "modification" produces a new object. - The eight essential
Stringmethods —length,charAt,substring,replace,equals,toUpperCase,toLowerCase,trim— cover the majority of text operations. - Immutability makes
Stringthread-safe and enables JVM string-pool optimizations, but it is inefficient for repeated concatenation. StringBuilderis the go-to for single-threaded mutable string building; it is faster because it skips synchronization.StringBuffermirrorsStringBuilder's API but adds thread safety through synchronized methods — use it only when threads genuinely share the buffer.
📚 Further Reading
- Java String API — Oracle Docs — the authoritative reference for every
Stringmethod and its exact contract - StringBuilder API — Oracle Docs — full method list including
reverse,capacity, andensureCapacity - Effective Java, Item 17: Minimize Mutability — Joshua Bloch's deep dive on why immutable objects are simpler and safer
- ⬅️ Previous: Fragments, Intents & Intent Filters
- ➡️ Next: Advanced UI Concepts: Themes, Styles & Localization