Boost Your Productivity: Enabling Hot Reload for Your Dockerized Spring Boot Kotlin Project

kotlin
spring boot
docker
programming
Yiotis Kaltsikis picture
Yiotis Kaltsikis4 min read

Share this post:


Recently, I’ve delved into the world of Kotlin and Spring Boot for a new side project. As someone coming from the TypeScript and NodeJS realm, I was determined to replicate the seamless hot reload experience I enjoyed with nodemon.

After hours of frustrated Googling and tinkering, I finally cracked the code. Surprisingly, there was a lack of readily available resources on achieving hot reload in a Dockerized Spring Boot project with Kotlin. So, I’ve decided to pen this post to lend a helping hand to future developers seeking a similar setup. Let’s dive right in!

1. [Optional] Bootstrap a Fresh Spring Boot Kotlin

If you already have an existing project, feel free to skip this section.

First things first, if you don’t have a Spring Boot project with Kotlin yet, let’s bootstrap a new one. We can effortlessly accomplish this using Spring Initializr.

Here’s the setup I used: Spring Initializr settings

Pay attention to Spring Boot DevTools, as we’ll need it to enable hot reload.

Once you’ve downloaded and unzipped the project, fire up IntelliJ and open it.

To test hot reload, let’s set up an example controller:

Create a Controllers package alongside the HotReloadExampleApplication.kt file in the src directory.

Inside that package, add a Kotlin class named GreetingsController that we we’ll use for our example.

Here’s how your file structure should look now: Project folder structure after adding GreetingsController

Copy the following code into the GreetingsController:

1package com.yiotiskl.hotreloadexample.controllers
2
3import org.springframework.web.bind.annotation.GetMapping
4import org.springframework.web.bind.annotation.PathVariable
5import org.springframework.web.bind.annotation.RestController
6
7@RestController
8class GreetingsController {
9@GetMapping("/greetings/{greeting}")
10    fun findOne(@PathVariable greeting: String): String {
11        return "$greeting, hot reload is disabled."
12    }
13}

Run the project in IntelliJ and use cURL to test our endpoint:

1curl --request GET --url http://localhost:8080/greetings/hello

You should see the following output in your terminal: hello, hot reload is disabled.

2. Dockerize the project and enable hot reload

Now, let’s move on to the exciting part. Currently, our project runs smoothly locally, but any changes made won’t take effect until we stop and restart the server in IntelliJ.

To enable hot reload and Dockerize the project, follow these steps:

First, open build.gradle.kts and add the following code at the end of the file:

1tasks.register<Copy>("getDependencies") {
2    from(sourceSets.main.get().runtimeClasspath)
3    into("runtime/")
4
5    doFirst {
6        val runtimeDir = File("runtime")
7        runtimeDir.deleteRecursively()
8        runtimeDir.mkdir()
9    }
10
11    doLast {
12        File("runtime").deleteRecursively()
13    }
14}

This creates a new Kotlin task that downloads all project dependencies, which we’ll use later in our Dockerfile.

Next, in the src/main/resource directory, create a new file called application.yml and paste the following configuration:

1spring:
2    devtools:
3        livereload:
4            enabled: true

This configuration enables hot reload in our application.

At this point, we are ready to Dockerize our project. In the root directory of the project, create a Dockerfile and paste the following contents into it:

1FROM eclipse-temurin:17
2
3WORKDIR /app
4
5COPY . /app
6
7EXPOSE 8080
8
9# Fetch project dependencies
10RUN chmod +x start.sh && ./gradlew getDependencies
11
12# script which watches source file changes in background and executes bootRun
13CMD ["sh", "start.sh"]

To build the image and start the container, we now need to implement start.sh. Create the file and paste the following contents:

1# Start spring boot devtools process in the background.
2# This is necessary for hot reload.
3(./gradlew -t :bootJar) &
4
5# Next, start the app.
6# The "PskipDownload" option ensures dependencies are not downloaded again.
7./gradlew bootRun -PskipDownload=true

Finally, let’s set up a docker-compose.yml for our project, enabling us to integrate a database or other services later if desired:

1version: '3.4'
2
3services:
4app:
5build:
6context: .
7dockerfile: Dockerfile
8ports: - 8080:8080
9volumes: - ./src/main:/app/src/main
10restart: always

We set up a volume that points to our app’s source code, allowing Spring DevTools to monitor these files and perform a hot reload when needed.

3. Run the App

With our setup in place, it’s time to fire up the project and test if everything works together:

Run docker-compose up. Once the server is up and running, try using cURL again to test the endpoint and confirm that you still see the same output we observed earlier.

Now, go back to our GreetingsController in IntelliJ and change the endpoint code to this:

1@GetMapping("/greetings/{greeting}")
2fun findOne(@PathVariable greeting: String): String {
3    return "$greeting, hot reload is enabled!!!"
4}

When you save the file, you should see the following output in the terminal: Terminal output with hot reload enabled

Try cURLing the endpoint again:

1curl --request GET --url http://localhost:8080/greetings/hello

You should now receive the updated value: hello, hot reload is enabled!!!.

And there you have it!

I hope this article helps enhance your developer experience when working on your Spring Boot projects.


Share this post: