java.lang.NullPointerException - 12 Causes and Fixes (Including Minecraft Crashes)

java.lang.NullPointerException means your code reached a reference that holds null and then used it as if it held an object. The JVM throws java.lang.NullPointerException at the exact moment of the dereference - a field read, a method call, an array index, or an unboxing - not where the null was created, which is why the stack trace line is often innocent and the real bug is three calls upstream.

On JDK 15 and later the message does most of the work for you. Instead of a bare one-liner you get something like Cannot invoke "String.toUpperCase()" because "region" is null, which names both the failed operation and the null reference. If you are seeing a bare message, you are on an older JDK or the detail flag is off - step 1 of the checklist below fixes that.

This page gives you the fastest fix first, then 12 distinct causes with a reproduction and a corrected version for each, a diagnostic checklist for cases that match none of them, and an explanation of why the JVM is built this way. Modded and plugin Minecraft crashes get their own two causes, because the usual culprit there is lifecycle timing rather than a missing null check. Person working on programming code on a laptop indoors. Glasses on the table.

Errorjava.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because "region" is null
Where it happensJava - any JVM version, any framework (Spring, Android, Bukkit/Spigot, Forge, Fabric, plain SE); thrown at runtime, never at compile time
What it meansYou called a method, read a field, indexed an array, or unboxed a wrapper on a reference whose value was null.

The Fast Fix

Read the first line of the stack trace, not the last. On JDK 15+ it names the null reference for you; guard that exact reference at the place it is produced.

// before: NPE if the key is absent
String region = config.get("region");
System.out.println(region.toUpperCase());

// after: supply a default where the null is born
String region = config.getOrDefault("region", "us-east-1");
System.out.println(region.toUpperCase());

If the message is bare java.lang.NullPointerException with no explanation, run on JDK 14 or later with -XX:+ShowCodeDetailsInExceptionMessages (on by default since JDK 15) and reproduce once. There is no single universal fix - the null always has a specific source, and the 12 causes below are the sources, ordered by how often they bite.

What Is Actually Causing It

1. A map or API lookup returned null for a missing key

Reproduce it

Map<String, String> config = new HashMap<>();
String region = config.get("region");      // key absent -> null
System.out.println(region.toUpperCase()); // NPE on this line

Why it happens — HashMap.get returns null for an absent key, and that null is indistinguishable from a key explicitly mapped to null. The next invokevirtual on that reference is where the JVM throws.

The fix

Map<String, String> config = new HashMap<>();
String region = config.getOrDefault("region", "us-east-1");
System.out.println(region.toUpperCase());

Changed: the default is supplied at the lookup, so the reference can never be null downstream. Note getOrDefault still returns null if the key exists and maps to null - use containsKey when that distinction matters.

Confirm it worked — Log config.containsKey("region") right before the lookup. false confirms this cause; true with a null value means the key was stored with a null and the producer is the bug.


2. A null wrapper was unboxed into a primitive

Reproduce it

Map<String, Integer> counts = new HashMap<>();
int n = counts.get("missing"); // NPE - no visible method call

Why it happens — Assigning an Integer to an int makes the compiler insert Integer.intValue(). The reference is null, so the call fails. This is the classic case where the failing line contains only an assignment, arithmetic, or a comparison - the dereference is invisible in the source.

The fix

Map<String, Integer> counts = new HashMap<>();
int n = counts.getOrDefault("missing", 0);

// explicit form when you need to branch on absence
Integer boxed = counts.get("missing");
int m = (boxed != null) ? boxed : 0;

Changed: the unboxing now operates on a value that is guaranteed non-null.

Confirm it worked — The helpful message names the unboxing directly: Cannot invoke "java.lang.Integer.intValue()" because the return value of "java.util.Map.get(Object)" is null. Seeing intValue, booleanValue, or longValue in the message is conclusive.


3. A chained getter had a null link in the middle

Reproduce it

record Address(String city) {}
record Customer(Address address) {}
record Order(Customer customer) {}

