Optional: Avoiding Null Pointer Exceptions

The Null Problem

Returning null to mean "there's no value here" is one of the most common sources of bugs in any Java program — Tony Hoare, who introduced the null reference in 1965, later called it his "billion-dollar mistake." The trouble is that null is invisible in a method's signature: nothing about Pose2d getVisionPose() warns a caller that it might come back null, so it's easy to forget the check and get a NullPointerException (Ch.12/13) instead.

The classic FRC case: a vision pose estimator can only report a robot position if a camera actually sees an AprilTag this frame — plenty of frames, it can't. Returning null on those frames leaves every caller responsible for remembering to check first.

Optional: Making "Might Be Absent" Part of the Type

java.util.Optional<T> is a container that either holds a value, or is explicitly empty — and unlike null, that possibility is written directly into the method's return type:

public Optional<Pose2d> getPose()
{
    if (!hasTargetThisFrame())
    {
        return Optional.empty();       // explicitly "no value" — not null
    }
    return Optional.of(currentPose);   // wraps a real, non-null value
}

Just by reading Optional<Pose2d> getPose()'s signature, a caller already knows the pose might not be there this frame — the type itself documents it, instead of relying on a comment or tribal knowledge.

Creating an Optional

Optional<Pose2d> empty = Optional.empty();       // definitely no value

// wraps a value — throws immediately if currentPose is null
Optional<Pose2d> present = Optional.of(currentPose);

// empty if currentPose is null, present otherwise
Optional<Pose2d> maybe = Optional.ofNullable(currentPose);

Optional.of(...) is a deliberate safety check: if you accidentally pass it a null, it throws right away — better to fail loudly at the source than to silently wrap a null and have it surface as a confusing failure somewhere else later.

Handling Both Cases Honestly

Optional<Pose2d> visionPose = vision.getPose();

// Run logic only when a value exists
visionPose.ifPresent(pose -> estimator.addVisionMeasurement(pose, timestamp));

// Or supply a fallback value for the empty case
Pose2d fallbackPose = visionPose.orElse(lastKnownPose);

// Or throw a specific exception if a value was truly required
Pose2d requiredPose = visionPose.orElseThrow(
    () -> new IllegalStateException("No vision pose"));

ifPresent runs a lambda (Lesson 19.1) only if a value is actually there — nothing happens otherwise. orElse supplies a fallback value for the empty case. orElseThrow is for the rarer case where an empty Optional really does mean something has gone wrong, and continuing anyway wouldn't make sense — passing it a lambda that builds the exception (rather than a bare new IllegalStateException(...)) matters here: orElseThrow only constructs and throws that exception when the Optional really is empty, instead of building it eagerly every time. (You'll also see orElseThrow(IllegalStateException::new) written as a constructor reference in other code — Lesson 19.1 has the full table of method-reference kinds, including that one.)

Optional also has a few less common but genuinely useful methods: a no-argument orElseThrow() (throws NoSuchElementException when nothing more specific is needed), orElseGet(supplier) (like orElse, but only computes its fallback value when it's actually needed, instead of always), and ifPresentOrElse(consumer, runnable) (runs one callback if a value is present, and a different one if it's empty — ifPresent alone has no "else").

The One Pitfall That Defeats the Whole Point

Optional has an isPresent() check and a get() method that returns the wrapped value — but calling get() without checking isPresent() first throws NoSuchElementException on an empty Optional, which is exactly the same class of crash Optional exists to prevent, just under a different exception name:

// never do this blind — this is a NullPointerException with extra steps
Pose2d uncheckedPose = visionPose.get();

Even the safer isPresent() + get() pairing is discouraged — it's really just a nested-null-check in disguise. ifPresent, orElse, and orElseThrow all handle both cases directly, without ever needing to call bare get().

Filtering and Transforming (a Brief Look)

Beyond the core methods above, Optional supports filter (keep the value only if it matches a condition, otherwise become empty) and map (transform the value, if present, into something else) — useful for chaining a couple of extra checks without falling back into nested if statements:

visionPose
    // only proceed if the pose has a valid X coordinate
    .filter(pose -> pose.getX() >= 0)
    .ifPresent(pose -> estimator.addVisionMeasurement(pose, timestamp));

map transforms the value inside an Optional, without ever having to check isPresent() first — on an empty Optional, it just stays empty:

double dashboardX = visionPose.map(pose -> pose.getX()).orElse(0.0);

visionPose.map(pose -> pose.getX()) turns an Optional<Pose2d> into an Optional<Double> — present with the X coordinate if visionPose had a value, empty otherwise — and .orElse(0.0) then unwraps that down to a plain double either way.

These compose well for simple cases; genuinely complex chains of Optional transformations are a more advanced topic than this curriculum covers in depth.

Common Pitfalls

  • Calling .get() without checking first. This throws on an empty Optional, defeating the entire purpose of using one.
  • Using Optional for every field or parameter, out of habit. It's meant for genuinely optional results (especially return values) — not a blanket replacement for every reference type in a program.
  • Passing a null to Optional.of(...). That throws immediately — use Optional.ofNullable(...) instead when the value genuinely might be null.

Key Takeaways

  • Optional<T> makes "this might not have a value" visible directly in a method's return type, instead of relying on an invisible, easy-to-forget null check.
  • Create one with Optional.empty(), Optional.of(value) (throws if value is null), or Optional.ofNullable(value) (safe either way).
  • Handle both cases with ifPresent(...), orElse(fallback), or orElseThrow(...) — never call bare .get() without checking first.
  • filter and map let you chain a couple of extra conditions/transformations without falling back into nested if checks.
  • Beyond the core methods: a no-argument orElseThrow(), orElseGet(supplier) (lazy, unlike eager orElse), and ifPresentOrElse(consumer, runnable) (a present/empty pair of callbacks) cover the less common cases.

Derived from other-reference-repo: oracle-java-tutorials/optional/java8-optional.md (ORACLE 16.1) Deck context: mechacoder-test/src/lessons/java-2.js, slide 9b ("Optional: Maybe a Value") — vision pose estimator example