Multi-target compilation with Kotlin

Kotlin allows to compile application code for different platforms, namely into JVM (byte code), JavaScript and native binaries. In one of our projects we were facing the challenge to use the same source code as well in a Java client as in a web application written in TypeScript/Angular. The main reason not to just copy the code and convert it manually to TypeScript is maintenance, i.e. the code is expected to evolve over time.

Thus we came up with the solution to convert the original Java code to Kotlin and compile it for both platforms.

Code migration

The original source was written in Java and is used to perform calculations for an animated widget in the UI. I.e., the user handles sliders of the widget that will change the appearance of the visualized data. The calculation logic has been extracted into a library.

Conversion to Kotlin

In a first step we copied the classes in a new project and tried to compile them with the javac compiler. This gradually showed us what other classes where necessary in order to compile. After thus having copied the transitive closure  (and manually cutting off some edges) we also copied the tests and made them run.

Using IntelliJ IDEA the transformation to Kotlin could be performed quite easily. Unfortunately there is no command line tool to perform this task, we depended on the IDE to perform this step.
After some manual changes and optimizations the code ran well and was also well readable.

Adaptations for JavaScript

The IDE itself does not support compilation for multiple targets, i.e. you have to decide whether to use the JS or JVM runtime environment as the target for your project.
Switching to the JS environment revealed many compilation errors, mainly because of  dependencies to the Java-API. Fortunately many to them where related to functions in java.lang.Math most of which could be replaced by calls to functions in kotlin.math.

Another dependency was the usage of Java’s BigDecimal class. We had to replace the code with manually written operations. In a sub-project we also tried to convert BigDecimal into Kotlin, too, but soon gave up.

The last problem was the usage of the method-level annotation @Throws that denotes for the JVM compiler that the method signature contains a throws clause. (In the Kotlin language itself there is no throws clause). In the JS runtime library no such annotation exists.

Since our goal was to use exactly the same source code for both targets we came up with the solution to write a dummy @Throws annotation stored in a secondary source folder only visible during compilation for the JavaScript target.

Maven build

Finally we wanted to compile for both targets in a single build and just added a second execution to the Kotlin maven compiler plugin:

...
<execution>
    <id>compile-js</id>
    <phase>compile</phase>
    <goals><goal>js</goal></goals>

    <configuration>
        ...
        <sourceDirs>
            <sourceDir>${project.basedir}/src/main/kotlin</sourceDir>
            <sourceDir>${project.basedir}/src/main/jskotlin</sourceDir>
        </sourceDirs>
    </configuration>
</execution>

The result is a JAR containing as well the class files as JavaScript code. The integration into the Angular web application worked seamlessly, as expected.

Conclusion

The approach worked well for an encapsulated business logic. However, one must not expect more complicated use cases to work out of the box. Particularly, when the number of dependencies to other frameworks and APIs is too big.

Posted in All