DEV Community

Cover image for How to Try Kotlin in Java Backend Codebase Without Risk
Alex Fedorov
Alex Fedorov

Posted on • Originally published at iwillteachyoukotlin.com

How to Try Kotlin in Java Backend Codebase Without Risk

Who’s heard of this lovely and astounding programming language Kotlin, and wanted to try it out on the real backend project and couldn’t?

This is common. The team discusses that everyone really wants to go Kotlin, and they still decide to create that new codebase in Java. You and your fellow colleagues are afraid that this will go crazy wrong, right?

And you are right to have such a feeling, as you don’t have enough confidence yet to make the switch. What if the whole team is still learning this new technology, and there is an unexpected challenge that nobody can resolve for weeks?

Now, you’re probably quite a bit into this project, and your backend codebase is all in verbose Java, and you think: “No way I can try Kotlin now! Not until next one…”

Wrong.

There is no point in waiting until the next opportunity because of the high chances that it’ll turn out the same!

Now, what if I told you that you can still try out Kotlin together with your teammates in this codebase without any risks, and no strings attached?

Let me explain.

Mixing Java and Kotlin code

With Kotlin’s 100% bidirectional interoperability, it’s possible to mix Java and Kotlin code easily.

What this means, is that you can convert a single file from Java to Kotlin to get the feels of what it’ll look like. And everything will work just like before.

Now, what if I told you that such a conversion is one hotkey away?

Let me show an example. Imagine you had this simple service class in your Java backend application:

// QuizService.java
package com.iwillteachyoukotlin.quizzy;

import org.springframework.stereotype.Service;

@Service
public class QuizService {

    private final QuizRepository quizRepository;

    public QuizService(QuizRepository quizRepository) {
        this.quizRepository = quizRepository;
    }

    public Quiz create(Quiz quiz) {
        if (quiz.id != null) {
            throw new BadRequestException("id should be empty");
        }

        return quizRepository.save(quiz);
    }

