Java Tutorial
🔍

Java String Class

Java String Class

java.lang.String is the class every Java developer uses from day one — but most developers never look inside it. The String class is not just a container for characters. It is a carefully engineered immutable type with a compact internal representation, a pool mechanism for memory efficiency, a rich API of over sixty methods, and a set of behaviours around equality, hashing, and comparison that influence how it works in collections, security, and concurrent systems.

This article is a complete reference for the String class — from how it stores characters internally in Java 9+, through every important method category, to patterns that appear in interviews at every level.

The String Class — Internal Structure

Before Java 9, String stored characters in a char[] — each character taking 2 bytes (UTF-16). Java 9 introduced Compact Strings: if all characters in a string fit within Latin-1 (ISO-8859-1, the first 256 Unicode code points), the string is stored in a byte[] using 1 byte per character — half the memory. If any character is outside Latin-1, a byte[] with UTF-16 encoding is used (2 bytes per character).

String internal structure (Java 9+):

  public final class String {
      private final byte[] value;          ← character data (1 or 2 bytes per char)
      private final byte coder;            ← LATIN1 = 0, UTF16 = 1
      private int hash;                    ← cached hashCode (0 = not computed yet)
      private boolean hashIsZero;          ← true only if hash genuinely computed to 0
  }

  Example: "hello" (all Latin-1)
  value   = [104, 101, 108, 108, 111]     ← h, e, l, l, o as bytes
  coder   = 0 (LATIN1)                    ← 5 bytes used

  Example: "नमस्ते" (Devanagari — outside Latin-1)
  value   = [0xE0,0xA4,... 12 bytes]      ← UTF-16 encoding
  coder   = 1 (UTF16)                     ← 2 bytes per character

  Before Java 9:
  value   = char[]{104, 101, 108, 108, 111}  ← always 2 bytes per char (10 bytes for "hello")

This compact representation reduces memory for typical English-language applications by roughly 50% — a significant improvement for systems that hold millions of strings in memory.

String Creation — All Ways

// File: StringCreationMethods.java public class StringCreationMethods { public static void main(String[] args) { // 1 — String literal (most common — goes to String Pool) String s1 = "hello"; // 2 — new String from literal (heap object — avoid this) String s2 = new String("hello"); // 3 — new String from char array char[] chars = {'J', 'a', 'v', 'a'}; String s3 = new String(chars); // 4 — new String from char array, offset and count String s4 = new String(chars, 0, 2); // "Ja" // 5 — new String from byte array (using platform default charset) byte[] bytes = {72, 101, 108, 108, 111}; // ASCII: H e l l o String s5 = new String(bytes); // 6 — new String from byte array with explicit charset String s6 = new String(bytes, java.nio.charset.StandardCharsets.UTF_8); // 7 — new String from StringBuilder or StringBuffer StringBuilder sb = new StringBuilder("Dynamic"); String s7 = new String(sb); // 8 — String.valueOf() — converts any type String s8 = String.valueOf(42); // "42" String s9 = String.valueOf(3.14); // "3.14" String s10 = String.valueOf(true); // "true" String s11 = String.valueOf('A'); // "A" String s12 = String.valueOf((Object)null); // "null" — no NPE // 9 — Concatenation — both compile-time and runtime String s13 = "Hello" + ", " + "World"; // compile-time constant System.out.println("s1 = " + s1); System.out.println("s3 = " + s3); System.out.println("s4 = " + s4); System.out.println("s5 = " + s5); System.out.println("s7 = " + s7); System.out.println("s8 = " + s8); System.out.println("s12 = " + s12); System.out.println("s13 = " + s13); System.out.println(); // Pool vs Heap — == shows which share the same object System.out.println("s1 == s2 : " + (s1 == s2)); // false — heap System.out.println("s1 == 'hello' : " + (s1 == "hello")); // true — pool System.out.println("s1.equals(s2) : " + s1.equals(s2)); // true — content } }
Output:
s1  = hello
s3  = Java
s4  = Ja
s5  = Hello
s7  = Dynamic
s8  = 42
s12 = null
s13 = Hello, World

s1 == s2       : false
s1 == 'hello'  : true
s1.equals(s2)  : true

String Length and Character Access

