Eureka and Micronaut - Microservices service discovery

Use Netflix Eureka service discovery to expose your Micronaut apps.

Authors: Sergio del Amo

Micronaut Version: 2.5.0

1. Getting started

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

2. What you will need

To complete this guide, you will need the following:

  • Some time on your hands

  • A decent text editor or IDE

  • JDK 1.8 or greater installed with JAVA_HOME configured appropriately

3. Solution

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

4. Writing the App

Let’s describe the microservices:

  • 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 and endpoint which recommends book names which are in stock.

Initially we are going to hardcode 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 tutorial we are going to use a discovery service.

The services register when they start up:

discovery service registration

When a service wants to do a request to other service, it uses the discovery service to retrieve the address.

discovery service flow

If you are using Java or Kotlin and IntelliJ IDEA, make sure you have enabled annotation processing.

annotationprocessorsintellij

4.1. Catalogue Microservice

Create the bookcatalogue microservice:

mn create-app example.micronaut.bookcatalogue --build=gradle --lang=java

The previous command creates a folder named bookcatalogue and a Micronaut app inside it.

bookcatalogue/src/main/java/example/micronaut/BooksController.java
package example.micronaut;

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

import java.util.Arrays;
import java.util.List;

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

    @Get (2)
    public List<Book> index() {
        Book buildingMicroservices = new Book("1491950358", "Building Microservices");
        Book releaseIt = new Book("1680502395", "Release It!");
        Book cidelivery = new Book("0321601912", "Continuous Delivery:");
        return Arrays.asList(buildingMicroservices, releaseIt, cidelivery);
    }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books
2 The @Get annotation is used to map the index method to /books requests that use an HTTP GET.

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

bookcatalogue/src/main/java/example/micronaut/Book.java
package example.micronaut;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;

import javax.validation.constraints.NotBlank;

@Introspected
public class Book {

    @NonNull
    @NotBlank
    private String isbn;
    @NonNull
    @NotBlank

    private String name;

    public Book() {}

    public Book(@NonNull @NotBlank String isbn, @NonNull @NotBlank String name) {
        this.isbn = isbn;
        this.name = name;
    }

    @NonNull
    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(@NonNull String isbn) {
        this.isbn = isbn;
    }

    @NonNull
    public String getName() {
        return name;
    }

    public void setName(@NonNull String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Book book = (Book) o;

        if (!isbn.equals(book.isbn)) return false;
        return name.equals(book.name);
    }

    @Override
    public int hashCode() {
        int result = isbn.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}

Write a test:

bookcatalogue/src/test/java/example/micronaut/BooksControllerTest.java
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 org.junit.jupiter.api.Test;

import javax.inject.Inject;
import java.util.List;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

@MicronautTest (1)
public class BooksControllerTest {

    @Inject
    @Client("/")
    HttpClient client; (2)

    @Test
    public void testRetrieveBooks() {
        HttpRequest request = HttpRequest.GET("/books"); (3)
        List books = client.toBlocking().retrieve(request, Argument.listOf(Book.class)); (4)
        assertEquals(3, books.size());
        assertTrue(books.contains(new Book("1491950358", "Building Microservices")));
        assertTrue(books.contains(new Book("1680502395", "Release It!")));
    }
}
1 Annotate the class with @MicronautTest to let Micronaut starts the embedded server and inject the beans. More info: https://micronaut-projects.github.io/micronaut-test/latest/guide/index.html.
2 Inject the HttpClient bean in the application context.
3 It is easy to create HTTP requests with a fluid API.
4 Parse easily JSON into Java objects.

Edit application.yml

bookcatalogue/src/main/resources/application.yml
micronaut:
  application:
    name: bookcatalogue (1)
  server:
    port: 8081 (2)
1 Configure the application name. The app name will be use by the discovery service.
2 Configure the app to listen at port 8081

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

bookcatalogue/src/test/resources/application-test.yml
micronaut:
  server:
    port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

Run the unit test:

bookcatalogue $ ./gradlew test

4.2. Inventory Microservice

Create the bookinventory microservice:

mn create-app example.micronaut.bookinventory --build=gradle --lang=java

The previous command creates a folder named bookinventory and a Micronaut app inside it.

Create a Controller:

bookinventory/src/main/java/example/micronaut/BooksController.java
package example.micronaut;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;

import javax.validation.constraints.NotBlank;
import java.util.Optional;

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

