Consul and the Micronaut Framework - Microservices Service Discovery

Use Consul service discovery to expose your Micronaut applications.

Authors: Sergio del Amo

Micronaut Version: 4.6.3

1. Getting Started

In this guide, we will create three microservices and register them with Consul Service discovery.

Consul is a distributed service mesh to connect, secure, and configure services across any runtime platform and public or private cloud

You will discover how the Micronaut framework eases Consul integration.

2. What you will need

To complete this guide, you will need the following:

3. 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.

4. Writing the App

Let’s describe the microservices you will build through the guide.

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

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

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

Initially we will hard-code the addresses where the different services are 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.

About registration patterns

We will use a self‑registration pattern. Thus, each service instance is responsible for registering and deregistering itself with the service registry. Also, if required, a service instance sends heartbeat requests to prevent its registration from expiring.

Services register when they start up:

discovery service registration

We will use client‑side service discovery, clients query the service registry, select an available instance, and make a request.

discovery service flow

4.1. Catalogue Microservice

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

mn create-app --features=discovery-consul,management,graalvm example.micronaut.bookcatalogue --build=maven --lang=kotlin
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-consul, management, and graalvm features.

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

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/src/main/kotlin/example/micronaut/BooksController.kt
package example.micronaut

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get

@Controller("/books") (1)
class BooksController {

    @Get (2)
    fun index(): List<Book> = listOf(
            Book("1491950358", "Building Microservices"),
            Book("1680502395", "Release It!"),
            Book("0321601912", "Continuous Delivery:"))
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /.
2 The @Get annotation maps the index method to an HTTP GET request on /books.

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

bookcatalogue/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/src/test/kotlin/example/micronaut/BooksControllerTest.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.Assertions.assertTrue
import org.junit.jupiter.api.Test

@MicronautTest (1)
class BooksControllerTest(@Client("/") val client: HttpClient) { (2)

    @Test
    fun testRetrieveBooks() {
        val request: HttpRequest<*> = HttpRequest.GET<Any>("/books") (3)
        val books = client.toBlocking().retrieve(request, Argument.listOf(Book::class.java)) (4)

        assertEquals(3, books.size)
        assertTrue(books.contains(Book("1491950358", "Building Microservices")))
        assertTrue(books.contains(Book("1680502395", "Release It!")))
    }
}
1 Annotate the class with @MicronautTest so the Micronaut framework will initialize the application context and the embedded server. More info.
2 Inject the HttpClient bean and point it to the embedded server.
3 Creating HTTP Requests is easy thanks to the Micronaut framework fluid API.
4 Parse easily JSON into Java objects.

Edit application.yml

bookcatalogue/src/main/resources/application.yml
micronaut:
  application:
    name: bookcatalogue (1)
1 Configure the application name. The application name will be used by the discovery service.

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/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.yml. The Micronaut framework applies this configuration file only for the dev environment.

bookcatalogue/src/main/resources/application-dev.yml
micronaut:
  server:
    port: 8081 (1)
1 Configure the application to listen on port 8081

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

bookcatalogue/src/test/resources/application-test.yml
consul:
  client:
    registration:
      enabled: false

Run the unit test:

bookcatalogue
./mvnw test

4.2. Inventory Microservice

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

mn create-app --features=discovery-consul,management,graalvm example.micronaut.bookinventory --build=maven --lang=kotlin
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-consul, management, and graalvm features.

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

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/src/main/kotlin/example/micronaut/BooksController.kt
package example.micronaut

import io.micronaut.http.MediaType.TEXT_PLAIN
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import java.util.Optional
import jakarta.validation.constraints.NotBlank

@Controller("/books") (1)
open class BooksController {

    @Produces(TEXT_PLAIN) (2)
    @Get("/stock/{isbn}") (3)
    open fun stock(@NotBlank isbn: String): Boolean? =
        bookInventoryByIsbn(isbn).map { (_, stock) -> stock > 0 }.orElse(null)

