Why Are Strings Immutable in Java?
A String in Java is an object whose characters are stored in an internal array — a char[] up to Java 8, and a more compact byte[] since Java 9. That array is private and never changed after the String is created.
Since the contents of a String cannot be modified after it is created, Strings are immutable. Whenever you perform an operation that appears to modify a String, Java creates a new String object instead of changing the existing one.
Example
String str = "Hello";
str = str + " World";
What Happens?
"Hello"is created."Hello World"is created as a new String object.- The reference
strnow points to the new object.
The original "Hello" object remains unchanged.
Benefits of String Immutability
- Improves security.
- Enables String Pool optimization.
- Makes Strings thread-safe.
- Allows efficient caching of hash codes.
- Prevents accidental modification of shared objects.
StringBuffer vs StringBuilder
StringBuffer and StringBuilder are mutable string classes, meaning their contents can be modified without creating a new object.
Unlike the String class, both allow efficient string manipulation.
StringBuffer
- Mutable.
- Thread-safe (methods are synchronized).
- Slightly slower because of synchronization.
- Suitable for multithreaded applications.
StringBuilder
- Mutable.
- Not thread-safe.
- Faster than
StringBuffer. - Suitable for single-threaded applications.
String vs StringBuffer vs StringBuilder
| Feature | String | StringBuffer | StringBuilder |
|---|---|---|---|
| Mutable | ❌ No | ✅ Yes | ✅ Yes |
| Thread-safe | Yes (Immutable) | ✅ Yes | ❌ No |
| Performance | Slow for frequent modifications | Slower than StringBuilder | Fastest |
| Best Use | Read-only text | Multithreaded applications | Single-threaded applications |
Interview Tip: Use
StringBuilderunless thread safety is required. UseStringBufferonly in multithreaded environments.
Do Wrapper Classes Override hashCode() and equals()?
Yes.
Wrapper classes such as:
IntegerCharacterBooleanDoubleFloatLong
override both the equals() and hashCode() methods.
This is because wrapper classes are immutable value objects.
equals() Method
The equals() method performs value-based comparison.
Two wrapper objects are considered equal if they store the same value, even if they are different objects in memory.
Example
Integer a = Integer.valueOf(5);
Integer b = Integer.valueOf(5);
System.out.println(a.equals(b));
Note: older examples use new Integer(5). That constructor is deprecated and marked for removal — javac now warns "Integer(int) in Integer has been deprecated and marked for removal". Use Integer.valueOf() or autoboxing (Integer a = 5;).
Output
true
Although a and b are different objects, they contain the same value.
hashCode() Method
The hashCode() method is overridden so that equal objects produce the same hash code.
This is required for proper functioning of hash-based collections such as:
HashMapHashSetHashtable
Objects with equal values generate identical hash codes, allowing efficient searching and retrieval.
Real-Life Example
Imagine two ₹10 currency notes.
Although they are different physical notes, they represent the same monetary value.
Similarly, two Integer objects containing 10 are considered equal.
Real-Time Testing Example
In automation frameworks, wrapper classes are commonly used to store:
- Retry counts
- Timeout values
- Test priorities
- Status codes
Collections such as HashSet and HashMap rely on equals() and hashCode() to correctly compare these values.
String Encoding and Character Sets in Java
A Java String is a sequence of characters, but files, HTTP requests and databases store bytes. A character set (charset) such as UTF-8 is the rule that turns characters into bytes and back. If one side writes with one charset and the other side reads with another, you get garbled text like é instead of é — a very common bug in API and database tests.
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
public class StringEncoding {
public static void main(String[] args) {
String text = "Price: \u20B9499 \u2014 caf\u00E9"; // rupee sign, em dash, e-acute
System.out.println("Default charset: " + Charset.defaultCharset());
System.out.println("Characters: " + text.length());
byte[] utf8 = text.getBytes(StandardCharsets.UTF_8); // String -> bytes
byte[] latin1 = text.getBytes(StandardCharsets.ISO_8859_1);
System.out.println("UTF-8 bytes: " + utf8.length + ", ISO-8859-1 bytes: " + latin1.length);
System.out.println("UTF-8 bytes of the rupee sign: " + Arrays.toString("\u20B9".getBytes(StandardCharsets.UTF_8)));
String back = new String(utf8, StandardCharsets.UTF_8); // bytes -> String, same charset
System.out.println("Decoded with UTF-8 matches: " + back.equals(text));
String wrong = new String(utf8, StandardCharsets.ISO_8859_1); // wrong charset = garbled text
System.out.println("Decoded with ISO-8859-1: " + wrong);
String lost = new String(latin1, StandardCharsets.ISO_8859_1); // characters outside Latin-1 are lost
System.out.println("Round trip through ISO-8859-1: " + lost);
}
}
Output
Default charset: UTF-8
Characters: 18
UTF-8 bytes: 23, ISO-8859-1 bytes: 18
UTF-8 bytes of the rupee sign: [-30, -126, -71]
Decoded with UTF-8 matches: true
Decoded with ISO-8859-1: Price: â¹499 â café
Round trip through ISO-8859-1: Price: ?499 ? café
getBytes(StandardCharsets.UTF_8)turns a String into bytes;new String(bytes, StandardCharsets.UTF_8)turns them back.- The rupee sign takes 3 bytes in UTF-8, so 18 characters became 23 bytes.
- Reading UTF-8 bytes as ISO-8859-1 produced
â¹andé— the classic "mojibake" pattern. When you seeÃorâin a test failure, suspect a charset mismatch. - ISO-8859-1 has no rupee sign or em dash, so those characters were replaced by
?and lost for good.
Encoding Rules for Testers
- Always pass the charset explicitly —
StandardCharsets.UTF_8— instead of relying on the default. - Java 18 and later use UTF-8 as the default charset everywhere. On Java 17 and earlier the default depends on the operating system (often Windows-1252 on Windows), so the same test can pass on a Mac and fail on a Windows CI agent.
- Compile sources as UTF-8: on a machine with a non-UTF-8 locale,
javacrejects non-English characters in a file withStringEncoding.java:7: error: unmappable character (0xE2) for encoding US-ASCII. Usejavac -encoding UTF-8, or in Maven set<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>. - Check the API and database: responses should declare
Content-Type: application/json; charset=utf-8, and MySQL tables should useutf8mb4(plainutf8in MySQL can't store emoji).
Exception Handling in Java
Exception handling is a mechanism for handling runtime errors without terminating the program unexpectedly.
It allows applications to recover from errors and continue normal execution whenever possible.
Building Blocks of Exception Handling
Java provides five main keywords for exception handling:
trycatchfinallythrowthrows
try Block
The try block contains code that may throw an exception.
catch Block
The catch block handles exceptions thrown inside the corresponding try block.
You can have multiple catch blocks to handle different exception types.
finally Block
The finally block always executes, regardless of whether an exception occurs.
It is commonly used for cleanup tasks such as:
- Closing database connections.
- Closing files.
- Releasing system resources.
throw Keyword
The throw keyword is used to explicitly throw an exception.
Example:
throw new ArithmeticException("Invalid operation");
throws Keyword
The throws keyword declares that a method may throw one or more exceptions.
It shifts the responsibility of handling the exception to the calling method.
Example:
public void readFile() throws IOException {
// code
}
Exception Handling Example
public class ExceptionExample {
public static void main(String[] args) {
try {
int divideByZero = 5 / 0;
} catch (ArithmeticException e) {
System.out.println(
"ArithmeticException = " + e.getMessage()
);
} finally {
System.out.println(
"This is the finally block"
);
}
}
}
Output
ArithmeticException = / by zero
This is the finally block
Why Exception Handling Is Important
Exception handling:
- Prevents unexpected application crashes.
- Separates error-handling code from business logic.
- Improves application reliability.
- Makes debugging easier.
- Ensures resources are released properly.
ClassNotFoundException vs NoClassDefFoundError
Both errors relate to missing classes during runtime, but they occur in different situations.
ClassNotFoundException
ClassNotFoundException is a checked exception.
It occurs when Java attempts to load a class dynamically using methods such as:
Class.forName()ClassLoader.loadClass()
and the class cannot be found in the classpath.
Common Causes
- Missing JAR files.
- Incorrect classpath.
- Class loader issues.
Since it is a checked exception, it must be handled using a try-catch block or declared with throws.
NoClassDefFoundError
NoClassDefFoundError is an Error, not an Exception.
It occurs when:
- The class existed during compilation.
- The class is missing at runtime.
Common Causes
- Required JAR removed after compilation.
- Static initialization failure.
- Incorrect deployment.
Since it is an Error, it usually indicates a serious application problem.
ClassNotFoundException vs NoClassDefFoundError
| Feature | ClassNotFoundException | NoClassDefFoundError |
|---|---|---|
| Type | Checked Exception | Error |
| When It Occurs | Class cannot be loaded dynamically | Class existed during compilation but is missing at runtime |
| Common Cause | Missing JAR or incorrect classpath | Missing class after compilation or initialization failure |
| Handling | Must be handled using try-catch | Indicates a serious runtime problem |
Real-Life Example
ClassNotFoundException
Trying to borrow a book that never existed in the library.
NoClassDefFoundError
The book existed when the catalog was printed but was removed from the shelf before you tried to borrow it.
Real-Time Testing Example
While executing Selenium automation scripts:
- A missing JDBC driver JAR may cause a
ClassNotFoundException. - A dependency that was available during compilation but missing during execution may cause a
NoClassDefFoundError.
Try It: The Classic Traps in One Program
import java.util.*;
public class StringsWrappers {
static class InvalidTestDataException extends Exception { // checked custom exception
InvalidTestDataException(String msg) { super(msg); }
}
static int parseTimeout(String raw) throws InvalidTestDataException {
try {
return Integer.parseInt(raw.trim());
} catch (NumberFormatException e) {
throw new InvalidTestDataException("timeout must be a number, got: '" + raw + "'");
}
}
public static void main(String[] args) {
String a = "Hello";
String b = a;
a = a + " World"; // creates a new String; b is unchanged
System.out.println("a = " + a + " | b = " + b);
String s1 = "Login", s2 = new String("Login");
System.out.println("s1 == s2: " + (s1 == s2) + " | s1.equals(s2): " + s1.equals(s2));
Integer x = 127, y = 127, p = 128, q = 128; // autoboxing uses Integer.valueOf
System.out.println("127 == 127: " + (x == y) + " | 128 == 128: " + (p == q) + " | 128 equals: " + p.equals(q));
Set<Integer> codes = new HashSet<>(List.of(200, 201));
System.out.println("HashSet contains Integer.valueOf(200): " + codes.contains(Integer.valueOf(200)));
StringBuilder sb = new StringBuilder();
for (String step : List.of("open", "login", "search")) sb.append(step).append(" > ");
sb.setLength(sb.length() - 3);
System.out.println("steps: " + sb);
for (String raw : List.of(" 30 ", "3O")) {
try {
System.out.println("timeout = " + parseTimeout(raw));
} catch (InvalidTestDataException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("finally runs for '" + raw + "'");
}
}
}
}
Output (compiled and run with Java 21):
a = Hello World | b = Hello
s1 == s2: false | s1.equals(s2): true
127 == 127: true | 128 == 128: false | 128 equals: true
HashSet contains Integer.valueOf(200): true
steps: open > login > search
timeout = 30
finally runs for ' 30 '
Caught: timeout must be a number, got: '3O'
finally runs for '3O'
- Immutability: changing
acreated a new String;bstill points to "Hello". ==vsequals(): two Strings with the same text were not==. Always compare text — including text read from a web page in a test — withequals().- The Integer cache trap:
127 == 127was true but128 == 128was false, because Java caches Integer objects from −128 to 127. An assertion likeactualCount == expectedCountonIntegervalues can pass with small numbers and fail with large ones — useequals()or compareintvalues. - hashCode/equals let a
HashSet<Integer>find 200 even though it's a different object. - Exception handling: a custom checked exception turned a vague
NumberFormatExceptioninto a clear test-data error, andfinallyran in both cases.
Step through the program and watch which objects the references point to in the Java Code Visualizer.
From Real Projects
Java has been the language of every automation framework I've worked with — Selenium, TestNG, POM classes and Apache POI for Excel data. Interviews for automation roles regularly include basic programs like this one, often followed by a question on OOP concepts. Practice writing it by hand and explaining how you'd test it. On Canolog, the many forms and screens across sales, inventory, finance and service are where keeping page details in POM classes and reusable steps in a business library paid off. Compare text with equals(), never ==, especially when the text comes from a web page.
📚 Official documentation: Selenium documentation · dev.java: Learn Java (Oracle)
Encoding in Test Code: Where It Breaks
Garbled characters in tests almost always come from one place where the charset isn't set. Set it explicitly at each boundary:
| Where | Set UTF-8 like this |
|---|---|
| Reading and writing files | Files.readString(path, StandardCharsets.UTF_8) |
| String to bytes and back | text.getBytes(StandardCharsets.UTF_8), new String(bytes, StandardCharsets.UTF_8) |
| Maven source files | <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> in pom.xml |
| REST Assured request bodies | .contentType("application/json; charset=UTF-8") |
| MySQL JDBC URL | ?useUnicode=true&characterEncoding=UTF-8 |
Never rely on the platform default. It was the operating system's charset until Java 17 (often windows-1252 on Windows machines), and only became UTF-8 everywhere in Java 18. A test that passes on a Mac and fails on a Windows Jenkins agent is usually this.
FAQs
1. Why Are Strings Immutable in Java?
Strings are immutable because their internal array (a char[] before Java 9, a byte[] since) is private and its contents cannot be modified after the String object is created. Any modification creates a new String object.
2. What Is the Difference Between StringBuffer and StringBuilder?
Both classes create mutable strings.
- StringBuffer is thread-safe because its methods are synchronized.
- StringBuilder is not thread-safe but offers better performance.
3. Do Wrapper Classes Override hashCode() and equals()?
Yes.
Wrapper classes such as Integer, Character, and Boolean override both methods.
equals()compares values.hashCode()ensures equal objects generate identical hash codes.
4. What Are the Building Blocks of Exception Handling in Java?
Java exception handling is built around:
trycatchfinallythrowthrows
5. What Is the Purpose of the finally Block?
The finally block always executes, whether or not an exception occurs.
It is typically used to release resources such as files, database connections, and network connections.
6. What Is the Difference Between ClassNotFoundException and NoClassDefFoundError?
- ClassNotFoundException is a checked exception that occurs when Java cannot load a class dynamically because it is missing from the classpath.
- NoClassDefFoundError is an Error that occurs when a class available during compilation is missing at runtime or fails during initialization.
7. What is the default charset in Java?
Since Java 18 the default charset is UTF-8 on every platform. In Java 17 and earlier it depended on the operating system, which is why code that relies on the default can behave differently on Windows and Linux.
8. How do you convert a String to UTF-8 bytes in Java?
Call text.getBytes(StandardCharsets.UTF_8), and convert back with new String(bytes, StandardCharsets.UTF_8). Using the StandardCharsets constant avoids the checked exception that the string name version throws.
9. Why does my text show characters like é instead of é?
The bytes were written as UTF-8 but read as ISO-8859-1 or windows-1252. Find the step that reads the data (file, HTTP response or database) and set its charset to UTF-8 explicitly.