Polyglot
Languages Java strings
Java § strings

Strings

Java provides String (an immutable reference type representing a sequence of UTF-16 code units) and two mutable builders, StringBuilder and StringBuffer. The String type is reference-typed but exhibits value-like behaviour: equals compares characters, hashCode is content-based, and the type is immutable. The language admits four string-literal forms — regular, escaped, text blocks, and concatenated. Formatting has evolved across revisions: the original String.format and printf-family methods, the C-inherited composite formatting, and (since Java 15) the more readable text blocks. Templates, a proposed string-interpolation feature, were withdrawn in Java 23 and remain absent.

String and immutability

String instances are immutable: every operation that appears to modify a string returns a new string instead:

String s = "hello";
String t = s.toUpperCase();        // returns a new string; s is unchanged
System.out.println(s);              // hello
System.out.println(t);              // HELLO

The immutability has consequences:

  • Strings can be safely shared across threads without synchronisation.
  • Equality comparison uses equals; == compares references, which the JVM’s interning of literals makes intermittently misleading.
  • Building a string by concatenation in a loop allocates one new string per iteration; the cost grows quadratically. The defence is StringBuilder.
String a = "hello";
String b = "hello";
boolean eq    = a.equals(b);          // true: content comparison
boolean ref_eq = a == b;              // typically true (interned), but not always

The JVM interns string literals — equal literals across the program share a single String instance — so reference equality often holds for literals. Programs should not rely on this; the conventional discipline is equals for content comparison and == only when reference identity is genuinely the question.

String literals

Three principal literal forms.

Regular

"hello"
"line1\nline2"
"quote: \""
"unicode: é"          // é
"hex: \\x41"               // \x41 (literal backslash-x-4-1)

Backslash escapes are interpreted. The standard escapes are: \n, \t, \r, \b, \f, \\, \", \', \0, \uXXXX, \xxx (octal up to three digits).

Text blocks

Java 15 introduced text blocks, denoted by triple double-quotes:

String json = """
    {
        "name": "alice",
        "age": 30
    }
    """;

String sql = """
    SELECT id, name
    FROM users
    WHERE active = true
    ORDER BY name
    """;

The opening """ must be followed by a newline; the content begins on the next line. The compiler aligns the content to the indentation of the closing """ — the common leading whitespace is stripped. The mechanism is the conventional choice for JSON, SQL, regular expressions, and any literal where readability of the multi-line structure matters.

Escapes within text blocks work as in regular strings, with two text-block-specific additions:

  • \ at end of line — line continuation; suppresses the newline.
  • \s — a literal space (useful when trailing whitespace must be preserved).
String continued = """
    one two \
    three four
    """;
// "one two three four\n"

String trailing = """
    keeps trailing space \s
    on this line
    """;

Concatenation

Adjacent string literals are not automatically concatenated (unlike C); the conventional form uses the + operator:

String banner =
    "+-------------------+\n" +
    "|     polyglot      |\n" +
    "+-------------------+\n";

The compiler folds the constant concatenation at compile time, so the result is a single string literal in the class file with no runtime cost.

StringBuilder and StringBuffer

For mutable string construction, the language provides two classes:

  • StringBuilder — the conventional choice; not thread-safe; faster.
  • StringBuffer — the older, thread-safe variant; rarely useful in modern code.
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 1000; i++) {
    sb.append("line ").append(i).append('\n');
}
String result = sb.toString();

The principal operations:

  • append(value) — append any value’s string representation.
  • insert(pos, value) — insert at position.
  • delete(start, end) — remove a range.
  • replace(start, end, str) — replace a range with a string.
  • reverse() — reverse the contents.
  • length(), charAt(i), indexOf(s), setCharAt(i, c) — standard string-like queries and updates.

Initial capacity may be reserved:

StringBuilder sb = new StringBuilder(1024);     // pre-allocate for 1024 chars

For known final size, the construction avoids reallocation as the buffer grows.

The String operations

The String class exposes a substantial set of instance and static methods:

OperationEffect
s.length()Number of UTF-16 code units (not characters).
s.charAt(i)Character at index i.
s.indexOf(value) / s.lastIndexOf(value)Position of substring or character; -1 if absent.
s.contains(value)Substring containment.
s.startsWith(value) / s.endsWith(value)Prefix/suffix tests.
s.substring(start, end)Returns a sub-string.
s.replace(old, new)Replaces all occurrences.
s.trim() / s.strip()Strip whitespace; strip is Unicode-aware (since Java 11).
s.toUpperCase() / s.toLowerCase()Case conversion (locale-aware by default).
s.split(regex)Tokenise by a regex.
String.join(sep, parts)Concatenate with a separator.
s.equals(other)Content equality.
s.equalsIgnoreCase(other)Case-insensitive content equality.
s.compareTo(other)Lexicographic comparison.
s.compareToIgnoreCase(other)Case-insensitive lexicographic.
s.isEmpty()Whether the string has length zero.
s.isBlank()Whether the string is empty or contains only whitespace (since Java 11).
s.lines()Stream of lines (since Java 11).
s.chars()IntStream of code units.
s.codePoints()IntStream of code points (handles surrogates).
s.formatted(args...)Format using s as the format string (since Java 15).

The conventional discipline for string comparison:

  • Use s.equals(t) for content equality, never s == t.
  • Use s.equalsIgnoreCase(t) for case-insensitive equality.
  • Use s.compareTo(t) for ordering.

Encoding and the UTF-16 underlying representation

Java strings store UTF-16 code units. A char is a 16-bit value; characters outside the Basic Multilingual Plane (code points above U+FFFF) are represented as a surrogate pair — two char values that together encode one code point.

String s = "🎉";
int    n = s.length();           // 2: the emoji is a surrogate pair

for (int i = 0; i < s.length(); i++) {
    char c = s.charAt(i);        // each code unit, not each grapheme
}

s.codePoints().forEach(cp -> {
    System.out.println(Integer.toHexString(cp));
});

The discipline:

  • s.length() is the count of code units, not characters.
  • s.charAt(i) is a code unit; for code-point access use s.codePointAt(i).
  • For byte conversion, use the explicit Charset: s.getBytes(StandardCharsets.UTF_8).
import java.nio.charset.StandardCharsets;

byte[] utf8 = "hello, 世界".getBytes(StandardCharsets.UTF_8);
String back = new String(utf8, StandardCharsets.UTF_8);

Since Java 9, the JVM may store strings using compact representations internally — Latin-1 if the string contains only ASCII/Latin-1 characters, UTF-16 otherwise. The optimisation is invisible to user code; the public surface remains UTF-16.

Formatting

Java admits several formatting interfaces.

String.format and printf

The String.format method produces a formatted string using printf-style format specifiers:

String s = String.format("%s is %d years old", name, age);
String p = String.format("pi: %.2f", 3.14159);

System.out.printf("hello, %s%n", name);
System.out.println(String.format("debug: %s = %d", varName, value));

The format specifiers are:

SpecifierTypeEffect
%sanytoString() of the argument
%dintegerdecimal
%x / %Xintegerhexadecimal (lower / upper)
%ointegeroctal
%banytrue / false based on null and boolean value
%ccharcharacter
%ffloatingfixed-point
%e / %Efloatingscientific
%g / %Gfloatinggeneral
%n(none)platform-specific newline
%%(none)literal %

Width, precision, and flag modifiers (-, 0, +, ,, () work as in C’s printf.

String.formatted

Java 15 introduced s.formatted(args...) as an instance-method form:

String s = "%s is %d years old".formatted(name, age);

The form admits text blocks as the format string:

String s = """
    %s is %d years old.
    """.formatted(name, age);

The combination is the conventional contemporary pattern for multi-line formatted output.

MessageFormat

java.text.MessageFormat provides a positional-placeholder format:

import java.text.MessageFormat;

String s = MessageFormat.format("{0} is {1, number, integer} years old", name, age);

The format specifiers are different from printf: positional {0}, {1}, with optional type and style hints. The mechanism is principally useful for internationalised messages where the argument order may vary by locale.

Comparison and culture

String comparison in Java has several axes:

"hello".equals("hello")                       // true
"hello".equalsIgnoreCase("HELLO")              // true
"hello".compareTo("world")                    // negative
"hello".compareToIgnoreCase("HELLO")          // 0

For culture-aware comparison, use Collator:

import java.text.Collator;
import java.util.Locale;

Collator de = Collator.getInstance(Locale.GERMAN);
de.compare("ä", "z");                          // locale-aware ordering

The conventional discipline:

  • Use equals and equalsIgnoreCase for ordinary equality.
  • Use compareTo for sorting in ASCII order.
  • Use Collator only when culture-aware ordering matters (user-facing sorted lists).

A note on string templates

Java 21 introduced string templates as a preview, with a syntax of the form STR."Hello, \{name}". The preview was withdrawn in Java 23 after a redesign was deemed necessary; as of Java 25 the feature is not yet shipped. Until it ships, the conventional alternative is String.format / formatted for formatted output and StringBuilder for incremental construction.