What you'll learn
Quick Answer
A NullPointerException means you called a method or accessed a field on a reference that holds null. The frequent sources are an uninitialised object field, a method returning null, an unfound map key, an autoboxed null Integer being unboxed to int, and a null returned from a database or API. Modern Java prints helpful messages naming the exact expression that was null. Prevent them by returning empty collections instead of null, validating inputs, and using Optional at API boundaries.
What It Means and How to Read It
In Java, an object variable holds a reference. If it holds null, it points at nothing — so any attempt to use it as an object fails.
String name = null;
int length = name.length(); // NullPointerExceptionFive operations trigger it: calling a method on null, reading or writing a field on null, taking .length of a null array, indexing a null array, and unboxing a null wrapper.
Older Java printed only a line number, which was painful when several things on that line could be null. Since JDK 14, helpful NullPointerException messages name the exact culprit:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "String.length()" because "<local1>.name" is nullRead it as: the thing after because is what was null; the thing after Cannot invoke is what you tried to do with it. On a chained call like a.getB().getC() it tells you precisely which link failed, which used to require a debugger.
If you are on JDK 14 or later and not seeing these, they can be enabled with -XX:+ShowCodeDetailsInExceptionMessages; they are on by default from JDK 15.
Where Nulls Actually Come From
Uninitialised fields. Object fields default to null, unlike local variables which the compiler forces you to initialise.
class User {
String name; // defaults to null
List<String> roles; // also null, not an empty list
}
new User().roles.add("admin"); // NPEMethods that return null. A search that finds nothing often returns null, and the caller forgets to check.
User u = repository.findByEmail(email); // null if absent
System.out.println(u.getName()); // NPEMap lookups. map.get(key) returns null for a missing key rather than throwing. Use getOrDefault when a sensible default exists.
int count = counts.get("missing"); // NPE on unboxing
int safe = counts.getOrDefault("missing", 0); // fineAutoboxing. That last example is worth pausing on. A Map<String, Integer> returns a null Integer, and assigning it to an int unboxes it by calling intValue() on null. The NPE appears on a line with no visible method call, which makes it genuinely confusing the first time.
Uninitialised arrays of objects. new String[5] creates five null references, not five empty strings.
Checking Properly
The basic guard is a null check, but where you put it matters.
if (user != null && user.isActive()) { ... }This is safe because && short-circuits — if the first test fails, the second never runs. Reversing the order would throw.
Compare constants first when using equals:
if (status.equals("ACTIVE")) // NPE if status is null
if ("ACTIVE".equals(status)) // safe: literal is never null
Objects.equals(a, b) // null-safe both waysFail fast on invalid inputs rather than letting a null travel deep into your code and explode somewhere unrelated:
public void register(User user) {
Objects.requireNonNull(user, "user must not be null");
// now the failure names the real problem, at the boundary
}A useful principle: an NPE thrown three layers below where the null entered is expensive to debug. Validating at the boundary converts it into a clear message at the point of the actual mistake.
For strings, String.valueOf(obj) returns the text "null" instead of throwing, which is occasionally what you want for logging.
Using Optional Correctly
Optional makes absence explicit in the type, so the compiler reminds the caller to handle it.
public Optional<User> findByEmail(String email) {
return Optional.ofNullable(database.lookup(email));
}
// The caller cannot forget
String name = findByEmail(email)
.map(User::getName)
.orElse("Unknown");Use ofNullable when the value may be null and of when it must not be — Optional.of(null) throws immediately, which is a useful assertion.
The mistake that defeats the purpose:
Optional<User> result = findByEmail(email);
User u = result.get(); // throws NoSuchElementException if emptyCalling get() without checking has simply renamed the exception. Use orElse, orElseGet, orElseThrow, map or ifPresent instead.
Optional is designed for return types. Using it for fields or method parameters is discouraged — it is not serialisable, adds allocation, and a nullable parameter is better expressed with an overload.
And for collections, prefer an empty list to an Optional or a null:
return results.isEmpty() ? Collections.emptyList() : results;An empty collection iterates safely with no checks at all, which is why it is the better default.
Designing Nulls Out
Prevention beats defensive checks scattered through the codebase.
- Never return null from a method returning a collection. Return an empty one. Every caller then works without a guard.
- Initialise fields at declaration where a sensible empty value exists —
private List<String> roles = new ArrayList<>();. - Validate at the boundary with
Objects.requireNonNull, so failures name the caller rather than some distant internal line. - Use Optional for lookups that may find nothing, so the type documents it.
- Prefer
getOrDefaultovergetfor maps with numeric or string values. - Use nullability annotations such as
@Nullableand@NonNull. Your IDE and static analysis will then warn at compile time rather than runtime.
One caution on the opposite extreme: wrapping everything in null checks makes code unreadable and hides genuine bugs. If a value should never be null, let it fail loudly rather than silently continuing with a substituted default. A crash at the point of the mistake is far cheaper than corrupted data discovered later.
