Java API design (for Kotlin consumption) ... Optional or null?


#1

Hi, I have a Java API that has:

@Nullable
<T> T findOne()

Now there is some discussion (being a Java API) that it should migrate towards:

<T> Optional<T> findOne()

Now thus far I personally have had the preference for the Java API to just return the type and for that return type to be annotated with @Nullable. In Kotlin it knows this is a nullable type and treats it as you’d expect (compiler enforcing null checking etc) … so to me this is all good and consistent with Kotlin usage.

I wonder if I am missing something. Are there arguments for returning Optional … for people using Kotlin? Are there areas where people like using Optional in Kotlin?

I have read the related discussion at: Proposal for the Option/MayBe datatype

Cheers, Rob.


#2

Functional languages doesn’t have null values, so Optional is a good choice.

Java has only nullable references, so Optional doesn’t fix any Java issue.

Kotlin has nullable and not nullable references, so doesn’t have a issue.

I agree with you, in my opinion a real JVM lack is a standard @Null/@NotNull annotations.


#3

Just something about the topic:

fun <T> T?.toOptional(): Optional<T> = Optional.ofNullable(this)

and

fun <T> Optional<T>.orNull(): T? = orElse(null)


#4

The annotations are significantly more efficient if they are checked by some proper tool. Objects have real cost and spraying small costs everywhere via some “best practice” is a problem that has seriously hurt many projects over the years. For instance, Minecraft’s performance took a nosedive after a new set of devs decided that passing coordinates via three separate method parameters everywhere was unclean and so introduced a Point(X,Y,Z) type class.


#5

Sure. JVM 10 might (hopefully will) have value objects to argue against that cost factor.

However, even if we had value objects I am also biased against Optional as I have seen quite a lot of Java8 code that has a style of:

Optional.ofNullable( foo ).map( ... )
Optional.ofNullable( foo ).orElse( bar )
Optional.ofNullable( foo ).orElseGet( ... )
Optional.ofNullable( foo ).orElseThrow( ... )

I find this style of code often is relatively hard for me to read. Relative to how we can write this in Kotlin with let?. , safe navigation etc I find this style of Optional use tends toward relatively ugly code (using map() and filter() here seems a bit forced to me).

Style is subjective and I am wondering if there is anyone out there that likes Optional for some cases in Kotlin? It is starting to look like there isn’t a hidden fan base for Optional.


#6

I’m not a fan of optional and will not enjoy using Java APIs that were “optional-ified” in future. The JDK docs actually say not to use Optional in APIs, as it was designed for the stream use case, but the Java library itself doesn’t follow that advice everywhere so it’s probably a lost cause.


#7

I thought that Optionals are recommended for return values, but not for parameters. Anyway, Java libraries should not be taken as reference as they are pretty inconsistent; for example, JPA sometimes return null and sometimes throws an exception.

I prefer Optional over null because API is more consistent with other monads. However, it can be pain in steams when stream contains Optionals.


#8

That’s funny about the Point class nuking performance. That sounds like a mistake I would have made a few years ago. The performance cost they hit probably wasn’t so much in the use of an object vs a literal, but instead it was likely in the allocation and garbage collection of those objects. For the same reason, I have to be careful not to use any Kotlin utility methods that instantiate iterator objects within my game loop.


#9

Do you have example code snippet where you prefer Optional where the consuming code is Kotlin?

Do you create Kotlin API using Optional (or are you talking Java API only)?

Thanks


#10

For pure Kotlin applications, I would consider null over Optional, but I need more experience to make a decision. Advantage of optional is that you always use .map() when you are converting stuff. For Java iterop, I would always go with Optional.

Here is the Kotlin consumption example for Optional:

kittenBusinessService
    .find(id)
    .map { KittenRest(it.name, it.cuteness) }
    .orElseThrow { NotFoundException("ID $id not found") }

With null:

kittenBusinessService
    .find(id)
    ?.let { KittenRest(it.name, it.cuteness) }
    ?: throw NotFoundException("ID $id not found")

Hmmm… map function for nullable types would make it similar to Optional. Jetbrains?


#11

You can write it yourself, if you prefer map over let. It is just one line of Kotlin code:

inline fun <T, R> T?.map(transform: (T) -> R): R? = this?.let { transform(it) }

orElseThrow is just as easy to define if needed, which is not surprising, because Kotlin’s T? is essentially Java’s Optional<T> that is done the right way, that is without neither runtime nor syntactic overhead.


