Kotlin / Gradle

Eureka and the Micronaut Framework - Microservices Service Discovery

Use Netflix Eureka service discovery to expose your Micronaut applications.

Sergio del Amo
On this guide
In this section

Getting started

In this guide, we will create three microservices and register them with Netflix Eureka service discovery. You will discover how the Micronaut framework eases Eureka integration.

What you will need

To complete this guide, you will need the following:

Solution

We recommend that you follow the instructions in the next sections and create the application step by step. However, you can go right to the completed example.

Writing the Application

Let’s describe the microservices:

  • bookcatalogue - It returns a list of books. It uses a domain consisting of a book name and an ISBN.

  • bookinventory - It exposes an endpoint to check whether a book has sufficient stock to fulfill an order. It uses a domain consisting of a stock level and an ISBN.

  • bookrecommendation - It consumes previous services and exposes an endpoint that recommends book names that are in stock.

Initially, we will hard-code the service addresses in the bookcatalogue service.

hardcoded

As shown in the previous image, the bookcatalogue hardcodes references to its collaborators.

In the second part of this guide, we will use a discovery service.

The services register when they start up:

discovery service registration

When a service wants to make a request to another service, it uses the discovery service to retrieve the address.

discovery service flow

Catalogue Microservice

Create the bookcatalogue microservice using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app --features=discovery-eureka,graalvm example.micronaut.bookcatalogue --build=gradle --lang=kotlin
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-eureka and graalvm features.

The previous command creates a directory named bookcatalogue and a Micronaut application inside it with default package example.micronaut.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

Create a BooksController class to handle incoming HTTP requests into the bookcatalogue microservice:

bookcatalogue/kotlin/src/main/kotlin/example/micronaut/BooksController.kt

The previous controller responds with a List<Book>. Create the Book POJO:

bookcatalogue/kotlin/src/main/kotlin/example/micronaut/Book.kt
package example.micronaut

import io.micronaut.serde.annotation.Serdeable
import jakarta.validation.constraints.NotBlank

@Serdeable
data class Book(@NotBlank val isbn: String,
                @NotBlank val name: String)

Write a test:

bookcatalogue/kotlin/src/test/kotlin/example/micronaut/BooksControllerTest.kt

Edit application.properties

bookcatalogue/src/main/resources/application.properties

Modify the Application class to use dev as a default environment:

The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.

bookcatalogue/kotlin/src/main/kotlin/example/micronaut/Application.kt
package example.micronaut

import io.micronaut.context.env.Environment.DEVELOPMENT
import io.micronaut.runtime.Micronaut.build

fun main(args: Array<String>) {
    build()
        .args(*args)
        .packages("example.micronaut")
        .defaultEnvironments(DEVELOPMENT)
        .start()
}

Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.

bookcatalogue/src/main/resources/application-dev.properties

Create a file named application-test.properties which is used in the test environment:

bookcatalogue/src/test/resources/application-test.properties
eureka.client.registration.enabled=false

Run the unit test:

bookcatalogue
./gradlew test

Inventory Microservice

Create the bookinventory microservice using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app --features=discovery-eureka,graalvm example.micronaut.bookinventory --build=gradle --lang=kotlin
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-eureka and graalvm features.

The previous command creates a directory named bookinventory and a Micronaut application inside it with default package example.micronaut.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

Create a Controller:

bookinventory/kotlin/src/main/kotlin/example/micronaut/BooksController.kt

The previous controller uses a POJO:

bookinventory/kotlin/src/main/kotlin/example/micronaut/BookInventory.kt
package example.micronaut

import io.micronaut.serde.annotation.Serdeable
import jakarta.validation.constraints.NotBlank

@Serdeable
data class BookInventory(@NotBlank val isbn: String,
                         val stock: Int)

Write a test:

bookinventory/kotlin/src/test/kotlin/example/micronaut/BooksControllerTest.kt
package example.micronaut

import io.micronaut.http.HttpRequest
import io.micronaut.http.HttpStatus.NOT_FOUND
import io.micronaut.http.HttpStatus.OK
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.http.client.exceptions.HttpClientResponseException
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import jakarta.inject.Inject
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertThrows
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test

@MicronautTest
class BooksControllerTest(@Client("/") val httpClient: HttpClient) {

    @Test
    fun testBooksController() {
        val rsp = httpClient.toBlocking().exchange(
                HttpRequest.GET<Any>("/books/stock/1491950358"), Boolean::class.java)
        assertEquals(OK, rsp.status())
        assertTrue(rsp.body() == true)
    }

    @Test
    fun testBooksControllerWithNonExistingIsbn() {
        val thrown = assertThrows(HttpClientResponseException::class.java) {
            httpClient.toBlocking().exchange(HttpRequest.GET<Any>("/books/stock/XXXXX"), Boolean::class.java)
        }
        assertEquals(NOT_FOUND, thrown.response.status)
    }
}