// File: StringLengthCharAccess.java public class StringLengthCharAccess { public static void main(String[] args) { String text = "DevStackFlow"; // length() — number of chars (not bytes internally) System.out.println("length() : " + text.length()); // 12 // charAt(index) — char at zero-based position System.out.println("charAt(0) : " + text.charAt(0)); // D System.out.println("charAt(11) : " + text.charAt(text.length() - 1)); // w // codePointAt(index) — Unicode code point at position System.out.println("codePointAt(0) : " + text.codePointAt(0)); // 68 (D) // toCharArray() — copy of internal chars char[] arr = text.toCharArray(); System.out.println("toCharArray()[0] : " + arr[0]); // D // getBytes() — byte representation using default charset byte[] bytes = text.getBytes(); System.out.println("getBytes length : " + bytes.length); // 12 (ASCII range) // getBytes with explicit charset byte[] utf8 = text.getBytes(java.nio.charset.StandardCharsets.UTF_8); System.out.println("UTF-8 bytes len : " + utf8.length); // 12 // chars() — IntStream of char values (Java 8+) System.out.print("chars() stream : "); text.chars().limit(5).forEach(c -> System.out.print((char)c + " ")); System.out.println(); // isEmpty() and isBlank() System.out.println(); System.out.println("''.isEmpty() : " + "".isEmpty()); // true System.out.println("' '.isEmpty() : " + " ".isEmpty()); // false System.out.println("' '.isBlank() : " + " ".isBlank()); // true } }
Output:
length()         : 12
charAt(0)        : D
charAt(11)       : w
codePointAt(0)   : 68
toCharArray()[0] : D
getBytes length  : 12
UTF-8 bytes len  : 12
chars() stream   : D e v S t

''.isEmpty()     : true
'  '.isEmpty()   : false
'  '.isBlank()   : true

Searching Within a String

// File: StringSearchDemo.java public class StringSearchDemo { public static void main(String[] args) { String text = "Java programming is fun. Java is everywhere."; // indexOf — first occurrence of char or substring System.out.println("indexOf('J') : " + text.indexOf('J')); // 0 System.out.println("indexOf('Java') : " + text.indexOf("Java")); // 0 System.out.println("indexOf('Java', 5) : " + text.indexOf("Java", 5)); // 25 — from pos 5 System.out.println("indexOf('Python') : " + text.indexOf("Python")); // -1 not found // lastIndexOf — last occurrence System.out.println("lastIndexOf('Java') : " + text.lastIndexOf("Java")); // 25 System.out.println("lastIndexOf('.') : " + text.lastIndexOf('.')); // 43 // contains — whether substring exists System.out.println("contains('fun') : " + text.contains("fun")); // true System.out.println("contains('boring') : " + text.contains("boring")); // false // startsWith and endsWith System.out.println("startsWith('Java') : " + text.startsWith("Java")); // true System.out.println("endsWith('.') : " + text.endsWith(".")); // true System.out.println("startsWith('is', 20) : " + text.startsWith("is", 20)); // true — at offset 20 // matches — tests entire string against a regex String phone = "9876543210"; System.out.println("phone matches \\d{10} : " + phone.matches("\\d{10}")); // true System.out.println("phone matches \\d{9} : " + phone.matches("\\d{9}")); // false System.out.println(); // regionMatches — compare a region of two strings String s1 = "Hello World"; String s2 = "world is great"; // regionMatches(ignoreCase, thisOffset, other, otherOffset, length) boolean match = s1.regionMatches(true, 6, s2, 0, 5); System.out.println("regionMatches (ignoreCase): " + match); // true — "World" == "world" } }
Output:
indexOf('J')          : 0
indexOf('Java')       : 0
indexOf('Java', 5)    : 25
indexOf('Python')     : -1
lastIndexOf('Java')   : 25
lastIndexOf('.')      : 43
contains('fun')       : true
contains('boring')    : false
startsWith('Java')    : true
endsWith('.')         : true
startsWith('is', 20) : true
phone matches \d{10} : true
phone matches \d{9}  : false

regionMatches (ignoreCase): true

Extracting Substrings

// File: StringExtractionDemo.java public class StringExtractionDemo { public static void main(String[] args) { String src = "Order: ORD-2024-001 | Customer: Priya Sharma | Amount: Rs.4999"; // substring(start) — from start to end System.out.println("substring(7) : " + src.substring(7)); // substring(start, end) — start inclusive, end exclusive System.out.println("substring(7,19) : " + src.substring(7, 19)); // ORD-2024-001 // Dynamic extraction — find by marker and slice int custStart = src.indexOf("Customer: ") + 10; int custEnd = src.indexOf(" |", custStart); System.out.println("Customer name : " + src.substring(custStart, custEnd == -1 ? src.length() : custEnd)); System.out.println(); // split — breaks at regex matches String csv = "Priya,28,Bengaluru,Developer,85000"; String[] parts = csv.split(","); for (int i = 0; i < parts.length; i++) { System.out.println("parts[" + i + "] : " + parts[i]); } System.out.println(); // split with limit String sentence = "one:two:three:four:five"; String[] limited = sentence.split(":", 3); System.out.println("split with limit=3 : " + java.util.Arrays.toString(limited)); // split preserving trailing empty — limit = -1 String trailing = "A,B,,C,"; System.out.println("Normal split : " + java.util.Arrays.toString(trailing.split(","))); System.out.println("Limit=-1 split : " + java.util.Arrays.toString(trailing.split(",", -1))); } }
Output:
substring(7)       : ORD-2024-001 | Customer: Priya Sharma | Amount: Rs.4999
substring(7,19)    : ORD-2024-001
Customer name      : Priya Sharma

parts[0]           : Priya
parts[1]           : 28
parts[2]           : Bengaluru
parts[3]           : Developer
parts[4]           : 85000

split with limit=3 : [one, two, three:four:five]
Normal split       : [A, B, , C]
Limit=-1 split     : [A, B, , C, ]

Modifying and Transforming Strings

Every method returns a new String — none modifies the original.

// File: StringTransformDemo.java public class StringTransformDemo { public static void main(String[] args) { String raw = " Hello, DevStackFlow! "; // Case conversion System.out.println("toUpperCase() : " + raw.trim().toUpperCase()); System.out.println("toLowerCase() : " + raw.trim().toLowerCase()); // Whitespace removal System.out.println("trim() : '" + raw.trim() + "'"); System.out.println("strip() : '" + raw.strip() + "'"); System.out.println("stripLeading() : '" + raw.stripLeading() + "'"); System.out.println("stripTrailing() : '" + raw.stripTrailing() + "'"); System.out.println(); // Replace — literal and regex String text = " too many spaces between words "; System.out.println("replace space : '" + text.trim().replace(" ", "-") + "'"); System.out.println("replaceAll \\s+ : '" + text.trim().replaceAll("\\s+", " ") + "'"); System.out.println("replaceFirst : '" + text.trim().replaceFirst("\\s+", "_") + "'"); System.out.println(); // concat — appends a String (similar to +) String part1 = "Hello"; String part2 = " World"; System.out.println("concat() : " + part1.concat(part2)); // repeat — Java 11+ System.out.println("'-'.repeat(20) : " + "-".repeat(20)); System.out.println("'ab'.repeat(4) : " + "ab".repeat(4)); System.out.println(); // indent — Java 12+ — adds leading whitespace to each line String multiline = "Line 1\nLine 2\nLine 3"; System.out.println("Original:\n" + multiline); System.out.println("indent(4):\n" + multiline.indent(4)); // strip + lines — Java 11+ — splits on line terminators String block = " first \n second \n third "; System.out.println("lines().stripped:"); block.lines() .map(String::strip) .forEach(System.out::println); } }
Output:
toUpperCase()    : HELLO, DEVSTACKFLOW!
toLowerCase()    : hello, devstackflow!
trim()           : 'Hello, DevStackFlow!'
strip()          : 'Hello, DevStackFlow!'
stripLeading()   : 'Hello, DevStackFlow!  '
stripTrailing()  : '  Hello, DevStackFlow!'

replace space    : 'too---many---spaces---between---words'
replaceAll \s+  : 'too many spaces between words'
replaceFirst     : 'too_many   spaces   between   words'

concat()         : Hello World
'-'.repeat(20)   : --------------------
'ab'.repeat(4)   : abababab

Original:
Line 1
Line 2
Line 3
indent(4):
    Line 1
    Line 2
    Line 3

lines().stripped:
first
second
third

Comparing Strings

