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.

NullPointerException meme

TLDR

  1. Read the exception message. On Java 15+ it names what was null: because "user" is null, because "this.repo" is null, or because the return value of "Foo.bar()" is null.
  2. Go to the first at frame in your own package. That line is where the null was used.
  3. Find why it was null, and fix it there:
  • a method’s return value: lookup returned null for “not found”. Fix: handle the missing case, or return Optional.
  • intValue() / longValue(): unboxing a null Integer/Long. Fix: getOrDefault, or check the wrapper.
  • this.someService in Spring: object created with new, or static field. Fix: inject it; use constructor injection.
  • names[0], this.field: never assigned. Fix: initialize it, make the field final.
  • no message, top frame in java.util: your code passed null into 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. Inject PaymentService where you need it instead of constructing it.
  • The field is static. @Autowired doesn’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 Optional or an empty collection instead of null from 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 (@Nullable from 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 final fields.

Related guides