Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

NullableJ: Handling Null in Java

DZone's Guide to

NullableJ: Handling Null in Java

Let's explore the NullableJ library, a creation designed to bridge the gap that Optional didn't quite close by offering four strategies to avoid the dreaded null in Java.

· Java Zone ·
Free Resource

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

"I call it my billion-dollar mistake." — C.A.R Hoare on "Null reference"

"nullis a long-standing problem in programming. Even with Java 8's Optional, we still didn't get thea natural way to handle null we deserved (IMHO, it does a decent job, but we need better). So, I wrote some code to deal with those ... scattered throughout my projects. NullableJ gathers those up into one place. This article discusses features in the NullableJ library for dealing with null. Dig in, I sincerely believe that you will find some of these useful.

Dealing With null

There are generally four ways to handle null.

  1. Ignore it
  2. Wrap it
  3. Replace it
  4. Fake it

NullableJ utilizes all those strategies.

NOTE: Ok ok, there are more ways — like prohibiting the use of it completely — but it just cannot be done at the library level.

Ignoring null With NullableJ

Ignoring null is basically, "If I see a null, I will just do nothing or do something else" — in other words, "a null check". Some languages have an easier way to do this than just checking if  value == null then .... The best example is Groovy's  ?.  operator. But since we live in the Java world, we have to find an alternative. One alternative is to use Lombok's @ExtensionMethod. NullableJ has a collection of often-used extension methods that are null safe. With @ExtensionMethod, a static method can be used like a regular method to the object of the first parameter type. For example, if the extension method is written as:

public class NullableJ {
    public static <T> T _or(T theObject, T theOrValue) {
        return (theObject != null) ? theObject : theOrValue;
    }
}  


Then the method _or can be used like this:

System.out.println("String: " + str._or(" "));
//                                  ^^^ and Elvis operator, anyone?  


Now let's see a slightly bigger example using NullableJ null-safe methods. Consider the following code.

public class SaleReport {
    public BigDecimal totalYearlySaleByPart(String partNumber, Color color, int year) {
        val item        = itemService.findItem(partNumber, color);
        val salesByYear = saleStatService.findItemSalesByYear(item);
        return salesByYear.get(year).stream().map(Sale::getTotal).collect(reducing(ZERO, BigDecimal::add));
    }
}  


This method is very readable, but a few things can go wrong.

  • There might be no item for the partNumber and color.
  • The legacy saleStatService.findItemSalesByYear(item) may return null (or worse, throw an NPE) if item is null.
  • There might be no item in the salesByYear map.

With NullableJ methods, we may adjust the method like this.

@ExtensionMethod({ NullableJ.class })
public class SaleReport {
    public BigDecimal totalYearlySaleByPart(String partNumber, Color color, int year) {
        val item        = itemService.findItem(partNumber, color);
        val salesByYear = item._isNotNull() ? saleStatService.findItemSalesByYear(item) : null;
        return salesByYear._get(year)._stream$().map(Sale::getTotal).collect(reducing(ZERO, BigDecimal::add));
    }
}  


Spot the differences? That exactly the point. NullableJ methods are designed to be used with Lombok's @ExtensionMethod to provide a natural way to handle null — this looks so natural that it is supernatural! NullableJ has many often-used methods at the ready.

Here are some more examples of such methods:

  • _isEmpty: check if the given string (also overload to list/array) is empty — returns true if null
  • _contains: check if the given string contains the given substring
  • _matches: check if the given string matches the regular expression — false if the string is null
  • _stream$: change array/collection to stream

As well as collection-related methods. For example:

  • _isEmpty: check if the array/collection/map is null or empty.
  • _size: return the size of the collection/map, or 0 for null.
  • _contains: check of the array/collection contains an element, or false if null
  • _get: get the value at the index or key, or returns null if none is there

There is also _whenXXX , which will return Otherwise (similar to Optional) if the condition is not met. For example:

  • _when: check if the predicate is passed
  • _whenMatches: check if the string matches the regular expression
  • _whenNotEmpty: check if the collection is empty

These allow you to write a method to parse a string to a number, if valid, like this:

return string._whenMatches("^[0-9]+$").map(Integer::parseInt).orElse(-1);  


The essence of this is in each of these extension methods. The null check is done, and the default behavior is used. The drawback of this approach is that we have to create these sorts of methods for any operations we want to use. NullableJ already has a bunch of commonly used methods. If there are more you need, just simply create one.

Find more from here.

Wrapping null With Nullable

Wrapping null means having another object hold the value that might be null. Since the wrapper itself is never null, we can do things to it without blowing up. This is the approach Java 8 uses with Optional. NullableJ's solution for this is Nullable .

Nullable is a copy-cat of Java 8 Optionals. However, there are a few subtle differences. Still, if you know Optional, you will almost know Nullable. So basically, Nullable is a wrapper for an object. Of course, that object can be null. You can check if the object is null (isPresent()). You can transform the object using map(...). And almost everything you do with Optional, you can do it with Nullable.

Nullable.of(myString).map(s->"\"" + s + "\" is not null.").orElse("The string is null");  