// File: StringComparisonFull.java import java.util.Objects; public class StringComparisonFull { public static void main(String[] args) { String a = "Apple"; String b = "banana"; String c = "Apple"; String d = null; // equals — content, case-sensitive System.out.println("equals() : " + a.equals(c)); // true System.out.println("equals() : " + a.equals(b)); // false // equalsIgnoreCase — content, case-insensitive System.out.println("equalsIgnoreCase() : " + "APPLE".equalsIgnoreCase(a)); // true // compareTo — lexicographic order // Returns: negative if a < b, 0 if equal, positive if a > b System.out.println("compareTo(b) : " + a.compareTo(b)); // negative (A < b) System.out.println("compareTo(c) : " + a.compareTo(c)); // 0 — equal System.out.println("compareToIgnoreCase(b) : " + a.compareToIgnoreCase("apple")); // 0 System.out.println(); // Objects.equals — null-safe comparison System.out.println("Objects.equals(a,c) : " + Objects.equals(a, c)); // true System.out.println("Objects.equals(a,null) : " + Objects.equals(a, d)); // false System.out.println("Objects.equals(null,null): " + Objects.equals(d, d)); // true System.out.println(); // contentEquals — compare with CharSequence (StringBuilder, StringBuffer) StringBuilder sb = new StringBuilder("Apple"); System.out.println("contentEquals(sb) : " + a.contentEquals(sb)); // true System.out.println("contentEquals('Apple') : " + a.contentEquals("Apple")); // true } }
Output:
equals()                : true
equals()                : false
equalsIgnoreCase()      : true
compareTo(b)            : -31
compareTo(c)            : 0
compareToIgnoreCase(b)  : 0

Objects.equals(a,c)     : true
Objects.equals(a,null)  : false
Objects.equals(null,null): true

contentEquals(sb)       : true
contentEquals('Apple')  : true

String Joining and Combining

// File: StringJoiningDemo.java import java.util.List; public class StringJoiningDemo { public static void main(String[] args) { // String.join — static, joins with delimiter System.out.println(String.join(", ", "Mumbai", "Delhi", "Bengaluru", "Pune")); System.out.println(String.join(" | ", new String[]{"Java", "SQL", "Spring"})); // join with Iterable List<String> names = List.of("Priya", "Rohan", "Sneha"); System.out.println(String.join(", ", names)); System.out.println(); // String.format — structured assembly String msg = String.format("Dear %s, order %s of Rs.%.2f is %s.", "Priya", "ORD-001", 1299.50, "PLACED"); System.out.println(msg); System.out.println(); // concat vs + String base = "Hello"; String joined = base.concat(", World").concat("!"); System.out.println("concat chain : " + joined); System.out.println(); // Collectors.joining — stream to joined String String result = names.stream() .map(String::toUpperCase) .collect(java.util.stream.Collectors.joining(", ", "[", "]")); System.out.println("stream joining : " + result); } }
Output:
Mumbai, Delhi, Bengaluru, Pune
Java | SQL | Spring
Priya, Rohan, Sneha

Dear Priya, order ORD-001 of Rs.1299.50 is PLACED.

concat chain     : Hello, World!

stream joining   : [PRIYA, ROHAN, SNEHA]

String and Regular Expressions

// File: StringRegexDemo.java public class StringRegexDemo { public static void main(String[] args) { // matches — entire string must match the pattern String email = "priya.sharma@meesho.com"; String phone = "9876543210"; String pincode = "560034"; System.out.println("=== matches() — whole string ==="); System.out.println("email valid : " + email.matches("[a-zA-Z0-9._%+\\-]+@[a-zA-Z0-9.\\-]+\\.[a-zA-Z]{2,}")); System.out.println("phone valid : " + phone.matches("[6-9]\\d{9}")); System.out.println("pin valid : " + pincode.matches("\\d{6}")); System.out.println(); // replaceAll — replace all regex matches System.out.println("=== replaceAll() ==="); String messy = " Too many spaces between words "; System.out.println("collapse spaces : " + messy.trim().replaceAll("\\s+", " ")); String rawPhone = "+91 (98765) 43-210"; System.out.println("digits only : " + rawPhone.replaceAll("[^0-9]", "")); String html = "<b>Hello</b> <i>World</i>"; System.out.println("strip tags : " + html.replaceAll("<[^>]+>", "")); System.out.println(); // replaceFirst — only first match System.out.println("=== replaceFirst() ==="); String txt = "error: disk full. error: memory low. error: timeout."; System.out.println(txt.replaceFirst("error:", "CRITICAL:")); System.out.println(); // split with regex System.out.println("=== split() ==="); String log = "2024-01-15 10:30:00 INFO Server started on port 8080"; String[] tokens = log.split("\\s+", 4); // split on whitespace, max 4 parts System.out.println("Date : " + tokens[0]); System.out.println("Time : " + tokens[1]); System.out.println("Level : " + tokens[2]); System.out.println("Message : " + tokens[3]); } }
Output:
=== matches() ===
email  valid : true
phone  valid : true
pin    valid : true

