The difference between this approach and the approach C# took when they implemented this a few years ago is intereting. With C#, you enable nullability in a project, and every variable is declared as non-null unless you explicitly mark it as nullable (or use compiler directives to disable nullability for a block of code). With this proposal, all existing variables will be effectively nullable, explicitly nullable, or explicitly non-nullable.<p>Kotlin, another JVM language like Java, also follows the C# approach, assuming everything is non-null unless explicitly marked as being non-null, except Kotlin doesn't have backwards compatibility to worry about. Kotlin also offers a special `lateinit var` declaration for leaving a non-nullable type unassigned until initialization in another method, throwing a special exception on uninitialized access, which is an interesting workaround for some code patterns that this approach doesn't seem to cover.<p>I wonder why the choice to offer three options was made. Why not keep the non-annotated variables as nullable and make annotated variables explicitly non-null instead? I can't think of a reason why you would want to declare something as nullable when not declaring anything automatically makes the type nullable anyway.<p>I think I like C#'s approach better, although this has the benefit of being usable in a legacy code base without having to solve any of the nullability issues. Then again, the C# approach does immediately highlight nullability issues in a code base, whereas this proposal hides them the same way they've always been hidden.<p>Additionally, I find this strange:<p>> One method may override another even if the nullness of their parameters and returns do not match.<p>This feels like a footgun for when a developer overrides/implements some kind of callback and returns null where the overridden method specifies non-null return values.
Looks good, finally a language-supported way to remove thousands of unnecessary Exceptions and null-checks. But the nullness-narrowing automatic conversions feel wrong. From the proposal :<p><pre><code> String? id(String! arg) { return arg; }
String s = null;
Object! o1 = s; // NPE
Object o2 = id(s); // NPE
Object o3 = (String!) s; // NPE
</code></pre>
Surely at least the two first cases should yield a compiler error. The last case you're explicit so it's borderline but I'd rather see something like :<p><pre><code> String s = ...
if (s != null) {
# Here the compiler knows that the effective type is String!
String! ss = s; # OK
}
</code></pre>
Like this no possibility of error.
IMO there really needs to be a way to mark all variables non-null by default at package or at least a file level. Otherwise there will be a strong safety argument for always using T! syntax on almost every variable and this will just be a lot of noise.
>It is not a goal (at this time) to apply the language enhancements to the standard libraries<p>Speaking from experience being forced to use php, it's a hassle to have ahead-of-time guarantees on your data that you need to strip out or build back up everytime you interact with the (massive) standard library<p>They should be a bit more enthused to add that kind of expressiveness to their STL and make it a first class citizen in the STL
Would be cool if Java got this feature, explicit optionality at a language level a la T? is an enormous developer QoL in Kotlin and Typescript in my experience. In Java there's tools like NullAway [1] but they're a hassle.<p>Language-level support is leagues better than Optional/Maybe in my experience too because it keeps the code focused on the actual logic instead of putting everything in a map/flatMap railway.<p>[1] <a href="https://github.com/uber/NullAway">https://github.com/uber/NullAway</a>
Archive link because the site is currently down: <a href="https://web.archive.org/web/20240802081039/https://bugs.openjdk.org/browse/JDK-8303099" rel="nofollow">https://web.archive.org/web/20240802081039/https://bugs.open...</a>
"It is not a goal to require programs to explicitly account for all null values that might occur; unaccounted-for null values may cause compile-time warnings, but not compile-time errors"<p>This is a bad decision. Java is mostly statically typed language, why introduce another dynamic behaviour? Hope there will be easy way to promote such warnings to errors.
It's unfortunate that we're learning all of these lessons so late...<p>* things should be non-nullable by default<p>* things should be immutable by default<p>* things should be of the narrowest scope by default<p>So many design decisions for new things make the trade-off of immediate convenience instead of "falling towards the safe path" (which requires much more careful design and UX). And we have sooooo many footguns as a result in almost every language, platform, and technology thanks to this cowboy mentality.<p>Civil and electrical engineering have codes. We have lessons-learned, which we re-learn every 30 years or so with the next batch of languages and techs.
Most of my work at Facebook was using Hack. Nullness is a key part of Hack's stype system [1] and this solves so many BS errors. That's not to say you couldn't get a null where you weren't expecting one, particularly because this was a later addition to the language so the code still had a lot of legacy "mixed" types that basically mean anything (mirroring the PHP roots where you could pass basically anything).<p>Some questions though.<p>First, what about nullable arrays? There are examples of String![] where the objects can be null but what about the array itself? As a reminder this is entirely legal in Java:<p><pre><code> String labels[] = null;
</code></pre>
Does that mean you have to declare it:<p><pre><code> String![]! labels;
</code></pre>
?<p>In Hack, this is easy:<p><pre><code> vec<String> $foo; // neither foo nor the elements are null
?vec<string> $foo; // the elements are non-null but foo can be null
vec<?string? $foo; // foo cannot be null, elements can be
?vec<?string> $foo; // either can be null
</code></pre>
In practice, there's really little reason to ever use a null array so the default really should be that something isn't nullable. I understand the issue with Java is that all legacy code assumes nullability so that's an issue.<p><pre><code> String? id(String! arg) { return arg; }
String s = null;
Object! o1 = s; // NPE
Object o2 = id(s); // NPE
Object o3 = (String!) s; // NPE
</code></pre>
As for this example, shouldn't (2) and (3) be compiler errors?<p>As for the last, I really like Hack's as enforcement operators here rather than Java's casting eg:<p><pre><code> class A {}
class extends B {}
void foo(B $b) {}
?B $b = new B();
foo($b); // compiler error
A $a = $b as A; // runtime error if $b is null
foo($a as B); // runtime error if $a is not a B
?B $b2 = $a as ?B; // $b2 is cast to B if it is one, otherwise null is returned
</code></pre>
Anyway, the question becomes can you retrofit this to the Java SDK? What does it look like for legacy code?<p>[1]: <a href="https://docs.hhvm.com/hack/types/nullable-types" rel="nofollow">https://docs.hhvm.com/hack/types/nullable-types</a>
seems all the good things about Kotlin are coming to Java now.<p>I will still prefer to work in Kotlin though as I don't have to deal with things like lombok though Java records are nice.
The formatted JEP link: <a href="https://openjdk.org/jeps/8316779" rel="nofollow">https://openjdk.org/jeps/8316779</a>
> In this JEP, nullness markers are explicit: to express a null-restricted or nullable type, the ! or ? symbol must appear in source. In the future, it may be useful, for example, for a programmer to somehow express that every type in a class or compilation unit is to be interpreted as null-restricted, unless a ? symbol is present. The details of any such a feature will be explored separately.<p>I think this question needs to be answered now. The addition of ? is useless and confusing unless this is planned to eventually happen.<p>I am inclined not to introduce breaking changes and to only have !.
Finally!
I tried many other solutions/workarounds in the past years, like using the eclipse/intellij notnull annotations and the various efforts of projects like <a href="https://www.lastnpe.org/" rel="nofollow">https://www.lastnpe.org/</a><p>I was wondering how "enforceable" these nullness check will be and how they will affect generics (will a "List!<String?>" be treated like a non-nullable list of nullable strings? and a List<String!> ? is the "unknown nullables" forwarded to the elements?)
One annoying detail of this proposal, is that it swaps the meaning of the nullness markers compared to Kotlin, which uses ! to mean "it came from Java and had no nullability annotations so we don't know" and no marker to mean not nullable (<a href="https://kotlinlang.org/docs/java-interop.html#notation-for-platform-types" rel="nofollow">https://kotlinlang.org/docs/java-interop.html#notation-for-p...</a>).
Can someone explain to me how nulls are even a problem? I know the <i>basics</i> of Java and did some basic projects in it for my CS degree, but never used it professionally or in a large project.<p>It seems to me that something somehow being assigned a null is a bug, but one that would stick out like a sore thumb. At some point, you're returning a Null, right? Any code calling a function that can return a Null should know that being handed a Null is a possibility and handle it, right?<p>I'd think that eliminating Nulls is a bandaid over the wrong problem. Or is eliminating Nulls really meant to catch any potential NPE's at compile time as a way for force better error checking?
Can't say I'm a fan of the proposed syntax. Random question marks and exclamation marks do not improve the readability of the language. Introducing new symbols wouldn't bother me as much if they made sense. E.g. square brackets make sense for arrays because they denote an area. Equal signs make sense for assignment and equality because of mathy history. But question marks and exclamation marks don't make much sense for nullability.<p>Which is easier to understand? I don't mind a few extra keystrokes if it improves readability.<p><pre><code> private String? foo;
private String! bar;
// or
private nullable String foo;
private nonnull String bar;</code></pre>
Unfortunately, this breaks the language semantics and I don't think it's a very good idea. Java is a static language. If these are a huge problem in your codebase, you can discover the problems using nothing more than static analysis and healthy programming habits.<p>Personally I've never found nulls unintuitive or hard to use. Changing the core of the language to align itself with another fleeting in vogue trend is going to leave us with a bunch of garbage and tech debt when the trend inevitably falls out of style.
> <i>In a Java program, a variable of type String may hold either a reference to a String object or the special value null. In some cases, the author intends that the variable will always hold a reference to a String object; in other cases, the author expects null as a meaningful value. Unfortunately, there is no way to formally express in the language which of these alternatives is intended, leading to confusion and bugs.</i><p>Casually just describes one of the largest sources of bugs in the history of programming.
It's interesting to see this right on the tail of <a href="https://jspecify.dev/blog/release-1.0.0/" rel="nofollow">https://jspecify.dev/blog/release-1.0.0/</a> by Meta et al. That was done AFAIK because JEP-305 died and it didn't seem like Oracle was going to ever do anything about it. Did Oracle do nothing to coordinate here?
As a Java developer this is great. I’ve started to use the @Nullable and @NotNull annotations a lot for parameters and return values and they’re very helpful.<p>But they never made it into the language, so it’s only ‘enforced’ by the IDE and it’s a lot more visual noise than ? or ! which I’m already used to from other languages.<p>I can’t wait, I hope it gets in.
I think we're still in the bashing rocks together stage of programming, but things like this definitely make me smile. Java is not an innovative language and the fact it is evolving towards something better shows that the industry as a whole is moving forward. We can't be more than a decade away from everyone being able to program in a language with the features of ML circa 1983.