Order order = new Order(new Customer(null));
String city = order.customer().address().city(); // NPE: address() returned null

Why it happens — Each call in the chain dereferences the result of the previous one. One null link anywhere breaks the chain, and before JDK 15 the stack trace pointed at the whole line without telling you which link failed.

The fix

String city = Optional.ofNullable(order.customer())
        .map(Customer::address)
        .map(Address::city)
        .orElse("unknown");

Changed: map short-circuits on the first null instead of dereferencing it, and orElse makes the absent case explicit.

Confirm it worked — On JDK 15+ the message names the exact failing hop (because the return value of "Customer.address()" is null). After the fix, construct the same new Order(new Customer(null)) and confirm you get "unknown" instead of a throw.


4. equals() or length() called on a possibly-null String

Reproduce it

String role = System.getenv("APP_ROLE"); // variable unset -> null
if (role.equals("admin")) {               // NPE
    System.out.println("elevated");
}

Why it happens — System.getenv, HTTP header getters, and JSON field accessors all return null for absent values. Putting the nullable value on the left of .equals dereferences it before any comparison happens.

The fix

String role = System.getenv("APP_ROLE");
if ("admin".equals(role)) {          // constant on the left, null-safe
    System.out.println("elevated");
}
// equivalent: Objects.equals(role, "admin")

Changed: the receiver is now a literal that can never be null; String.equals returns false for a null argument.

Confirm it worked — Run the program with APP_ROLE unset. Before the fix it throws; after it prints nothing and continues.


5. A for-each loop ran over a method that returned null

Reproduce it

static List<String> findTags(String id) {
    return null; // "nothing found"
}

for (String tag : findTags("x")) { // NPE on the implicit iterator() call
    System.out.println(tag);
}

Why it happens — The enhanced for loop compiles to collection.iterator(), so a null collection throws before the first iteration. Returning null to mean "empty" pushes the failure into every caller.

The fix

static List<String> findTags(String id) {
    return List.of(); // empty, never null
}

for (String tag : findTags("x")) {
    System.out.println(tag);
}

Changed: the method contract is now empty collection instead of null, which removes the check from every call site at once.

Confirm it worked — The helpful message says Cannot invoke "java.util.List.iterator()". Add a unit test asserting findTags("nope") is non-null and empty so the contract stays enforced.


6. An instance field or object array element was never assigned

Reproduce it

class Service {
    private List<String> items;              // defaults to null
    void add(String s) { items.add(s); }     // NPE on first call
}

String[] names = new String[3];
System.out.println(names[0].length());       // NPE - elements default to null

Why it happens — Every reference-typed field and every element of a newly allocated object array starts as null. Allocating the array allocates slots, not objects, and a field you only assign inside an init() method is null until that method actually runs.

The fix

class Service {
    private final List<String> items = new ArrayList<>(); // assigned at construction
    void add(String s) { items.add(s); }
}

String[] names = new String[3];
Arrays.fill(names, "");                 // or assign each slot before reading
System.out.println(names[0].length());

Changed: the field is final and initialised in the declaration, so the compiler refuses to leave any constructor path unassigned.

Confirm it worked — A stack frame ending in <init> or a message naming this.items confirms it. Making the field final turns the whole class of bug into a compile error.


More causes (6 remaining)

7. A Spring @Autowired field was used before injection

Reproduce it

@Service
class ReportService {
    @Autowired private UserRepo repo;
    private final long count = repo.count(); // NPE - field injection has not run
}

// also null: creating the bean yourself bypasses the container entirely
ReportService s = new ReportService();

Why it happens — Field injection happens after the object is constructed, so field initialisers and the constructor body see null. Instantiating the class with new skips injection completely, which is the usual cause when a dependency is null only in tests or in a manually created helper.

The fix

@Service
class ReportService {
    private final UserRepo repo;
    private final long count;

    ReportService(UserRepo repo) {   // injected before the body runs
        this.repo = repo;
        this.count = repo.count();
    }
}