=== replaceAll() ===
collapse spaces : Too many spaces between words
digits only     : 919876543210
strip tags      : Hello World

=== replaceFirst() ===
CRITICAL: disk full. error: memory low. error: timeout.

=== split() ===
Date     : 2024-01-15
Time     : 10:30:00
Level    : INFO
Message  : Server started on port 8080

intern(), hashCode(), and contentEquals()

// File: StringAdvancedDemo.java public class StringAdvancedDemo { public static void main(String[] args) { // intern() — returns pooled canonical form String heap = new String("developer"); String pooled = heap.intern(); String literal = "developer"; System.out.println("heap == literal : " + (heap == literal)); // false System.out.println("pooled == literal : " + (pooled == literal)); // true System.out.println("heap.equals(pooled): " + heap.equals(pooled)); // true System.out.println(); // hashCode() — content-based, cached, same for equal strings String s1 = "hello"; String s2 = new String("hello"); System.out.println("s1.hashCode() : " + s1.hashCode()); // same System.out.println("s2.hashCode() : " + s2.hashCode()); // same System.out.println("hash == hash : " + (s1.hashCode() == s2.hashCode())); // true System.out.println("identity hash s1 : " + System.identityHashCode(s1)); // different System.out.println("identity hash s2 : " + System.identityHashCode(s2)); // from s1 System.out.println(); // contentEquals — compare String content with CharSequence String str = "hello"; StringBuilder sb = new StringBuilder("hello"); StringBuffer sbf = new StringBuffer("hello"); System.out.println("contentEquals(SB) : " + str.contentEquals(sb)); // true System.out.println("contentEquals(SBF): " + str.contentEquals(sbf)); // true System.out.println(); // String.valueOf vs toString for null safety Object obj = null; System.out.println("String.valueOf(null) : " + String.valueOf(obj)); // "null" — safe // obj.toString() would throw NullPointerException } }
Output:
heap == literal   : false
pooled == literal : true
heap.equals(pooled): true

s1.hashCode()     : 99162322
s2.hashCode()     : 99162322
hash == hash      : true
identity hash s1  : 1173230247
identity hash s2  : 856419764

contentEquals(SB) : true
contentEquals(SBF): true

String.valueOf(null) : null

String — Full API Quick Reference

CategoryMethods
Length / Accesslength(), charAt(i), codePointAt(i), isEmpty(), isBlank()
SearchingindexOf(s), indexOf(s,from), lastIndexOf(s), contains(s), startsWith(s), endsWith(s), matches(regex), regionMatches(...)
Extractionsubstring(s), substring(s,e), split(regex), split(regex,limit), toCharArray(), getBytes(), chars(), lines()
CasetoUpperCase(), toLowerCase(), toUpperCase(Locale), toLowerCase(Locale)
Whitespacetrim(), strip(), stripLeading(), stripTrailing(), indent(n)
Replacereplace(c,c), replace(s,s), replaceAll(regex,s), replaceFirst(regex,s)
Combineconcat(s), String.join(sep,...), String.format(...), formatted(...), repeat(n)
Compareequals(o), equalsIgnoreCase(s), compareTo(s), compareToIgnoreCase(s), contentEquals(cs)
ConvertString.valueOf(x), toString(), intern(), hashCode()
Modern Javastrip(), stripLeading(), stripTrailing(), isBlank(), lines(), repeat(), indent(), formatted(), translateEscapes()

Basic Example

