Compiler option(s) proposal: provide nullability assumptions when interacting with non-annotated Java code

The documentation is currently clear that null-safety guarantees begin to deteriorate when interacting with non-Kotlin code. I’m not looking to propose a change to default compiler behavior, as I imagine this topic was thoroughly explored and discussed long before my (admittedly recent) adoption of the Kotlin language.

That said, I was hoping to discuss a potential opt-in compiler feature that could enforce more guarded interactions with non-annotated Java code. Consider the following minimal example:

fun uuidFromString(val uuid: String?) = UUID.fromString(uuid)

The UUID.fromString() implementation from java.util is currently non-annotated, and will throw at runtime if it receives a null value. And as we’re bridging Kotlin and Java domains, the Kotlin compiler will not warn or fail when I attempt to invoke this method with a value of the nullable type String?.

I’ve searched around for some rationale on why this was the preferred default compiler behavior, but haven’t seen much beyond a comment suggesting that the alternative was too cumbersome. At any rate, I accept that this is unlikely to change as a default behavior. BUT, is there room to explore compiler options that would enforce more guarded code when bridging the language barrier?

Specifically, as pertains to the code example above, I would love to see a compiler option that rejects any attempt to pass a nullable value to a Java parameter that hasn’t been explicitly annotated as nullable as well. This would also rely on corresponding annotations (on the Kotlin side) that explicitly permit an unguarded invocation in situations where this was deemed safe.

Thanks in advance for any consideration!

2 Likes

So you’re proposing a compiler option to treat all platform types as nullable types?

1 Like

No (although I suppose that would be categorically in the vein of what I’m discussing). But I’m actually proposing compiler options that force more guards around null safety during interop, not less.

As a point of initial discussion, but I’m proposing a compiler option that would treat parameters in a Java function as explicitly non-nullable (barring the existence of any annotations that indicate to the contrary). This would prevent a caller in a Kotlin context from unintentionally invoking the function with a null value in a potentially unsafe way.

1 Like

I see.

So you want to require non-nullable arguments wherever platform types are currently required:

val x: Int = 0
someJavaMethod(arg1) // OK

val x: Int? = null
someJavaMethod(arg2) // Error

I misunderstood and thought you were also talking about returned values:

val x: Int? = someJavaMethod() // OK
val x: Int = someJavaMethod() // Error
1 Like

That’s correct, I was attempting to describe what you show in your first example.

That being said, your second example would also be a candidate for a related compiler option. The general spirit fo these proposed compiler options would be to force null-safe handling during Java interop in cases where nullability is unknown.

1 Like

You could achieve some this using inspections–although for passing args from Kotlin to Java, you may have to write your own. I think you can set IntelliJ to treat certain warnings as errors.

But still, with the proposal, how would I call a Java method that accepts null but isn’t annotated?

javaMethodThatExpectsNull(null) // Error

Per my initial proposal, I was imagining that this would require annotations on the caller side:

This would also rely on corresponding annotations (on the Kotlin side) that explicitly permit an unguarded invocation in situations where this was deemed safe.

E.g.,

@AllowUnknownNullability
javaMethodThatExpectsNull(null)

Alternatively, the compiler options could incorporate some sort of an -allowunknownnullability java.util.* pragma (or something to that effect).

1 Like

Before Kotlin was released, they automatically annotated standard library with nullability annotations and there were no platform types. But they decided to move on from this model. I suppose, inferring nullability from bytecode at compilation is very slow process, maintaining external nullability annotations for each library is burdensome and error-prone (imagine typescript-like situation where you need “annotation” addition for each library and its version) and treating every value as nullable will cause very bad code (I don’t know about you, but I can’t stand numerous !! everywhere or mindless checks).

That said, at this point I believe it should be an optional mode. May be someone will come with automatic nullability detector to generate external annotations. From that we will need community to maintain those annotation libraries. It’ll allow to remove most of platform types.

I know I’m kinda reviving a dead thread, but this is still the first hit on Google on the matter. I just wanted to add the following points to a (in my opinion) still valid debate:

  • I understand and support the decision to assume that Platform-types are “null-safe” if the Kotlin developer declares them as such, e.g.val valueFromJava: String = MyJavaUtilsClass.getValue() This just makes the language more accessible and easier to use.
  • However: I think there also is a strong argument for introducing a compiler option that would allow us to treat platform types as nullable in general. Why? Well, this just seems to be the reality for most Java libraries in existence today. Very few use @Nonnull and its alternatives in a consistent manner, or even use them at all. More precisely, reusing the code from above I would expect a compiler error in case this imagined option is activated: MyJavaUtilsClass.getValue().substring(0) // Compiler error: getValue() returns a 'String?'

In large-scale enterprise applications its especially hard to track which libraries are null-safe or not, and its easy to overlook that a call to Java library is made that returns Platform types - so we just introduced yet another potential NPE situation into our application, all the while assuming our application is perfectly guarded from NPEs. The Kotlin compiler being able to interpret @Nullable/@NotNull/etc. as suggested in the original post in my opinion would be the cherry on top of the cake, but I could also live with my suggested “assume Platforms are nullable” option. Any thoughts on this matter?

1 Like