Exceptions ·
‹ Previous Next ›
⏱ 5 min read Modified: 2026-10-30

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 of get() 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) returns false for null, while city.equals("London") throws an NPE.
  • Objects.requireNonNullElse(value, default) (Java 9+) substitutes a default value.
  • Optional as 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, or arr[arr.length] instead of arr[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 args array: args[0] when the program is started without arguments gives Index 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 matching instanceof instead of a bare cast, getOrDefault() instead of get().
  • Do not catch bug exceptions: catch (NullPointerException e) or catch (Exception e) «just in case» hides the cause. How to build a try-catch for 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

Please log in or register to have a possibility to add comment.