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_HOMEconfigured 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.
- 
Download and unzip the source 
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.
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:
When a service wants to do a request to other service, it uses the discovery service to retrieve the address.
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=groovy| If you don’t specify the --buildargument, Gradle is used as the build tool.If you don’t specify the --langargument, 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:
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
@CompileStatic
@Controller("/books") (1)
class BooksController {
    @Get (2)
    List<Book> index() {
        [
            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 @Getannotation maps the index method to/booksrequests that use an HTTP GET. | 
The previous controller responds a List<Book>. Create the Book POJO:
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.micronaut.core.annotation.NonNull
import io.micronaut.serde.annotation.Serdeable
import javax.validation.constraints.NotBlank
@CompileStatic
@EqualsAndHashCode
@Serdeable
class Book {
    @NonNull
    @NotBlank
    final String isbn
    @NonNull
    @NotBlank
    final String name
    Book(@NonNull @NotBlank String isbn,
         @NonNull @NotBlank String name) {
        this.isbn = isbn
        this.name = name
    }
}Write a test:
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.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification
@MicronautTest (1)
class BooksControllerSpec extends Specification {
    @Inject
    @Client("/")
    HttpClient client (2)
    void "it is possible to retrieve books"() {
        when:
        HttpRequest request = HttpRequest.GET("/books") (3)
        List<Book> books = client.toBlocking().retrieve(request, Argument.listOf(Book)) (4)
        then:
        books.size() == 3
        books.contains(new Book("1491950358", "Building Microservices"))
        books.contains(new Book("1680502395", "Release It!"))
    }
}| 1 | Annotate the class with @MicronautTestso the Micronaut framework will initialize the application context and the embedded server. More info. | 
| 2 | Inject the HttpClientbean 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
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.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.runtime.Micronaut
import static io.micronaut.context.env.Environment.DEVELOPMENT
@CompileStatic
class Application {
    static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application)
                .defaultEnvironments(DEVELOPMENT)
                .start()
    }
}Create src/main/resources/application-dev.yml. The Micronaut framework applies this configuration file only for the dev environment.
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:
eureka:
  client:
    registration:
      enabled: falseRun the unit test:
./gradlew test4.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=groovy| If you don’t specify the --buildargument, Gradle is used as the build tool.If you don’t specify the --langargument, 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:
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import io.micronaut.http.annotation.Produces
import javax.validation.constraints.NotBlank
import static io.micronaut.http.MediaType.TEXT_PLAIN
@CompileStatic
@Controller("/books") (1)
class BooksController {
    @Produces(TEXT_PLAIN) (2)
    @Get("/stock/{isbn}") (3)
    Boolean stock(@NotBlank String isbn) {
        bookInventoryByIsbn(isbn).map(bi -> bi.stock > 0).orElse(null)
    }
    private Optional<BookInventory> bookInventoryByIsbn(String isbn) {
        if (isbn == "1491950358") {
            return Optional.of(new BookInventory(isbn, 4))
        }
        if (isbn == "1680502395") {
            return Optional.of(new BookInventory(isbn, 0))
        }
        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/jsonasContent-Type. We are returning a String, not a JSON object. Because of that, we set it totext/plain. | 
| 3 | The @Getannotation maps the index method to/books/stock/{isbn}requests that use an HTTP GET. | 
The previous controller uses a POJO:
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.micronaut.core.annotation.NonNull
import io.micronaut.serde.annotation.Serdeable
import javax.validation.constraints.NotBlank
@CompileStatic
@EqualsAndHashCode
@Serdeable
class BookInventory {
    @NonNull
    @NotBlank
    final String isbn
    final int stock
    BookInventory(@NonNull @NotBlank String isbn,
                  int stock) {
        this.isbn = isbn
        this.stock = stock
    }
}Write a test:
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.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.Specification
import static io.micronaut.http.HttpStatus.NOT_FOUND
import static io.micronaut.http.HttpStatus.OK
@MicronautTest
class BooksControllerSpec extends Specification {
    @Inject
    @Client("/")
    HttpClient httpClient
    void "for a book with inventory true is returned"() {
        when:
        HttpResponse<Boolean> rsp = httpClient.toBlocking().exchange(
                HttpRequest.GET("/books/stock/1491950358"), Boolean)
        then:
        rsp.status() == OK
        rsp.body()
    }
    void "for an invalid ISBN 404 is returned"() {
        when:
        httpClient.toBlocking().exchange(HttpRequest.GET("/books/stock/XXXXX"), Boolean)
        then:
        HttpClientResponseException e = thrown()
        e.response.status == NOT_FOUND
    }
}Edit 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.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.runtime.Micronaut
import static io.micronaut.context.env.Environment.DEVELOPMENT
@CompileStatic
class Application {
    static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application)
                .defaultEnvironments(DEVELOPMENT)
                .start()
    }
}Create src/main/resources/application-dev.yml. The Micronaut framework applies this configuration file only for the dev environment.
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:
eureka:
  client:
    registration:
      enabled: falseRun the unit test:
./gradlew test4.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=groovy| If you don’t specify the --buildargument, Gradle is used as the build tool.If you don’t specify the --langargument, 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.
package example.micronaut
import org.reactivestreams.Publisher
interface BookCatalogueOperations {
    Publisher<Book> findAll()
}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)
interface BookCatalogueClient extends BookCatalogueOperations {
    @Get("/books")
    Publisher<Book> findAll()
}| 1 | Use @Clientto use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use theidmember 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:
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.micronaut.core.annotation.NonNull
import io.micronaut.serde.annotation.Serdeable
import javax.validation.constraints.NotBlank
@CompileStatic
@EqualsAndHashCode
@Serdeable
class Book {
    @NonNull
    @NotBlank
    final String isbn
    @NonNull
    @NotBlank
    final String name
    Book(@NonNull @NotBlank String isbn,
         @NonNull @NotBlank String name) {
        this.isbn = isbn
        this.name = name
    }
}Create an interface to map operations with bookinventory, and a Micronaut Declarative HTTP Client to consume it.
package example.micronaut
import io.micronaut.core.annotation.NonNull
import reactor.core.publisher.Mono
import javax.validation.constraints.NotBlank
interface BookInventoryOperations {
    Mono<Boolean> stock(@NonNull @NotBlank String isbn)
}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)
interface BookInventoryClient extends BookInventoryOperations {
    @Consumes(TEXT_PLAIN)
    @Get("/books/stock/{isbn}")
    Mono<Boolean> stock(@NonNull @NotBlank String isbn)
}| 1 | Use @Clientto use declarative HTTP Clients. You can annotate interfaces or abstract classes. You can use theidmember to provide a service identifier or specify the URL directly as the annotation’s value. | 
Create a Controller which injects both clients.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.http.annotation.Controller
import io.micronaut.http.annotation.Get
import org.reactivestreams.Publisher
import reactor.core.publisher.Flux
@CompileStatic
@Controller("/books") (1)
class BookController {
    private final BookCatalogueOperations bookCatalogueOperations
    private final BookInventoryOperations bookInventoryOperations
    BookController(BookCatalogueOperations bookCatalogueOperations,
                   BookInventoryOperations bookInventoryOperations) { (2)
        this.bookCatalogueOperations = bookCatalogueOperations
        this.bookInventoryOperations = bookInventoryOperations
    }
    @Get (3)
    Publisher<BookRecommendation> index() {
        Flux.from(bookCatalogueOperations.findAll())
                .flatMap(b -> Flux.from(bookInventoryOperations.stock(b.isbn))
                        .filter(Boolean::booleanValue)
                        .map(rsp -> b)
                ).map(book -> new BookRecommendation(book.name))
    }
}| 1 | The class is defined as a controller with the @Controller annotation mapped to the path /books. | 
| 2 | Constructor injection | 
| 3 | The @Getannotation maps the index method to/booksrequests that use an HTTP GET. | 
The previous controller returns a Publisher<BookRecommendation>. Create the BookRecommendation POJO:
package example.micronaut
import groovy.transform.CompileStatic
import groovy.transform.EqualsAndHashCode
import io.micronaut.core.annotation.NonNull
import io.micronaut.serde.annotation.Serdeable
import javax.validation.constraints.NotBlank
@CompileStatic
@EqualsAndHashCode
@Serdeable
class BookRecommendation {
    @NonNull
    @NotBlank
    final String name
    BookRecommendation(@NonNull @NotBlank String name) {
        this.name = name
    }
}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.
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
class BookInventoryClientStub implements BookInventoryOperations {
    @Override
    Mono<Boolean> stock(@NonNull @NotBlank String isbn) {
        if (isbn == "1491950358") {
            return Mono.just(true) (2)
        }
        if (isbn == "1680502395") {
            return Mono.just(false) (3)
        }
        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 stockwould be true | 
| 3 | Similarly, we decided that other book’s stockmethod would be false | 
| 4 | Finally, any other book will have their stockmethod return an empty value | 
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
class BookCatalogueClientStub implements BookCatalogueOperations {
    @Override
    Publisher<Book> findAll() {
        Book buildingMicroservices = new Book("1491950358", "Building Microservices")
        Book releaseIt = new Book("1680502395", "Release It!")
        Flux.just(buildingMicroservices, releaseIt)
    }
}Write a test:
package example.micronaut
import io.micronaut.http.HttpRequest
import io.micronaut.http.client.HttpClient
import io.micronaut.http.client.annotation.Client
import io.micronaut.test.extensions.spock.annotation.MicronautTest
import jakarta.inject.Inject
import spock.lang.IgnoreIf
import spock.lang.Specification
import io.micronaut.core.type.Argument
@MicronautTest
class BookControllerSpec extends Specification {
    @Inject
    @Client("/")
    HttpClient client
    @IgnoreIf({env['CI'] as boolean})
    void "retrieve books"() {
        when:
        List<BookRecommendation> books = client.toBlocking().retrieve(HttpRequest.GET("/books"), Argument.listOf(BookRecommendation))
        then:
        books.size() == 1
        books[0].name == "Building Microservices"
    }
}Edit 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.
package example.micronaut
import groovy.transform.CompileStatic
import io.micronaut.runtime.Micronaut
import static io.micronaut.context.env.Environment.DEVELOPMENT
@CompileStatic
class Application {
    static void main(String[] args) {
        Micronaut.build(args)
                .mainClass(Application)
                .defaultEnvironments(DEVELOPMENT)
                .start()
    }
}Create src/main/resources/application-dev.yml. The Micronaut framework applies this configuration file only for the dev environment.
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:
eureka:
  client:
    registration:
      enabled: falseRun the unit test:
./gradlew test4.4. Running the application
Run bookcatalogue microservice:
./gradlew run14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081Run bookinventory microservice:
./gradlew run14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082Run bookrecommendation microservice:
./gradlew run14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080You 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:runor./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:
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:
eureka:
  client:
    registration:
      enabled: true
    defaultZone: "${EUREKA_HOST:localhost}:${EUREKA_PORT:8761}"5.4. Book Recommendation
Append to bookrecommendation.application.yml the following snippet:
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.
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)
interface BookCatalogueClient extends BookCatalogueOperations {
    @Get("/books")
    Publisher<Book> findAll()
}| 1 | Use the configuration value micronaut.application.nameused inbookcatalogueas serviceid. | 
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)
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.nameused inbookinventoryas serviceid. | 
5.5. Running the Application
Run bookcatalogue microservice:
./gradlew run14:28:34.034 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 499ms. Server Running: http://localhost:8081Run bookinventory microservice:
./gradlew run14:31:13.104 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 506ms. Server Running: http://localhost:8082Run bookrecommendation microservice:
./gradlew run14:31:57.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 523ms. Server Running: http://localhost:8080You 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:
 
You can run a cURL command to test the whole application:
curl http://localhost:8080/books[{"name":"Building Microservices"}]6. Next steps
Read more about Eureka Support in the Micronaut framework.
7. Help with the Micronaut Framework
The Micronaut Foundation sponsored the creation of this Guide. A variety of consulting and support services are available.