Integrating Kotlin with a Dropwizard App
Published on:Table of Contents
Kotlin is a newish language that runs on the JVM and integrates seamlessly with existing Java code. Kotlin is concise, ergonomic, and thoughtful. We’ll look at low cost and frictionless ways of integrating Kotlin into an existing Java codebase. You can add in Kotlin piecemeal so there is very little time lost to writing Kotlin today. For purposes of demonstration our frame of mind will be from writing a Dropwizard app.
Setup
Basic structure of our app:
- JDK8
- Maven for building
- Intellij IDE
Your setup may be different, but can always be adapted.
When creating your first Kotlin class, Intellij will ask to modify your pom.xml
. Here’s what it’s adding:
A couple of dependencies
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib-jdk8</artifactId>
<version>${kotlin.version}</version>
</dependency>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>
The kotlin stdlib defines all the function goodies Kotlin brings to the table and also Java interoperation. It will increase your shaded jars by approximately 1MB (tad overestimate).
We also can’t forget to instruct maven to compile our kotlin code (something has to convert it into JVM bytecode).
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
<configuration>
<jvmTarget>1.8</jvmTarget>
</configuration>
</plugin>
Data Classes
Value objects are key to any application. Having small, immutable, hashable classes that are equal to another instance that contains the same values make understanding code significantly easier, as there can’t be clever usage tricks / hacks.
I currently write my Java value objects using AutoValue. Why AutoValue? Even though the amount of code to write dramatically decreases, it is still a non-negligible amount to write and maintain. Below is PointJ
class for representing x and y coordinates using AutoValue
.
@AutoValue
public abstract class PointJ {
public abstract int x();
public abstract int y();
public static Builder builder() {
return new AutoValue_PointJ.Builder();
}
public abstract Builder toBuilder();
public static PointJ create(int x, int y) {
return builder()
.x(x)
.y(y)
.build();
}
public PointJ withX(int x) {
return toBuilder().x(x).build();
}
public PointJ withY(int y) {
return toBuilder().y(y).build();
}
@AutoValue.Builder
public abstract static class Builder {
public abstract Builder x(int x);
public abstract Builder y(int y);
public abstract PointJ build();
}
}
The generated class (AutoValue_PointJ
) expands to 101 lines, so we’re saving anywhere between 2x and 3x the amount of code we’d need to write otherwise. A big win!
Aside: using a builder here was not necessary, but keeps a certain form of consistency as classes scale up to additional members. Using a builder makes it obvious where x
and y
is set.
Using Kotlin data classes, we need only a single line
data class Point(val x: Int, val y: Int)
Quick usage in Kotlin:
fun usePoint() {
val point = Point(x = 10, y = 20)
val x = point.x
// Simulate translation along y-axis and destructure
// the data class into two variables
val (new_x, new_y) = point.copy(y = 30)
}
- No need for a builder anymore as we see the explicit
x = 10
in initialization - Data is immutable (you can’t reassign x or y to different values)
- Easily create copies with updated properties
How does the same usage look like in Java?
final Point point = new Point(10, 20);
final int x = point.getX();
final Point newPoint = point.copy(x, 30);
- The lack of explicit argument names make instantiation more error prone (may be contrived with a
Point
class, but a concern nonetheless). The fix would be to create a custom builder – but I understand if this suggestion is met with disdain. - The function
getX()
is needed in Java else someone could change the value ofx
and ruin immutability - To update a point (and receive a new value) we have to pass in previous values (creating helper functions like
withX
andwithY
are possible) - Lack of type inference makes code more verbose
- Denoting variables as final makes the immutable path, which should be the default, more verbose than the mutable way
To make updating a little easier on the Java side, you can define expression functions that delegate to the Kotlin copy
:
data class Point(val x: Int, val y: Int) {
fun withX(x: Int): Point = copy(x = x)
fun withY(y: Int): Point = copy(y = y)
}
It’s up to the reader how they feel about adding these with
functions. I’m
torn. I don’t like to pollute data classes with too many functions, as they
start to move away from truly being a “data” class. But on the other hand,
these functions are concise, less error prone, and it makes Kotlin data classes
easier to use in Java.
Jackson
If you’re using Dropwizard, you’re using Jackson. If you’re using Jackson, you’re using Jackson annotations. To update our AutoValue class for the bare minimum number of Jackson annotations needed:
@AutoValue
@JsonDeserialize(builder = PointJ.Builder.class)
public abstract class PointJ {
@JsonProperty
public abstract int x();
@JsonProperty
public abstract int y();
public static Builder builder() {
return new AutoValue_PointJ.Builder();
}
public abstract Builder toBuilder();
public static PointJ create(int x, int y) {
return builder()
.x(x)
.y(y)
.build();
}
public PointJ withX(int x) {
return toBuilder().x(x).build();
}
public PointJ withY(int y) {
return toBuilder().y(y).build();
}
@AutoValue.Builder
@JsonPOJOBuilder(withPrefix = "")
public abstract static class Builder {
@JsonCreator
private static Builder create() {
return PointJ.builder();
}
public abstract Builder x(int x);
public abstract Builder y(int y);
public abstract PointJ build();
}
}
At this point, you start feeling less satisified with AutoValue. What about Kotlin?
- Add the
jackson-module-kotlin
dependency - Register the
KotlinModule
on theObjectMapper
<dependency>
<groupId>com.fasterxml.jackson.module</groupId>
<artifactId>jackson-module-kotlin</artifactId>
</dependency>
And a java junit test case.
@Test
public void deserialize() throws IOException {
final ObjectMapper mapper = Jackson.newObjectMapper();
mapper.registerModule(new KotlinModule());
final Point point = mapper.readValue("{ \"x\": 1, \"y\": 2 }", Point.class);
assertThat(point.getX()).isEqualTo(1);
assertThat(point.getY()).isEqualTo(2);
}
Done. We did not need to modify our data class at all!
I can’t recommend data classes enough. In a single line of Kotlin
- Have an immutable value class
- Have Jackson support
- Decent out of the box Java interop
- Can add concise
with
methods to make Java interop even better - Eliminate 53 lines of an AutoValue class, which previously held the gold standard.
To give one final example, you know the configuration class in the Dropwizard getting started page? Those 27 lines can be reduced to:
data class MyConfig(
@field:NotEmpty val template: String?,
@field:NotEmpty val defaultName: String = "Stranger"
): Configuration()
A few new things here:
- Default value for
defaultName
- Allow
template
to benull
with theString?
type - Annotate the backing fields of the data class with
@NotEmpty
. By default, the annotation would apply to the argument, which is not validated – fix with@field
.
It seems contradictory, doesn’t it? That there is a annotation for NotEmpty
on a type that clearly states that it can be null
. If we remove the contradicting ?
and the annotation, Jackson will provide an ok error message when we try and deserialize a null
value. Though another solution would be to have a MyConfigRaw
with the annotations and MyConfig
with the non-nullable types and convert the raw data class to MyConfig
after validation. Data classes are concise enough that this is a viable alternative. Or live with the contradiction.
Also, don’t forget, to get this to work in your app, you’ll need to bootstrap with the KotlinModule
:
@Override
public void initialize(final Bootstrap<MyConfig> bootstrap) {
bootstrap.getObjectMapper().registerModule(new KotlinModule());
}
Algebraic Data Types
After you get a taste of data classes you’ll want to write your business logic in Kotlin. Algebraic Data Types allow a single type to encompass other types without inheritance and all those problems. ADTs are perfect for business logic as it allows inner functions to be pure and outer level functions deal with the side effects (logging, metrics, etc). Previously, I used Derive4j, which is a decent Java-only solution, but it only gets you so far due to constraints of the language.
Kotlin solves this with a combination of sealed classes and smart casts.
One of my pet peeves is that there is often a lack of precision associated with
date and time types. For instance, if I said “I was born in 2018” or even “I
was born in January 2018”, it is not the same as saying “I was born on January
1st, 2018”, yet all would be represented as LocalDate.of(2018, 1, 1)
and they
would all be considered equal. Same thing with time “I have practice on
Tuesdays” vs “I have practice on Tuesdays at 5:30pm”. There is not enough
granularity between the times to know if they really are the same. I could be
talking about soccer and violin practices that happen both on Tuesdays. So we
are going to fix this by creating a new type for each granularity (year, month,
day). This task would be burdensome in Java, but not so in Kotlin
sealed class DatePrecision
data class Year(val year: Int) : DatePrecision()
data class Month(val year: Int, val month: Int) : DatePrecision()
data class Day(val year: Int, val month: Int, val day: Int) : DatePrecision()
Working with these classes is easy (using explicit types for readibility)
val dp: DatePrecision = Year(year = 2018)
val dy: Year = Year(year = 2018)
val dt: DatePrecision = Month(year = 2018, month = 1)
assertThat(dp).isNotEqualTo(dt)
assertThat(dy).isEqualTo(dp)
assertThat(dp).isEqualTo(Year(year = 2018))
Using when
statement for smart casts, we have each precision’s fields available.
fun datePrint(dt: DatePrecision) = when(dt) {
is Year -> println("Year precision: ${dt.year}")
is Month -> println("Month precision: ${dt.year} - ${dt.month}")
is Day -> println("Day precision: ${dt.year} - ${dt.month} - ${dt.day}")
}
Now the business logic can determine how 2018 and January 2018 are treated
instead of needing a companion enum that holds the precision of a LocalDate
.
It becomes significantly less error prone as you can’t accidentally interpret a
monthly date as a yearly one.
I’m a large proponent of ADTs and I could continue to extol their benefits ad nauseam, but I’ll spare you and leave just one more thought.
Anyone that knows me, knows that I’m not a fan of exceptions. I prefer Rust’s form of error handling, Haskell’s, or even Go’s. Being able to communicate the fact that the return value is either a success or has some sort of error is invaluable. I won’t dive any deeper as Kotlin’s premier functional companion, Arrow, defines an Either
type. The docs should be enough to get one started on their way to fewer exceptions. The one gripe is that Either
has the error type listed first in the signature. I find this confusing, as it is more intuitive to have a separate Result
type with success listed before failure, but this argument would probably fall on deaf ears as the hard core functional crowd made up their minds a long time ago.
Yeah Basic
Here’s a hodgepodge of Kotlin features you’ll immediately love:
Iterate iterators!
Iterator<String> lines = /* ... */;
while (lines.hasNext()) {
final String line = lines.next();
}
is less pretty than Kotlin’s
val lines: Iterator<String> = /* ... */
for (line in lines) {
}
Lazy and eager collections! Java has too much ceremony surrounding lazy streams.
final List<Integer> list = Collections.singletonList(1);
final List<Integer> anotherList = list.stream()
.map(x -> x + 1)
.collect(Collectors.toList());
Kotlin gives us the ergonomic option and the lazy option (not that it makes much of a difference here, but if there are many chains then it would become noticeable)
val list = listOf(1)
val eager: List<Int> = list.map { it + 1 }
val lazy: List<Int> = list.asSequence().map { it + 1 }.toList()
Single constructor classes! All of my Jersey resource classes only have a single constructor – in fact, I think almost all my classes have a single constructor.
class MyResource {
private final MyDb mydb;
public MyResource(MyDb mydb) {
this.mydb = mydb;
}
}
becomes
class MyResource(private val mydb: MyDb) {
}
Raw string literals! Writing string literals that contain quotes, can be a pain in Java, especially writing inline tests for JSON serialization and deserialization – it’s never a copy and paste task. Kotlin to the rescue.
final ObjectMapper mapper = Jackson.newObjectMapper();
final String jsonStr = "[\"a\", \"b\", \"c\"]";
final List<String> list = mapper.readValue(jsonStr,
new TypeReference<List<String>>() {});
assertThat(list).containsExactly("a", "b", "c");
becomes
val mapper = Jackson.newObjectMapper()
val jsonStr = """["a", "b", "c"]"""
val list: List<String> = mapper.readValue(jsonStr)
assertThat(list).containsExactly("a", "b", "c")
It doesn’t look like a large improvement, but I want to point out:
- Raw string literal allows us to sanely type JSON by hand or copy and paste. Use trimMargin if you want a multiline string trimmed
- Reified generics means we don’t need to specify
List<String>
type twice - And, as always, type inference saves unnecessary typing
In the interest of keeping this post a reasonable length, I’ll direct you to idioms in Kotlin for more reasons
Testing
If the Dropwizard project in question can’t have its production code touched for some reason (eg. adding Kotlin code seems like rocking the boat too much). Then you can always add Kotlin exclusively for tests. You still benefit from all the niceties Kotlin brings to the table with none of the downsides (not that there are many to begin with). I find this approach similar Java projects that use Groovy for tests (spoiler: I like Kotlin better).
Dropwizard currently bundles junit 4. Dropwizard 1.3 will optionally bundle junit 5. I personally am still using junit 4 because Dropwizard 1.3 is not released yet so I haven’t gotten a chance to explore the new features for junit 5 (though it appear to be not as straightforward as it should be). I hate all the ceremony surrounding parameterized tests in junit 4, which has been fixed in 5. Unfortunately, parameterized tests are labelled as experimental. So when you see guides for testing kotlin, make sure you note the framework used: junit 4/5, and kotlin specific frameworks like Spek (built on junit), and KotlinTest
Lots of options here, lots of room for confusion. I keep it simple. I use the frameworks included with dropwizard: junit4 and AssertJ. The examples I’ve posted thus far use this approach. I tend to prefer to write business logic tests in Kotlin and tests that are heavy in junit annotations to be written in java. The thought is to keep tests straightforward enough for anyone to pickup and contribute to, yet concise and readable. I groan when projects use Groovy for tests as it makes the barrier to contributing code with tests much harder – and I don’t want that to be the case with Kotlin tests.
Coroutines
I had my first trip up with Kotlin when I started dabbling in coroutines.
java.lang.NoClassDefFoundError: kotlin/coroutines/experimental/CoroutineContext$Element$DefaultImpls
This is due to an old version of kotlin-stdlib
being picked up, as it was not
explicitly stated as a dependency. Curiously, Intellij didn’t generate it when
setting up a project for Kotlin. I expect this is an oversight and will soon be
remedied. The fix was to state it in the pom
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>
Anyways, I hope this helps someone frantically googling (like I was). Onto the good stuff. Since coroutines are still experimental, subject to change, and the setup process isn’t flawless, I’ve left them to the end of this post.
Jersey supports asynchronous endpoints. Using coroutines, let’s simulate a redis increment and an http request (each taking a second to complete). Below is such an endpoint:
@Path("/")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
class ProxyResource {
@GET
fun proxy(@Suspended resp: AsyncResponse) {
launch {
val incr = redisIncr().await()
val content = async {
delay(1, SECONDS)
Utility.httpRequest().await()
}.await()
resp.resume("The answer is $content + $incr")
}
}
fun redisIncr(): Deferred<Int> = async {
delay(1, SECONDS)
10
}
}
For a bit of Java interop fun, I created Utility.httpRequest()
to return a CompletableFuture
.
public class Utility {
public static CompletableFuture<String> httpRequest() {
return CompletableFuture.completedFuture("Boom");
}
}
Our endpoint executes the redis command and the http request sequentially. This is ideal when the http request depends on the redis one before it; however, since the dependency is not needed we can execute both coroutines concurrently shaving latency in half.
launch {
val incr = redisIncr()
val content = async {
delay(1, SECONDS)
Utility.httpRequest().await()
}
resp.resume("The answer is ${content.await()} + ${incr.await()}")
}
Caution
By default, launch
and async
schedule their tasks on the
ForkJoinPool.commonPool()
. This pool is typically used for CPU intensive
tasks as, by default, it will only run the number of tasks concurrently equal
to the number of cores - 1 (my four core, hyper-threaded cpu has a parallelism of
seven). My recommendation is if you want to use the default common pool (ie not
specify a context param to launch or async), keep everything CPU intensive or
in a deferred.
Replacing
val content = async {
delay(1, SECONDS)
Utility.httpRequest().await()
}.await()
with
val content = async {
Thread.sleep(1000)
Utility.httpRequest().await()
}.await()
Caused 16 concurrent requests to finish in three seconds instead of one! (16 requests at 7 parallelism = 3 seconds)
Another option is to use the Unconfined
context which should use the thread
pool that Jetty uses to service requests. If my hunch is correct this will
allow us to still benefit from Jetty’s handling of request pressure but still
allow us to increase throughput.
Conclusion
If you’ve followed through the whirlwind of a tour of data classes, algebraic data types, lambda, function expressions, smart casts, interoperability, coroutines, reified generics, raw strings, and immutability then congratulations! It may seem overwhelming to see all features mentioned in tandem, but each feature examined individually is digestible. Kotlin isn’t here to make writers feel smart and readers dumb, but improve on Java, a language sorely lacking these features. I hope you give Kotlin a try in your next app. Let me know how it goes!
Comments
If you'd like to leave a comment, please email [email protected]