The Past, Present, and Future of Null-Safety in Java - Mala Gupta
About this talk
This session addresses the existence of null in Java and the common pitfalls of null pointer exceptions. The speaker discusses why null values are integral to Java, citing reasons like uninitialized variables, lazy resource loading, and memory management by the garbage collector. They explore various solutions over time, including the use of optional types, null checks, and annotations, as well as preparing for future enhancements like Project Valhalla. Modern approaches like using records and sealed classes are highlighted for better handling of nulls and improving code safety. The talk emphasizes the need for careful consideration in method designs to mitigate the risks associated with null values.
Full transcript
This is what we're going to talk about in the session today. Of course, we're going to talk about why null exists. A lot of reasons why we had the null pointer exceptions, even though we know it's quite straightforward and simple to fix, but we still get get that exception. Common solutions, what we had in the past, modern solutions, and what are the some of the solutions which
are planned. Not here yet, but they are being worked on. And which one to pick when you have a problem. And before I do that, um if you could help me uh understand or answer this question, could you tell me those? I should have done that before. Yes. Do you think this code has been written by a developer or generated by an AI tool? Sorry, come again?
The comments in the code. Those are the AI ads this one. Um yes, I I missed to minimize that part. Let's disregard that part. But whatever answer you say, I would negate that. So >> [laughter] >> But I would say optional is on the print of the AI ads. A lot of people still do not think that it's mostly that uh-huh So that's like people like her
that Um so so AI came out so optional was introduced in Java 8. So that's a pretty I would say um a concept which has been for around for a while. And AI is pretty good at using the concepts which have been around for a while. But now, if you give them context, they can even use uh the ones which haven't been released like Valhalla. If you
give them the link to let's say this is the Valhalla project which is in works at Oracle, can you go and modify my code which uses this code base as Ragunath mentioned. If you get give it the right context, it could do that. But of course as the person who is generating the code, you have to kind of be wise enough to understand what it's generating correctly
or not. So it it's a combination. So I have few methods which I wrote and then I asked the AI tools to write other one. The interesting part about this is I had single prompt which is this one given the following code and write a method which does that. But every AI model it generated something which is completely different. So this was long time ago so I
do not remember the name of the model. So this one is Claude 4.7. This one is Claude 4 Sonic. Gemini 2.5 and this one is ChatGPT 4o. So the point that I'm trying to make here is if you give the same prompt to different models, you would get different code. And again, you would have to determine whether the code is good enough or not. And and now
let me go back to the slides that I had. Okay, so before I talk about the null pointer exception, I want to get answers from you. Why do you think even the null exist in Java? Because if we do not have nulls, we wouldn't we will not have any null pointer exceptions. Why do you think they even exist in Java? Don't take it down. But the put
the whole thing in. Uh yes, that is correct. The The reference variables they might not have a value. So, the one of the best ways to represent no value is null. And any other answers? The books there. Let's say if the bumping up up in the sense what it what it is the court keeper in the book. Okay, let me go by the primitives then you what
in that that is simple right? That makes more widgets. Um that is I would say partially incorrect statement because whenever you say an object is null uh no, but you're right. The the default values would amount to let's say zero or an empty string again. So, the default value kind of uh Yes. Okay, that So, so that's the default of missing thing You might believe in the
start of that thing. What makes it work with the stand club? hidden Still so this is I don't want that. Let's say let's say pencil. Huh? Right. In the that what it is that I can do about this big one. Right. Right. Let's see what we got. And that that stupid that whether they want every exceptional boy whether they want to put this whole lot of class.
That makes sense. So, there are different reasons why we would need the null in our system. Uh so, let me show you what we have. So, we kind of created a very unscientific poll across social media and these were the top three reasons that a lot of people kind of agreed with. Uh so, we could have values that are not initialized. If I have a cup of
coffee which is empty, that doesn't mean it's an it's an error. It just means that I have an empty cup and I do not need the coffee yet. And then we talk about the lazy loading of resources to to save our resources. I'm not going to make the dinner until the kids are downstairs because they could tell me that they are coming downstairs, but until they are
downstairs, I am not going to do that. So I would prepare that lazily. And of course, even though the garbage collectors now they are changing at a very fast pace, they're becoming better, but we still see a lot of code base where the objects are set to null so that the garbage collector can reclaim that memory. So these are top three reasons why we still have null
in Java. And when we talk about the nulls [clears throat] that is followed by the null pointer because we know it's it's a very simple concept. If an object is null, just check for it. Don't access its members, fields, or methods and you will not get null pointer exception. But why do we still get it? Why is it still the number exception that we get in production?
So the first reason again, I'm I'll not be covering the exhaustive list of reasons say the first one is just a few I would cover. The first one is non-deterministic. If I have a chain call, I would not know whether so if you look at 147, I don't know whether inventory is null, find coffee is null, brewing suggestions is null. So either one of them could throw
a null pointer exception. Of course, with Java 14 there were helpful null pointer exceptions Has anyone tried that in Java 14? Helpful null pointer exceptions. So earlier if one line would generate null pointer exception, you would get an error like the one which is already shown on the screen, but with Java 14 and above, the syntax error the compilation error would Sorry, the runtime error would tell
you the exact line or the method call that generates the error. So, it's helpful. Of course, you can use debuggers to do everything, but having that error in written is makes a lot of sense. And the other reason is how we create the methods. Let's say I have a method which uh the name of the method is find coffee. What happens if the method doesn't return coffee?
The most obvious way is to return null, but I don't know whether that's an error or that's the default behavior. Let's say if you're talking about um asking for a coffee at 1:00 p.m. from barista. Let's say it says it's closed, so you don't get a coffee. So, that's not the reason why you would ring up the manager saying it's an error. It's the default business logic
that they apply at 1:00 a.m. we do not serve coffee. But if you do that, the same call at 10:00 a.m. might be an error. So, the value or the method is returning null for an error and for a business logic which is absolutely correct. So, the signature hides the null returns a lot of times. And this is one of the reason why the uh code base
throws null pointer exception because the actual implementation or the reason uh changes. And also, it would help if the method could mention that it it it will return null. So, instead of naming the method like find coffee, if I say find coffee or return null, then even though I know that if the return type of a method is a reference type, it could return null. But if
I make that very specific, then I know my calling method should be able or should be looking at handling that null value. What do you do when you do not see a value? And this is exactly what optional does. Even though it is kind of abused by a lot of developers because optional, even though we mentioned that it's a container which contains a value, the value could
be present or in absent, but it's not a box that we should be opening. It's a signal that a method should be sending to the calling side. Like if the value is not present, this is what you're supposed to do. And this was the reason why optional was added to Java 8. So if there's one very popular talk from Stuart Marks and Brian Goetz, the language architects,
where they talk about why they introduced optional in while they were working on the stream API. Because they wanted to find a method, a way of determining when a stream doesn't return a value, how do they address that without breaking that chain of calls? So I could say this go to the stream, find a value, filter this value, but what if I do not get any value?
Then they return an optional type. So then the chain could go on and it could say if the value is present, do this thing, and if the value is absent, do this thing or throw an exception. So that is how it was that that's the main reason it was created. So like a signal side like this needs to be done when you do not have a value.
Um the the the display is off. Um should I just replug it? Ah, okay. And and the other one is the hidden future bugs. A lot of time when we work with the code bases, the business logic keeps changing. If I go back to uh the example that I talked about for the coffee part. If I order at 1:00 p.m., it's an error. If I order at,
let's say, 10:00 p.m., it's not uh an error. It's not the usual business uh logic, it's an error. So, uh when the code bases change, these um business logics, they are coded in the application, but kind of hidden and not propagated properly as they should be. And when it goes from one developer to another, this is how they um um the errors could propagate. So, what are
the common solutions that we are looking at? Uh of course, the null checks, which have been from the test of time available, they are universal. We are not talking about any dependencies, but the only problem with them is uh they are impossible to enforce, and they communicate nothing. So, this is example of one uh I think I opened an correct Let me go back. Just a moment.
Yes, this is the null check here that I'm talking about. Even though it kind of addresses the problem and make sure that no value uh every value reference variable is checked, and it doesn't throw a null pointer exception, the logic is kind of difficult to understand. Now, if you talk about the other So, then uh the other one is the annotations. So, JSR 305 introduced annotations and
they are used by multiple uh code bases that you might have written yourself or the library codes that you have access. And they work and there are multiple uh annotations that we're talking about. We have nullness annotations, check return value annotations, concurrency, and a lot of others one. The problem with the annotations is the standardization. Every company or would have different kind of annotation to serve the
to address the same issue. Uh you would see non-null, not null, different cases. So, that the standardization is one thing where the problem creeps in. And again, we are back to kind of square one. Optional we already talked about uh how why optional was introduced with Java 8, what was the reason, and how it uh is used to do a Uh so, this is one example where
we talk about if uh I'm calling a method and what happens if a method doesn't return the value. So, when I say or else, I do not break the chain of uh the calling uh methods and I also address a missing Okay. Sure thing. The next one is JSpecify, which is uh standard which has uh Google is leading this project and these are the standard annotations for
Java static analysis. Anyone aware of them? I know you are. So, uh here we are talking about multiple annotations which are uh standardized. A lot of companies, including the frameworks like Spring, they have adopted it. And the amazing part about these is, let me show you one quick example. Um, wait. So, in this case, I used just one annotation on the class. And what that means is
I am specifying that every reference variable in this class should be not And if I try to pass any null value, that would generate an error. If you see here, coffee is supposed to be not null. But what I'm doing is I'm passing a null value here, and that would mark it with a swiggly line. Of course, you also get the options to correct it, but this
is how you do not have to use a check on each and every reference value that is present in your class. And you can use that at package level, at class level. So, that really takes off a lot of a load of the individual class and methods, and of course, the developers. So. How it works? All right. Sorry, come again. How it works in the the guy
the type of coffee it's ready that says on it's ready that that's ready. So, if you look at the example that I had, In this one, the animal should not be a It's like the baby does not be a total she just say the coffee has to be not Mhm. That check. So, coffee coffee should be null, and all the components of coffee of the record coffee,
which is name, origin, brewing instruction, should be not null. Again, when I talk about the record Okay, that one. Yes. That that one. So if I if I try to create a coffee here, let's say. Yes. Yes. So So you want you you're saying that I could mark my class, but you still want some null values in on some variables. That could be done. Okay. Yeah. Yeah,
that could be done. So I could say uh Am I using the right? So now one of the companies could be null and the others would be not null by default. If if the object that we're talking about is it is defined in the same then the components of that object, let's say I'm talking about another record which is defined outside of this class, and only this
class is marked with the null marked annotation. So, that record will not be bound by having the components as not null. You get that? So, let's say you're using an API which has it If all of them are defined in a class which is marked by this annotation, then yes, they would be marked not null. But, let's say if you define car in this class, but the
components, let's say engine, is defined in another class or source file which is not marked, then it could be null. Yes, and if you want to mandate that, you could define that at the package Yes. Yes. So, then you can mark you can have this annotation at the package level. You can create a package info class, and then you can have this So, let me go back
to the solutions document. And this is J Specify. And then let me quickly talk about how many Have you all worked with records? That's what I work with in Scala. Okay. What? All right, then. What Okay. So, if if you use records records are effectively final. So, that could be one way to address the uh null values because what we do is we create an object with
null values in the beginning. Later, if you kind of miss populating them, that could be one of the reasons why the null would creep in. So, because they are final, once you assign null value, you cannot change them. So, you will have to assign a value to the components of a record when you create a record. And the other thing This is the construction. And at the
time of deconstruction, record component has a null value, you can specify that you um In this case, okay. When you deconstruct, you can mandate that a particular should be not null. And if that case doesn't matches, then the match would return false. And in that case, you will not get a return a null value. So, that's how the record deconstruction would help you address null values and
hence the null and null pointer exceptions. The other example is to use sealed classes and pattern matching. Anyone has used sealed classes here? The what best Kotlin, okay. [laughter] We have sealed classes in Java as well. Now, you can migrate to Java. No, no. Other cool features as well. So, when when you talk about uh uh an abstract class or a class or an interface, what happens
is any class or an interface could extend or inherit depending on whether it's accessible or not. But when I talk about a sealed class or a sealed interface, the entity must allow the other class to inherit or extended. Only then the inheritance would happen. In this case, when I say interface coffee result is sealed, that it it means that it must have a permits clause, which here
specifically mentions found, not found, and unavailable are the only types which would be allowed to inherit to to implement this interface. And And why would I want to do that? Because in this case, I have an exhaustive list of the classes or the interfaces that I know would inherit or extend a particular class or an interface. And that would help in In Okay, let me show you
an example rather than just talking about it. So in this case, this is the same in code. Here I have the interface coffee result, which is uh implemented by three records, found, not found, and unavailable. And here what I'm trying to do is I'm also trying to do um uh data-oriented programming, where I'm uh representing the states of what happens when I do not uh find any
coffee. Is that the result found, not found, or unavailable? And of course, I can add more depending on the business requirements. And here I have a switch case statement. In this case, I can figure out if the value that is returned by the method, what happens if the result is found? What do I do? What is the business logic? What I should be doing? Uh what if
it's not found and unavailable? The um the benefit is I cannot introduce Let's say I have a record which does not implement the sealed interface. What happens next? If I am switching on the returned value, I cannot use this case here. That means I must explicitly handle or address the values that are returned. And that's one of the ways in which I prevent the null pointer exceptions
introduction. No, so in this case we are talking also I I agree. The the the later part was coming now. So, let's say I use the default case with a switch expression. And then what would happen I can address three cases where which were defined earlier. But if I say default, then any other thing would go past without giving me the exact steps that need to be
taken care of. Right, so this is Okay, so I could add What you're saying is correct. I could do this here. coffee result. Oh, sorry. It's been a while since I did I forgot So now if I say and then I could say XYZ. And what happens now? When I say default, that care of all the records which implement the interface uh coffee result. But now if
I do not address the fourth result, then it would be a compilation problem. So that is the benefit of using switch expressions with sealed classes. Now it is very you cannot just skip that part. It's exhaustive. It's exhaustive. So now let me go back to category. The order of the sun. And now there are a couple of non-hostile collections that we could be looking at. These collection
classes will not allow you to add null values. So again, if you cannot get the null values in, there are fewer chances of getting null values out and of course reduced chances of having null What do we have now? Uh pattern matching I already talked about using the code example. And you have options with the optional class as well. And now comes the I would say interesting
part. Where which uh solution should you be using on which part? Uh let's say we are talking about uh the first one is First of all, we're talking about is null a valid domain state or a bug? So, that's one of the most uh important questions for you to ask. If it's uh uh if it's valid, then you have to mark your method or variable as nullable
and then you can use optional to get the value or to get the alternate value that you're looking at. But, if it's a bug, you can use objects. requires not null at construction. Then again, do you control the boundary? Because you could be using code that you or your team wrote or you could be using an API where you cannot control what happens in that API. What
do you do then? Then uh if you control the boundary, then you can of course modify the code and use annotations like null mark and nullable. But, if you do not control the boundary, then you will have to use adapter wraps like with optional.ofNullable. Like, this is what I want to do if the value is null and this is what if it doesn't. And uh of course,
it also depends on the versions of Java that you're using. For eight to 11, you can use optionals, objects.not not null. J specify and nullable. And as I mentioned, J specify is supported by Spring Framework as well. So, you can just modify some of the settings. You don't even have to go to your classes to make your package or classes uh null safe. So, that's a big
uh plus if you're already using the Spring Framework. Um if you're using uh Java version 16 and above, you can use records. For 17 and above, C classes and interfaces in Java 21. You can use the switch null case that I showed for the And one interesting project that is in works at Oracle, it has been in works for quite a lot of time. So Brian Goetz
leads the project and he's quite enthusiastic when he talks project Valhalla. So nullability is not the only topic that the project is addressing, of the main one. So if you in this case we what we doing is when this comes in, you do not really have to bother about any of the things that we talked about earlier or maybe not. Because now I could use an exclamation
mark or bang as it called also after the data type and that would mean that I do not want this value to be assigned a null value anytime. And that would be enforced by the compiler. That changes the game all together. I do not need any null checks, I do not need any annotations or I do not need the annotations from core API or core Java API
or J specify and many other things. And I could use so this is as simple as using a bang after the data type. And I'm done with my presentation. If you have any more questions, I'll be happy to answer them. >> [music]
More from this event
See all 126 talks →
AI Is Not the Risk. Architectural Drift Is - Sunil Kalkunte
17:39
Breaking the Monolith: Tesco’s Journey to Federated GraphQL with xAPI - Vishwas Chandrashekar
29:13
A Practical Introduction to LangChain4j - Venkat Subramaniam
1:01:28
Beyond the AI Models: How Lowe’s is Building the Store That Knows - Swaroop Shivaram
13:59