DEV Community

Machine coding Master
Machine coding Master

Posted on

Stop AI NullPointerExceptions: Compile-Time Zero-NPE Java with JSpecify, NullAway, and Claude Code

Stop AI NullPointerExceptions: Compile-Time Zero-NPE Java with JSpecify, NullAway, and Claude Code

AI agents generate Java at blistering speeds, but LLMs still hallucinate null safety by littering codebases with redundant null-checks instead of enforcing structural invariants. If your agentic workflow in 2026 isn't hard-gated by strict compile-time diagnostics, you are simply automating the generation of production NullPointerExceptions.

Why Most Developers Get This Wrong

  • Relying on fragmented annotations: LLMs mix JSR-305, SpotBugs, and JetBrains annotations indiscriminately, breaking static analysis across modular boundaries.
  • Overusing Optional<T>: Models default to wrapping internal domain fields and method parameters in Optional, degrading memory efficiency and polluting domain models.
  • Treating the LLM as a typist, not a compiler loop: Developers still manually review AI-generated diffs instead of forcing the agent to resolve raw AST compiler errors autonomously.

The Right Way

Make non-null the default and wire deterministic compiler errors into Claude Code's execution loop.

  • Enforce standard JSpecify (org.jspecify:jspecify:1.0.0) across your codebase to give LLMs a single, unambiguous annotation grammar.
  • Mark entire packages with @NullMarked, forcing the compiler to treat every unannotated type as strictly non-null.
  • Wire Uber's NullAway into Error Prone with TreatWarningsAsErrors enabled to fail builds immediately upon any contract violation.
  • Run Claude Code CLI inside a closed feedback loop (claude "fix NullAway errors after running ./gradlew compileJava"), allowing the agent to self-correct based on exact line-and-column diagnostic outputs.

I built javalld.com while prepping for senior roles — complete LLD problems with execution traces, not just theory.

Show Me The Code

// package-info.java: @org.jspecify.annotations.NullMarked
package com.payment.core;

import org.jspecify.annotations.Nullable;

public final class SettlementService {
    // NullAway halts compilation if order is null, or if nullable ledger is dereferenced unsafely
    public Receipt settle(Order order, @Nullable AuditLedger ledger) {
        String txId = order.transactionId(); // Guaranteed non-null by @NullMarked
        long auditCode = (ledger != null) ? ledger.record(txId) : 0L; 
        return new Receipt(txId, auditCode);
    }
}
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  • Standardize on JSpecify: Stop letting AI guess between checker frameworks; standardize on @NullMarked and @Nullable.
  • Fast diagnostics win: NullAway adds single-digit millisecond overhead to compilation, making it fast enough for real-time agent loops.
  • Close the agent loop: Feed raw javac and NullAway errors directly back into Claude Code so it fixes its own nullability violations before code ever hits git.

Top comments (0)