Changed: constructor injection makes the dependency available inside the constructor and lets the field be final, so a missing bean fails at startup rather than as a runtime NPE.

Confirm it worked — If the stack trace contains an <init> frame of your own bean, it is construction-time. After the fix, a missing dependency produces a UnsatisfiedDependencyException at startup - a hard failure you cannot ship past.


8. Minecraft plugin grabbed a server object too early

Reproduce it

public class MyPlugin extends JavaPlugin {
    // runs when the plugin is constructed, before worlds are loaded
    private final World world = Bukkit.getWorld("world"); // null

    @Override
    public void onEnable() {
        getLogger().info(world.getSpawnLocation().toString()); // NPE
    }
}

Why it happens — Your plugin class is instantiated early in server startup; worlds, other plugins, and most registries do not exist yet, so the lookup returns null and the null is stored in a final field forever. Forge and Fabric mods hit the same shape when a registry object is read in a constructor or static initialiser instead of inside the registration event - this is the single most common source of a java lang nullpointerexception minecraft crash that is not a missing null check.

The fix

public class MyPlugin extends JavaPlugin {
    private World world;

    @Override
    public void onEnable() {
        world = Bukkit.getWorld("world");   // resolved in the enable phase
        if (world == null) {
            getLogger().severe("World 'world' not found - disabling");
            getServer().getPluginManager().disablePlugin(this);
            return;
        }
        getLogger().info(world.getSpawnLocation().toString());
    }
}

Changed: the lookup moved into onEnable, and a failed lookup disables the plugin loudly instead of throwing later.

Confirm it worked — Log the value immediately after the lookup inside onEnable. If it is non-null there but null in a field initialiser, the cause is confirmed as lifecycle timing.


9. A Bukkit lookup or config key returned null at runtime

Reproduce it

Player target = Bukkit.getPlayer("Notch"); // offline -> null
target.sendMessage("hello");               // NPE

String prefix = getConfig().getString("prefix"); // key missing -> null
sender.sendMessage(prefix + " ready");

Why it happens — Bukkit.getPlayer returns null when no online player matches, and FileConfiguration.getString returns null for a key that is not present - which happens whenever config.yml was never written to disk because saveDefaultConfig() was not called, or an existing server has an older config file without the new key.

The fix

Player target = Bukkit.getPlayerExact("Notch");
if (target == null) {
    sender.sendMessage("That player is not online.");
    return;
}
target.sendMessage("hello");

// in onEnable(): saveDefaultConfig();
String prefix = getConfig().getString("prefix", "[MyPlugin]"); // default supplied
sender.sendMessage(prefix + " ready");

Changed: the player lookup returns early with a user-facing message, and the config read supplies a default so an old config file cannot crash the plugin.

Confirm it worked — Run the command against an offline name - you should get the message, not a stack trace in logs/latest.log. For config, delete config.yml, restart, and confirm the plugin regenerates it and still works.


10. switch ran on a null String or enum selector

Reproduce it

String mode = System.getProperty("mode"); // unset -> null
switch (mode) {                           // NPE before any case is tested
    case "fast" -> System.out.println("fast");
    default -> System.out.println("idle");
}

Why it happens — A switch on a String calls hashCode() and equals() on the selector; a switch on an enum calls ordinal(). Both dereference it, so the throw happens before default is reached - which is why people are surprised that default does not catch null.

The fix

String mode = System.getProperty("mode", "idle"); // default at the source
switch (mode) {
    case "fast" -> System.out.println("fast");
    default -> System.out.println("idle");
}

Changed: the selector is non-null by construction. On Java 21+, a pattern switch also lets you write an explicit case null -> label instead.

Confirm it worked — Run with -Dmode absent. Before the fix it throws with Cannot invoke "String.hashCode()"; after it prints idle.


11. A null was passed into a collection or Optional that rejects nulls

Reproduce it

record User(String name, String email) {}

List<User> users = List.of(new User("ana", null));
Map<String, String> byName = users.stream()
        .collect(Collectors.toMap(User::name, User::email)); // NPE on the null value

