Java Strings
A String is a sequence of characters. In Java, strings are objects — instances of the java.lang.String class — but the language gives them special treatment: you write literals in double quotes, and the + operator concatenates them. Strings are immutable: once created, their contents never change.
This tutorial covers creation, the most useful methods, immutability and why it matters, the String pool, StringBuilder for efficient concatenation, and modern features like text blocks (multi-line string literals).
1. Creating Strings
class=class="tok-str">"tok-cmt">// literal - reused from the string pool
String a = "Hello";
class=class="tok-str">"tok-cmt">// new - always a fresh object
String b = new String("Hello");
class=class="tok-str">"tok-cmt">// from a char array
char[] chars = {'J', 'a', 'v', 'a'};
String c = new String(chars);
class=class="tok-str">"tok-cmt">// from a byte array (with a charset)
byte[] bytes = "Java".getBytes();
String d = new String(bytes);
class=class="tok-str">"tok-cmt">// from a StringBuilder
String e = new StringBuilder().append("Hi").append("!").toString();
class=class="tok-str">"tok-cmt">// empty string
String empty = "";
String alsoEmpty = String.Empty; class=class="tok-str">"tok-cmt">// does not exist - use ""
Strings created with new always allocate a fresh object. String literals are taken from the JVM's string pool: when the same literal appears multiple times, the JVM reuses one String instance. This is an optimisation, and it is why == on literals sometimes appears to work — but you must never rely on it.
2. Equality: .equals() vs ==
The single most common string bug in Java is using == to compare contents:
String a = "hello";
String b = "hello";
String c = new String("hello");
System.out.println(a == b); class=class="tok-str">"tok-cmt">// true (pool reuse) - DON'T rely on this!
System.out.println(a == c); class=class="tok-str">"tok-cmt">// false (c is a fresh object)
System.out.println(a.equals(c)); class=class="tok-str">"tok-cmt">// true - correct content comparison
System.out.println(a.equalsIgnoreCase("HELLO")); class=class="tok-str">"tok-cmt">// true
class=class="tok-str">"tok-cmt">// null-safe comparison
import java.util.Objects;
String maybeNull = null;
System.out.println(Objects.equals(a, maybeNull)); class=class="tok-str">"tok-cmt">// false, no NPE
Use s1.equals(s2) or, for null safety, Objects.equals(s1, s2). The method equalsIgnoreCase compares ignoring case.
3. Useful String Methods
Here is a tour of the methods you will use most often:
String s = "Hello, Java!";
s.length(); class=class="tok-str">"tok-cmt">// class="tok-num">12
s.charAt(class="tok-num">0); class=class="tok-str">"tok-cmt">// 'H'
s.indexOf("Java"); class=class="tok-str">"tok-cmt">// class="tok-num">7
s.substring(class="tok-num">7); class=class="tok-str">"tok-cmt">// "Java!"
s.substring(class="tok-num">0, class="tok-num">5); class=class="tok-str">"tok-cmt">// "Hello"
s.toUpperCase(); class=class="tok-str">"tok-cmt">// "HELLO, JAVA!"
s.toLowerCase(); class=class="tok-str">"tok-cmt">// "hello, java!"
s.trim(); class=class="tok-str">"tok-cmt">// remove leading/trailing whitespace
s.replace('l', 'L'); class=class="tok-str">"tok-cmt">// "HeLLo, Java!"
s.replaceAll("[aeiou]", "*"); class=class="tok-str">"tok-cmt">// regex - "H*ll*, J*v*!"
s.split(", "); class=class="tok-str">"tok-cmt">// ["Hello", "Java!"]
s.contains("Java"); class=class="tok-str">"tok-cmt">// true
s.startsWith("Hello"); class=class="tok-str">"tok-cmt">// true
s.endsWith("!"); class=class="tok-str">"tok-cmt">// true
s.equals("Hello, Java!"); class=class="tok-str">"tok-cmt">// true
s.isEmpty(); class=class="tok-str">"tok-cmt">// false
String.format("%s has %d chars", s, s.length()); class=class="tok-str">"tok-cmt">// formatted
4. Immutability and Why It Matters
Once a String object is created, its character sequence cannot change. Operations that appear to modify a String actually return a new String:
String s = "Hello";
s.concat(" World"); class=class="tok-str">"tok-cmt">// returns "Hello World" but s is still "Hello"
s.toUpperCase(); class=class="tok-str">"tok-cmt">// returns "HELLO" but s is still "Hello"
class=class="tok-str">"tok-cmt">// to keep the change, reassign:
s = s.concat(" World"); class=class="tok-str">"tok-cmt">// now s is "Hello World"
class=class="tok-str">"tok-cmt">// strings as HashMap keys - safe because hashcode never changes
Map<String, Integer> counts = new HashMap<>();
counts.put("apple", class="tok-num">3);
class=class="tok-str">"tok-cmt">// there is no way to mutate "apple" and break the map
Immutability gives four concrete benefits. Strings are thread-safe without synchronisation. The JVM can pool literals safely. Strings can be used as HashMap keys without fear of their hashcode changing. Sensitive data such as file paths or SQL is safe from being mutated after construction.
5. StringBuilder for Efficient Concatenation
In a tight loop, + on strings is wasteful: each + creates a new String object and copies the characters. StringBuilder holds an internal mutable buffer and grows it as needed:
class=class="tok-str">"tok-cmt">// SLOW: each + creates a new String
String csv = "";
for (int i = class="tok-num">0; i < class="tok-num">1000; i++) {
csv += i + ","; class=class="tok-str">"tok-cmt">// O(n^class="tok-num">2) - class="tok-num">1000 allocations
}
class=class="tok-str">"tok-cmt">// FAST: StringBuilder mutates a single buffer
StringBuilder sb = new StringBuilder();
for (int i = class="tok-num">0; i < class="tok-num">1000; i++) {
sb.append(i).append(",");
}
String csv2 = sb.toString(); class=class="tok-str">"tok-cmt">// O(n)
class=class="tok-str">"tok-cmt">// Java class="tok-num">8+ Stream approach
String csv3 = java.util.stream.IntStream.range(class="tok-num">0, class="tok-num">1000)
.mapToObj(String::valueOf)
.collect(java.util.stream.Collectors.joining(","));
For single-line concatenations of two or three strings, the JVM optimises + into a StringBuilder call for you. The concern is only with loops or repeated concatenation across statements.
6. Text Blocks (Java 15+)
Multi-line string literals are written with three double quotes:
String json = """
{
"name": "Alice",
"age": class="tok-num">30,
"roles": ["admin", "user"]
}
""";
class=class="tok-str">"tok-cmt">// strip incidental whitespace with indent()
String sql = """
SELECT id, name, email
FROM users
WHERE active = true
ORDER BY name
""";
Text blocks preserve the line structure. Leading whitespace is stripped intelligently (the JVM aligns to the least-indented line). They are perfect for embedding JSON, SQL, HTML or YAML in Java source.
7. String Formatting
Use String.format or System.out.printf for formatted output. The format string uses C-style specifiers:
String name = "Alice";
int age = class="tok-num">30;
double salary = class="tok-num">95_000.5;
String s = String.format("%s is %d years old and earns $%,.class="tok-num">2f",
name, age, salary);
class=class="tok-str">"tok-cmt">// "Alice is class="tok-num">30 years old and earns $class="tok-num">95,class="tok-num">000.50"
System.out.printf("%-10s | %class="tok-num">5d | %class="tok-num">8.2f%n", name, age, salary);
class=class="tok-str">"tok-cmt">// "Alice | class="tok-num">30 | class="tok-num">95000.50"
class=class="tok-str">"tok-cmt">// %s string, %d int, %f double, %b boolean, %c char, %n newline
class=class="tok-str">"tok-cmt">// %-10s left-aligned in class="tok-num">10 chars, %,d groups thousands, %class="tok-num">5d pad to class="tok-num">5
8. Modern Alternatives
Java 8 added String.join, String.join with a delimiter, and Collectors.joining for streams:
class=class="tok-str">"tok-cmt">// String.join with a delimiter
String csv = String.join(",", "a", "b", "c"); class=class="tok-str">"tok-cmt">// "a,b,c"
class=class="tok-str">"tok-cmt">// joining a collection
List<String> names = List.of("Alice", "Bob", "Carol");
String joined = String.join(", ", names); class=class="tok-str">"tok-cmt">// "Alice, Bob, Carol"
class=class="tok-str">"tok-cmt">// joining via stream collector
String result = names.stream()
.collect(Collectors.joining(", ", "[", "]"));
class=class="tok-str">"tok-cmt">// "[Alice, Bob, Carol]"
class=class="tok-str">"tok-cmt">// Java class="tok-num">11+
"abc".repeat(class="tok-num">3); class=class="tok-str">"tok-cmt">// "abcabcabc"
"".isBlank(); class=class="tok-str">"tok-cmt">// true
" hi ".strip(); class=class="tok-str">"tok-cmt">// "hi" (Unicode-aware)
"a\nb\nc".lines(); class=class="tok-str">"tok-cmt">// Stream of "a","b","c"
Java 11 added String.repeat, String.isBlank, String.strip (Unicode-aware trim) and String.lines for splitting by line terminators.
Exercises
- Reverse a string with
new StringBuilder(s).reverse(). - Count the vowels in a string by iterating with a
forloop andswitch. - Replace all spaces in a string with underscores without using
replaceAll. - Build a 1000-line CSV string with
StringBuilderand print its length. - Write a multi-line SQL query using a text block and print it.