
1. Introduction
A NullPointerException is one of Java’s most common exceptions. It occurs when accessing or interacting with a reference variable that points to null. Validating objects, especially when they’re parameters in methods or constructors, is important to ensure robust and reliable code. We can do this by writing our null-checking code, using third-party libraries, or choosing a more convenient approach.
In this tutorial, we’ll look at the latter – a flexible, built-in solution Java offers with the Objects.requireNonNull() methods.
2. Null Value Handling
To briefly review, many alternatives are available to avoid manual null checks. Instead of wrapping our code with if statements, which could be error-prone and time-consuming, we can choose from various libraries. Spring, Lombok (@NonNull), and Uber’s NullAway are just a few of them.
In contrast, if we want to maintain consistency, avoid introducing additional libraries to our code base, or use vanilla Java, we can choose between Optional class or Objects methods. While Optional simplifies null values handling, it often adds coding overhead and clutters the code for simple use cases. Since it’s a separate object, it also consumes extra memory. Moreover, Optional only transforms a NullPointerException into a NoSuchElementException, i.e., it doesn’t resolve the defect.
On the other hand, the Objects class provides static utility methods for working with objects more efficiently. Introduced in Java 7 and updated several times across Java 8 and 9, it also provides methods to validate conditions before performing operations.
3. Advantages of Objects.requireNonNull()
The Objects class simplifies checking and handling null values by providing a set of requireNonNull() static methods. These methods offer a straightforward and concise approach to checking for null objects before use.
The primary purpose of the requireNonNull() methods is to check if an object reference is null and throw a NullPointerException if it is. By explicitly throwing the exception, we communicate the intent of the check, making the code easier to understand and maintain. This also informs developers and maintainers that the error is deliberate, which helps prevent unintentional changes to the behavior over time.
The Objects class is part of the java.util package, which makes it easily accessible without requiring external libraries or dependencies. Its methods are well-documented, providing developers with clear instructions for correct usage.
4. Use Cases and Overloaded Methods
Let’s now focus on various use cases and overloaded variants of the requireNonNull() method.
These methods allow handling null values in different scenarios, from simple null checks to more complex validations with custom messages.
Additionally, two other methods support default values – requireNonNullElse() and requireNonNullElseGet(). By understanding these use cases, we can effectively incorporate requireNonNull() into our code base.
4.1. Single Parameter Validation in Methods and Constructors
First, let’s look at the implementation of the simplest one – requireNonNull() with a single parameter:
public static <T> T requireNonNull(T obj) {
if (obj == null) {
throw new NullPointerException();
} else {
return obj;
}
}
The method checks if the provided object reference is null. If it is, it throws a NullPointerException. Otherwise, the unchanged object is returned.
This method is primarily used for parameter validation in methods and constructors. For instance, to make sure the name passed to the greet() method isn’t null, we can use the following approach:
void greet(String name) {
Objects.requireNonNull(name, "Name cannot be null");
logger.info("Hello, {}!", name);
}
@Test
void givenObject_whenGreet_thenNoException() {
Assertions.assertDoesNotThrow(() -> greet("Baeldung"));
}
@Test
void givenNull_whenGreet_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> greet(null));
}
By using requireNonNull(), we can ensure methods receive valid arguments. We can detect null objects before we use them, preventing errors when those objects are accessed.
4.2. Multiple Parameter Validation With Custom Error Messages
Next, let’s check how to handle scenarios where our methods or constructors receive multiple arguments. A variant of Objects.requireNonNull() accepts a String message beside the object reference checked for nullity. This enables throwing a NullPointerException with a custom error message in case the object reference is null:
public static <T> T requireNonNull(T obj, String message) {
if (obj == null)
throw new NullPointerException(message);
return obj;
}
We can use this method for validation in constructors with multiple parameters:
static class User {
private final String username;
private final String password;
public User(String username, String password) {
this.username = Objects.requireNonNull(username, "Username is null!");
this.password = Objects.requireNonNull(password, "Password is null!");
}
// getters
}
@Test
void givenValidInput_whenNewUser_thenNoException() {
Assertions.assertDoesNotThrow(() -> new User("Baeldung", "Secret"));
}
@Test
void givenNull_whenNewUser_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> new User(null, "Secret"));
Assertions.assertThrows(NullPointerException.class, () -> new User("Baeldung", null));
}
NullPointerExceptions with custom messages provide more context by specifying what went wrong and which parameter caused the issue. This, in turn, improves error reporting and makes debugging easier.
4.3. Deferring Message Creation With Supplier
Finally, we can customize a NullPointerException with another overloaded method:
public static <T> T requireNonNull(T obj, Supplier<String> messageSupplier) {
if (obj == null) {
throw new NullPointerException(messageSupplier == null ? null : (String)messageSupplier.get());
} else {
return obj;
}
}
The second parameter is a Supplier for the error message. It allows deferred message creation until after the null check, which could improve the performance. This is particularly valuable in costly operations, such as string concatenation or complex calculations:
void processOrder(UUID orderId) {
Objects.requireNonNull(orderId, () -> {
String message = "Order ID cannot be null! Current timestamp: " + getProcessTimestamp();
message = message.concat("Total number of invalid orders: " + getOrderAmount());
message = message.concat("Please provide a valid order.");
return message;
});
logger.info("Processing order with id: {}", orderId);
}
private static int getOrderAmount() {
return new Random().nextInt(100_000);
}
private static Instant getProcessTimestamp() {
return Instant.now();
}
@Test
void givenObject_whenProcessOrder_thenNoException() {
Assertions.assertDoesNotThrow(() -> processOrder(UUID.randomUUID()));
}
@Test
void givenNull_whenProcessOrder_thenException() {
Assertions.assertThrows(NullPointerException.class, () -> processOrder(null));
}
In the example above, getOrderAmount() and getProcessTimestamp() could involve time-consuming operations, such as database queries or external API calls. By deferring the message creation, this approach prevents unnecessary performance costs when the orderId isn’t null.
Nevertheless, it’s important to ensure that the overhead of creating the message Supplier is lower than directly generating the String message.
5. Best Practices
As we’ve seen earlier, when designing methods and constructors, we need to enforce parameter restrictions to ensure our code’s predictable behavior. Using Objects.requireNonNull() at the start of a method or constructor helps catch invalid arguments early. This practice also keeps our code clean, easier to maintain, and simpler to debug.
What’s more, requireNonNull() can be essential in building a fail-fast system. The fail-fast principle implies errors are detected immediately, preventing cascading failures and reducing debugging complexity. If a method validates its parameters upfront, it fails quickly with a clear exception, making the source of the issue apparent. These checks are necessary for the system to avoid producing confusing errors, incorrect results, or even problems in unrelated parts of the code.
Another good practice is explicitly documenting the parameter restrictions for public or protected methods. We can use the Javadoc @throws tag to specify exceptions thrown if parameter restrictions are violated. If multiple methods throw a NullPointerException, we can cover this in the class-level Javadoc instead of repeating it for each method.
6. Conclusion
In this tutorial, we demonstrated how to efficiently validate method and constructor parameters using Objects.requireNonNull() and its overloaded variations. These methods provide a simple yet powerful way to handle null checks in Java. A built-in support for custom error messages and deferred message creation adds flexibility, making it suitable for various use cases.
Moreover, adopting best practices like enforcing parameter restrictions, using fail-fast principles, and documenting exceptions improves our codebase’s overall quality and maintainability.
As always, the complete source code is available over on GitHub.
The post Guide to Objects.requireNonNull() in Java first appeared on Baeldung.