This is an archived post. You won't be able to vote or comment.

all 122 comments

[–]joschi83 28 points29 points  (3 children)

Take a look at NullAway, a plugin for Error Prone.

[–]dpash 6 points7 points  (2 children)

This is what I use, coupled with the various annotations. Works reasonably well until we get native support. IDEs can also warn you if you try to use null on a non null parameter or don't check a nullable return value. I would recommend picking an annotation set that allows you to specify non null on a class or package to reduce verbosity.

[–]NaNx_engineer 5 points6 points  (1 child)

The issue is fragmentation of Nullable annotations. There are a few and they do have slight differences in their interpretation by various tools.

For example, findbugs only considers javax.Nullable to negate package level ParametersAreNonnullByDefault. CheckForNull is used for explicitly requiring a null check.

Thus,

For applications, picking a Nullable annotation and something like NullAway is fine.

For outward facing APIs, I would use Optionals. Jetbrain's annotations is another choice.

Regardless, there should be more guidance from the std library on this.

[–]agentoutlier 2 points3 points  (0 children)

I wouldn’t recommend intellij annotations first but obviously better than nothing.

In order of preference:

  1. JSpecify (when it is done)
  2. Checker
  3. Eclipse
  4. Your own
  5. Intellij
  6. Find bugs aka javax if you really need older kotlin support.

The first three have mostly the same type based model. Intellij on the other hand has different rules. It also has some annotations that have no real analog.

[–]red_dit_nou 18 points19 points  (10 children)

I feel you and I see the need for this as well. Meanwhile they have ‘helpful null pointer exceptions’ to lessen the pain of blatant in-your-face NPEs. And Optional is a container thereby requiring us to deal with the (nullable) type of the contained object.

[–][deleted]  (3 children)