Edit application.properties

bookinventory/src/main/resources/application.properties

Modify the Application class to use dev as a default environment:

The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.

bookinventory/kotlin/src/main/kotlin/example/micronaut/Application.kt
package example.micronaut

import io.micronaut.context.env.Environment.DEVELOPMENT
import io.micronaut.runtime.Micronaut.build

fun main(args: Array<String>) {
    build()
        .args(*args)
        .packages("example.micronaut")
        .defaultEnvironments(DEVELOPMENT)
        .start()
}

Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.

bookinventory/src/main/resources/application-dev.properties

Create a file named application-test.properties which is used in the test environment:

bookinventory/src/test/resources/application-test.properties
eureka.client.registration.enabled=false

Run the unit test:

bookinventory
./gradlew test

Recommendation Microservice

Create the bookrecommendation microservice using the Micronaut Command Line Interface or with Micronaut Launch.

mn create-app --features=discovery-eureka,reactor,graalvm example.micronaut.bookrecommendation --build=gradle --lang=kotlin
Note
If you don’t specify the --build argument, Gradle with the Kotlin DSL is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.
If you don’t specify the --test argument, JUnit is used for Java and Kotlin, and Spock is used for Groovy.

If you use Micronaut Launch, select Micronaut Application as application type and add the discovery-eureka, reactor, and graalvm features.

The previous command creates a directory named bookrecommendation and a Micronaut application inside it with default package example.micronaut.

Note
If you have an existing Micronaut application and want to add the functionality described here, you can view the dependency and configuration changes from the specified features, and apply those changes to your application.

Create an interface to map operations with bookcatalogue, and a Micronaut Declarative HTTP Client to consume it.

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookCatalogueOperations.kt
package example.micronaut

import org.reactivestreams.Publisher

interface BookCatalogueOperations {
    fun findAll(): Publisher<Book>
}
bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookCatalogueClient.kt

The client returns a POJO. Create it in the bookrecommendation:

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/Book.kt
package example.micronaut

import io.micronaut.serde.annotation.Serdeable
import jakarta.validation.constraints.NotBlank

@Serdeable
data class Book(@NotBlank val isbn: String,
                @NotBlank val name: String)

Create an interface to map operations with bookinventory, and a Micronaut Declarative HTTP Client to consume it.

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookInventoryOperations.kt
package example.micronaut

import reactor.core.publisher.Mono
import jakarta.validation.constraints.NotBlank

interface BookInventoryOperations {
    fun stock(@NotBlank isbn: String): Mono<Boolean>
}
bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookInventoryClient.kt

Create a Controller which injects both clients.

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookController.kt

The previous controller returns a Publisher<BookRecommendation>. Create the BookRecommendation POJO:

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookRecommendation.kt
package example.micronaut

import io.micronaut.serde.annotation.Serdeable
import jakarta.validation.constraints.NotBlank

@Serdeable
data class BookRecommendation(@NotBlank val name: String)

BookCatalogueClient and BookInventoryClient will fail to consume the bookcatalogue and bookinventory during the tests phase.

Using the @Fallback annotation, you can declare a fallback implementation of a client that will be picked up and used once all possible retries have been exhausted.

Create @Fallback alternatives in the test classpath.

bookrecommendation/kotlin/src/test/kotlin/example/micronaut/BookInventoryClientStub.kt
bookrecommendation/kotlin/src/test/kotlin/example/micronaut/BookCatalogueClientStub.kt
package example.micronaut

import io.micronaut.context.annotation.Requires
import io.micronaut.context.env.Environment.TEST
import io.micronaut.retry.annotation.Fallback
import jakarta.inject.Singleton
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux

@Requires(env = [TEST])
@Fallback
@Singleton
class BookCatalogueClientStub : BookCatalogueOperations {

    override fun findAll(): Publisher<Book> {
        val buildingMicroservices = Book("1491950358", "Building Microservices")
        val releaseIt = Book("1680502395", "Release It!")
        return Flux.just(buildingMicroservices, releaseIt)
    }
}

Write a test:

bookrecommendation/kotlin/src/test/kotlin/example/micronaut/BookControllerTest.kt
package example.micronaut

import io.micronaut.core.type.Argument
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.test.extensions.junit5.annotation.MicronautTest
import jakarta.inject.Inject
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable

@MicronautTest
class BookControllerTest(@Client("/") val client: HttpClient) {

    @DisabledIfEnvironmentVariable(named = "CI", matches = "true")
    @Test
    fun testRetrieveBooks() {
        val books = client.toBlocking().retrieve(HttpRequest.GET<Any>("/books"),
            Argument.listOf(BookRecommendation::class.java))
        assertEquals(1, books.size)
        assertEquals("Building Microservices", books[0].name)
    }
}

Edit application.properties

bookrecommendation/src/main/resources/application.properties

Modify the Application class to use dev as a default environment:

