⏱ Reading time: 3 min

Starting with Kotlin 1.3, the standard library ships with a new cool feature: inline classes.

Inline classes are a nice and clean way to add strong typings to your code. They are meant to remove the heap overhead involved when creating wrapper types for simpler types or even for primitive ones.

You can declare a new inline class like you do with your data classes, but with the limitation that an inline class can hold just a single property.
While at compile-time they appear like normal classes or data classes, at runtime the type is represented using the type and the value of the property defined. That’s why only a single parameter is allowed.
The behavior is the same as the inline functions, where the body of the function is added instead of the function call.

Inline classes can also have methods, but cannot have stored properties different from the one defined into the constructor. At runtime, the method calls are represented as static method calls.

Let’s say that we have a StringWrapper inline class, which has the method greet().

inline class StringWrapper(val string: String) {
    fun greet() = "Hello, $string!"
}

StringWrapper("Damiano").greet()

The compiler will translate the invocation as a static method call, like

StringWrapperKt.greet("Damiano");

Inline classes can also implement interfaces. And this is the point of this blog post.

I want to tell you a little story, which will denote the magic behind inline classes. The story has Kotlin 1.3.21 as main character.

Once upon a time…

In a project for a customer of ours, we have a form which the user can fill that is completely driven by a customer’s backend service, that was already in place at the project’s start. By consuming it, the Android app receives a JSON descriptor and renders a list of UI controls based on it. When the user finished to fill them, he/she can perform a submit to a REST endpoint. If not treated carefully, the managing of the form items can be tricky. Specially when sending the data to the backend using a JSON DTO.

For a particular case, we needed to make an abstraction for a field of the DTO. Such field, in certain cases, could hold a plain string value and a three-properties object in others.
We defined a marker interface, ProductValue. Then we defined the two implementations, named respectively StringProductValue and PropertiesProductValue.

Magic happens

Since StringProductValue was meant to contain only a plain string, we decided to try out the new inline class feature.

inline class StringProductValue(val value: String): ProductValue

How cool is this! We wrap our string value adding a stronger and meaningful type as StringProductValue. And we didn’t add any overhead, since at compile time the class will be directly unwrapped by the compiler to a plain String, and seen as a String at runtime.

Say that again.

At compile time the class will be directly unwrapped by the compiler to a plain String, and seen as a String at runtime.

In fact, the problem occurred when trying to pass an instance of our StringProductValue inline class to a method that accepted an instance of the abstract type ProductValue. At compile time we had no issues, but at runtime we received a ClassCastException.

java.lang.ClassCastException: java.lang.String cannot be cast to my.package.ProductValue

In a nutshell, the method which maps the value to the DTO accepts a ProductValue interface implementation, but at runtime our implementation class is seen as a java.lang.String, thus an exception is thrown just by calling such method with the “unwrapped” string.

For our use case, a data class suited better and obviously fixed the issue.

TL;DR

Use inline classes responsibly. You can let them implement interfaces and you can add methods to them. However, making abstractions using interface implementations made with inline classes isn’t fully supported right now. Moreover, it can lead to unexpected runtime errors.

In conclusion, I must say that inline classes are a very powerful feature. But they are still experimental, and there must be a reason for that.

Have a nice Kotlin and thanks for reading!

PS. don’t forget to follow my other articles about the Kotlin programming world: https://blog.molo17.com/tag/kotlin/