The Joys of Guava – Preconditions

The Guava library, for me, is hands down the best Java utility library out there. It is a shining example of clean code and API design, both in its implementation and documentation. If you’re not already using it in your Java projects – you absolutely should be! Check out the wiki here to get started.

In this series I want to highlight different pieces of Guava that I have found particularly useful. Often there’s a Guava utility that fits your need perfectly, so becoming familiar with the different features of the library can be extremely helpful!

Preconditions

By definition, a precondition is "a condition that must be fulfilled before other things can happen or be done". Software developers are intimately familiar with this concept, knowing that defining and checking preconditions can help in detecting errors as soon as possible and ultimately preventing bugs (EJ 49).

Most methods and constructors will have restrictions on their parameters, but enforcing those restrictions can be tedious and quickly lead to excessive boilerplate code. Consider a constructor with two object reference parameters that are required to be non-null:

public final class Person
{
    private final String name;
    private final LocalDate birthday;

    public Person(String name, LocalDate birthday)
    {
        if (name == null)
        {
            throw new NullPointerException("Name cannot be null");
        }
        if (birthday == null)
        {
            throw new NullPointerException("Birthday cannot be null");
        }
        this.name = name;
        this.birthday = birthday;
    }
}

To me, it feels like a lot of code for such a simple setup. This approach certainly works, but it will clutter your code considerably as you add more parameters or validity checks.

The static utilities in the Preconditions class are designed to cut down on this type of clutter and make your life easier. Let’s take a look at the available methods.

checkNotNull

The most common method from Preconditions that I use is checkNotNull. Exactly as it states, this method will verify that the value you give it is not null. It will also return the value directly so you can inline your calls!

Using our previous example, here’s what it would look like using Preconditions:

import static com.google.common.base.Preconditions.checkNotNull;

public final class Person
{
    private final String name;
    private final LocalDate birthday;

    public Person(String name, LocalDate birthday)
    {
        this.name = checkNotNull(name);
        this.birthday = checkNotNull(birthday);
    }
}

You still have the null checks you need, but the boilerplate is gone! Under the hood, checkNotNull is doing the exact same thing we were previously doing manually:

public static <T extends @NonNull Object> T checkNotNull(T reference) {
  if (reference == null) {
    throw new NullPointerException();
  }
  return reference;
}

Having it wrapped up in a well-named method that you can statically import is incredibly convenient.

Let’s say you still want to have helpful error messages with your NullPointerExceptions. Guava has you covered with an overload!

public Person(String name, LocalDate birthday)
{
    this.name = checkNotNull(name, "Name cannot be null");
    this.birthday = checkNotNull(birthday, "Birthday cannot be null");
}

Just as smooth and slick as before.

But what about Objects.requireNonNull()?!?

Yes, since Java 7 the JDK does provide us an almost identical method in Objects.requireNotNull(). So why use one over the other? I recommend the Guava version for two reasons:

  1. Consistency with usage of other Preconditions methods in your code.
  2. Available overload with printf-style exception messages (discussed later).

checkArgument

For verifying more general argument constraints, there’s the checkArgument method. Imagine a setter method in our Person class that can only accept values of zero or greater:

/**
 * Sets the age of this person.
 *
 * @param age the new age in years
 * @throws IllegalArgumentException if age is negative
 */
public void setAge(int age)
{
    if (age < 0)
    {
        throw new IllegalArgumentException("Age cannot be negative: " + age);
    }
    this.age = age;
}

While we can’t inline our calls to checkArgument like we could with checkNotNull, it can still simplify our code while maintaining the functionality we desire:

public void setAge(int age)
{
    checkArgument(age >= 0, "Age cannot be negative: %s", age);
    this.age = age;
}

There are a few things to note about this example:

  1. The first argument to checkArgument, the condition, should express the condition that should be true. This is the inverse of the condition in our previous if statement. I find that writing the conditions for checkArgument tends to result in code that is easier to read and interpret.
  2. All Preconditions methods have printf-style overloads for their error message creation. Anything after the second argument is treated as a variable to be printed using the formatting in the error message. Note that only %s is supported for efficiency instead of the full range of Formatter specifiers.

checkState

As you might imagine, checkState behaves much like checkArgument except that it throws an IllegalStateException instead of an IllegalArgumentException. Any time you need to verify the state or capabilities of some object before allowing an action to be taken, the checkState method will provide exactly what you need.

Others

There are additional methods in the Preconditions class that deal with indices: checkElementIndex, checkPositionIndex, and checkPositionIndexes. I rarely end up using these methods, but you can read more about them in the wiki or the Javadocs.

Final Notes

The Preconditions class has to be one of the most useful APIs in the entire Guava library. After static imports, the methods are clearly named and indicate which exception will be thrown if the condition fails. They can cut down on boilerplate validation checking in your code or perhaps provide a quick and easy way to beef up your error checking if you have some holes in your precondition verification.

One thing to note about all these methods is that they throw unchecked runtime exceptions if they fail. As such, only use Preconditions methods to detect programming errors like null values. If clients can and should realistically recover from the failure, you’d want to use a checked exception type and avoid the Preconditions class.

The Preconditions class also doesn’t cover every type of precondition failure imaginable. Methods that throw exceptions like UnsupportedOperationException or NoSuchElementException should not be migrated just for the sake of using the Preconditions utilities.

For further reading on when to use the Preconditions class, see the Conditional Failures Explained article in the Guava User Guide.

Leave a Reply