← All Articles

Java Exceptions Explained: NPE, ClassCast, and the Ones After Them

At a Glance
java.lang.NullPointerExceptionConcurrentModificationExceptionClassCastException in generics code

Java 14+ NPEs name the exact null expression. For everything else: the exception type tells you the family, the first frame in your code tells you the site.

NullPointerException (NPE)

Exception in thread "main" java.lang.NullPointerException:
    Cannot invoke "String.length()" because "name" is null
    at com.app.User.display(User.java:23)

Modern Java tells you everything: which method was called and which variable was null. With older JVMs, you get only the line number.

The causes, in observed frequency: (1) a method returned null (map.get with a missing key, repository lookups, JSON fields absent); (2) a field never initialized on some code path; (3) autoboxing — Integer null unboxed into int (int x = map.get("missing"); throws with a confusing trace). Fixes per cause: use Optional or an explicit null check at the source; initialize fields in the constructor; use map.getOrDefault(...) or check containsKey before unboxing. And note: Objects.requireNonNull(arg, "arg") at the top of a public method turns a vague NPE deep in the call stack into a precise one at the boundary — a habit worth adopting.

ClassCastException

Object o = "hello";
Integer i = (Integer) o;  // ClassCastException: class String cannot be cast to Integer

With generics, this usually leaks through raw types or unchecked conversions:

List list = new ArrayList();  // raw type — no checking
list.add("string");
Integer i = (Integer) list.get(0);  // ClassCastException at runtime

Fix: eliminate raw types (List<String>), and where external data forces a cast, check first: if (o instanceof Integer i) { use(i); } — pattern matching (Java 16+) makes the check-and-cast a single safe idiom.

ConcurrentModificationException

for (String s : list) {
    if (s.startsWith("x")) list.remove(s);  // ConcurrentModificationException
}

Why: you modified the collection while iterating it — a fail-fast guard, not an actual threading problem (though real concurrency causes it too).

Fixes, best to worst: (1) list.removeIf(s -> s.startsWith("x")); — the intended API; (2) iterate over a copy when removal logic is complex: new ArrayList<>(list); (3) use an explicit Iterator and call it.remove(); (4) never list.remove() inside a for-each — it is always a bug. For genuinely concurrent code, CopyOnWriteArrayList or ConcurrentHashMap change the semantics deliberately.

IllegalArgumentException / IllegalStateException

The good exceptions: they mean a caller (IAE) or a lifecycle (ISE) contract was violated, and they usually come with a message that names the rule. The fix is upstream: validate before calling, or check state before transitioning (if (!started) throw new IllegalStateException("start() first")). When writing libraries, prefer these over NPE for invalid inputs — they carry intent.

ArrayIndexOutOfBounds / StringIndexOutOfBounds

Off-by-one at index access, or an empty collection assumed non-empty. Fixes: loop with < arr.length, use the enhanced for, guard with if (!list.isEmpty()), and replace manual index math with subList/streams where it expresses the intent. Boundary tests (empty, one element) catch these before production does.

NumberFormatException

Integer.parseInt("12.5");  // NumberFormatException: For input string: "12.5"

Fix: validate/trim input first, use Double.parseDouble when decimals are legitimate, and wrap user-supplied conversions in try/catch that reports WHICH value failed — NumberFormatException should carry the offending string (and it does, in the message).

Checked vs Unchecked: The Decision Everyone Gets Wrong

Checked (extends Exception)Unchecked (extends RuntimeException)
ExamplesIOException, SQLExceptionNPE, IAE, ISE
Compiler forces handlingYesNo
Use forRecoverable, caller-actionable conditionsProgramming errors, contract violations
The mistakeMaking callers handle a bug (wrap in RuntimeException)Catching broad RuntimeException as flow control

The practical rule: if the caller can do something meaningful (retry, fallback, report), checked. If the only correct response is "fix the code", unchecked. And never write catch (Exception e) {} to make the compiler stop complaining — that converts a loud design problem into a silent one.

Reading a Java Stack Trace Fast

Read the top two frames in your packages, skip the 15 lines of framework reflection, and note the "Caused by:" chain — the FIRST cause is the root. When the trace is full of InvocationTargetException or proxy classes (Spring, reflection), the real exception is in the Caused by section, not the top.

Related Errors

NullReferenceException in C# · Segmentation fault