The Micronaut framework supports the concept of one or many default environments. A default environment is one that is only applied if no other environments are explicitly specified or deduced.

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/Application.kt
package example.micronaut

import io.micronaut.context.env.Environment.DEVELOPMENT
import io.micronaut.runtime.Micronaut.build

fun main(args: Array<String>) {
    build()
        .args(*args)
        .packages("example.micronaut")
        .defaultEnvironments(DEVELOPMENT)
        .start()
}

Create src/main/resources/application-dev.properties. The Micronaut framework applies this configuration file only for the dev environment.

bookrecommendation/src/main/resources/application-dev.properties

Create a file named application-test.properties which is used in the test environment:

bookrecommendation/src/test/resources/application-test.properties
eureka.client.registration.enabled=false

Run the unit test:

bookrecommendation
./gradlew test

Running the application

Run bookcatalogue microservice:

bookcatalogue
./gradlew run
14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081

Run bookinventory microservice:

bookinventory
./gradlew run
14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082

Run bookrecommendation microservice:

bookrecommendation
./gradlew run
14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080

You can run a cURL command to test the whole application:

curl http://localhost:8080/books
[{"name":"Building Microservices"}]

Eureka and the Micronaut framework

Eureka is a REST (Representational State Transfer) based service that is primarily used in the AWS cloud for locating services for the purpose of load balancing and failover of middle-tier servers.

Eureka Server

Spring-Cloud-Netflix provides a very neat way to bootstrap Eureka. To bring up Eureka server using Spring-Cloud-Netflix:

  • Clone the sample Eureka server application.

  • Run this project as a Spring Boot application (e.g. import into IDE and run main method, or use mvn spring-boot:run or ./gradlew bootRun). It will start up on port 8761 and serve the Eureka API from /eureka.

Book Catalogue

Append to bookcatalogue service application.properties the following snippet:

bookcatalogue/src/main/resources/application.properties
eureka.client.registration.enabled=true
eureka.client.defaultZone=${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}

The previous configuration registers a Micronaut application with Eureka with minimal configuration. Discover a more complete list of configuration options at EurekaConfiguration.

Book Inventory

Append the following snippet to the bookinventory service application.properties:

bookinventory/src/main/resources/application.properties
eureka.client.registration.enabled=true
eureka.client.defaultZone=${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}

Book Recommendation

Append the following snippet to the bookrecommendation service application.properties:

bookrecommendation/src/main/resources/application.properties
eureka.client.registration.enabled=true
eureka.client.defaultZone=${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}

Modify BookInventoryClient and BookCatalogueClient to use the service id instead of a hard-coded URL.

bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookCatalogueClient.kt
bookrecommendation/kotlin/src/main/kotlin/example/micronaut/BookInventoryClient.kt

Running the Application

Run bookcatalogue microservice:

bookcatalogue
./gradlew run
14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081

Run bookinventory microservice:

bookinventory
./gradlew run
14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082

Run bookrecommendation microservice:

bookrecommendation
./gradlew run
14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080

You can run a cURL command to test the whole application:

curl http://localhost:8080/books
[{"name":"Building Microservices"}]

Open http://localhost:8761 in your browser.

You will see the services registered in Eureka:

eurekaui

You can run a cURL command to test the whole application:

curl http://localhost:8080/books
[{"name":"Building Microservices"}]

Generate a Micronaut Application Native Executable with GraalVM

We will use GraalVM, an advanced JDK with ahead-of-time Native Image compilation, to generate a native executable of this Micronaut application.

Compiling Micronaut applications ahead of time with GraalVM significantly improves startup time and reduces the memory footprint of JVM-based applications.

Note
Only Java and Kotlin projects support using GraalVM’s native-image tool. Groovy relies heavily on reflection, which is only partially supported by GraalVM.

GraalVM Installation

The easiest way to install GraalVM on Linux or Mac is to use SDKMan.io.

Java 25
sdk install java 25.0.2-graal

For installation on Windows, or for a manual installation on Linux or Mac, see the GraalVM Getting Started documentation.

The previous command installs Oracle GraalVM, which is free to use in production and free to redistribute, at no cost, under the GraalVM Free Terms and Conditions.

Alternatively, you can use the GraalVM Community Edition:

Java 25
sdk install java 25.0.2-graalce

Native Executable Generation

To generate a native executable using Gradle, run:

./gradlew nativeCompile

The native executable is created in build/native/nativeCompile directory and can be run with build/native/nativeCompile/micronautguide.

It is possible to customize the name of the native executable or pass additional parameters to GraalVM:

build.gradle

Start the native executables for the three microservices and run the same curl request as before to check that everything works with GraalVM.

Next Steps

Read more about Eureka Support in the Micronaut framework.

Help with the Micronaut Framework

The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.

License

Note
All guides are released with an Apache License 2.0 for the code and a Creative Commons Attribution 4.0 license for the writing and media (images).