java.lang.NullPointerException means some code used a reference that was null as if it pointed to an object: it called a method on it, read or wrote a field, took .length of an array, threw it, synchronized on it, or unboxed it into a primitive. Fixing it is a two-step job: find out which reference was null, then find out why it was null at that point.
On Java 15 and later, the first step is usually done for you by the exception message.
TLDR
- Read the exception message. On Java 15+ it names what was null:
because "user" is null,because "this.repo" is null, orbecause the return value of "Foo.bar()" is null. - Go to the first
atframe in your own package. That line is where the null was used. - Find why it was null, and fix it there:
- a method’s return value: lookup returned
nullfor “not found”. Fix: handle the missing case, or returnOptional. intValue()/longValue(): unboxing a nullInteger/Long. Fix:getOrDefault, or check the wrapper.this.someServicein Spring: object created withnew, or static field. Fix: inject it; use constructor injection.names[0],this.field: never assigned. Fix: initialize it, make the fieldfinal.- no message, top frame in
java.util: your code passednullinto the JDK. Fix: correct the argument in the frame below.
Read the message: it names the null reference
Since JDK 15, helpful NullPointerException messages (JEP 358) are on by default. Instead of a bare NullPointerException, you get the operation that failed and the expression that was null:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.example.Profile.getTheme()" because the return value of "com.example.User.getProfile()" is null
at com.example.SettingsService.themeFor(SettingsService.java:42)
at com.example.SettingsController.show(SettingsController.java:18)
Read it as: on line 42, getProfile() returned null, and the code then called getTheme() on that result. You don’t need to guess which link of user.getProfile().getTheme().length() broke. The message tells you.
The “because” part comes in a few forms, and each points somewhere different:
| Message says | What was null | Where to look next |
|---|---|---|
because "name" is null |
a local variable or parameter | where it was assigned, or the caller that passed it |
because "this.repository" is null |
a field | the constructor or injection that should have set it |
because the return value of "Foo.bar()" is null |
a method’s return value | bar() and the cases where it returns null |
because "<local4>" is null |
a local variable, but the class has no debug info | compile with javac -g (Maven and Gradle already do) |
because "args[0]" is null / because "<array>" is null |
an array element or the array itself | where the array was filled |
On JDK 14 the feature exists but is off; add -XX:+ShowCodeDetailsInExceptionMessages to the JVM options. On JDK 8 to 13 you only get the line number, so split long chained expressions over several lines while you debug, which turns one ambiguous line into several precise ones.
A bare java.lang.NullPointerException with no message on Java 15+, or with a custom message instead of “Cannot invoke …”, means the exception was thrown explicitly by code (for example Objects.requireNonNull, or List.of(..., null)) rather than by the JVM. The top frame of the stack trace is then inside the JDK or a library, and the cause is the null argument your code passed in. Look at the first frame that belongs to your own package.
Find the first frame from your code
In a framework application the NPE’s top frames are often your code, but a long trace with proxies, filters, and Caused by: sections can bury it. The frame that matters is the first at line in your own package; everything above it is where the null was used, everything below it is how execution got there.
If the trace is long, as Spring, Hibernate, and reactive stacks usually are, paste it into Debugly’s stack trace formatter. It highlights your application’s frames, collapses the library ones, and marks the root-cause exception, so the at com.yourcompany... line you need is the first thing you see.
The common causes, and how to recognize each
A method returned null and the caller didn’t expect it
The message says the return value of "...find...()" is null or the null local was assigned from a lookup. Repository methods, Map.get, System.getenv, getClass().getResource(), and many older APIs return null for “not found”.
User user = userRepository.findByEmail(email); // null when no row matches
return user.getDisplayName();
// Cannot invoke "User.getDisplayName()" because "user" is null
The fix depends on what “not found” means for this caller. If it’s a normal case, handle it; if it should never happen, fail with a message that says what was missing rather than a bare NPE several calls later:
User user = userRepository.findByEmail(email);
if (user == null) {
throw new UserNotFoundException(email);
}
return user.getDisplayName();
For APIs you own, returning Optional<User> makes the “not found” case part of the signature, so callers can’t forget it. Spring Data repositories already support Optional<User> findByEmail(String email).
Auto-unboxing a null Integer, Long, or Boolean
This one surprises people because no method call is visible on the line:
Map<String, Integer> counts = new HashMap<>();
int c = counts.get("missing");
// Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null
Any time the message mentions intValue(), longValue(), or booleanValue(), a wrapper type was being converted to a primitive. Typical sources are Map.get, JPA entity fields declared as Integer for a nullable column, and JSON fields mapped to Long. Use counts.getOrDefault("missing", 0), or keep the wrapper type and check it.
A Spring bean field is null
Cannot invoke "com.example.PaymentClient.charge(...)" because "this.paymentClient" is null
A this.something field that should have been injected is null. In Spring Boot that almost always means one of:
- The object was created with
new PaymentService(), so Spring never touched it. Only objects Spring creates get injected. InjectPaymentServicewhere you need it instead of constructing it. - The field is
static.@Autowireddoesn’t inject static fields. - The method runs during construction, before field injection happens (for example, called from the constructor).
Constructor injection removes all three: the dependency becomes a constructor argument, so you can’t construct the object without it, and a missing bean fails at startup with NoSuchBeanDefinitionException instead of an NPE at runtime. See Spring @Autowired field is null for the less common cases.
A field or array element that was never assigned
Object fields default to null, and so does every element of a new object array:
String[] names = new String[3];
names[0].trim();
// Cannot invoke "String.trim()" because "names[0]" is null
For fields, the message reads because "this.cache" is null. Check whether every constructor initializes it, and whether some code path sets it back to null. Declaring the field final makes the compiler enforce initialization.
Null inside a collection or as an argument
Some APIs reject null outright and throw an NPE from inside the JDK:
List.of("a", null); // NPE, no message
Map.of("key", null); // NPE
new ConcurrentHashMap<>().put("k", null); // NPE
Optional.of(null); // NPE; use Optional.ofNullable
switch (status) { ... } // NPE if status is a null String or enum
Here the top frame is in java.util or java.base, and the bug is in the frame below it, where your code passed null in.
Preventing the next one
Null checks everywhere make code harder to read without making it safer. What works better is deciding at each boundary whether a value can be null, and making that visible:
- Return
Optionalor an empty collection instead ofnullfrom your own methods. - Validate inputs at the edge (controller, message consumer, public API) with
Objects.requireNonNull(param, "param")or Bean Validation, so a bad value fails where it enters rather than three layers later. - Use nullness annotations (
@Nullablefrom JSpecify, or your framework’s) and a checker such as NullAway or IntelliJ’s inspections. They catch most of the cases above at compile time. - Prefer constructor injection and
finalfields.
Related guides
- What is a stack trace in Java? explains how to read
atframes andCaused by:chains. - Common Java exceptions
- ClassCastException and IllegalArgumentException