Very Optional, right? With Nullable, you can do these too.

assertNull(Nullable.of(null).map(s->"\"" + s + "\" is not null.").get());  


of and get will not throw an exception if the value is null.

If what you need to do is more complex — but not complex enough to pull it out as a separate method — you can also use  Nullable.from(...), which takes suppliers.

Nullable.from(()->myMap.get(theKey.toUpperCase()).replaceAll("^PREFIX: ", "")).get();  


If the supplier throws an NPE, Nullable.from(...)  will just be Nullable.ofNull(). With this, there is no need to change your code to the Nullable (Optional) style, just do it normally.

NOTE: If you need a similar thing but for any type of exception, wait for it a bit. I am working on another project called FaillableJ that will provide that. Or, you can just create one (look at the code of  Nullable.from(...) for inspiration).

There are a few more differences. Find out more from here.

Replacing null With NullValues

Sometimes, we need to pass on a value to external systems — which we have no control over. And these systems might not be able to deal with null very well. The option is to find a value that is not null, but equivalent to null.  NullValues can return a sensible null replacement for a given type. It employs many strategies like known values, new object, field singleton, annotated field, and more. The example code below shows a PhoneNumber class that specifies its null value using the "annotated field" strategy.

public class PhoneNumber {
  @NullValue// Specify that this value is a null value.
  public static final PhoneNumber nullPhoneNumber = new PhoneNumber("xxx-xxx-xxxx");
  private String number;
  ...
}
...    // NullValues figured out that the nullPhoneNumber is the null value of the type.
  assertEquals("xxx-xxx-xxxx", NullValues.of(PhoneNumber.class).getNumber());  


Now, the null value of PhoneNumber can be used whenever PhoneNumber is needed, but null is not acceptable.

Having a central place to get things is good, but the replacement values might be suitable for all situations. For example, 0 is a good null value for integer addition and subtraction, but a destructive replacement for multiplication and division. Because of that, NullValues are designed in such a way that its components can be reused in case different strategies are needed.

Find out more from here.

Faking null With NullableData

NullableData can create an instance of any interface that acts as a null object. The resulting object (called a nullable-data object) implements the data interface and holds the actual value of that interface type. But if the provided value is null, this nullable data instance will act as a null object. If the methods have a default implementation, the implementation will be called. All other methods will do nothing and return the null value of that method return type (by calling NullValues.nullValueOf(returnType)). Here is the example.

public static interface Person {
    public String getFirstName();
    public String getLastName();
    public default String getFullName() {
        return (getFirstName() + " " + getLastName()).trim();
    }
}  


With the following implementation:

@Value
public static class PersonImpl implements Person {
    private String firstName;
    private String lastName;
}  


Then a nullable data object can be created and used like this.

Person nullablePerson = NullableData.of(new PersonImpl("Peter", "Pan"), Person.class);
assertTrue(nullablePerson instanceof Person);
assertEquals("Peter",     nullablePerson.getFirstName());
assertEquals("Pan",       nullablePerson.getLastName());
assertEquals("Peter Pan", nullablePerson.getFullName());  


If the data is null, the exact same operation can be used, but it will act as a null object of that type.

Person nullablePerson = NullableData.of(null, Person.class);
assertTrue(nullablePerson instanceof Person);
assertEquals("", nullablePerson.getFirstName());
assertEquals("", nullablePerson.getLastName());
assertEquals("", nullablePerson.getFullName());  


If you want to know if the underlying value is null, NullableData has you covered. The null-data instance secretly implements the IAsNullable interface. The method asNullable from IAsNullable will return an instance of Nullable of the underlying object. Because the implementation of IAsNullable is hidden, the instance has to be cast to IAsNullable before it can be used as such. You can also make the data interface to implement the IAsNullable interface.

public static interface NullablePerson extends Person, IAsNullable {}
...

val nullablePerson = NullableData.of(new PersonImpl("Peter", "Pan"), NullablePerson.class);
assertTrue(nullablePerson instanceof Person);
assertTrue(nullablePerson.asNullable().isPresent());

val nullablePerson2 = NullableData.of(null, NullablePerson.class);
assertTrue(nullablePerson2 instanceof Person);
assertFalse(nullablePerson2.asNullable().isPresent());  


See this on GitHub here.

Conclusion

The NullableJ library offers multiple ways of dealing with null in Java. There are multiple ways because there are multiple needs and circumstances. If Lombok's @ExtensionMethod can be used, the NullableJ class offers a natural way of ignoring nullNullable wraps a value that might be null so you can continue to interact with it. NullValues return a replacement value of null for a given class, so you can pass it onto the code that does not deal with null well. NullableData creates a fake object for a given type that act likes a null instance of that data. With all these, dealing with null should be much easier so you can focus on more important things like your business logic.

Happy coding!

How do you break a Monolith into Microservices at Scale? This ebook shows strategies and techniques for building scalable and resilient microservices.

Topics:
java ,null ,null checks ,optional ,nullablej ,library ,tutorial

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}