    @Produces(MediaType.TEXT_PLAIN) (2)
    @Get("/stock/{isbn}") (3)
    public Boolean stock(@NotBlank String isbn) { (1)
        return bookInventoryByIsbn(isbn).map(bi -> bi.getStock() > 0).orElse(null);
    }

    private Optional<BookInventory> bookInventoryByIsbn(String isbn) {
        if (isbn.equals("1491950358")) {
            return Optional.of(new BookInventory(isbn, 4));

        } else if (isbn.equals("1680502395")) {
            return Optional.of(new 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 a Micronaut’s response uses application/json as Content-Type. We are returning a String not a JSON object. Because of that, we set it to text/plain.
3 The @Get annotation is used to map the index method to /books/stock/{isbn} requests that use an HTTP GET.

The previous controller uses a POJO:

bookinventory/src/main/java/example/micronaut/BookInventory.java
package example.micronaut;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;

import javax.validation.constraints.NotBlank;
import javax.validation.constraints.NotNull;
import java.util.Objects;

@Introspected
public class BookInventory {

    @NonNull
    @NotBlank
    private String isbn;

    @NonNull
    @NotNull
    private Integer stock;

    public BookInventory() {}

    public BookInventory(@NonNull @NotBlank String isbn, @NonNull @NotNull Integer stock) {
        this.isbn = isbn;
        this.stock = stock;
    }

    @NonNull
    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(@NonNull String isbn) {
        this.isbn = isbn;
    }

    @NonNull
    public Integer getStock() {
        return stock;
    }

    public void setStock(@NonNull Integer stock) {
        this.stock = stock;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BookInventory that = (BookInventory) o;

        if (!isbn.equals(that.isbn)) return false;
        return stock.equals(that.stock);
    }

    @Override
    public int hashCode() {
        int result = isbn.hashCode();
        result = 31 * result + stock.hashCode();
        return result;
    }
}

Write a test:

bookinventory/src/test/java/example/micronaut/BooksControllerTest.java
package example.micronaut;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.client.RxHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.http.client.exceptions.HttpClientResponseException;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

@MicronautTest
public class BooksControllerTest {

    @Inject
    @Client("/")
    RxHttpClient rxHttpClient;

    @Test
    public void testBooksController() {
        HttpResponse<Boolean> rsp = rxHttpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/1491950358"), Boolean.class);
        assertEquals(rsp.status(), HttpStatus.OK);
        assertTrue(rsp.body());
    }

    @Test
    public void testBooksControllerWithNonExistingIsbn() {
        HttpClientResponseException thrown = assertThrows(HttpClientResponseException.class, () -> {
            rxHttpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/XXXXX"), Boolean.class);
        });

        assertEquals(
                HttpStatus.NOT_FOUND,
                thrown.getResponse().getStatus()
        );

    }
}

Edit application.yml

bookinventory/src/main/resources/application.yml
micronaut:
  application:
    name: bookinventory (1)
  server:
    port: 8082 (2)
1 Configure the application name. The app name will be used later in the tutorial.
2 Configure the app to listen at port 8082

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

bookinventory/src/test/resources/application-test.yml
micronaut:
  server:
    port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

Run the unit test:

bookinventory $ ./gradlew test

4.3. Recommendation Microservice

Create the bookrecommendation microservice:

mn create-app example.micronaut.bookrecommendation --build=gradle --lang=java

The previous command creates a folder named bookrecommendation and a Micronaut app inside it.

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

bookrecommendation/src/main/java/example/micronaut/BookCatalogueOperations.java
package example.micronaut;

import io.reactivex.Flowable;

public interface BookCatalogueOperations {
    Flowable<Book> findAll();
}
bookrecommendation/src/main/java/example/micronaut/BookCatalogueClient.java
package example.micronaut;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import io.reactivex.Flowable;

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

interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Flowable<Book> findAll();
}
1 Use @Client to use declarative HTTP Clients

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

bookrecommendation/src/main/java/example/micronaut/Book.java
package example.micronaut;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;

import javax.validation.constraints.NotBlank;
import java.util.Objects;

@Introspected
public class Book {

    @NonNull
    @NotBlank
    private String isbn;

    @NonNull
    @NotBlank
    private String name;

    public Book() {}

    public Book(@NonNull @NotBlank String isbn, @NonNull @NotBlank String name) {
        this.isbn = isbn;
        this.name = name;
    }

    @NonNull
    public String getIsbn() {
        return isbn;
    }

    public void setIsbn(@NonNull String isbn) {
        this.isbn = isbn;
    }

    @NonNull
    public String getName() {
        return name;
    }

    public void setName(@NonNull String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        Book book = (Book) o;

        if (!isbn.equals(book.isbn)) return false;
        return name.equals(book.name);
    }

    @Override
    public int hashCode() {
        int result = isbn.hashCode();
        result = 31 * result + name.hashCode();
        return result;
    }
}

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

bookrecommendation/src/main/java/example/micronaut/BookInventoryOperations.java
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import io.reactivex.Maybe;
import javax.validation.constraints.NotBlank;

public interface BookInventoryOperations {
    Maybe<Boolean> stock(@NonNull @NotBlank String isbn);
}
bookrecommendation/src/main/java/example/micronaut/BookCatalogueClient.java
package example.micronaut;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import io.reactivex.Flowable;

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

interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Flowable<Book> findAll();
}
1 Use @Client to use declarative HTTP Clients

Create a Controller which injects both clients.

bookrecommendation/src/main/java/example/micronaut/BookController.java
package example.micronaut;

import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.reactivex.Flowable;

@Controller("/books") (1)
public class BookController {

    private final BookCatalogueOperations bookCatalogueOperations;
    private final BookInventoryOperations bookInventoryOperations;

    public BookController(BookCatalogueOperations bookCatalogueOperations,
                          BookInventoryOperations bookInventoryOperations) { (2)
        this.bookCatalogueOperations = bookCatalogueOperations;
        this.bookInventoryOperations = bookInventoryOperations;
    }

    @Get (3)
    public Flowable<BookRecommendation> index() {
        return bookCatalogueOperations.findAll()
                .flatMapMaybe(b -> bookInventoryOperations.stock(b.getIsbn())
                        .filter(Boolean::booleanValue)
                        .map(rsp -> b)
                ).map(book -> new BookRecommendation(book.getName()));
    }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books
2 Constructor injection
3 The @Get annotation is used to map the index method to /books requests that use an HTTP GET.

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

bookrecommendation/src/main/java/example/micronaut/BookRecommendation.java
package example.micronaut;

import io.micronaut.core.annotation.Introspected;
import io.micronaut.core.annotation.NonNull;

import javax.validation.constraints.NotBlank;

@Introspected
public class BookRecommendation {

    @NonNull
    @NotBlank
    private String name;

    public BookRecommendation() {}

    public BookRecommendation(@NonNull @NotBlank String name) {
        this.name = name;
    }

    @NonNull
    public String getName() {
        return name;
    }

    public void setName(@NonNull String name) {
        this.name = name;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;

        BookRecommendation that = (BookRecommendation) o;

        return name.equals(that.name);
    }

    @Override
    public int hashCode() {
        return name.hashCode();
    }
}

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/java/example/micronaut/BookInventoryClientStub.java
package example.micronaut;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.core.annotation.NonNull;
import io.micronaut.retry.annotation.Fallback;
import io.reactivex.Maybe;

import javax.inject.Singleton;
import javax.validation.constraints.NotBlank;

@Requires(env = Environment.TEST) (1)
@Fallback
@Singleton
public class BookInventoryClientStub implements BookInventoryOperations {

    @Override
    public Maybe<Boolean> stock(@NonNull @NotBlank String isbn) {
        if (isbn.equals("1491950358")) {
            return Maybe.just(Boolean.TRUE); (2)

        } else if (isbn.equals("1680502395")) {
            return Maybe.just(Boolean.FALSE); (3)
        }
        return Maybe.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/java/example/micronaut/BookCatalogueClientStub.java
package example.micronaut;

import io.micronaut.context.annotation.Requires;
import io.micronaut.context.env.Environment;
import io.micronaut.retry.annotation.Fallback;
import io.reactivex.Flowable;

import javax.inject.Singleton;

@Requires(env = Environment.TEST)
@Fallback
@Singleton
public class BookCatalogueClientStub implements BookCatalogueOperations {

    @Override
    public Flowable<Book> findAll() {
        Book buildingMicroservices = new Book("1491950358", "Building Microservices");
        Book releaseIt = new Book("1680502395", "Release It!");
        return Flowable.just(buildingMicroservices, releaseIt);
    }
}

Write a test:

bookrecommendation/src/test/java/example/micronaut/BookControllerTest.java
package example.micronaut;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.client.RxStreamingHttpClient;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.test.extensions.junit5.annotation.MicronautTest;
import io.reactivex.Flowable;
import org.junit.jupiter.api.Test;

import javax.inject.Inject;

import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
public class BookControllerTest {

    @Inject
    @Client("/")
    RxStreamingHttpClient client;

    @Test
    public void testRetrieveBooks() {
        Flowable<BookRecommendation> books = client.jsonStream(HttpRequest.GET("/books"), BookRecommendation.class);
        assertEquals(books.toList().blockingGet().size(), 1);
        assertEquals(books.toList().blockingGet().get(0).getName(), "Building Microservices");
    }
}

Edit application.yml

bookrecommendation/src/main/resources/application.yml
micronaut:
  application:
    name: bookrecommendation (1)
  server:
    port: 8080 (2)
1 Configure the application name. The app name will be used later in the tutorial.
2 Configure the app to listen at port 8080

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

bookrecommendation/src/test/resources/application-test.yml
micronaut:
  server:
    port: -1 (1)
1 Start the micronaut microservice at a random port when running the tests.

Run the unit test:

bookinventory $ ./gradlew test

4.4. Running the app

Run bookcatalogue microservice:

To run the application execute ./gradlew run.

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

Run bookinventory microservice:

To run the application execute ./gradlew run.

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

Run bookrecommendation microservice:

To run the application execute ./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"}]

5. Eureka and Micronaut

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.

5.1. 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 app (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.

5.2. Book Catalogue

Add discovery-client dependency.

build.gradle
implementation("io.micronaut.discovery:micronaut-discovery-client")

Append to bookcatalogue service application.yml the following snippet:

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

Previous configuration registers a Micronaut app with Eureka with minimal configuration. Discover a more complete list of Configuration options at EurekaConfiguration.

Disable Eureka registration in tests:

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

5.3. Book Inventory

Add discovery-client dependency.

build.gradle
implementation("io.micronaut.discovery:micronaut-discovery-client")

Also, append to bookinventory.application.yml the following snippet:

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

Disable Eureka registration in tests:

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

5.4. Book Recommendation

Add discovery-client dependency.

build.gradle
implementation("io.micronaut.discovery:micronaut-discovery-client")

Also, append to bookrecommendation.application.yml the following snippet:

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

Modify BookInventoryClient and BookCatalogueClient to use the service id instead of a harcoded ip.

bookrecommendation/src/main/java/example/micronaut/BookCatalogueClient.java
package example.micronaut;

import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.micronaut.retry.annotation.Recoverable;
import io.reactivex.Flowable;

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

interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Flowable<Book> findAll();
}
1 Use the configuration value micronaut.application.name used in bookcatalogue as service id.
bookrecommendation/src/main/java/example/micronaut/BookInventoryClient.java
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Consumes;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.client.annotation.Client;
import io.reactivex.Maybe;
import io.micronaut.retry.annotation.Recoverable;
import javax.validation.constraints.NotBlank;

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

interface BookInventoryClient extends BookInventoryOperations {

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

Disable Eureka registration in tests:

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

5.5. Running the App

Run bookcatalogue microservice:

To run the application execute ./gradlew run.

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

Run bookinventory microservice:

To run the application execute ./gradlew run.

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

Run bookrecommendation microservice:

To run the application execute ./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"}]

6. Generate a Micronaut app’s Native Image with GraalVM

We are going to use GraalVM, the polyglot embeddable virtual machine, to generate a Native image of our Micronaut application.

Native images compiled with GraalVM ahead-of-time improve the startup time and reduce the memory footprint of JVM-based applications.

Use of GraalVM’s native-image tool is only supported in Java or Kotlin projects. Groovy relies heavily on reflection which is only partially supported by GraalVM.

6.1. Native Image generation

The easiest way to install GraalVM is to use SDKMan.io.

# For Java 8
$ sdk install java 21.1.0.r8-grl

# For Java 11
$ sdk install java 21.1.0.r11-grl

You need to install the native-image component which is not installed by default.

$ gu install native-image

To generate a native image using Gradle run:

$ ./gradlew nativeImage

The native image will be created in build/native-image/application and can be run with ./build/native-image/application

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

build.gradle
nativeImage {
    args('--verbose')
    imageName('mn-graalvm-application') (1)
}
1 The native image name will now be mn-graalvm-application

Start the native images 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 Eureka Support inside Micronaut.

8. Help with Micronaut

Object Computing, Inc. (OCI) sponsored the creation of this Guide. A variety of consulting and support services are available.