Optional<String> o = Optional.of(users.get(0).email());     // NPE

Why it happens — Collectors.toMap builds its result with HashMap.merge, which rejects null values; List.of and Map.of reject null elements outright; Optional.of throws on null by contract. Here the NPE is not yours to dereference - a library is rejecting your null deliberately.

The fix

Map<String, String> byName = users.stream()
        .filter(u -> u.email() != null)
        .collect(Collectors.toMap(User::name, User::email));

// keep the nulls if they are meaningful
Map<String, String> withNulls = new HashMap<>();
users.forEach(u -> withNulls.put(u.name(), u.email()));

Optional<String> o = Optional.ofNullable(users.get(0).email());

Changed: nulls are either filtered out, collected into a HashMap that permits them, or wrapped with ofNullable instead of of.

Confirm it worked — Look for HashMap.merge, Objects.requireNonNull, or ImmutableCollections frames in the stack trace - those frames mean a library refused your null rather than tripping over it.


12. Static initialisers ran in the wrong order

Reproduce it

class Config {
    static final Config INSTANCE = new Config();      // runs first
    static final List<String> ENVS = List.of("prod"); // still null above

    private final String env;
    Config() { this.env = ENVS.get(0); }              // NPE
}

Why it happens — Static initialisers execute top to bottom, and a static field that has not been initialised yet still holds its default value of null. Because the failure happens during class initialisation, the JVM wraps it: callers see java.lang.ExceptionInInitializerError with the NullPointerException as its cause.

The fix

class Config {
    static final List<String> ENVS = List.of("prod"); // dependency declared first
    static final Config INSTANCE = new Config();

    private final String env;
    Config() { this.env = ENVS.get(0); }
}

Changed: the dependency is declared above its consumer. For mutually dependent classes, move the value behind a lazy holder or a method call instead of a static field read.

Confirm it worked — A <clinit> frame in the stack trace, or an ExceptionInInitializerError wrapping the NPE, points at static initialisation every time. Print ENVS as the first statement of the constructor to see the null directly.

Two male developers at desks programming in a modern office workspace with large monitors. ## None of Those? Narrow It Down
  1. Read the exception message itself. On JDK 15+ it says Cannot invoke "X" because "Y" is null, which identifies the null reference outright - if you have that sentence, skip to the cause that matches Y. If the message is bare, rerun on JDK 14+ with -XX:+ShowCodeDetailsInExceptionMessages (default-on since 15) and reproduce once; that alone resolves most unknown cases.
  2. Find the first stack frame inside your own package. Frames above it in JDK or framework code usually mean you handed a null into a library (cause 11), not that the library is broken.
  3. Scan the frame names for <clinit> or an ExceptionInInitializerError wrapper - that is static initialisation order (cause 12). A <init> frame of your own class means a field was read before assignment or injection (causes 6 and 7).
  4. Look at the failing source line. If it contains no visible method call - a plain assignment, an arithmetic operation, a comparison, or an array index - the dereference is an autoboxing unbox (cause 2) or a null array element (cause 6).
  5. Set a conditional breakpoint (ref == null) or log every reference appearing on the failing line. Knowing which one is null tells you whether the null came from a lookup, a field, a parameter, or a method return - three different fixes.
  6. Walk the null backwards to where it was produced: a lookup that can miss, a method that returns null to mean empty, an unassigned field, or an uninjected dependency. The fix belongs at the producer, not at the crash site; guarding only the crash site moves the NPE one line later.
  7. For Minecraft, open the crash report in crash-reports/ (or logs/latest.log for a Bukkit/Spigot plugin) and find the first frame in a mod or plugin package rather than a vanilla one. That package names the owning mod - then check whether it is a lifecycle problem (cause 8) or a null lookup/config key (cause 9) before blaming the game.
  8. If it only happens in one environment, diff the inputs: an unset environment variable, a config key missing from an older file, an empty database column, a different locale. Feed that exact null into a unit test so the fix is pinned down permanently.

