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

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 Likes

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.

2 Likes

Just something about the topic:

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

and

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

2 Likes

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.

1 Like

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.

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.

2 Likes

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.

1 Like

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.

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

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?

1 Like

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.

2 Likes

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())

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.

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.

1 Like

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.

1 Like

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.)

1 Like

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.

3 Likes

IMO the problem with Optional is that it is not designed as a proper Maybe/Option type - as it should have been. I have yet to see a single case where a null is to be preferred over a proper Option type.

Regarding Kotlin’s T? being Optional<T> done “the right way”, I disagree! It’s a workaround for a bad language design. Granted, it’s nice when risk getting a null (e.g., from a Java API) or internally in a class because the Optional is not designed for use in fields (here is why it should have been a proper Option/Maybe-type).

Why?

  • Dealing with a proper Monad results in a consistent design. It should not matter whether you call map on a list, stream, option, or whatever else - and not the ugly and confusing ?:

  • ?: syntax sugar, or syntax mess? I see the idea, but it’s messy. People are already struggling remembering syntax to the point where there is a quote along the lines of “the first thing anyone forgets about a programming language is the syntax” (cannot remember and find the exact quote, so this is by memory). Do I need to remind how much people struggle with regular expressions? Perl? Heck even the ternary if (foo()? bar : baz;) is confusing to a lot of programmers. Now the programmer - apart from the fully standardized, consistent and well defined methods map, flatMap etc. - needs to remember ?., ?:, plus handling things like (safe) casts.

  • It breaks Java interoperability. Writing everything in Kotlin is nice… in the real world, we deal with customers who are not always ready to embrace Kotlin, and they are not always rational enough to listen to reason. We should not design for breaking the interop. Also a lot of libraries are still written in Java.

  • What does null mean? Is it an error, is it deliberate, etc., what is the semantic meaning? Had we had a proper Option I could do: typealias EmptySearch = Option.None, how would you even do that with a null?

  • What is the type of null? It’s both all and no types at the same time. What is the type of Optional? That’s clear and well defined. T? is also well defined, but what about data from the world outside Kotlin (database, json, java, etc.)?

  • Once we permit the null we are not sure what will happen - in a pure Kotlin world maybe, and there I’m not even sure. Can you guarantee that the null you store in the DB will never (and I mean never ever in all the existence of this application and all other applications using the same db) try to do something explicitly not-null with that value? Can you guarantee that you will remember to deal with all such values for all database calls in some other way now and in all future? Probably not, so make the value explicit! Making it harder to trigger the time bomb known as null, does not make it impossible. However, now you risk a false sense of security, because all of your nulls and sources of it is well defined, right? (If the loading of the question is not clear, the answer is “no”)

  • What happens in frameworks? Can you guarantee that Spring, Hibernate, JPA, Mockito, Jackson and so on, will always respect your non-nulls? Can you guarantee that none of their reflection tricks will never inject a null? At least if you limit the sources of null values to the absolute minimum you will at least have an easier time debugging once the framework does something you didn’t anticipate.

  • Optional supports things that a plainly impossible using null, e.g., composition as mentioned above

So I would not return a null if I can avoid it.

The correct guideline as I see it is the same as Java:

  • avoid nulls whenever possible
  • when you need an option type, use null internally in the class, but isolate it to that class
  • never (disclaimer, read “never” as: there might be exceptions, I haven’t seen them yet) return a null! Return Optional instead
  • don’t use null arguments, use constructor/function overloads instead

A general thing: do not confuse being lazy with pragmatism. I quite often hear “being pragmatic” as an argument for bad design, whether it is using null not using immutable type, overusing frameworks, etc. Introducing well known bad designs, just because it’s faster and easier right now is not the definition of pragmatic. Doing solid designs that prevents known errors sources, ensures ease of work in the long run etc., is being pragmatic. Kotlin embraces this by defaulting to finalizing classes etc. Experience shows us nulls are bad! Even with explicit nullable types, such as Kotlin provides.

So unless I oversee any crucial arguments, returning null is still a bad design choice. There are a multitude of reasons to avoid it, and what I’ve seen of arguments for is IMO claims of pragmatism that will turn out to be fallacies once one looks further into them.

I might have forgotten some of the definitions of a Monad but I don’t think so. In what way are nullable types in kotlin different from a Monad? Which feature are they missing? The syntax might be a bit different to fit with the kotlin language but otherwise they are the same.

Ok you don’t like the syntax, that is however no argument against nullable types in kotlin. Also the kotlin syntax is quite consistent. Every time you see a ? you deal with nullable types.

It might surprise you, but for many people those functions are not well known or understood. They come from a functional programming background which only lately became more popular again so unless you have a background in those you wouldn’t know them.

How so? Kotlin has checks at the beginning of all public functions to ensure nullability is not broken by java and in the java world you have to check for null in any case. The only thing to ask for is to return Optional for those java libs/programs that use them but that is not always the case.

What does Option.None mean?

How would you use that typealias? What is the benefit of it? I can’t think of a single situation where this is actually required

The type of null is Nothing?. Nothing is the so called Bottom Type. Nothing? is the nullable version of this and has the single value of null.

As for your last 3 points. You can’t guarantee it with optionals. There is one big problem in all your arguments. Both java and kotlin are restricted by the JVM and the JVM has null. There is no way around that. Kotlin took it as far as possible and if that’s not enough for you I suggest you start using a language that is not restricted by the JVM since that’s the only way to get away from nulls.
Btw a language without null but with Optionals just renamed the concept of null. On a theoretical system they end up being just the same. The only thing Optionals provide that javas nulls don’t is some amount of safety, but this is also provided by kotlins nulls.

1 Like

@rohdef To be fair, Kotlin doesn’t really have “Null” in the classical sense of being a member of every type. Kotlin’s “Null” is really another name for Optional.None.
From this very enjoyable article

You could have boldly named your None as null and it would have been just as safe to use.

As for “avoiding nulls whenever possible”, check out this other enjoyable article.

2 Likes