Blog

Asserting Optional Values in Java With JUnit and AssertJ

Optional in Java was supposed to end NullPointerException pain. According to codingnconcepts.com, testing Optional values is still awkward with JUnit 5 and a breeze with AssertJ. The difference comes down to how much fluent API each library gives you.

If you write Java tests, you have probably written at least one Optional.isPresent() check followed by optional.get(). There are cleaner ways.

What happened

  • JUnit 5 has basic Optional support via isPresent() and isEmpty()
  • AssertJ has a rich Optional-specific API
  • AssertJ's isPresent() and isNotEmpty() are aliases
  • AssertJ's hasValueSatisfying() lets you check inner object properties
  • JUnit 5 requires manual ifPresent() blocks for nested assertions

JUnit 5's basic Optional support

JUnit 5 ships with assertTrue and assertFalse, which is enough to verify Optional state. AssertTrue(optional.isPresent()) confirms a value exists. AssertFalse(optional.isPresent()) confirms emptiness.

For deeper checks, you pair those with assertEquals on the value: assertEquals("foo", optional.get()). That works for primitives and strings.

For objects inside Optional, the path is awkward. You call ifPresent and assert on the unwrapped value inside the lambda. JUnit 5 does not provide a built-in way to chain assertions on the contents.

AssertJ's fluent Optional API

AssertJ treats Optional as a first-class type. AssertThat(optional).isPresent() works directly, without the awkward .isPresent() call.

More useful, AssertJ has aliases. IsPresent() and isNotEmpty() do the same thing. IsEmpty() and isNotPresent() do the same thing. Pick whichever reads better.

For value checks, hasValue() and contains() are aliases. Both confirm the Optional holds the expected value. The chain form reads naturally: assertThat(optional).isPresent().contains("foo").

For objects, hasValueSatisfying() takes a lambda with the unwrapped value. Inside the lambda, you write AssertJ assertions as normal. That is the killer feature for nested object checks.

Our analysis: where JUnit 5 falls short

JUnit 5's Optional support is functional but minimal. Every check requires combining assertTrue with .isPresent(), which reads awkwardly. The lack of a built-in way to run checks against the contents of an Optional without unwrapping it is the biggest gap.

For projects that do not use AssertJ, that gap is workable. You write ifPresent blocks. They are ugly but correct.

For projects that already use AssertJ, there is no reason to write the awkward JUnit 5 version. The AssertJ API was designed for exactly this. Switching costs nothing because the import is the only difference.

Worked example: say you are testing a User lookup service

Say you have a method findUser that returns Optional<User>. You want to verify the user has the right name, is over 18, and holds a premium account.

With JUnit 5:

“`java

UserOptional.ifPresent(u -> {

AssertEquals("Jack", u.getFirstName());

AssertTrue(u.getAge() > 18);

AssertTrue(u.getIsPremiumUser());

});

“`

With AssertJ:

“`java

AssertThat(userOptional).isPresent().hasValueSatisfying(u -> {

AssertThat(u.getFirstName()).isEqualTo("Jack");

AssertThat(u.getAge()).isGreaterThan(18);

AssertThat(u.getIsPremiumUser()).isTrue();

});

“`

The AssertJ version chains cleanly. The JUnit 5 version nests. Same assertions, different readability.

What to watch next

AssertJ's Optional support continues to evolve. Recent versions added .containsInstanceOf() for type checking and .usingRecursiveComparison() for deep equality on contained objects. Both are worth knowing.

For Kotlin code, Arrow's Optional-like Either type has similar fluent assertions through Kotest. The pattern transfers even if the syntax differs.

If you are using Java's Stream API alongside Optional, AssertJ has equally fluent stream assertions. Combining them gives you a consistent assertion style across your test suite.

Key takeaways

  • AssertJ has richer Optional support than JUnit 5
  • Use hasValueSatisfying() for nested object assertions inside Optional
  • JUnit 5 works but requires manual ifPresent() blocks for nested checks

Frequently asked questions

Are isPresent() and isNotEmpty() different in AssertJ?

No. They are aliases and can be used interchangeably. Pick whichever reads better in context.

Can I check Optional without AssertJ?

Yes. JUnit 5's assertTrue(optional.isPresent()) works for basic checks.

What is hasValueSatisfying()?

An AssertJ method that runs assertions on the value inside an Optional, without requiring you to unwrap it manually.

Does AssertJ handle Optional of Optional?

AssertJ treats nested Optional as a regular object. You would need to assert on the inner Optional separately.

Related