🏷 Null Safety
8 patterns
Topic: Null Safety
All Java patterns related to Null Safety — java.evolved
Streams
Stream.ofNullable()
Old
Stream<String> s = val != null
? Stream.of(val)
: Stream.empty();
Modern
Stream<String> s =
Stream.ofNullable(val);
hover to see modern →
JDK 9+
learn more →
Errors
Helpful NullPointerExceptions
Old
// Old NPE message:
// "NullPointerException"
// at MyApp.main(MyApp.java:42)
// Which variable was null?!
Modern
// Modern NPE message:
// Cannot invoke "String.length()"
// because "user.address().city()"
// is null
// Exact variable identified!
hover to see modern →
JDK 14+
learn more →
Errors
Null case in switch
Old
// Must check before switch
if (status == null) {
return "unknown";
}
return switch (status) {
case ACTIVE -> "active";
case PAUSED -> "paused";
default -> "other";
};
Modern
return switch (status) {
case null -> "unknown";
case ACTIVE -> "active";
case PAUSED -> "paused";
default -> "other";
};
hover to see modern →
JDK 21+
learn more →
Errors
Optional chaining
Old
String city = null;
if (user != null) {
Address addr = user.getAddress();
if (addr != null) {
city = addr.getCity();
}
}
if (city == null) city = "Unknown";
Modern
String city = Optional.ofNullable(user)
.map(User::address)
.map(Address::city)
.orElse("Unknown");
hover to see modern →
JDK 9+
learn more →
Errors
Optional.orElseThrow() without supplier
Old
// Risky: get() throws if empty, no clear intent
String value = optional.get();
// Verbose: supplier just for NoSuchElementException
String value = optional
.orElseThrow(NoSuchElementException::new);
Modern
// Clear intent: throws NoSuchElementException if empty
String value = optional.orElseThrow();
hover to see modern →
JDK 10+
learn more →
Errors
Objects.requireNonNullElse()
Old
String name = input != null
? input
: "default";
// easy to get the order wrong
Modern
String name = Objects
.requireNonNullElse(
input, "default"
);
hover to see modern →
JDK 9+
learn more →
Tooling
JUnit 6 with JSpecify null safety
Old
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.*;
class UserServiceTest {
// JUnit 5: no null contracts on the API
// Can assertEquals() accept null? Check source...
// Does fail(String) allow null message? Unknown.
@Test
void findUser_found() {
// Is result nullable? API doesn't say
User result = service.findById("u1");
assertNotNull(result);
assertEquals("Alice", result.name());
}
@Test
void findUser_notFound() {
// Hope this returns null, not throws...
assertNull(service.findById("missing"));
}
}
Modern
import org.junit.jupiter.api.Test;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import static org.junit.jupiter.api.Assertions.*;
@NullMarked // all refs non-null unless @Nullable
class UserServiceTest {
// JUnit 6 API is @NullMarked:
// assertNull(@Nullable Object actual)
// assertEquals(@Nullable Object, @Nullable Object)
// fail(@Nullable String message)
@Test
void findUser_found() {
// IDE warns: findById returns @Nullable User
@Nullable User result = service.findById("u1");
assertNotNull(result); // narrows type to non-null
assertEquals("Alice", result.name()); // safe
}
@Test
void findUser_notFound() {
@Nullable User result = service.findById("missing");
assertNull(result); // IDE confirms null expectation
}
}
hover to see modern →
JDK 17+
learn more →
Enterprise
Spring Null Safety with JSpecify
Old
import org.springframework.lang.NonNull;
import org.springframework.lang.Nullable;
public class UserService {
@Nullable
public User findById(@NonNull String id) {
return repository.findById(id).orElse(null);
}
@NonNull
public List<User> findAll() {
return repository.findAll();
}
@NonNull
public User save(@NonNull User user) {
return repository.save(user);
}
}
Modern
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
@NullMarked
public class UserService {
public @Nullable User findById(String id) {
return repository.findById(id).orElse(null);
}
public List<User> findAll() {
return repository.findAll();
}
public User save(User user) {
return repository.save(user);
}
}
hover to see modern →
JDK 17+
learn more →