scottshipp
Posted on November 19, 2021
Photo by Vlad Bagacian from Pexels
Java is a great language but it can really bite you if you don't watch out! Here's the first installment of a series of helpful things to watch out for in Java!
😰 Today's Pitfall
The following code sometimes throws a NullPointerException
. Can you guess why?
public boolean checkIfDelivered(Food food) {
if(food == null) {
return false;
} else {
return food.wasDelivered();
}
}
It's hard to imagine a NullPointerException
happening here, isn't it? Considering the null
check?
Yet, here is the exception you may see from Java code like this:
Exception in thread "main" java.lang.NullPointerException
at com.company.MyClass.checkIfDelivered(MyClass.java:20)
at com.company.MyClass.main(MyClass.java:13)
OK so it's actually impossible to see the problem without another piece of vital information. Here's part of the Food
interface you should know about.
interface Food {
Boolean wasDelivered();
}
🤔 Why it happens
This all has to do with conflicting types. The return type of Food.wasDelivered()
is Boolean
, with a capital "B." But the return type of the checkIfDelivered()
method is boolean
.
Yes, that's right: it's all because of one letter. 😆
Well, that's not entirely fair. There's a lot more to it than a letter: boolean
is a primitive type in Java, whereas Boolean
is a special class called a "wrapper class."
Primitive types in Java are represented by reserved keywords like boolean
, int
, and char
. Notice that they are always shortened and lowercased.
Every primitive can be "wrapped" in a corresponding Java class called a "wrapper class." Each wrapper class is capitalized according to Java's class naming convention and lengthened to its full name. int
becomes Integer
, char
becomes Character
, and boolean
becomes Boolean
.
The Java tutorial explains it this way:
…the Java platform provides wrapper classes for each of the primitive data types. These classes "wrap" the primitive in an object. Often, the wrapping is done by the compiler—if you use a primitive where an object is expected, the compiler boxes the primitive in its wrapper class for you. Similarly, if you use a number object when a primitive is expected, the compiler unboxes the object for you.
—From the Oracle Java Tutorial
By the way, it is more common to use the terms "boxing" and "unboxing" instead of "wrapping" and "unwrapping." You can read more about autoboxing in the Java tutorial.
The automatic "unboxing" of a Boolean
to a boolean
is exactly the problem with the code snippet above! The food.wasDelivered()
method returns a Boolean
object, which means it can also return null
. The checkIfDelivered
method returns a primitive boolean
, which means if you try and return food.wasDelivered()
the compiler attempts to unbox it.
And…BOOM! 💥 When it is null, a NullPointerException
results.
👀 Where you'll see it
It's common these days to retrieve data over the network from web services. When that happens, the response is usually in a format like JSON, which does not have the same "primitive" types as Java.
It may be common for the JSON representation of a Food
to look like this:
{
wasDelivered: null
}
Null is an entirely valid value in the JSON schema.
Or it may just be missing altogether, like this:
{}
In either of these cases, most Java developers will use a JSON-to-Java conversion library, such as Jackson, to map the JSON object to a Java object. These libraries can be configured to default the value of wasDelivered
on the resulting Java object to false
or true
, but more often either of the above cases will just result in the value being null
.
If the resulting object is a Food
object like in our example code, you now know what will happen. 😈
🛠️ How to fix it
It's considered a best practice to prefer the use of primitive types in Java because of the performance overhead that comes from autoboxing. So, if you can, avoid the problem rather than fixing it. Use boolean
throughout your code and then you won't have to think that much about what happens if the JVM attempts to box or unbox a value.
But, as you can see from the above example of a JSON response being mapped to a Java object, you're not going to be able to avoid it in some cases. It may even be preferable in our Food example that wasDelivered
is stored as a Boolean
, because it may be useful as a way to know that the value was null
or missing in the JSON response.
So that means you just need to exercise your due diligence when retrieving the value.
The offending method can be rewritten just slightly as follows, and all will be well:
public boolean checkIfDelivered(Food food) {
if(food == null || food.wasDelivered() == null) {
return false;
} else {
return food.wasDelivered();
}
}
You may find that you write a lot of boilerplate methods like the above. In that case, wouldn't it be nice to have a utility to use instead? The popular Apache Commons Lang library has the exact thing you need.
It's called BooleanUtils
and has two nice methods you should be aware of. The first, toBoolean
, translates the wrapper Boolean
to false if it has a null value. The second, toBooleanDefaultIfNull
, lets you explicitly define the behavior, meaning you can default to true
if that makes more sense for your case.
Here is our offending method rewritten with toBoolean
:
public boolean checkIfDelivered(Food food) {
if(food == null) {
return false;
} else {
return BooleanUtils.toBoolean(food.wasDelivered());
}
}
Homework
Now that you know a little more about autoboxing, don't forget to read the Java tutorial to learn more.
Posted on November 19, 2021
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
November 28, 2024
November 21, 2024