Why This Error Exists At All

Java gives every reference type a single value that means "points at no object", and it gives every uninitialised field, array slot, and unset variable that value by default. That decision buys a lot: allocation is cheap because new Object[1_000_000] does not have to construct a million objects, class loading is simple because the JVM can zero-fill an object's memory and call the constructor afterwards, and "absent" needs no extra type. The price is that the type String actually means "a String or nothing", and the compiler cannot tell those two apart.

So the check has to happen at runtime, and the JVM does it at the only place it can: the moment you ask the reference to behave like an object. At the bytecode level that is a small, fixed set of instructions - getfield, putfield, invokevirtual/invokeinterface/invokespecial, arraylength, aaload/aastore, and monitorenter. Every NullPointerException in existence is one of those instructions hitting a zero reference. That is why the stack trace points at the use and not at the assignment, and why the trace tells you where the null was noticed, never where it was born. It is also why NPE is an unchecked RuntimeException: since almost every line of Java contains a potential dereference, making it checked would require a throws clause on essentially every method.

Once you see the error that way, the whole family becomes recognisable. ExceptionInInitializerError caused by an NPE is a getstatic of a field whose initialiser has not run yet. A NoSuchElementException from Optional.get() is the same bug after someone moved nullability into the type system and then opted out of it. The modern tooling - Optional for return values, @Nullable/@NonNull annotations read by Error Prone's NullAway or by your IDE, and Kotlin's nullable types on the same JVM - are all attempts to pull that runtime check back to compile time. Each one works exactly to the extent that the null is described at the boundary where it enters your program, which is also the rule that makes the fixes above stick. A to-do list and completed tasks in a notebook on an office desk for productivity and organization.

Stop It From Coming Back

  • Run NullAway as an Error Prone plugin in your build and annotate packages with @NullMarked (JSpecify) or fields and returns with @Nullable. It turns an unguarded dereference of a nullable value into a compile error instead of a production stack trace.
  • Call Objects.requireNonNull(arg, "arg must not be null") at the top of constructors and public API methods. The failure then names the offending parameter at the boundary where the bad value entered, rather than ten frames deeper where it is finally used.
  • Never return null for a collection or array - return List.of(), Collections.emptyList(), or an empty array, and use Optional<T> for a genuinely optional single value. This removes the null check from every current and future caller.
  • Make dependency fields final and use constructor injection in Spring (and explicit assignment in onEnable for Bukkit plugins / the registration event for Forge and Fabric). Final fields force the compiler to prove every construction path assigns them.
  • Turn on your IDE's nullability inspections (IntelliJ's @Nullable/@NotNull contract checks, or SpotBugs' NP_* detectors) in CI, not just locally, so a flagged dereference blocks the merge.
  • Add a unit test per external input boundary that feeds null or missing data - unset env var, absent config key, empty query result, offline player - and asserts a defined behaviour. These are the inputs that produce nulls in production and never in your dev run.
  • Keep -XX:+ShowCodeDetailsInExceptionMessages enabled (it is the default on JDK 15+) and log full stack traces with their Caused by: chains. A truncated or detail-free NPE log turns a 30-second fix into an afternoon.

Errors You Will Probably Hit Next

  • java.lang.NullPointerException: Cannot invoke "String.length()" because "<local1>" is null
  • java.lang.ExceptionInInitializerError
  • java.util.NoSuchElementException: No value present

The stack trace tells you where the null was noticed; your job is to find where it was produced. Read the helpful message to name the reference, walk it backwards to the lookup, field, parameter, or injection that created it, and fix it there - guarding the crash site only moves the exception to the next line. When the trace shows <clinit> or <init>, stop looking for a missing null check and start looking at initialisation order.

댓글

이 블로그의 인기 게시물

TypeError: Cannot read properties of undefined (reading 'map') - 11 Causes and Fixes

psql: FATAL: password authentication failed for user - 10 Causes and Fixes

Error: ERR_MODULE_NOT_FOUND: Cannot find package - 10 Causes and Fixes