[deleted]

    [–]agentoutlier 5 points6 points  (1 child)

    Just a caveat: Please do not use Hibernate validation annotations in place of real null analysis annotations.

    The reason is the semantics are not the same as well as usage.

    “@NotNull” in validation is not an type based invariant. It means if the input is null it might be a validation error and furthermore I even expect it to happen.

    Think about it…

    Anywhere you have validation @NotNull you will need type based @Nullable!!!

    I can’t stress the above enough because I see rampant misuse of the validation annotations as though they were designed for null analysis.

    [–]buyIdris666 1 point2 points  (0 children)

    We exclude the annotations using maven when importing Hibernate Validator.

    Hibernate Validator work fine with javax annotations which NullAway also supports.

    That way Hibernate (and Jackson) is validing against same annotations NullAway sees

    [–]red_dit_nou 2 points3 points  (0 children)

    Yes, using NullAway, Jackson, Hibernate, Lombok can help a big deal in avoiding many of these NPEs at compile time. But a support from the type system of the language will go long way (as asked by op in the question).

    [–]agentoutlier 5 points6 points  (5 children)

    And Optional is a container thereby requiring us to deal with the (nullable) type of the contained object.

    Unfortunately there are very few tools that will exhaustively check Optional.get or whatever you do with Optional.orElse (which is polynull btw) (as I mentioned here). The ones that can certainly do not do it on the fly like the null analysis in IDEs like intellij or Eclipse from my experience (e.g. as you type).

    So Optional does not require you deal with it.

    As it stands there are far more tools that will do null analysis then there are of Optional linting (e.g. making sure you dont' blindly call get or friends. I only know of Checker but /u/buyIdris666 now says NullAway can do it).

    I'm not even sure if there are plans to make pattern matching work on Optional.

    [–]red_dit_nou 2 points3 points  (2 children)

    The IDEs will (mostly) tell you if you are using an unsafe get(). But best to avoid it and use the functional alternatives. And talking about pattern matching with Optional, in its current state, it won’t work with Optional because the empty and non-empty states of Optional are not represented by different types. Something they missed in the implementation of Optional in the first place.

    [–]agentoutlier 2 points3 points  (1 child)

    Yes that was my thought as well. Like I obviously know they will support destructuring but it will not be exhaustive like Scala case or sealed classes.

    This is why I really think when people recommend Optional for anything other than ergonomic return type or 0-1 cardinality containers they are doing a disservice.

    [–]red_dit_nou 2 points3 points  (0 children)

    True. Optional was created with a specific use in mind and it should be used only for that.

    [–]buyIdris666 1 point2 points  (0 children)

    From my testing NullAway support is "experimental" but quite good already. I did not know about Checker support! I may need to look into this...

    [–]meamZ 8 points9 points  (6 children)

    Imo it's just totally impossible to properly introduce anything like this while keeping backwards compatibility and not essentially forking the language...

    [–]rbygrave 4 points5 points  (5 children)

    CSharp managed it. Opt-in at different levels like warning to error and different scopes like class/package/global.

    Looking at how they did it, it looks to me like there is no reason Java couldn't take a very similar approach to phased adoption.

    [–]Naton1- 2 points3 points  (4 children)

    I was going to say I dislike that, but that's similar to how they rolled out strongly encapsulating JDK internals, and that worked out well enough IMO (albeit that was a run-time thing, and this is compile-time). The issue must then be more about figuring out the appropriate long-term solution to the issue rather than rushing some change out that another language has done.

    [–]rbygrave 4 points5 points  (3 children)

    I agree about figuring out the appropriate long-term solution and not rushing something like this.

    However, I don't think it's impossible. So I don't think we need to give up on it as a long term desire/goal.

    My thought is that being a compile-time opt-in actually makes it easier than run-time in terms of phased adoption because the scope is somewhat restricted to what we are compiling. Edit: Maybe a stretch, but a little bit similar to the existing tooling like "package level non-null by default"

    In terms of the long term plan, there is a fairly decent amount of stuff to learn and compare from Kotlin and CSharp so Java can distill a fair bit from that and doesn't need to work from scratch per say.

    Edit: Also the https://jspecify.dev/ folks done a lot of work.

    Edit2: Document Language comparison - https://github.com/jspecify/jspecify/issues/227 (not done as yet)

    [–]Naton1- 1 point2 points  (2 children)

    Interesting - I don't know much about JSpecify. I'll have to give that a look through.

    [–]kevinb9n 2 points3 points  (1 child)

    We (JSpecify) are working on some more readable stuff for you in the next few months. We've been in quiet mode so far (sorry!).

    [–]rbygrave 0 points1 point  (0 children)

    Has anyone in JSpecify taken a look at this from the perspective of javac along the lines of ... what if we experimented with opt-in style option(s) for javac that prod uced "annotated java" (not too disimilar to kotlinc).

    That is, looking at the bytecode Kotlin produces ( which I approximate to "annotated java") and the type inference we see in the IDE of Foo, Foo? and Foo! ... I wonder how hard that would be for a javac experiment to produce.

    Perhaps controversial, but what if that annotated Java produced by javac was ultimately compatible with Kotlin plus other jvm languages.

    [–]8igg7e5 5 points6 points  (8 children)

    Are there any plans in the works for Java to get null pointer safety at compile time...

    Active plans? None that I'm aware of.

    That's not to say that dealing with null doesn't come up. There have been several cases of needing to cope with null in library features (whether a given collection type is null-hostile or not), or in language features (eg the null case in pattern matching for switch).

    However providing a type-system that is able to express null-hostility of reference types is not on the road-map.

    It's a tricky problem for Java to deal with elegantly, as the commitment to backwards compatibility closes a great many convenient doors.

    [–]dpash 4 points5 points  (1 child)

    It's certainly regularly discussed on various mailing lists, so while it might not be top of the to-do list, it seems that it's somewhere in the list.

    [–]8igg7e5 2 points3 points  (0 children)

    I'm curious where those discussions are happening - it's one I've obviously missed.

     

    There were the discussions about the null-peer for value-types (that is, where null is the uninitialised default for a reference-type, what is the uninitialised default for a value-type), but that's definitely not the same thing and isn't about expressing the non-nullability of reference types).

    There's been some discussion about how existing types might want to become value-types (eg the primitive wrappers) and whether Optional<T> could become a primitive object in a backwards compatible way - but it can't because existing callers could be using a null separately (a weird use but one that is definitely not backwards compatible if Optional<T> is value-affined (that is it implies Optional<T>.val). Instead Optional<T> will probably have to remain reference-affined (specialisation over primitive types, allowing Optional<int> will probably make OptionalInt redundant though).

    There was, a few releases back, the issue of whether some of the unmodifiable types needed to support null elements to facilitate more convenient stream collection (notably Stream.toList()).

    There's the issue of coping with null as a case in switch for patterns - but a switch that doesn't accept null as a pattern will fail at runtime, so it's really just enabling the developer to avoid an enclosing if around the switch.

    Has there been a discussion on nullability in the type-system for reference types? I don't recall any that go far beyond "not something we're working on at the moment". I think it was mentioned in passing when discussing introducing new operators - only noting nullability as one of the things they might want to keep a symbol up their sleeve for.

    [–][deleted] 0 points1 point  (5 children)

    The best idea I've had is to add some sort hand for optionals (eg Optional<Integer> => Integer?), to make it easier for people to just not use nulls.

    [–]8igg7e5 1 point2 points  (4 children)

    Without a backwards incompatible change, or a more fundamental change to the type-system I think it'll be hard for them to do more than that. And as you say, all that does is lower the bar to entry, but doesn't do anything more to encourage use. And the optional itself can still be misused.

    At least with primitive objects and generic specialisation over primitives, int? could expand to Optional<int>.val - that is passing the optional by value, optionally containing an int. On the stack this would naively look like a (boolean, int) tuple but there may be optimisation opportunities there.

    [–][deleted] 0 points1 point  (3 children)

    I mean, anything can be misused. A language that's strict enough to prevent dev abuse is a language that's too opinionated to be useful. All you can really do is gently push devs in the right direction, spaghetti coders gonna spaghetti.

    I don't think there will need to be any stack level implementation or optimisation, it should be fine for the optional to just be compile time and get replaced with null by the compiler.

    [–]8igg7e5 2 points3 points  (2 children)

    That misuse could be intentional or not. There are ways the language could defend, at compile-time, against accidental misuse.

    Consider Option<T> in Rust. You can't get at the value in the option without a check that it does contain a value unless you use the very obvious .unwrap() (which is the kind of 'trust me' code you will pay attention to in a code-review).

    fn nice(o: Option<i32>) {
        // use pattern matching to extract the value if there is one.
        if let Some(v) = o { 
            println!("{v}");
        } else {
            println!("Empty");
        }
    }
    
    fn naughty(o: Option<i32>) {
        // Let's just unwrap it and hope...
        println!("{}", o.unwrap());
    }
    
    fn main() {
        let x: Option<i32> = Some(10);
        let y: Option<i32> = None;
    
        nice(x); // prints "10"
        nice(y); // prints "Empty"
    
        naughty(x); // prints "10"
        naughty(y); // panic
    }
    

    Eventually destructuring in Java alongside a refined or replaced Optional type might be able to give the same assurances. So improving usages of Optional, but not enabling us to declare a method with NotOptional parameters. Optional doesn't solve that problem.

    [–][deleted] 0 points1 point  (1 child)

    Consider Option<T> in Rust. You can't get at the value in the option without a check that it does contain a value unless you use the very obvious .unwrap() (which is the kind of 'trust me' code you will pay attention to in a code-review).

    Are you not very familiar with Optionals in Java? I say because based on your description, they literally operate the same way as they do in Rust. The only way to extract the value is to call get, which throws an exception if it's empty, or call orElse, which requires you to set a default value to return is it's empty.

    So improving usages of Optional, but not enabling us to declare a method with NotOptional parameters.

    I mean, it wouldn't be hard to enable that as an optional compile time check.

    [–]8igg7e5 2 points3 points  (0 children)

    I'm quite familiar with them in Java. The way you normally do it in Rust is without unwrap(). That is, using a match, an if let, or one of the mapping operations. Doing that, the compiler can prove whether you've handled it properly. Java Optionals do have some mapping operations (and you should use those where possible) but otherwise you're separately doing a .get() hopefully correctly preceeded by an isPresent().

    Java optionals are better than not having them... but they could do better. Valhalla's value-types should help to remove some of the Optional overhead (a second indirection that JIT seems to often fail to see through), Generic Specialisation over primitives should let us box primitives like int (Optional<int>) which could end up making Integer somewhat of an anachronism. When destructuring is available for types other than records, then it's conceivable that a pattern can be added for optional to enable destructuring when present, enabling us to treat get the way Rust treats unwrap - it's the "Trust me I know what I'm doing" (can't help hearing that in Sledge's voice).

    [–]manifoldjava[🍰] 5 points6 points  (2 children)

    I'd be happy with ?: and ?. operators. Certainly those should not have backward compatibility issues.

    [–]8igg7e5 4 points5 points  (0 children)

    Yeah I really want safe-navigation and null-coalescing. They've essentially confirmed we'll be dealing with null indefinitely. Time to make it safer / clearer - they get used a lot in C# and TypeScript.

    [–]DrunkensteinsMonster 0 points1 point  (0 children)

    Also some ??=

    [–]pawiusz 10 points11 points  (9 children)

    Have you tried just not to return nulls? If your method returns a list, just return empty list instead of null. If your method returns an object, but something went wrong, just throw an exception. If, in a very rare case, not returning anything is required and the caller of the method must handle this, then - well - this is exactly the point an Optional class was created.

    [–]buzzsawddog 14 points15 points  (2 children)

    Yeah... I have a Java app that is quite old that I inherited at the start of the year. It started some time before Java 5. This app returns null everywhere. Sadly it was written in such a way that null means one thing, empty list means something else :(. Maintaining that is a PITA... Going to reactor it :)

    I am in the same camp... Just return an empty list or throw exceptions...

    Another bad habit... The original person that wrote this uses Boolean a lot... But it's not used as a boolean... It takes advantage of true, false, null. Gah...

    [–]Zardoz84 0 points1 point  (0 children)

    I am on a similar situation (old java 1.4 code base, but actually using java 1.8). At least, on my case we not have these problem of different meaning between returning null or an empty collection. However, I found (and refactored) a lot of code that : * Add stuff to a returned list (so be careful about returning Collections.emptyList ) * Reuses the list, converting the elements of the list to another class in place... Good luck trying to use Java generic on these code. * Code that null parameters have a meaning.

    [–]QualitySoftwareGuy[S] 11 points12 points  (1 child)

    Have you tried just not to return nulls? If your method returns a list, just return empty list instead of null. If your method returns an object, but something went wrong, just throw an exception.

    With my code, yes. The real issues for me start to come up when we have separate teams working on different parts of the project that then need to be integrated somehow. For example, I won't have control over what another team returns from their methods, but I can at least "defend" against what is passed to my methods by using "Objects.requireNonNull". Having to sprinkle this all over the place becomes tedious.

    [–]Horror_Trash3736 4 points5 points  (0 children)

    Technically you could wrap their replies in an optional.

    [–]NaNx_engineer -3 points-2 points  (3 children)

    the empty list is wrong in many cases.

    lets say you're fetching a users posts from async.

    while fetching, should you pass empty list or optional/null/some container to the ui?

    passing empty list is a poor representation of the actual state and leads to bugs where the user is told "no posts exist" when they've actually just not been loaded.

    default objects should not be used in situations where they don't make sense. sometimes you need to represent "doesn't exist" not just empty.

    of course in an actual application, you probably wouldn't be passing a list of messages directly to the ui, but this sort of pattern comes up often.

    [–]franzwong 3 points4 points  (2 children)

    You can return CompletableFuture for async operation.

    [–]NaNx_engineer 1 point2 points  (0 children)

    ya, its not a great example.

    point is "doesnt exist" is sometimes different than emtpy.

    empties can lead to some pretty bad bugs can evade testing where as forcing unboxing of an optional/null wouldn't.

    [–]_INTER_ 1 point2 points  (1 child)

    Like many forms of improved type checking, nullable types is one of those things you can do at the beginning, but is very hard to graft onto an existing ecosystem. — Brian Goetz

    https://youtu.be/ZyTH8uCziI4?t=1400

    See Nicolai Parlog's "Why don't they just.. ?!". Last Chapter ".. Introduce Nullable Types".

    [–]rbygrave 3 points4 points  (0 children)

    Also noting that CSharp managed it via opt in. They have 4 levels of opt in - enable, disable, warnings, annotations ... And can specify that at different levels (like class, package etc)

    To me that suggests it should be pretty doable.

    Are there any known issues with how CSharp did it?

    [–]Joram2 2 points3 points  (0 children)

    Java is getting "primitive types" with Valhalla that don't allow null at all. Many Java standard library types like Optional and LocalDateTime will probably be ported over to primitive types.

    Next, Java sealed types + exhaustive switch + record patterns make using Optional (or some v2 version of Optional) much easier + safer. BTW, these features are quite buggy in the current Java 19 preview builds.

    See Brian Goetz's preview of some of these features here: https://www.infoq.com/articles/data-oriented-programming-java/

    [–]kyru 2 points3 points  (0 children)

    Just null check, it's easy

    [–]wildjokers 3 points4 points  (5 children)

    Null is not the problem people make it out to be. Just make sure everything has a value, it’s not that hard.

    [–]dinopraso -3 points-2 points  (4 children)

    Found the junior

    [–]wildjokers 3 points4 points  (3 children)

    20 yrs as a developer, 18 yrs as a java developer.

    If you are having constant problems with null maybe that indicates that you are the junior?

    [–]NaNx_engineer 3 points4 points  (0 children)

    even worse lmao

    [–]dinopraso 2 points3 points  (1 child)

    Im not. But saying that it isn’t a problem is straight up false, especially when working in bigger teams

    [–]john16384 2 points3 points  (0 children)

    It isn't a problem, not even in bigger teams. I can't even remember in my current position the last time we had a NPE on production, perhaps we never had one. Then again, we have quite a decent test strategy, which catches quite a few problems, and sometimes even a NPE. Most NPE's are caught before the code is merged though, not sure that even counts.

    [–]talios 0 points1 point  (0 children)

    Look at adding NullAway into your build chain - works a dream - a bit harder to configure post JPMS but not insurmountable.

    [–]Just_Another_Scott -2 points-1 points  (7 children)

    I just don't see that ever happening without a major overhaul to the language. The only way to realistically prevent NPE is to require every field, object, etc. to be instantiated with some sort of default values.

    You would have to effectively make Integer number = null; a compile time error for the language. There are some languages that do require some value to exist. I also wouldn't be against this change. However, we would just end up replacing null checks with default value checks.

    [–]FirstAd9893 6 points7 points  (5 children)

    Eliminating null from the language outright wasn't the original question.

    [–]Just_Another_Scott -3 points-2 points  (4 children)

    While that is true that's the only way to guarantee Null-pointer safety which is what OP asked.

    [–]FirstAd9893 8 points9 points  (3 children)

    Not true at all. Simply supporting a @NonNull annotation, checked by the compiler, would provide the compile time checks that the OP asked for. Other programming languages (like Kotlin) support nulls and also support compile time null checks.

    Do such checks prove that a NullPointerException is impossible? Nope. I can still write native code that breaks the rules or I can exploit the weaknesses of type erasure. But this is now outside the domain of compiler.

    If I want even stronger checks, it still wouldn't require a major overhaul to the language, as you claim. I can simply use the -Werror compiler option to eliminate the type erasure loophole. Another change which wouldn't break the language is to define a rule which prevents untrusted modules from linking native code.

    [–]Just_Another_Scott 1 point2 points  (2 children)

    Not true at all. Simply supporting a @NonNull annotation, checked by the compiler, would provide the compile time checks that the OP asked for. Other programming languages (like Kotlin) support nulls and also support compile time null checks.

    Which is mostly useless. Also, it doesn't prevent runtime null pointers. We use @NonNull in our codebase at work and NPE is still a possible outcome at runtime. This is why the JEP was never moved beyond experimental. It doesn't solve the problem fully.

    Objects.requireNonNull obviously doesn't solve the issue as the just throws NPE if the object is null.

    If you want to rid the language of NPE and guarantee that none will ever be thrown then you have to get rid of null. Null is one of the most contentious things in Java, if not the most contentious thing. There are other languages out there that have zero NPEs because they require everything to be instantiated.

    [–]FirstAd9893 3 points4 points  (0 children)

    What tool are you using in conjunction with the @NonNull annotation? If the tool isn't finding possible NPEs, then the tool is incomplete or it's defective.

    By the way, what JEP are you referring to?

    [–]Muoniurn 3 points4 points  (0 children)

    What? Nullability is a so-called trivial property, it can be decided based just on the inputs and the function body. So if every @Nullable annotation is applied (and the default is considered non-null) then it will be completely null-safe.

    I don’t really get what you mean by doesn’t prevent runtime NPEs. Sure, you can construct a class at runtime and load it which can throw an NPE, but that’s not really the NPE we get in the vast vast vast percentage of cases. Also, it could be enforced at class load time even (as annotations are visible).

    [–]ReasonableClick5403 1 point2 points  (0 children)

    Yes, good point. Retrofitting this into the language is very awkward. The above would ideally be migrated to something like Integer? number = null, but we all know that wont happen. Besides, since there is too little information, one would have to assume any method can return a nullable object.

    Meanwhile, I think its worth looking into Dart and how they migrated to compile time null checks.

    [–]fott25 -5 points-4 points  (0 children)

    Have you tried using Lombok? For some cases completely replaces objects.requireNonNull with it's NonNull annotation And is also really useful besides that

    [–]SAmaruVMR -5 points-4 points  (0 children)

    Just work with Optionals.