The Curse of null: Why Do We Write the Same Thing Over and Over?
\n\nIf you've been writing Java for more than a week, you've certainly encountered NullPointerException. And, most likely, your first reaction was: "Aha, I just need to check!". This is how miles of code like this are born:
\n\nif (user != null) {\n if (user.getAddress() != null) {\n String city = user.getAddress().getCity();\n if (city != null) {\n System.out.println(city.toUpperCase());\n }\n }\n}\n\n\nThis code works. But it's ugly. It turns reading logic into torture, and maintenance into a nightmare. The question "How do I avoid checking for nulls in Java?" on Stack Overflow has gathered thousands of views for a reason. Every Java developer asks it sooner or later.
\n\nThe good news: avoiding null checks is not only possible but necessary. Java is not JavaScript, and we have tools to write clean, safe code without "defensive" constructs. Let's break them all down — from the essential toolkit to advanced techniques.
\n\n1. Optional<T> — Your Main Ally
\n\nSince Java 8, we have Optional. It's a container that can hold a value or be empty. It was literally created to avoid null checks and make code more declarative.
How does it work?
\n\nInstead of returning null from a method, return Optional. And instead of checks, use method chains.
// Bad\npublic String getCity(User user) {\n if (user == null) return "Unknown";\n Address address = user.getAddress();\n if (address == null) return "Unknown";\n return address.getCity();\n}\n\n// Good\npublic Optional<String> getCity(User user) {\n return Optional.ofNullable(user)\n .map(User::getAddress)\n .map(Address::getCity);\n}\n\n// Usage\nString city = getCity(someUser).orElse("Unknown");\n\n\nNotice: not a single if. map only works if the value is not null. If null is encountered at any stage, the chain is interrupted, and you get Optional.empty().
Popular Optional Methods
\n\n- \n
orElse(T other)— return the value or a fallback \n orElseGet(Supplier<? extends T> other)— lazy version, computed only if needed \n orElseThrow(Supplier<? extends X> exceptionSupplier)— throw your own exception \n ifPresent(Consumer<? super T> consumer)— perform an action if the value is present \n filter(Predicate<? super T> predicate)— filter by a condition \n
// Example with filter\nOptional<String> name = Optional.of("Java");\nname.filter(n -> n.startsWith("J"))\n .ifPresent(System.out::println); // Prints "Java"\n\n\nImportant: Optional is intended for return values, not for class fields or method parameters. Don't overuse it.
2. Objects.requireNonNull() — Utility for Explicit Contracts
\n\nSometimes null is an error, and it needs to be detected as early as possible. Instead of writing checks manually, use java.util.Objects.requireNonNull().
public class UserService {\n private final UserRepository repository;\n\n public UserService(UserRepository repository) {\n this.repository = Objects.requireNonNull(repository, \n "UserRepository must not be null");\n }\n\n public User findById(Long id) {\n Objects.requireNonNull(id, "ID must not be null");\n return repository.findById(id);\n }\n}\n\n\nThis approach is called