These tutorials target Micronaut Framework 3. Read, Guides for Micronaut Framework 4.

Eureka and the Micronaut Framework - Microservices Service Discovery

Use Netflix Eureka service discovery to expose your Micronaut applications.

Authors: Sergio del Amo

Micronaut Version: 3.9.2

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

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 application step by step. However, you can go right to the completed example.

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

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

4.1. 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=java
If you don’t specify the --build argument, Gradle is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.

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.

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/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() {
        return Arrays.asList(
                new Book("1491950358", "Building Microservices"),
                new Book("1680502395", "Release It!"),
                new Book("0321601912", "Continuous Delivery:")
        );
    }
}
1 The class is defined as a controller with the @Controller annotation mapped to the path /books.
2 The @Get annotation maps the index method to /books requests that use an HTTP GET.

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

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

import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;

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

@Serdeable
public class Book {

    @NonNull
    @NotBlank
    private final String isbn;

    @NonNull
    @NotBlank
    private final String name;

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

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

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

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

        Book book = (Book) o;
        return Objects.equals(isbn, book.isbn) && Objects.equals(name, book.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, name);
    }
}

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 jakarta.inject.Inject;
import org.junit.jupiter.api.Test;

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<Book> 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 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 name will be use 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/java/example/micronaut/Application.java
package example.micronaut;

import io.micronaut.runtime.Micronaut;

import static io.micronaut.context.env.Environment.DEVELOPMENT;

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application.class)
                .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
eureka:
  client:
    registration:
      enabled: false

Run the unit test:

bookcatalogue
./gradlew test

4.2. 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=java
If you don’t specify the --build argument, Gradle is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.

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.

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

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;

import static io.micronaut.http.MediaType.TEXT_PLAIN;

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

    @Produces(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));
        }
        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 controller 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 maps 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.NonNull;
import io.micronaut.serde.annotation.Serdeable;

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

@Serdeable
public class BookInventory {

    @NonNull
    @NotBlank
    private final String isbn;

    private final int stock;

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

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

    public int getStock() {
        return stock;
    }

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

        BookInventory that = (BookInventory) o;
        return stock == that.stock && Objects.equals(isbn, that.isbn);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, stock);
    }
}

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.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.Test;

import static io.micronaut.http.HttpStatus.NOT_FOUND;
import static io.micronaut.http.HttpStatus.OK;
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("/")
    HttpClient httpClient;

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

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

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

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

import io.micronaut.runtime.Micronaut;

import static io.micronaut.context.env.Environment.DEVELOPMENT;

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application.class)
                .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
eureka:
  client:
    registration:
      enabled: false

Run the unit test:

bookinventory
./gradlew test

4.3. 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=java
If you don’t specify the --build argument, Gradle is used as the build tool.
If you don’t specify the --lang argument, Java is used as the language.

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.

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

import org.reactivestreams.Publisher;

public interface BookCatalogueOperations {
    Publisher<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 org.reactivestreams.Publisher;

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

interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Publisher<Book> findAll();
}
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/java/example/micronaut/Book.java
package example.micronaut;

import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;

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

@Serdeable
public class Book {

    @NonNull
    @NotBlank
    private final String isbn;

    @NonNull
    @NotBlank
    private final String name;

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

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

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

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

        Book book = (Book) o;
        return Objects.equals(isbn, book.isbn) && Objects.equals(name, book.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(isbn, name);
    }
}

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 reactor.core.publisher.Mono;

import javax.validation.constraints.NotBlank;

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

import io.micronaut.core.annotation.NonNull;
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 javax.validation.constraints.NotBlank;

import static io.micronaut.http.MediaType.TEXT_PLAIN;

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

interface BookInventoryClient extends BookInventoryOperations {

    @Consumes(TEXT_PLAIN)
    @Get("/books/stock/{isbn}")
    Mono<Boolean> stock(@NonNull @NotBlank String isbn);
}
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/java/example/micronaut/BookController.java
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)
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 Publisher<BookRecommendation> index() {
        return Flux.from(bookCatalogueOperations.findAll())
                .flatMap(b -> Flux.from(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 maps the index method to /books requests that use an HTTP GET.

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

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

import io.micronaut.core.annotation.NonNull;
import io.micronaut.serde.annotation.Serdeable;

import javax.validation.constraints.NotBlank;

@Serdeable
public class BookRecommendation {

    @NonNull
    @NotBlank
    private final String name;

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

    @NonNull
    public String getName() {
        return 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.core.annotation.NonNull;
import io.micronaut.retry.annotation.Fallback;
import jakarta.inject.Singleton;
import reactor.core.publisher.Mono;

import javax.validation.constraints.NotBlank;

import static io.micronaut.context.env.Environment.TEST;

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

    @Override
    public Mono<Boolean> stock(@NonNull @NotBlank String isbn) {
        if (isbn.equals("1491950358")) {
            return Mono.just(true); (2)
        }
        if (isbn.equals("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/java/example/micronaut/BookCatalogueClientStub.java
package example.micronaut;

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

import static io.micronaut.context.env.Environment.TEST;

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

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

Write a test:

bookrecommendation/src/test/java/example/micronaut/BookControllerTest.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 jakarta.inject.Inject;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledIfEnvironmentVariable;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;

@MicronautTest
public class BookControllerTest {

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

    @DisabledIfEnvironmentVariable(named = "CI", matches = "true")
    @Test
    public void testRetrieveBooks() {
        List<BookRecommendation> books = client.toBlocking()
                .retrieve(HttpRequest.GET("/books"), Argument.listOf(BookRecommendation.class));
        assertEquals(1, books.size());
        assertEquals("Building Microservices", books.get(0).getName());
    }
}

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

import io.micronaut.runtime.Micronaut;

import static io.micronaut.context.env.Environment.DEVELOPMENT;

public class Application {

    public static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application.class)
                .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
eureka:
  client:
    registration:
      enabled: false

Run the unit test:

bookrecommendation
./gradlew test

4.4. 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"}]

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

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

5.2. Book Catalogue

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 application with Eureka with minimal configuration. Discover a more complete list of configuration options at EurekaConfiguration.

5.3. Book Inventory

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}"

5.4. Book Recommendation

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 hardcoded URL.

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 org.reactivestreams.Publisher;

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

interface BookCatalogueClient extends BookCatalogueOperations {

    @Get("/books")
    Publisher<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.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 javax.validation.constraints.NotBlank;

import static io.micronaut.http.MediaType.TEXT_PLAIN;

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

interface BookInventoryClient extends BookInventoryOperations {

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

5.5. 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"}]

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. Native executable generation

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

Java 11
sdk install java 22.3.r11-grl
If you still use Java 8, use the JDK11 version of GraalVM.
Java 17
sdk install java 22.3.r17-grl

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

After installing GraalVM, install the native-image component, which is not installed by default:

gu install native-image

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
graalvmNative {
    binaries {
        main {
            imageName.set('mn-graalvm-application') (1)
            buildArgs.add('--verbose') (2)
        }
    }
}
1 The native executable name will now be mn-graalvm-application
2 It is possible to pass extra arguments to build the native executable

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