DEV Community

Cover image for Null safety: Kotlin vs. Java

Null safety: Kotlin vs. Java

Nicolas Frankel on February 16, 2023

Last week, I was at the FOSDEM conference. FOSDEM is specific in that it has multiple rooms, each dedicated to a different theme and organized by a...
Collapse
 
cicirello profile image
Vincent A. Cicirello

Any thoughts on what, if any, benefit one gets from Lombok's approach? If all it does is check, but throw a NullPointerException if null, that doesn't seem any different than if you didn't check at all in the first place.

Collapse
 
nfrankel profile image
Nicolas Frankel

Indeed πŸ˜…

Collapse
 
oussama_lahmidi_43fd5e509 profile image
Oussama Lahmidi • Edited

The difference is that it checks for nullability before executing method logic so it prevents it from execution.

Collapse
 
cicirello profile image
Vincent A. Cicirello

But it seems that all it does is throw the same exception that would be thrown if you didn't check to begin with. Or am I missing something?

Thread Thread
 
oussama_lahmidi_43fd5e509 profile image
Oussama Lahmidi

Yes but here's an example:
Let's say you have this function :

void doSomething(Person person) {
// Some logic
 var name = person.getName();
}
Enter fullscreen mode Exit fullscreen mode

Let's say person is NULL. Without the annotation, the method will start execution and will only raise the NPE when you it reaches var name = person.getName() statement. While when you use the annotation, nullability is checked before invocation of the method, hence interrupting the logic of the method.

Thread Thread
 
cicirello profile image
Vincent A. Cicirello

Yes, I understand what it does. My point is that what it does serves no useful purpose. Whether the NullPointerException is thrown by Lombok's generated null check or by the attempt to invoke a method on a null reference, the result is essentially the same behavior. Preventing execution of any of the method body, but still throwing the exception, would only make a difference if that exception was caught somewhere. And catching a NullPointerException is a code smell, as is catching most runtime exceptions.

Thread Thread
 
alxgrk profile image
Alexander Girke • Edited

I agree that Lombok's approach is barely useful, but also think there is one exception. It's basically what @oussama_lahmidi_43fd5e509 expressed by // some logic in his code example: if the logic has side effects (like sending a message to a queue or writing to a database), you probably wouldn't want that code to execute if the rest of the method fails due to such a programming mistake. Of course, this can only serve as a very small safety net to human errors, but might save you from bad consequences.

Thread Thread
 
cicirello profile image
Vincent A. Cicirello

Thanks. That is a good example. You have a bug to fix either way, but in that case Lombok's generated null check may minimize extent such as preventing writing to a DB or some other similar thing.

Collapse
 
msegmx profile image
Mehmet Gunacti

Thanks for this great article!

I always wondered; is there a good argument against a flag that would make the Java compiler become null aware? Introducing such a flag would obviously break backward compatibility, but for newly created projects it might solve the NPE problem.. and who knows, 10 years from now when enough projects/libraries have adopted the approach it might become the default?

Collapse
 
nfrankel profile image
Nicolas Frankel

Java language architects value backward compatibility over code safety

You missed my my assumption in the conclusion. If you want to go beyond it, you'll need to ask the architects themselves

Collapse
 
ant_kuranov profile image
Anton Kuranov

Nullability is good when used with immutable data. But it starts to bother you when working with stateful structures. For example the lazy initialization problem, where Kotlin developers needed to implement such ugly hacks like lateinit. Another example is web validation: initially you receive from your page a form mapped to a "draft" DTO where all fields are nullable. And after the validation you should map this DTO to another "clean" one where all required fields are non-nullable. So in some cases the static nullability becomes mostly annoying than really useful.

The general problem is that in most cases the nullability can not be defined statically at compile time because it depends on the context. So I think a good programming language apart from nullable and non-nullable types should also consider a type with unchecked nullability.

Collapse
 
nfrankel profile image
Nicolas Frankel

It's an interesting viewpoint. Why would you use mutable data structures?

I find myself using more and more immutable ones. Even better, Kotlin extension functions allow you going from one immutable incomplete data structure to another immutable complete data structure. You can materialize in our code the valid state of your structures.

Collapse
 
ant_kuranov profile image
Anton Kuranov

When you write something more complex that an API wrapper over a database, and your application becomes stateful, you need mutable structures. For example JPA, STM, UIs, Graphs: they are all based on stateful structures.

Thread Thread
 
nfrankel profile image
Nicolas Frankel

"When your application becomes stateful, you need mutable data structures" could be seen as a tautology but us wrong: you can create another immutable structure reflecti5 the new state.

The fact that JPA, designed 15 years ago, uses mutability is no proof. Just don't use it.

Collapse
 
cicirello profile image
Vincent A. Cicirello

I have a question related to this statement:

Java will never be able to compete with Kotlin in this regard, as Java language architects value backward compatibility over code safety.

Shouldn't it be possible to add an annotation-based approach for null safety to Java while maintaining backwards compatibility? Maybe adopting the specific approach of one of the many libraries, or a variation.

Collapse
 
nfrankel profile image
Nicolas Frankel

The problem is the compiler: Kotlin's is null aware, not Java's. Hence, it's able to deduce whether a variable can be null or not. Annotations require work and are error-prone

Collapse
 
jmfayard profile image
Jean-Michel πŸ•΅πŸ»β€β™‚οΈ Fayard • Edited

It's a great example of a YAGNI or PAGNI exception.
Most of the time the decisions you take can be reversed if it turns out not so great. So don't design a cathedral from day one, start with something simple and iterate.
It's a really important principle for developer sanity.
At the same time, there a few key areas that a senior developer should know where you should invest time and efforts in up-front design, because "fixing" the mistake after the fact sucks a lot.

See YAGNI and PAGNIs here
lukeplant.me.uk/blog/posts/yagni-e...
simonwillison.net/2021/Jul/1/pagnis/

Collapse
 
nfrankel profile image
Nicolas Frankel

I'm not sure I understand your comment in regard to the content πŸ€”

Collapse
 
jmfayard profile image
Jean-Michel πŸ•΅πŸ»β€β™‚οΈ Fayard

I mean that it's good that Java tries to fix the initial mistake in its type system soundness. But that's obviously much harder than it would have been if like Kotlin they got it right from the start. It's one area where upfront design is really worth it.

Thread Thread
 
nfrankel profile image
Nicolas Frankel

Got it now.

The thing is, Java is not trying to fix anything... yet?