Prototyping null checks
Dan Smith
daniel.smith at oracle.com
Tue Jan 13 23:17:09 UTC 2026
If you're following implementation activity on Github, you may have noticed a new 'bworld' ("bang world") branch. This is a prototype implementing some of our current thinking on run-time null checks. This email provides a brief summary of the expected behavior.
Note that of course this is not the final version of the feature. Nor is it perfectly aligned with earlier JEP drafts describing some of these features. It's a snapshot. But we've been wanting something concrete that we could play with, and it addresses that need for now.
The prototype is a work in progress, so not everything may be functioning properly today. But here's a description of what we're after:
### Types marked with !
The focus of this prototype is marking certain types with '!' markers that indicate a non-null barrier, and enforcing those restrictions at run-time.
(We've set aside most things related to compile-time checking for now, including generics and '?' markers that express intentional nullability. We'll come back to those.)
The following types can be marked with '!':
- Field declaration types (including record components)
- Local variable declaration types (including type patterns)
- Formal parameter types (including receiver parameters, but excluding variable arity parameters)
- 'catch' exception parameter types
- Method result types
- The types targeted by a cast or instanceof
- Element types of array creation expressions (but *not* element types of array types)
Where allowed, '!' can be applied to *any* reference type—the feature is not limited to value class types.
### Compiler null checks
At certain points in the program, described below, javac generates a call to the 'java.lang.runtime.Checks' API to assert that a value is non-null.
(This is deliberately chosen as a new API point, rather than using 'Objects.requireNonNull', because we prefer a void return, and because introducing something new gives the JVM more freedom to give these checks special treatment.)
Sometimes, javac can prove that a value is non-null, and in that case it is free to elide the check.
### Null-checked casts
A cast to a '!' type performs a compiler null check in addition to the usual class check.
The use of '!' in an instanceof expression is already implicit (i.e., the result has always been 'false' for null), but is allowed to be written explicitly.
### Null checking for local variables & exception parameters
Every assignment to a local variable or an exception parameter declared with a ! type gets a compiler null check. Local variables declared with 'var' are *not* considered to have '!' in their declaration, and so are not null-checked.
### Null checking for fields
A field with a ! type is marked as null-checked (0x0200) and strictly-initialized (0x0800) in the class file. The JVM is responsible for enforcing null checks on writes.
(For now, the JVM supports run-time checks for value-class-typed fields, but not other fields. Eventually we will update the implementation. In the mean time, javac will put what metadata it can in the class file, but the run-time field checks will only work for value class types.)
Static fields with '!' must be initialized. Instance fields with '!' must be set before the 'super()' call—for identity classes, this means the assignment must happen in the prologue of a constructor with an explicit 'super()' call. (Yes, this is inconvenient, and we continue to explore how to migrate these classes toward an early-by-default interpretation.)
### Null checking for methods
On entry to the body of a concrete method, a compiler null check is applied to each parameter marked with '!'. Subsequently, writes to these parameters get the same checks as local variables.
If the method has a '!' return type, each return statement applies a compiler null check to its result.
### Null checking for arrays
An array creation expression with a '!' component type produces a null-checked array, which dynamically rejects 'null' writes at run time. Like fields, the JVM is responsible for enforcing these checks. (And also like fields, today's implementation only supports run-time checks for value-class-typed arrays.)
These arrays must be initialized with non-null values, so generally they must be created with an explicit initializer. (Exception: an array of constant length 0 can be created with 'new Foo![0]'.) In the future, we expect to introduce other array creation expression forms to conveniently populate arrays without enumerating every element.
There's also a new reflective creation method, overloading 'Array.newInstance', that supports creating a new array populated with the contents of a source array. This method introduces the concept of array modifiers, and specifically supports a ACC_NULL_CHECKED (0x0200) modifier to opt in to null checking. (We eventually envision support for ACC_FINAL, too.)
### Optional behaviors
In some cases, we'd like to experiment with two or more different compilation strategies, and javac will support command-line configuration to turn behaviors on/off:
- Null literal warnings: lint warning if a null literal gets assigned to a '!' target type
- Override warnings: lint warning if a method overrides a superclass method and removes some '!' markers from the superclass signature
- Use-site checks: since overrides can be mismatched and binaries can change, we'd like to experiment with javac inserting compiler null checks at the use site of an untrusted method call to validate its parameters and returns; and also inserting compiler null checks at untrusted field reads
- Parameter checking: the null-checking API supports checking parameters one-by-one, or in bulk (up to 5 at a time); we'd like to explore the footprint impact of both strategies
- Array creation implementation: the 'java.lang.runtime.ArrayCreation' class supports two compilation targets for array creation expressions: static factories and indy bootstraps; we'd like to experiment with them both
More information about the valhalla-spec-experts
mailing list