🔴 The Error You're Seeing
Confirm this matches your console output. If it does, you're in the right place.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.devinhyderabad.shop.Customer.getCity()" because the return value of "com.devinhyderabad.shop.OrderService.findCustomer(java.lang.Long)" is null
at com.devinhyderabad.shop.OrderService.printCustomerCity(OrderService.java:14)
at com.devinhyderabad.shop.ShopApp.main(ShopApp.java:6)
// Same code on Java 13 or with -XX:-ShowCodeDetailsInExceptionMessages — no detail message at all:
Exception in thread "main" java.lang.NullPointerException
at com.devinhyderabad.shop.OrderService.printCustomerCity(OrderService.java:14)
at com.devinhyderabad.shop.ShopApp.main(ShopApp.java:6)⚡ Quick Fix Works 80% of the time
Guard the value the message pointed at before dereferencing it — or fail fast at the boundary with Objects.requireNonNull.
// Message said findCustomer(...) returned null — check exactly that
Customer customer = orderRepository.findCustomer(id);
if (customer == null) {
throw new IllegalArgumentException("No customer for id " + id);
}
System.out.println(customer.getCity());🧠 Why this Happens
Tap to expand the deep technical explanation
At the failing line the JVM executes an invokevirtual/getfield against an object reference whose bits are all zero — HotSpot raises NullPointerException at exactly that bytecode index. Before JEP 358 the exception carried no message because computing one costs analysis the JVM refuses to do on every throw. JEP 358 solved this by generating the description lazily: only when an NPE actually occurs does the JVM replay the abstract interpretation of the failing bytecode to see which operand slot held null. That is why the feature could be switched on by default in JDK 15 without slowing normal execution.
The HITEC City Parking Spot Analogy:
An NPE is a delivery rider holding a parcel and staring at an address slip nobody filled in — there is no door to knock on. Since Java 15 the rider reads the blank field aloud ("the sender line was empty"), which is exactly the Cannot invoke ... because ... is null message.
🔁 How to Reproduce Confirm this is your error
Back OrderRepository.findCustomer(Long) with an empty HashMap so it returns null for id 42L, then call findCustomer(42L).getCity(). Run on any JDK 15+ to see the helpful message; re-run with -XX:-ShowCodeDetailsInExceptionMessages to see the legacy bare form. (Lab capture: OpenJDK 25.)
🛠️ Solutions (5 Ways to Fix)
Read the JEP 358 message, then guard that exact value
👉 Use this if you run JDK 15+ and the exception line already tells you what was null.
The message has a fixed grammar. "Cannot invoke "X.m()" because "name" is null" means variable name was null. "... because the return value of "pkg.Class.method(...)" is null" means that call returned null and you chained off it. "... because "array[i]" is null" means the element is null, not the array. Go straight to that spot and add the missing check instead of sprinkling guards everywhere.
// Message: ... because the return value of "...OrderRepository.findCustomer(java.lang.Long)" is null
Customer customer = orderRepository.findCustomer(id);
if (customer == null) {
throw new IllegalArgumentException("No customer for id " + id);
}
System.out.println(customer.getCity());Fail fast at boundaries with Objects.requireNonNull
👉 Use this for constructors, setters, and config loading where a null would only explode much later.
Validate required collaborators the moment they arrive. The stack trace then points at the wiring mistake, not at some unrelated read three requests later.
public OrderService(OrderRepository repo, Clock clock) {
this.repo = Objects.requireNonNull(repo, "repo");
this.clock = Objects.requireNonNull(clock, "clock");
}
// Null-tolerant default in one line
String label = Objects.requireNonNullElse(order.getLabel(), "unlabeled");Return Optional from lookups that can miss
👉 Use this when you own an API whose result may legitimately not exist.
A method typed Optional<Customer> makes absence part of the contract, so callers cannot forget the check — the compiler forces the unwrap.
Optional<Customer> findCustomer(Long id);
// Caller side: no NPE possible
String city = repository.findCustomer(id)
.map(Customer::getCity)
.orElse("unknown");Triage bare NPEs on Java 8 / 11 production logs
👉 Use this when the runtime is old and the trace has no detail message.
Take the top frame (class + line), open the file there, and walk every dereference on that line left to right — each dot can be the culprit. Remember a[i].m() throws when element a[i] is null OR when a itself is null; the bytecode offset via javap -c disambiguates if needed.
# Confirm which instruction threw on the failing line
javap -c target/classes/com/devinhyderabad/shop/OrderService.class | less
# Long-term: upgrade the runtime to get JEP 358 messages by defaultMake null-dereference bugs compile errors with NullAway
👉 Use this when NPEs keep resurfacing across a large codebase and you want the build to catch them instead of production.
NullAway (Uber's ErrorProne plugin) treats an unguarded dereference of an @Nullable value as a compile error. Annotate the boundaries once — method parameters, return types, fields — and every caller that forgets the null check fails CI. This converts null-safety from runtime whack-a-mole into a static guarantee that scales with team size.
<!-- pom.xml -->
<dependency>
<groupId>com.uber.nullaway</groupId>
<artifactId>nullaway</artifactId>
<scope>provided</scope>
</dependency>
<!-- javac plugin args -->
<arg>-Xep:NullAway:ERROR</arg>
<arg>-XepOpt:NullAway:AnnotatedPackages=com.devinhyderabad</arg>📋 Version Notes
NPE carries no detail message at all — debugging means opening the top-frame file and inspecting the line manually. java.util.Optional exists since this release.
JEP 358 ships behind the flag -XX:+ShowCodeDetailsInExceptionMessages (opt-in only).
Helpful NPE messages become the default; the flag flips them back off.
Behavior unchanged from 15; records and pattern matching just make the guard clauses around null checks shorter.
🛡️ How to Prevent This Next Time
Validate required values at API boundaries with Objects.requireNonNull, return Optional from lookups that legitimately miss, never return null collections (return empty ones), and wire NullAway or ErrorProne into CI so unguarded dereferences fail the build before they fail production.