public class Main { public static void main(String[] args) { String text = " Java Programming "; String trimmed = text.trim(); System.out.println("Trimmed : '" + trimmed + "'"); System.out.println("Length : " + trimmed.length()); System.out.println("Upper : " + trimmed.toUpperCase()); System.out.println("Contains : " + trimmed.contains("Program")); System.out.println("Replaced : " + trimmed.replace("Java", "Python")); } }

Output

Trimmed  : 'Java Programming'
Length   : 17
Upper    : JAVA PROGRAMMING
Contains : true
Replaced : Python Programming

How it works:

  1. ›trim() returns a new String with the leading and trailing spaces removed — text itself is unchanged.
  2. ›length() counts the characters in trimmed, which is 17 once the padding is gone.
  3. ›toUpperCase() and replace() each build and return a brand new String from trimmed's content.
  4. ›contains() scans the characters and returns true because "Program" appears inside "Java Programming".

Best Practices

Use Objects.equals(a, b) for null-safe comparison when both sides might be null. String.equals() throws NullPointerException when called on a null reference. "constant".equals(var) handles a null var safely but not a null constant. Objects.equals(a, b) handles null on both sides — the cleanest option when either value might be absent.

Prefer strip() over trim() for user input in Java 11+. strip() recognises Unicode whitespace including non-breaking spaces, while trim() only removes characters with ASCII code ≤ 32. User input from web forms or mobile apps may contain Unicode whitespace that trim() silently leaves behind.

Use String.valueOf() instead of calling toString() directly on objects that might be null. String.valueOf(obj) returns "null" safely when obj is null. obj.toString() crashes. For any value that might be null — database fields, optional configuration, user-supplied data — String.valueOf() is the safer conversion.

Use lines() instead of split("\\n") for multiline string processing in Java 11+. lines() correctly handles all line terminators — \n, \r\n, and \r — while split("\\n") only handles Unix-style. It also returns a lazy Stream<String> which integrates naturally with stream operations.

Common Mistakes

Mistake 1 — Calling String Methods on a Null Reference

String value = getFromDatabase(); // might return null String upper = value.toUpperCase(); // NullPointerException if null // Fix — null check first if (value != null) { upper = value.toUpperCase(); } // Or use Objects for comparison, String.valueOf for conversion String safe = String.valueOf(value).toUpperCase(); // "NULL" if null — check intended behaviour

Mistake 2 — split(".") to Split by Dot

String version = "1.8.0.302"; String[] parts = version.split("."); // dot = any char in regex — returns empty array System.out.println(parts.length); // 0 // Fix — escape the dot String[] correct = version.split("\\."); System.out.println(correct.length); // 4

Mistake 3 — Using startsWith/endsWith Without Null Guard

String filename = getFileName(); // might be null if (filename.endsWith(".pdf")) { // NullPointerException // Fix if (filename != null && filename.endsWith(".pdf")) { // safe }

Mistake 4 — Comparing with == After Runtime Operations

String input = getUserInput(); // always a heap String String target = "admin"; if (input == target) { // false — different objects even if content matches allowAccess(); } // Fix — always use equals() if (target.equals(input)) { // content comparison — correct allowAccess(); }

Interview Questions

Q1. What is the internal structure of a String in Java 9 and later?

In Java 9+, String uses a byte[] (not char[]) internally via the Compact Strings optimisation. A coder field indicates the encoding: 0 for LATIN1 (one byte per character) and 1 for UTF16 (two bytes per character). If all characters in the string fit within Latin-1, Java stores them as one byte each, halving memory usage for typical English text. Before Java 9, every character occupied two bytes in a char[] regardless of its value. The hash field caches the result of hashCode() after first computation — safe because the content can never change.

Q2. What is the difference between equals(), ==, compareTo(), and contentEquals() for Strings?

== compares object references — only true if both variables point to the exact same object. equals() compares character content, case-sensitively. equalsIgnoreCase() does the same ignoring case. compareTo() returns a negative integer if the calling string is lexicographically less than the argument, zero if equal, positive if greater — used for sorting. contentEquals(CharSequence) compares the String's content with any CharSequence — StringBuilder or StringBuffer — returning true when the characters match.

Q3. What does String.intern() do and when is it useful?