    private fun bookInventoryByIsbn(isbn: String): Optional<BookInventory> {
        if (isbn == "1491950358") {
            return Optional.of(BookInventory(isbn, 4))
        }
        if (isbn == "1680502395") {
            return Optional.of(BookInventory(isbn, 0))
        }
        return Optional.empty()
    }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books.
2 By default, Content-Type of a controller response is application/json : override this with text/plain since we are returning a String, not a JSON object.
3 The @Get annotation maps the index method to an HTTP GET request on /books/stock/{isbn}.

Create the POJO used by the controller:

bookinventory/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/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())
    }

    @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.yml

bookinventory/src/main/resources/application.yml
micronaut:
  application:
    name: bookinventory (1)
1 Configure the application name. The name will be used later in the guide.

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/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.yml. The Micronaut framework applies this configuration file only for the dev environment.

bookinventory/src/main/resources/application-dev.yml
micronaut:
  server:
    port: 8082 (1)
1 Configure the application to listen on port 8082

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

bookinventory/src/test/resources/application-test.yml
consul:
  client:
    registration:
      enabled: false

Run the unit test:

bookinventory
./mvnw test

4.3. Recommendation Microservice

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

mn create-app --features=discovery-consul,management,reactor,graalvm example.micronaut.bookrecommendation --build=maven --lang=kotlin
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-consul, management, reactor, and graalvm features.

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

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/src/main/kotlin/example/micronaut/BookCatalogueOperations.kt
package example.micronaut

import org.reactivestreams.Publisher

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


import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.micronaut.retry.annotation.Recoverable
import org.reactivestreams.Publisher

@Client("http://localhost:8081") (1)
@Recoverable(api = BookCatalogueOperations::class)

interface BookCatalogueClient : BookCatalogueOperations {

    @Get("/books")
    override fun findAll(): Publisher<Book>
}
1 Use @Client to use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use the id member to provide a service identifier or specify the URL directly as the annotation’s value.

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

bookrecommendation/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/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/src/main/kotlin/example/micronaut/BookInventoryClient.kt
package example.micronaut


import io.micronaut.http.MediaType.TEXT_PLAIN
import io.micronaut.http.annotation.Consumes
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.micronaut.retry.annotation.Recoverable
import reactor.core.publisher.Mono
import jakarta.validation.constraints.NotBlank

@Client("http://localhost:8082") (1)
@Recoverable(api = BookInventoryOperations::class)

interface BookInventoryClient : BookInventoryOperations {

    @Consumes(TEXT_PLAIN)
    @Get("/books/stock/{isbn}")
    override fun stock(@NotBlank isbn: String): Mono<Boolean>
}
1 Use @Client to use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use the id member to provide a service identifier or specify the URL directly as the annotation’s value.

Create a Controller which injects both clients.

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

import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux

@Controller("/books") (1)
class BookController(
        private val bookCatalogueOperations: BookCatalogueOperations,
        private val bookInventoryOperations: BookInventoryOperations) { (2)

    @Get (3)
    fun index(): Publisher<BookRecommendation> =

        Flux.from(bookCatalogueOperations.findAll())
                .flatMap { b ->
                    Flux.from(bookInventoryOperations.stock(b.isbn))
                            .filter { hasStock -> hasStock }
                            .map { b }
                }.map { (_, name) -> BookRecommendation(name) }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books.
2 Clients are injected via constructor injection
3 The @Get annotation maps the index method to an HTTP GET request on /books.

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

bookrecommendation/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/src/test/kotlin/example/micronaut/BookInventoryClientStub.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 reactor.core.publisher.Mono
import jakarta.validation.constraints.NotBlank

@Requires(env = [TEST]) (1)
@Fallback
@Singleton
open class BookInventoryClientStub : BookInventoryOperations {

    override fun stock(@NotBlank isbn: String): Mono<Boolean> {
        if (isbn == "1491950358") {
            return Mono.just(true) (2)
        }
        if (isbn == "1680502395") {
            return Mono.just(false) (3)
        }
        return Mono.empty() (4)
    }
}
1 Make this fallback class to be effective only when the Micronaut environment TEST is active
2 Here we arbitrarily decided that if everything else fails, that book’s stock would be true
3 Similarly, we decided that other book’s stock method would be false
4 Finally, any other book will have their stock method return an empty value
bookrecommendation/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/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.yml

bookrecommendation/src/main/resources/application.yml
micronaut:
  application:
    name: bookrecommendation (1)
1 Configure the application name. The name will be used later in the guide.

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/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.yml. The Micronaut framework applies this configuration file only for the dev environment.

bookrecommendation/src/main/resources/application-dev.yml
micronaut:
  server:
    port: 8080 (1)
1 Configure the application to listen on port 8080

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

bookrecommendation/src/test/resources/application-test.yml
consul:
  client:
    registration:
      enabled: false

Run the unit test:

bookrecommendation
./mvnw test

4.4. Running the application

Run bookcatalogue microservice:

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

Run bookinventory microservice:

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

Run bookrecommendation microservice:

bookrecommendation
./mvnw mn: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"}]

5. Consul and the Micronaut framework

5.1. Install Consul via Docker

The quickest way to start using Consul is via Docker:

docker run -p 8500:8500 consul

The following screenshots show how to install/run Consul via Kitematic, a UI for Docker.

kitematic consul 1

Configure ports:

kitematic consul 2

5.2. Book Catalogue

Append to bookcatalogue service application.yml the following snippet:

bookcatalogue/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

This configuration registers a Micronaut application with Consul with minimal configuration. Discover a more complete list of configuration options at ConsulConfiguration.

5.3. Book Inventory

Modify the application.yml of the bookinventory application with the following snippet:

bookinventory/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

5.4. Book Recommendation

Append to bookrecommendation.application.yml the following snippet:

bookrecommendation/src/main/resources/application.yml
consul:
  client:
    registration:
      enabled: true
    defaultZone: "${CONSUL_HOST:localhost}:${CONSUL_PORT:8500}"

Modify BookInventoryClient and BookCatalogueClient to use the service id instead of a hardcoded URL.

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


import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.micronaut.retry.annotation.Recoverable
import org.reactivestreams.Publisher

@Client(id = "bookcatalogue") (1)
@Recoverable(api = BookCatalogueOperations::class)

interface BookCatalogueClient : BookCatalogueOperations {

    @Get("/books")
    override fun findAll(): Publisher<Book>
}
1 Use the configuration value micronaut.application.name used in bookcatalogue as service id.
bookrecommendation/src/main/kotlin/example/micronaut/BookInventoryClient.kt
package example.micronaut


import io.micronaut.http.MediaType.TEXT_PLAIN
import io.micronaut.http.annotation.Consumes
import io.micronaut.http.annotation.Get
import io.micronaut.http.client.annotation.Client
import io.micronaut.retry.annotation.Recoverable
import reactor.core.publisher.Mono
import jakarta.validation.constraints.NotBlank

@Client(id = "bookinventory") (1)
@Recoverable(api = BookInventoryOperations::class)

interface BookInventoryClient : BookInventoryOperations {

    @Consumes(TEXT_PLAIN)
    @Get("/books/stock/{isbn}")
    override fun stock(@NotBlank isbn: String): Mono<Boolean>
}
1 Use the configuration value micronaut.application.name used in bookinventory as service id.

5.5. Running the App

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
14:28:34.084 [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [bookcatalogue] with Consul

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
14:31:13.154 [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [bookinventory] with Consul

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
14:31:57.439 [nioEventLoopGroup-1-3] INFO  i.m.d.registration.AutoRegistration - Registered service [bookrecommendation] with Consul

Consul comes with a HTML UI. Open http://localhost:8500/ui in your browser.

You will see the services registered in Consul:

consului

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

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

6. Generate a Micronaut Application Native Executable with GraalVM

We will use GraalVM, the polyglot embeddable virtual machine, to generate a native executable of our Micronaut application.

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

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

6.1. GraalVM installation

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

Java 21
sdk install java 21.0.5-graal

For installation on Windows, or for 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 21
sdk install java 21.0.2-graalce

6.2. Native executable generation

To generate a native executable using Maven, run:

./mvnw package -Dpackaging=native-image

The native executable is created in the target directory and can be run with target/micronautguide.

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

7. Next steps

Read more about Consul support in the Micronaut framework.

8. Help with the Micronaut Framework

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

9. License

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