Common Exceptions in Java: Causes and How to Fix Them
The line int apples = stock.get("apple"); looks perfectly safe: get() is called on a map that definitely exists. Yet the program crashes with a NullPointerException — the key "apple" is not in the map, get() returns null, and auto-unboxing to int quietly calls intValue() on it.
import java.util.HashMap;
import java.util.Map;
public class StockDemo {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("pear", 5);
int apples = stock.get("apple");
System.out.println(apples);
}
} Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
at StockDemo.main(StockDemo.java:8) Common exceptions in Java are a handful of unchecked exceptions that the JVM and the standard library throw when the program logic is wrong: NullPointerException (using a null reference), ArrayIndexOutOfBoundsException (an index outside the array), ClassCastException (an invalid type cast), NumberFormatException (a string that is not a number), IllegalArgumentException and IllegalStateException (an invalid argument or object state) and ConcurrentModificationException (modifying a collection while iterating over it). They are almost always fixed by changing the code, not by adding a catch block: a null check, the right loop bound, instanceof before a cast.
Common Java exceptions: summary table
Every exception in this lesson is an unchecked subclass of RuntimeException: the compiler does not require you to handle it, and it shows up only at run time. All of them except ConcurrentModificationException from java.util live in the java.lang package and need no import.
| Exception | Package | Kind | Typical cause | How to fix |
|---|---|---|---|---|
NullPointerException | java.lang | unchecked | Calling a method, accessing a field or an array element, or auto-unboxing on null | A null check, getOrDefault(), Objects.requireNonNullElse() |
ArrayIndexOutOfBoundsException | java.lang | unchecked | An index below 0 or not less than the array length, most often i <= arr.length | i < arr.length, a for-each loop, checking args.length |
StringIndexOutOfBoundsException, IndexOutOfBoundsException | java.lang | unchecked | charAt()/substring() outside the string, list.get(i) with i >= size() | Compare the index with length() or size() |
ClassCastException | java.lang | unchecked | Casting an object to a type it does not actually have | instanceof with pattern matching, generics without raw types |
NumberFormatException | java.lang | unchecked | Integer.parseInt() or Double.parseDouble() received a non-number: "abc", " 42", "3,14" | strip(), try-catch at the input boundary, Scanner.hasNextInt() |
IllegalArgumentException | java.lang | unchecked | A method received an invalid argument value | Validate or normalize the value before the call |
IllegalStateException | java.lang | unchecked | A method called at the wrong time: the stream is already consumed, the Scanner is closed | Respect the call order, create a new object |
ConcurrentModificationException | java.util | unchecked | Removing or adding collection elements inside a for-each loop | removeIf(), Iterator.remove() |
ArithmeticException | java.lang | unchecked | Integer division by zero: / by zero | Check the divisor before dividing |
UnsupportedOperationException | java.lang | unchecked | add() or remove() on an immutable collection such as List.of(...) | Copy it into a mutable one: new ArrayList<>(List.of(...)) |
Why these exceptions are unchecked and where they sit in the class tree is explained in Checked vs unchecked exceptions in Java.
What to catch and what to fix
Catching is usually justified only for NumberFormatException and IllegalArgumentException — at the points where data comes in from the user, a file or the network. NullPointerException, ArrayIndexOutOfBoundsException, ClassCastException and ConcurrentModificationException mean a bug in the code, and a catch only hides it. Why a swallowed exception is dangerous is shown in Mistakes beginners make with exceptions.
NullPointerException: using a null reference
NullPointerException (NPE) is thrown when a program uses a reference that is null as if it pointed to an object: calls a method on it, reads or writes a field, takes an element or the length of an array, or unboxes null into a primitive. Since Java 15 the JVM message names the operation and the exact expression that was null.
Typical causes of NullPointerException
The messages in the table are for code compiled in an IDE or by a build tool (Maven, Gradle), where variable names are kept; what the message looks like without them is shown below.
| What the code does | Example | JVM message (Java 15+) |
|---|---|---|
Calls a method on null | name.length() | Cannot invoke "String.length()" because "name" is null |
| Reads a field | user.name | Cannot read field "name" because "user" is null |
| Writes a field | user.name = "Anna" | Cannot assign field "name" because "user" is null |
| Reads an array element | arr[0] with int[] arr = null | Cannot load from int array because "arr" is null |
| Reads the array length | arr.length | Cannot read the array length because "arr" is null |
Unboxes null into a primitive | int n = map.get("x"); | Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null |
| Uses an uninitialized field | this.title.trim() | Cannot invoke "String.trim()" because "this.title" is null |
Two more sources of NPE are throw with a reference that is null (covered in What you can and cannot throw) and synchronized on null.
Example: helpful NullPointerException with javac and javac -g
public class NpeDemo {
public static void main(String[] args) {
String name = null;
System.out.println(name.length());
}
} Compiling and running from the command line without extra options:
javac NpeDemo.java
java NpeDemo Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is null
at NpeDemo.main(NpeDemo.java:4) If you compile the class with the -g option (javac -g NpeDemo.java), the real variable name replaces <local1>:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "name" is null
at NpeDemo.main(NpeDemo.java:4) Where <local1> comes from
Local variable names are stored in a class file only as debug information, which javac writes with the -g option. Without it the JVM knows only the slot number: in a static main slot 0 holds args, so the first declared variable is <local1>. IntelliJ IDEA, Maven and Gradle compile with debug information by default, so in an IDE you see the variable name. Field names ("this.title") and method names are always present in the message.
Detailed NPE messages (helpful NullPointerExceptions, JEP 358) appeared in Java 14 and are enabled by default since Java 15. In Java 8 and 11 the same exception is printed without any text — just java.lang.NullPointerException and the line number.
How to fix NullPointerException
Find the expression that was null in the message and decide: either the value may legitimately be missing (then supply a default or check it), or the null comes from a bug earlier in the code (then fix the place where the reference was never initialized).
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;
public class NpeFixDemo {
public static void main(String[] args) {
Map<String, Integer> stock = new HashMap<>();
stock.put("pear", 5);
int apples = stock.getOrDefault("apple", 0);
System.out.println("Apples: " + apples);
String city = null;
System.out.println("London".equals(city));
System.out.println(Objects.requireNonNullElse(city, "not specified"));
if (city != null) {
System.out.println(city.length());
}
}
} Apples: 0
false
not specified getOrDefault()instead ofget()when the key may be missing — this is how the example at the top of the lesson is fixed.- Constant on the left:
"London".equals(city)returnsfalsefornull, whilecity.equals("London")throws an NPE. Objects.requireNonNullElse(value, default)(Java 9+) substitutes a default value.Optionalas a method return type tells the caller explicitly that there may be no result.Objects.requireNonNull(arg, "arg")at the start of a method throws the NPE right at the entry, with a clear message, instead of ten calls later — see the table of standard exceptions in The throw statement and throw new.
ArrayIndexOutOfBoundsException and other IndexOutOfBounds exceptions
ArrayIndexOutOfBoundsException is thrown when an array element is accessed with an index below 0 or not less than the array length: an array of three elements accepts only the indexes 0, 1 and 2. Since Java 11 the message contains both the index and the length: Index 3 out of bounds for length 3.
public class AioobeDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30};
for (int i = 0; i <= numbers.length; i++) {
System.out.println(numbers[i]);
}
}
} 10
20
30
Exception in thread "main" java.lang.ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3
at AioobeDemo.main(AioobeDemo.java:5) Typical causes:
<=instead of<in the loop condition, as in the example, orarr[arr.length]instead ofarr[arr.length - 1]for the last element.- Counting from 1: a loop
for (int i = 1; i <= n; i++)copied from math notation. - A negative index after a calculation:
Index -1 out of bounds for length 3. - An empty
argsarray:args[0]when the program is started without arguments givesIndex 0 out of bounds for length 0.
StringIndexOutOfBoundsException and IndexOutOfBoundsException
ArrayIndexOutOfBoundsException and StringIndexOutOfBoundsException are subclasses of IndexOutOfBoundsException, so catch (IndexOutOfBoundsException e) catches all three.
| Exception | Where it occurs | Example | Message |
|---|---|---|---|
ArrayIndexOutOfBoundsException | Accessing an array element | arr[3] with int[] arr = new int[3] | Index 3 out of bounds for length 3 |
StringIndexOutOfBoundsException | String methods: charAt(), substring() | "Java".charAt(10) | Contains the index and the string length; the exact text differs between JDK versions |
IndexOutOfBoundsException | List methods: get(), set(), remove(int) | new ArrayList<String>().get(0) | Index 0 out of bounds for length 0 |
import java.util.ArrayList;
import java.util.List;
public class ListIndexDemo {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
System.out.println(list.size());
System.out.println(list.get(0));
}
} 0
Exception in thread "main" java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
...
at ListIndexDemo.main(ListIndexDemo.java:8) How to fix ArrayIndexOutOfBoundsException
Compare the index with the length strictly with <, and if you do not need the index inside the loop, use for-each, where going out of bounds is impossible. Check external data (arguments, user input) before using it as an index.
public class AioobeFixDemo {
public static void main(String[] args) {
int[] numbers = {10, 20, 30};
for (int n : numbers) {
System.out.println(n);
}
String first = args.length > 0 ? args[0] : "no arguments";
System.out.println(first);
}
} Output when started without arguments:
10
20
30
no arguments ClassCastException: an invalid type cast
ClassCastException is thrown when an object is cast to a class or interface it is not actually an instance of. The compiler accepts such a cast if the static type of the variable (Object, a superclass, an interface) could in principle hold the target type; the real object is checked only at run time.
public class ClassCastDemo {
public static void main(String[] args) {
Object value = 42;
String text = (String) value;
System.out.println(text);
}
} Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at ClassCastDemo.main(ClassCastDemo.java:4) How to read the message: the real class of the object is java.lang.Integer, and it was cast to java.lang.String; the part in parentheses tells you which module and class loader both classes come from. For your own classes you will see are in unnamed module of loader 'app'. A cast between obviously incompatible types, such as (String) 42, does not compile at all: incompatible types: int cannot be converted to String.
ClassCastException without an explicit cast
What does this code print? It contains no cast operator at all, but the compiler inserts one itself when reading from a generic collection.
import java.util.ArrayList;
import java.util.List;
public class HiddenCastDemo {
public static void main(String[] args) {
List raw = new ArrayList();
raw.add(42);
List<String> names = raw;
System.out.println("Size: " + names.size());
String first = names.get(0);
}
} Size: 1
Exception in thread "main" java.lang.ClassCastException: class java.lang.Integer cannot be cast to class java.lang.String (java.lang.Integer and java.lang.String are in module java.base of loader 'bootstrap')
at HiddenCastDemo.main(HiddenCastDemo.java:10) The number got into a List<String> through the raw type List without a type parameter. The compiler warned about it (uses unchecked or unsafe operations), but the code compiled, and the exception appeared much later — on line 10, when the element was read. Do not use raw types and do not ignore unchecked warnings.
How to fix ClassCastException
Check the type with instanceof before casting (more in Upcasting and Downcasting in Java). Since Java 16 the check and the cast are a single construct — pattern matching: if (a instanceof Dog dog).
public class AnimalCastDemo {
static class Animal { }
static class Dog extends Animal {
void bark() {
System.out.println("Woof!");
}
}
static class Cat extends Animal { }
public static void main(String[] args) {
Animal[] animals = {new Dog(), new Cat()};
for (Animal a : animals) {
if (a instanceof Dog dog) {
dog.bark();
} else {
System.out.println("Not a dog: " + a.getClass().getSimpleName());
}
}
}
} Woof!
Not a dog: Cat Without the check, the cast (Dog) a for the cat would end with class AnimalCastDemo$Cat cannot be cast to class AnimalCastDemo$Dog. And if you find yourself checking the type in a long if-else chain, that is a sign to replace it with a polymorphic method in the base class.
NumberFormatException: For input string
NumberFormatException is thrown by Integer.parseInt(), Long.parseLong(), Double.parseDouble() and the valueOf() methods when a string is not a valid number. The message For input string: "..." shows the original string in quotes — they reveal even stray spaces. It is a subclass of IllegalArgumentException.
public class NfeDemo {
public static void main(String[] args) {
String input = "abc";
int number = Integer.parseInt(input);
System.out.println(number);
}
} Exception in thread "main" java.lang.NumberFormatException: For input string: "abc"
...
at NfeDemo.main(NfeDemo.java:4) Most often the exception is caused by strings that look like a number to a human:
| Call | Result | Why |
|---|---|---|
Integer.parseInt("42"), Integer.parseInt("+7") | 42, 7 | Valid notation, a + sign is allowed |
Integer.parseInt(" 42") | For input string: " 42" | Whitespace is not trimmed automatically |
Integer.parseInt("3.14") | For input string: "3.14" | A decimal number for an int |
Double.parseDouble("3,14") | For input string: "3,14" | parseDouble accepts only a dot as the decimal separator, whatever the locale |
Integer.parseInt("3000000000") | For input string: "3000000000" | Greater than Integer.MAX_VALUE (2147483647); use Long.parseLong() |
Integer.parseInt("") | For input string: "" | An empty string, for example an empty form field |
Integer.parseInt(null) | NumberFormatException, not an NPE | The message text for null differs between JDK versions |
How to fix NumberFormatException
User input cannot be made valid in advance, so here a try-catch is appropriate — but in one place, at the input boundary, with a clear result instead of a «magic» zero. strip() (Java 11+) removes surrounding whitespace, and when reading from the console you can ask scanner.hasNextInt() first.
import java.util.OptionalInt;
public class SafeParseDemo {
static OptionalInt parseInt(String s) {
if (s == null) {
return OptionalInt.empty();
}
try {
return OptionalInt.of(Integer.parseInt(s.strip()));
} catch (NumberFormatException e) {
return OptionalInt.empty();
}
}
public static void main(String[] args) {
String[] inputs = {"42", " 7 ", "abc", "3.14", null};
for (String s : inputs) {
System.out.println(s + " -> " + parseInt(s));
}
}
} 42 -> OptionalInt[42]
7 -> OptionalInt[7]
abc -> OptionalInt.empty
3.14 -> OptionalInt.empty
null -> OptionalInt.empty The calling code decides what to do with an empty result: ask for the number again, use a default value or report an error.
IllegalArgumentException: an invalid argument
IllegalArgumentException means that a method received an argument value it cannot work with: a negative size, an unknown constant name, a number out of range. JDK methods throw it, and it is also the standard exception to throw in your own code when validating parameters. The message text is chosen by the author of the method.
| JDK call | Message |
|---|---|
new ArrayList<>(-1) | Illegal Capacity: -1 |
"ab".repeat(-1) | count is negative: -1 |
DayOfWeek.valueOf("monday") | No enum constant java.time.DayOfWeek.monday |
Integer.parseInt("abc") | For input string: "abc" (the subclass NumberFormatException) |
The most frequent case in practice is valueOf() of an enum: the constant name is matched exactly, including case and whitespace.
import java.time.DayOfWeek;
public class IaeDemo {
public static void main(String[] args) {
String input = "monday";
DayOfWeek day = DayOfWeek.valueOf(input);
System.out.println(day);
}
} Exception in thread "main" java.lang.IllegalArgumentException: No enum constant java.time.DayOfWeek.monday
...
at IaeDemo.main(IaeDemo.java:6) How to fix IllegalArgumentException
Read the message and the documentation of the method: it lists the allowed values. Normalize the input before the call, and if it comes from the user and can be anything, catch the exception at the input boundary.
import java.time.DayOfWeek;
import java.util.Locale;
public class IaeFixDemo {
public static void main(String[] args) {
String input = " monday ";
DayOfWeek day = DayOfWeek.valueOf(input.strip().toUpperCase(Locale.ROOT));
System.out.println(day);
}
} MONDAY Locale.ROOT in toUpperCase() protects you from locale surprises: in the Turkish locale a lowercase i becomes a dotted İ, and "friday" would no longer match FRIDAY. How to throw IllegalArgumentException from your own method is shown in The throw statement and throw new.
IllegalStateException: a method called at the wrong time
IllegalStateException is thrown when the method arguments are fine, but the object is in a state in which the method must not be called: the stream has already been consumed, the Scanner is closed, the iterator has not returned an element yet. The bug is not in the data but in the order of calls.
import java.util.stream.Stream;
public class StreamReuseDemo {
public static void main(String[] args) {
Stream<String> names = Stream.of("Anna", "Boris");
names.forEach(System.out::println);
System.out.println(names.count());
}
} Anna
Boris
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
...
at StreamReuseDemo.main(StreamReuseDemo.java:7) Other JDK methods that throw this exception:
| Situation | Message |
|---|---|
A second terminal operation on the same Stream | stream has already been operated upon or closed |
scanner.nextLine() after scanner.close() | Scanner closed |
matcher.group() before a successful matcher.find() or matches() | No match found |
iterator.remove() before next() or twice in a row | No message |
How to fix IllegalStateException
Respect the life cycle of the object: do not use it after it is closed, and call methods in the order the documentation requires. A stream is single-use — keep the source data in a collection and create a new stream from it for each operation.
import java.util.List;
public class StreamFixDemo {
public static void main(String[] args) {
List<String> names = List.of("Anna", "Boris");
names.forEach(System.out::println);
System.out.println(names.stream().count());
}
} Anna
Boris
2 IllegalArgumentException vs IllegalStateException in one sentence: a bad value passed in is an IllegalArgumentException, a valid call made at the wrong moment is an IllegalStateException. Which one to throw from your own code is discussed in When to create a custom exception and when to reuse a standard one.
ConcurrentModificationException: modifying a collection while iterating
ConcurrentModificationException (package java.util) is thrown by the iterator of ArrayList, HashMap and other standard collections when the collection is modified behind the iterator's back while it is being traversed. The most common case is calling list.remove() or list.add() inside a for-each loop over the same list.
import java.util.ArrayList;
import java.util.List;
public class CmeDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Anna", "Boris", "Vera"));
for (String name : names) {
if (name.startsWith("A")) {
names.remove(name);
}
}
System.out.println(names);
}
} Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(...)
at java.base/java.util.ArrayList$Itr.next(...)
at CmeDemo.main(CmeDemo.java:7) The exception is reported not on the line with remove() but on line 7, the loop header: for-each calls iterator.next(), which notices that the modification counter of the list (modCount) no longer matches the expected value. This exception has no message.
What does this code print?
The same loop, but now we remove «Boris» instead of the first element:
for (String name : names) {
if (name.startsWith("B")) {
names.remove(name);
}
}
System.out.println(names); [Anna, Vera] No exception, although the code is just as wrong. After the second-to-last element is removed, the list size becomes 2, the iterator cursor is also 2, and hasNext() returns false — the loop ends before next() gets a chance to check anything. «Vera» is never even tested by the condition. Iterators of standard collections are fail-fast only on a best-effort basis: the absence of an exception does not prove the code is correct.
How to fix ConcurrentModificationException
Remove elements with removeIf() (Java 8+) or with remove() of the iterator itself — both update the iterator state together with the list.
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class CmeFixDemo {
public static void main(String[] args) {
List<String> names = new ArrayList<>(List.of("Anna", "Boris", "Vera"));
names.removeIf(name -> name.startsWith("A"));
System.out.println(names);
Iterator<String> it = names.iterator();
while (it.hasNext()) {
if (it.next().startsWith("V")) {
it.remove();
}
}
System.out.println(names);
}
} [Boris, Vera]
[Boris] Not only about threads
The word «Concurrent» in the name is misleading: all the examples above are single-threaded. If a collection really is modified and read by several threads, a plain ArrayList or HashMap is the wrong choice altogether — use CopyOnWriteArrayList or ConcurrentHashMap from java.util.concurrent: their iterators never throw ConcurrentModificationException.
How to read the message and prevent these exceptions
An exception report has three parts: the fully qualified class name of the exception, the text after the colon and the stack trace. The class name tells you the category of the bug, the text gives the concrete value (an index, a string, the expression that was null), and the first at line with your own class points to the file and line number. How the stack trace works is explained in What is an exception in Java.
| Message fragment | What to look for in the code |
|---|---|
because "x" is null, because the return value of "...get(Object)" is null | Where x or the method result got its null |
Index 3 out of bounds for length 3 | The index equals the length: a <= condition or an extra + 1 |
class A cannot be cast to class B | Where an object of class A ended up in a variable treated as B |
For input string: " 42" | The quotes reveal spaces, commas and empty strings in the input |
ConcurrentModificationException without text | add()/remove() on a collection inside its own for-each loop |
To keep these exceptions from appearing at all:
- Validate arguments at the start of a method (
Objects.requireNonNull(),throw new IllegalArgumentException(...)) — the error then surfaces where the bad data entered, not deep in the call chain. - Prefer constructs that cannot go wrong: for-each instead of an index loop,
removeIf()instead of removing inside a loop, pattern matchinginstanceofinstead of a bare cast,getOrDefault()instead ofget(). - Do not catch bug exceptions:
catch (NullPointerException e)orcatch (Exception e)«just in case» hides the cause. How to build atry-catchfor expected failures is covered in Java Try Catch Finally. - Do not ignore compiler and IDE warnings:
unchecked, «Possible NullPointerException», «Array index is out of bounds» often point to a future exception before the program even runs.
Frequently Asked Questions
java.lang.NullPointerException: how to fix it in three steps?
First, find the first at line with your own class in the stack trace: it gives the file and line number. Second, read in the message which expression was null (in Java 15+ it is named after the word because). Third, decide whether a value must be there: if yes, fix the place where the reference was never initialized; if it may legitimately be missing, add a check or a default value. Techniques with examples are in How to fix NullPointerException.
How do I get a detailed NullPointerException message in Java 8 or 11?
You cannot: helpful NullPointerExceptions appeared only in Java 14. In Java 14 they are turned on with the flag -XX:+ShowCodeDetailsInExceptionMessages; since Java 15 they are on by default (you can turn them off with -XX:-ShowCodeDetailsInExceptionMessages). On Java 8 and 11 you only have the line number from the stack trace — one more reason not to write long chains like a.getB().getC().getD() on a single line.
Why does catch (IllegalArgumentException e) catch NumberFormatException?
Because NumberFormatException is a subclass of IllegalArgumentException, and catch catches the specified class and all its subclasses. For the same reason catch (IndexOutOfBoundsException e) catches both ArrayIndexOutOfBoundsException and StringIndexOutOfBoundsException. If you need both blocks, the catch for the subclass must come first, otherwise the code does not compile.
Why does List.of(...).add() throw UnsupportedOperationException?
List.of(), Set.of(), Map.of() (Java 9+), Collections.unmodifiableList() and stream().toList() (Java 16+) return immutable collections: any call to add(), remove() or set() throws UnsupportedOperationException without a message. Arrays.asList() allows set() but not add() or remove(). If the list must change, copy it: new ArrayList<>(List.of(...)).
How do I remove entries from a HashMap while iterating without ConcurrentModificationException?
Use removeIf() on the map views: map.entrySet().removeIf(e -> e.getValue() == 0), map.keySet().removeIf(...) or map.values().removeIf(...). Another way is an explicit Iterator over entrySet() with iterator.remove(). Calling map.remove(key) inside a for-each over keySet() leads to the same exception as with a list.
How are StackOverflowError and OutOfMemoryError different from these exceptions?
They are not exceptions but errors: subclasses of Error, not Exception. They signal JVM-level problems (the call stack overflowed, memory ran out), catch (Exception e) does not catch them, and code usually does not handle them. An example with output is in catch (Exception e) and StackOverflowError.
Comments