#12

Why would you want to check for null as the last step? Isn’t it better to eliminate nulls as soon as possible?

Here is an example. Note that I am not entirely sure whether : Any is needed for the type parameter:

// Inferred type of "kittenRest" is "KittenRest"
val kittenRest = kittenBusinessService.find(id)
    .throwNotFoundIfNull { "ID $id not found" }
    .let { KittenRest(it.name, it.cuteness) }

inline fun <T: Any> T?.throwNotFoundIfNull(lazyMessage: () -> String) = this ?: throw NotFoundException(lazyMessage())

#13

In “pure” functional programming, there are no exceptions as they break the call flow. If there is a problem, you pass the value that will skip the steps and return it as a result. In this case, you pass null (Kotlin) or Optional.empty (Java) or None (Scala). Throwing the exception is the last thing where you are basically returning the result to JAX-RS so it can generate 404 HTTP response.

If there is a need for a real error processing (e.g. IO error), you would return Try monad which can be either Success or Failure.

Also, imagine that null can appear in multiple places in call chain. You would have to add throwNotFoundIfNull at each and every place.


#14

My take is that being easier to understand for other developers and being more efficient, is more important than being pure.

In your example you must know that map will return null for a null input. In my example, a fellow developer can read from top to bottom and understand that let will never be called on a null. You could argue that knowing that map can handle nulls is essential to being a good developer, but I appreciate it when someone already made it clear how nulls are handled. It just makes it a lot easier to read the code; I don’t have to look at the documentation/code of each function used in a chain.

Kotlin allows me to focus on my problem because I can be sure that a variable cannot be null. Introducing an API which makes me think about nulls again feels like going back to the dark days of Java :wink:

Here is an example from some of my code. I have a number of steps, but I only want to do something with the actions. I don’t even have to think about null anymore when I reach forEach:

steps.map { it as? Action }.filterNotNull().forEach { it.execute() }

I understand that it is difficult to stop processing when you have an object instead of a collection, and you do not want to use exceptions. But if you are going to use exceptions anyway, I think you should throw them as soon as possible.


#15

I think it’s worth remembering that functional languages work the way they do because they’re a parallel line of evolution to imperative languages, not because everything they do is inherently better than the imperative way. Haskell still defines a standard library string to be a linked list of characters for instance, because that’s how Lisp worked and it happens to make some kinds of mathematical reasoning easier, but nobody who cares about performance would argue for actually representing strings like that normally.

Doing things in Kotlin the way Haskell does them often doesn’t make sense to me, and avoiding exceptions or Kotlin’s optionality features is one of those times.


#16

An advantage Optional/Maybe has over nullable types is that you can compose it. For example, you can have an Optional<Optional<Foo>>, but you can’t have a Foo??.

This is especially an issue with generic types where you want to be able to make a value of a type parameter optional. For example, suppose you have a type parameter T, and you try to use T? somewhere. If the user makes T a nullable type Foo?, now your T? is the same type as T.

This was an issue in Kotlin-Argparser, as I want the user to be able to specify any type for parsed arguments, including a nullable type, but I need to be able to distinguish between “the value is null” and “the value was never set”.

I didn’t actually use java.util.Optional, as I was trying to avoid the JDK 8 dependency. I ended up rolling a minimal Optional-like type in Kotlin. (Mine actually uses null for the empty case, but non-empty is in a property, which may be nullable.)


#17

This is actually a pretty sensible way to go. Use the ?: notation when you want “option-ish” behavior around nulls, and then come up with a proper Option/Maybe class for when you want all the other features. java.util.Optional doesn’t do all the goodies that you get elsewhere, so you might as well roll your own.

That said, it would be reasonable for an Option/Maybe class and other related things like Try to eventually find their way into the Kotlin standard library, since so many people will need them over and over again.

Useful links about why java.util.Optional is problematic and what to do about it:

And as an alternative, there are worse places to begin than Javaslang’s Option and Try:

Which are probably easier than adopting the full enchilada of how Scala does it:

Scala notably blurs the lines between an Option and a List that just happens to have only one thing in it. If/when Kotlin decides its time to expand its standard library to jettison all dependencies on the java.util collections classes, then I imagine that there will be fun times about just how much of what Scala/Haskell/others do is worth bringing into Kotlin.