intern() returns the canonical pooled representation of a String. If the pool already contains an equal String, that reference is returned; otherwise, the String is added to the pool and its reference returned. It is useful in memory-intensive systems that process millions of repeated strings — log levels, status codes, enum-like values — where deduplication into the pool dramatically reduces heap usage. In typical application code, intern() is unnecessary and adds synchronisation overhead. Always use equals() for comparison regardless of whether intern() was called.

Q4. What is the difference between trim() and strip() in Java?

trim() removes leading and trailing characters whose Unicode value is ≤ 32 — the ASCII whitespace range. strip() (Java 11+) removes leading and trailing whitespace as defined by Character.isWhitespace(), which includes Unicode whitespace characters beyond the ASCII range — non-breaking spaces, ideographic spaces, and others. For user input from international sources or web forms, strip() is more correct. stripLeading() and stripTrailing() remove only from one side.

Q5. Why is String declared final in Java?

To prevent subclassing. Without final, a developer could write a MutableString extends String that overrides methods to allow modification — breaking every assumption that libraries, the JVM, and frameworks make about String immutability. Making String final ensures that every String reference refers to exactly java.lang.String — no subtypes, no surprises. This is essential for the String Pool to work safely, for HashMap keys to be reliable, and for security-critical operations to remain unbreakable.

Q6. What exception does String.format() throw on type mismatch and how do you handle it?

java.util.IllegalFormatConversionException is thrown when an argument's type does not match the format specifier — for example, passing a double to %d. java.util.MissingFormatArgumentException is thrown when there are fewer arguments than format specifiers. Both are runtime exceptions — not caught at compile time. Handle them by ensuring argument types match specifiers, or use try-catch around format calls that accept user-defined templates. For production code, unit test all format strings with representative data.

FAQs

Is String serialisable in Java?

Yes. java.lang.String implements java.io.Serializable. A String field in a serialisable class is serialised normally. This means Strings can be written to streams, sent over networks, and stored in files as part of serialised objects without any extra configuration.

What is the String.chars() method and when do you use it?

chars() returns an IntStream of the Unicode code points (char values) of each character. It enables stream-style processing of individual characters: str.chars().filter(c -> c == 'a').count() counts the occurrences of 'a'. The stream yields int values — cast to char for display. It is useful when you want to apply stream operations — filter, map, distinct, sorted — to the characters of a string.

What does String.lines() do differently from split("\\n")?

lines() (Java 11+) handles all common line terminators — \n, \r\n, and \r — while split("\\n") only recognises Unix \n. lines() returns a lazy Stream<String> that integrates with stream operations. split("\\n") returns an eager String[]. For processing multiline text from files or APIs that might use Windows-style \r\n line endings, lines() is more robust.

Can a String contain null characters?

Yes. String can contain the null character '\0' (Unicode code point 0). It is a valid character. new String(new char[]{'\0', 'A'}) creates a two-character String containing a null byte followed by 'A'. length() returns 2. However, null characters in Strings cause problems with C-based systems, native libraries, and certain display contexts — handle them explicitly if your data might contain them.

What is translateEscapes() in Java 15?

translateEscapes() processes escape sequences in a String — turning the literal two characters \n into an actual newline character, \t into a tab, and so on. Before Java 15, you had to use replace("\\n", "\n") manually. This method is particularly useful when reading escape sequences from configuration files or user input that should be treated as actual escape characters.

Summary

java.lang.String is the most complete and most used class in the Java standard library. Its internal compact representation (byte array in Java 9+) reduces memory use for typical text. Its immutability enables pool sharing, thread safety, stable hash codes, and security. Its API covers searching, extraction, transformation, comparison, formatting, regex, streams, and conversion — over sixty methods addressing virtually every text-processing need.

The methods that appear in nearly every real Java project: equals(), equalsIgnoreCase(), trim() / strip(), split(), replace() / replaceAll(), substring(), indexOf(), contains(), startsWith(), endsWith(), toUpperCase(), toLowerCase(), isEmpty() / isBlank(), String.format(), String.valueOf(), String.join(), intern(), compareTo(), and matches().

For interviews, be ready to explain the compact strings optimisation, describe the difference between every comparison method, explain why String is final, demonstrate intern() and its use case, and distinguish trim() from strip().

What to Read Next

Practice Programs

Put this topic into practice — each program has full code, output and a step-by-step explanation you can run online.

Browse all Java programs →