    public Quiz getQuiz(int quizId) {
        return quizRepository
                .findById(quizId)
                .orElseThrow(() -> new NotFoundException("quiz not found"));
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, you can press CMD+ALT+SHIFT+K (or CTRL+ALT+SHIFT+K), or you could use Convert Java File to Kotlin File action:

Open Find Action tool, type convert to kotlin, press enter, and see the file fully convert to kotlin automatically

The result is the following code. Notice how both constructor and field definition has merged into a single declaration in Kotlin:

// QuizService.kt
package com.iwillteachyoukotlin.quizzy

import org.springframework.stereotype.Service

@Service
class QuizService(private val quizRepository: QuizRepository) {

    fun create(quiz: Quiz): Quiz {
        if (quiz.id != null) {
            throw BadRequestException("id should be empty")
        }

        return quizRepository.save(quiz)
    }

    fun getQuiz(quizId: Int): Quiz {
        return quizRepository
                .findById(quizId)
                .orElseThrow { NotFoundException("quiz not found") }
    }
}
Enter fullscreen mode Exit fullscreen mode

Note for Lombok users:

Use Delombok on this file before converting to Kotlin.

Of course, you need to add Kotlin support to your build tool before you can even run this code:

Add Kotlin support to Gradle

(Note: refer to this guide if you’re using Maven)

Add a Gradle plugin appropriately:

plugins {
    id "org.jetbrains.kotlin.jvm" version "1.3.11"
}
Enter fullscreen mode Exit fullscreen mode

Note for Spring Boot users:

You need to add this plugin, so that Kotlin classes will open automatically where needed, so that Spring Boot can use reflection on them:

id "org.jetbrains.kotlin.plugin.spring" version "1.3.11"

And if you’re using JPA/Hibernate entities, you’ll need this plugin:

id "org.jetbrains.kotlin.plugin.jpa" version "1.3.11"

This adds the default constructor for your entities.

Also, you’ll need to add a dependency on Kotlin standard library and reflection library:

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8"
    implementation "org.jetbrains.kotlin:kotlin-reflect"
    // …
}
Enter fullscreen mode Exit fullscreen mode

Now you have one Kotlin file among the sea of Java files. Java classes are calling this Kotlin code, and this Kotlin code is calling to Java code.

And it all perfectly works. And all tests pass!

Now, simple automatic conversion is ok and all, but it’s not enough to really leverage the power of Kotlin.

1. Use Elvis operator instead of most null checks

If you had any null check anywhere that returns or throws an exception, for example:

fun update(quiz: Quiz): Quiz {
    if (quiz.id == null) {
        throw BadRequestException("id should not be empty")
    }

    return quizRepository.save(quiz)
}
Enter fullscreen mode Exit fullscreen mode

These null checks that either throw or return are usually called guard if statements. In Kotlin these can be done more succinctly with "Elvis" operator ?::

fun update(quiz: Quiz): Quiz {
    quiz.id ?: throw BadRequestException("id should not be empty")

    return quizRepository.save(quiz)
}
Enter fullscreen mode Exit fullscreen mode

2. Convert Optionals to nullable

Another example that can be simplified with Kotlin is the usage of Optional type:

fun getQuiz(quizId: Int): Quiz {
    return quizRepository
            .findById(quizId)
            .orElseThrow { NotFoundException("quiz not found") }
}
Enter fullscreen mode Exit fullscreen mode

Here we’re going to use another method of our repository that returns either a found object or null:

fun getQuiz(quizId: Int): Quiz {
    return quizRepository.findByIdOrNull(quizId)
            ?: throw NotFoundException("quiz not found")
}
Enter fullscreen mode Exit fullscreen mode

As you can see, here we use the Elvis operator, as well. Quite handy, isn’t it?

3. Make JUnit test names more readable

Let’s imagine that the application above had this test in its integration test suite:

@Test
public void failsToCreate_whenIdIsProvided() throws Exception {
    // ARRANGE
    final Quiz quiz = new Quiz(
            42,
            "title",
            "description",
            "cta",
            "https://example.org/image.png"
    );

    // ACT
    final ResultActions actions = mockMvc.perform(post("/quizzes")
            .contentType(APPLICATION_JSON_UTF8)
            .content(objectMapper.writeValueAsString(quiz)));

    // ASSERT
    actions.andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.message", equalTo("id should be empty")));
}
Enter fullscreen mode Exit fullscreen mode

Now, after automatic conversion to Kotlin it’ll look something like this:

@Test
@Throws(Exception::class)
fun failsToCreate_whenIdIsProvided() {
    // ARRANGE
    val quiz = Quiz(
            42,
            "title",
            "description",
            "cta",
            "https://example.org/image.png"
    )

    // ACT
    val actions = mockMvc!!.perform(post("/quizzes")
            .contentType(APPLICATION_JSON_UTF8)
            .content(objectMapper!!.writeValueAsString(quiz)))

    // ASSERT
    actions.andExpect(status().isBadRequest)
            .andExpect(jsonPath("$.message", equalTo("id should be empty")))
}
Enter fullscreen mode Exit fullscreen mode

First, we can throw away this @Throws annotation immediately. It’s mostly useless in Kotlin.

And now, we can use actual human-readable sentences in the method names using backticks:

@Test
fun `create quiz - fails to create when id is provided`() {
    // …
}
Enter fullscreen mode Exit fullscreen mode

My general structure is: "{method name or use case name} - {expected outcome} when {condition}."

4. Use lateinit instead of nullable

In cases, when something is being provided later (like dependency injection, or initialized in the set-up section of the test suite), you should use lateinit instead. It’s much cleaner.

Look, this code is a result of automatic conversion:

@SpringBootTest
@RunWith(SpringRunner::class)
class QuizzesIntegrationTest {

    @Autowired
    private val context: WebApplicationContext? = null

    @Autowired
    private val objectMapper: ObjectMapper? = null

    private var mockMvc: MockMvc? = null

    private var quizId: Int = 0

    @Before
    fun setUp() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context!!)
                .build()
    }

    // …

}
Enter fullscreen mode Exit fullscreen mode

All these fields are initialized later, AND before the first usage, so we can tell the compiler about that:

@Autowired
private lateinit var context: WebApplicationContext

@Autowired
private lateinit var objectMapper: ObjectMapper

private lateinit var mockMvc: MockMvc

private var quizId: Int = 0
Enter fullscreen mode Exit fullscreen mode

Now, the problem is that all these nullable values were unsafely unwrapped everywhere in the test suite with !! operator:

a lot of warnings about unnecessary non-null (!!) assertion

Of course, we can fix them all manually, but these are things that tools should fix for us.

And they do, look:

use intent actions menu and select remove unnecessary non-null assertion, it removes !! automatically

It still would be annoying to go through each occurrence though. So we should just apply code cleanups to the whole file:

use find action tool and type code cleanup, the tool window shows up, select a single file only there, and proceed, all unnecessary non-null assertions disappear from the whole file

5. Use named arguments for readability

Now, you see this snippet of code:

// ARRANGE
val quiz = Quiz(
        42,
        "title",
        "description",
        "cta",
        "https://example.org/image.png"
)
Enter fullscreen mode Exit fullscreen mode

We can make this code much more descriptive if we were to use named arguments, like so:

use intent actions menu and choose to add names to call arguments, every argument now is named

Now, you’ll get this cute little readable snippet of code. Also, you can re-order the arguments when you pass them as you wish, and as it makes more sense.

// ARRANGE
val quiz = Quiz(
        id = 42,
        title = "title",
        description = "description",
        ctaText = "cta",
        imageUrl = "https://example.org/image.png"
)
Enter fullscreen mode Exit fullscreen mode

Unfortunately, to make this feature work, the class Quiz can’t be a Java class, it has to be in Kotlin, so we’ll have to convert the entity below to Kotlin:

@Entity(name = "quizzes")
@Data
@RequiredArgsConstructor
@AllArgsConstructor
@JsonIgnoreProperties({"hibernateLazyInitializer"})
public class Quiz {
    @Id
    @GeneratedValue
    public Integer id = null;

    public final String title;
    public final String description;
    public final String ctaText;
    public final String imageUrl;

    Quiz() {
        title = "";
        description = "";
        ctaText = "";
        imageUrl = "";
    }
}
Enter fullscreen mode Exit fullscreen mode

This is a result of full (auto + manual) conversion:

@Entity(name = "quizzes")
@JsonIgnoreProperties("hibernateLazyInitializer")
data class Quiz(
        @Id
        @GeneratedValue
        var id: Int? = null,

        val title: String,
        val description: String,
        val ctaText: String,
        val imageUrl: String
)
Enter fullscreen mode Exit fullscreen mode

As you can see we’re getting rid of all the Lombok stuff and using a data class now because it can do most of the things, you would need from the entity.

6+. There is so much more you can improve!

If you want to learn more about how good Kotlin code will look like, I highly recommend to go through the Kotlin Koans.

You can do that right in your browser, or in your IDE if you wish.

Now, remember, at the beginning of this post, I’ve promised the "Without Risk" part, didn’t I?

Trying out without any risks whatsoever

Now, if I decided not to make the switch to Kotlin yet (because I still need to convince my colleagues, for example), I can use Local History feature of my IDE to go back in time to when I started playing with Kotlin:

local history tool window

To open that tool in IntelliJ, you can use the contextual menu on the whole project in the project structure tool. There is an option Local History > Show History there:

right click on the project name in the project structure tool, choose Local History - Show History

When you’ve found the place where you started playing with Kotlin, you can revert to the first Kotlin-related change, for example:

choosing revert option on a specific change in the past

Now, there is not a single Kotlin file, and all the tests are passing. This is a handy feature in general if you screwed something up, and haven’t made a commit in Git in a while.

Did that spike your curiosity about Kotlin?

I have written a 4-part (350-pages total) “Ultimate Tutorial: Getting Started With Kotlin” (+ more to come), and you can get it as a free bonus by becoming a member of my monthly newsletter.

On top of just Kotlin, it is full of goodies like TDD, Clean Code, Software Architecture, Business Impacts, 5 WHYs, Acceptance Criteria, Personas, and more.

—Sign up here and start learning how to build full-fledged Kotlin applications!

Thank you and ask for you!

Thank you so much for reading this article! I hope you enjoyed it. Please, tell me what you think about this in the comments!

Also, it would make me so much happier share this post with your friends and colleagues who you think might benefit from it. Or you could share it on your favorite social media!

You are welcome to read my blog about Kotlin, and my blog about TDD and best software engineering practices.

And let’s connect on LinkedIn: I post short weekly updates about software developer’s productivity and happiness, teamwork, mental health, and a bit about Kotlin.

If you want to hear more of my opinions, follow me on Twitter.


Originally published on iwillteachyoukotlin blog.

Top comments (1)

Collapse
 
zbgreen profile image
Zachary Green

I've been looking to get more into Kotlin, especially due to being used in Android. I think the technical and readability improvements speak for themselves, but what would you say to skeptical or unsure leadership about switching to it? Like you said, I think the interoperability helps a lot and may ease some migration concerns. There's just a lot of fear of change and they may not see the use case for using a "like java" language.