DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • How to Migrate From JUnit 4 to JUnit 5 Step by Step
  • How Spring Boot Starters Integrate With Your Project
  • Day in the Life of a Developer With Google’s Gemini Code Assist: Part 1

Trending

  • The 4 R’s of Pipeline Reliability: Designing Data Systems That Last
  • Java Virtual Threads and Scaling
  • Cookies Revisited: A Networking Solution for Third-Party Cookies
  • Evolution of Cloud Services for MCP/A2A Protocols in AI Agents
  1. DZone
  2. Coding
  3. Frameworks
  4. Micronaut Tutorial: Server Application

Micronaut Tutorial: Server Application

In this part of my tutorial series on the Micronaut framework we are going to create a simple HTTP server-side application running on Netty.

By 
Piotr Mińkowski user avatar
Piotr Mińkowski
·
Updated Apr. 29, 19 · Tutorial
Likes (2)
Comment
Save
Tweet
Share
22.0K Views

Join the DZone community and get the full member experience.

Join For Free

Micronaut Tutorial

In this part of my tutorial series on the Micronaut framework we are going to create a simple HTTP server-side application running on Netty. We have already discussed the most interesting core features of Micronaut like beans, scopes, and unit testing in the first part of this series. For more details you may refer to my article Micronaut Tutorial: Beans and Scopes.

Assuming that we have a basic knowledge of the core mechanisms of Micronaut, we may proceed to the key part of that framework and discuss how to build simple microservice application exposing a REST API over HTTP.

Embedded Server

First, we need to include a dependency in our pom.xml that's responsible for running an embedded server during the application startup. By default, Micronaut starts on Netty server, so we only need to include the following dependency:

<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-server-netty</artifactId>
</dependency>

Assuming, we have the following main class defined, we only need to run it:

public class MainApp {

    public static void main(String[] args) {
        Micronaut.run(MainApp.class);
    }

}

By default, the Netty server runs on port 8080. You may override it to force the server to run on a specific port by setting the following property in your application.yml or bootstrap.yml. You can also set the value of this property to -1 to run the server on randomly generated port.

micronaut:
  server:
    port: 8100

Creating a Web Application

If you are already familiar with Spring Boot you should not have any problems with building a simple REST server-side application using Micronaut. The approach is almost identical. We just have to create a controller class and annotate it with @Controller. Micronaut supports all HTTP method types. You will probably use: @Get, @Post, @Delete, @Put or @Patch. Here's our sample controller class that implements methods for adding new Person object, finding all persons or a single person by id:

@Controller("/persons")
public class PersonController {

    List<Person> persons = new ArrayList<>();

    @Post
    public Person add(Person person) {
        person.setId(persons.size() + 1);
        persons.add(person);
        return person;
    }

    @Get("/{id}")
    public Optional<Person> findById(Integer id) {
        return persons.stream()
                .filter(it -> it.getId().equals(id))
                .findFirst();
    }

    @Get
    public List<Person> findAll() {
        return persons;
    }

}

Request variables are resolved automatically and bind to the variable with the same name. Micronaut populates methods arguments from URI variables like /{variableName} and GET query parameters like ?paramName=paramValue. If the request contains a JSON body you should annotate it with @Body. Our sample controller is very simple. It does not perform any input data validation. Let's change it.

Validation

To be able to perform HTTP request validation, we should first include the following dependencies in our pom.xml:

<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-validation</artifactId>
</dependency>
<dependency>
    <groupId>io.micronaut.configuration</groupId>
    <artifactId>micronaut-hibernate-validator</artifactId>
</dependency>

Validation in Micronaut is based on JSR-380, also known as Bean Validation 2.0. We can use javax.validation annotations such as @NotNull, @Min or @Max. Micronaut uses implementation provided by Hibernate Validator, so even if don't use any JPA in your project, you have to include micronaut-hibernate-validator to your dependencies. After that we may add a validation to our model class using some javax.validation annotations. Here's Person model class with validation. All the fields are required: firstName and lastName cannot be blank, id cannot be greater than 10000, age cannot be lower than 0.

public class Person {

    @Max(10000)
    private Integer id;
    @NotBlank
    private String firstName;
    @NotBlank
    private String lastName;
    @PositiveOrZero
    private int age;
    @NotNull
    private Gender gender;

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getFirstName() {
        return firstName;
    }

    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public Gender getGender() {
        return gender;
    }

    public void setGender(Gender gender) {
        this.gender = gender;
    }

}

Now, we need to modify the code of our controller. First, it needs to be annotated with @Validated. Also the@Body parameter of the POST method should be annotated with @Valid. The REST method argument may also be validated using JSR-380 annotation. Alternatively, we may configure validation using URI templates. The annotation @Get("/{id:4}") indicates that a variable can contain 4 characters max (is lower than 10000) or a query parameter is optional as shown here: @Get("{?max,offset}").
Here's the current implementation of our controller. Besides validation, I have also implemented pagination for findAll based on offset and limit optional parameters:

@Controller("/persons")
@Validated
public class PersonController {

    List<Person> persons = new ArrayList<>();

    @Post
    public Person add(@Body @Valid Person person) {
        person.setId(persons.size() + 1);
        persons.add(person);
        return person;
    }

    @Get("/{id:4}")
    public Optional<Person> findById(@NotNull Integer id) {
        return persons.stream()
                .filter(it -> it.getId().equals(id))
                .findFirst();
    }

    @Get("{?max,offset}")
    public List<Person> findAll(@Nullable Integer max, @Nullable Integer offset) {
        return persons.stream()
                .skip(offset == null ? 0 : offset)
                .limit(max == null ? 10000 : max)
                .collect(Collectors.toList());
    }

}

Since we have finished the implementation of our controller, it is a right time to test it.

Testing With an Embedded Server

We have already discussed testing with Micronaut in the first part of this tutorial series. The only difference in comparison to those tests is a necessity of running an embedded server and call an endpoint via HTTP. To do that we have to include the dependency with Micronaut HTTP client:

<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-http-client</artifactId>
    <scope>test</scope>
</dependency>

We should also inject an instance of an embedded server in order to be able to detect its address (for example if a port number is generated automatically):

@MicronautTest
public class PersonControllerTests {

    @Inject
    EmbeddedServer server;

// tests implementation ...

}

We are building a Micronaut HTTP Client programmatically by calling static method create. It is also possible to obtain a reference to HttpClient by annotating it with @Client.

The following test implementation is based on JUnit 5. I have provided the positive test for all the exposed endpoints and one negative scenario with not valid input data ( age field lower than zero). The Micronaut HTTP client can be used in both asynchronous non-blocking mode and synchronous blocking mode. In that case we force it to work in blocking mode.

@MicronautTest
public class PersonControllerTests {

    @Inject
    EmbeddedServer server;

    @Test
    public void testAdd() throws MalformedURLException {
        HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
        Person person = new Person();
        person.setFirstName("John");
        person.setLastName("Smith");
        person.setAge(33);
        person.setGender(Gender.MALE);
        person = client.toBlocking().retrieve(HttpRequest.POST("/persons", person), Person.class);
        Assertions.assertNotNull(person);
        Assertions.assertEquals(Integer.valueOf(1), person.getId());
    }

    @Test
    public void testAddNotValid() throws MalformedURLException {
        HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
        final Person person = new Person();
        person.setFirstName("John");
        person.setLastName("Smith");
        person.setAge(-1);
        person.setGender(Gender.MALE);

        Assertions.assertThrows(HttpClientResponseException.class,
                () -> client.toBlocking().retrieve(HttpRequest.POST("/persons", person), Person.class),
                "person.age: must be greater than or equal to 0");
    }

    @Test
    public void testFindById() throws MalformedURLException {
        HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
        Person person = client.toBlocking().retrieve(HttpRequest.GET("/persons/1"), Person.class);
        Assertions.assertNotNull(person);
    }

    @Test
    public void testFindAll() throws MalformedURLException {
        HttpClient client = HttpClient.create(new URL("http://" + server.getHost() + ":" + server.getPort()));
        Person[] persons = client.toBlocking().retrieve(HttpRequest.GET("/persons"), Person[].class);
        Assertions.assertEquals(1, persons.length);
    }

}

We have already built a simple web application that exposes some methods over a REST API, validates input data, and includes JUnit API tests. Now, we may discuss some more advanced, interesting Micronaut features. First of them is built-in support for API versioning.

API Versioning

Since 1.1, Micronaut has supported API versioning via a dedicated @Version annotation. To test this feature, we will add a new version of the findAll method to our controller class. The new version of this method requires to set input parameters max and offset, which were optional for the first version of the method.

@Version("1")
@Get("{?max,offset}")
public List<Person> findAll(@Nullable Integer max, @Nullable Integer offset) {
    return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
}

@Version("2")
@Get("?max,offset")
public List<Person> findAllV2(@NotNull Integer max, @NotNull Integer offset) {
    return persons.stream()
            .skip(offset == null ? 0 : offset)
            .limit(max == null ? 10000 : max)
            .collect(Collectors.toList());
}

The versioning feature is not enabled by default. To do that, you need to set property micronaut.router.versioning.enabled to true in application.yml. We will also set default version to 1, which is compatible with tests created in the previous section that does not use versioning feature:

micronaut:
  router:
    versioning:
      enabled: true
      default-version: 1

Micronaut automatic versioning is supported by a declarative HTTP client. To create such a client we need to define an interface that contains a signature of the target server-side method, and is annotated with @Client. Here's declarative client interface responsible only for communicating with version 2 of findAll method:

@Client("/persons")
public interface PersonClient {

    @Version("2")
    @Get("?max,offset")
    List<Person> findAllV2(Integer max, Integer offset);

}

The PersonClient declared above may be injected into the test and used for calling API method exposed by server-side application:

@Inject
PersonClient client;

@Test
public void testFindAllV2() {
    List<Person> persons = client.findAllV2(10, 0);
    Assertions.assertEquals(1, persons.size());
}

API Documentation With Swagger

Micronaut provides built-in support for generating Open API/Swagger YAML documentation at compilation time. We can customize produced documentation using standard Swagger annotations. To enable this support for our application we should add the following swagger-annotations dependency to pom.xml, and enable annotation processing for micronaut-openapi module inside Maven compiler plugin configuration:

<dependency>
    <groupId>io.swagger.core.v3</groupId>
    <artifactId>swagger-annotations</artifactId>
</dependency>
...
<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.7.0</version>
    <configuration>
        <source>${jdk.version}</source>
        <target>${jdk.version}</target>
        <compilerArgs>
            <arg>-parameters</arg>
        </compilerArgs>
        <annotationProcessorPaths>
            <path>
                <groupId>io.micronaut</groupId>
                <artifactId>micronaut-inject-java</artifactId>
                <version>${micronaut.version}</version>
            </path>
            <path>
                <groupId>io.micronaut.configuration</groupId>
                <artifactId>micronaut-openapi</artifactId>
                <version>${micronaut.version}</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
    ...
</plugin>

We have to include some basic information to the generated Swagger YAML like application name, description, version number, or author name using the @OpenAPIDefinition annotation:

@OpenAPIDefinition(
info = @Info(
title = "Sample Application",
version = "1.0",
description = "Sample API",
contact = @Contact(url = "https://piotrminkowski.wordpress.com", name = "Piotr Mińkowski", email = "piotr.minkowski@gmail.com")
)
)
public class MainApp {

    public static void main(String[] args) {
        Micronaut.run(MainApp.class);
    }

}

Micronaut generates a Swagger file based on the title and version fields inside the @Info annotation. In that case, our YAML definition file is available under the name sample-application-1.0.yml, and will be generated to the META-INF/swagger directory. We can expose it outside application using HTTP endpoint. Here's the appropriate configuration provided inside application.yml file.

micronaut:
  static-resources:
    swagger:
      paths: classpath:META-INF/swagger
      mapping: /swagger/**

Assuming our application is running on port 8100 Swagger definition is available under the path http://localhost:8100/swagger/sample-application-1.0.yml. You can call this endpoint and copy the response to any Swagger editor as shown below.

Swagger Editor

Management and Monitoring Endpoints

Micronaut provides some built-in HTTP endpoints used for management and monitoring. To enable them for the application we first need to include the following dependency:

<dependency>
    <groupId>io.micronaut</groupId>
    <artifactId>micronaut-management</artifactId>
</dependency>

There are no endpoints exposed by default outside the application. If you would like to expose them all you should set the endpoints.all.enabled property to true. Alternatively, you can enable or disable the single endpoint just by using its id instead of all in the name of property. Also, some of the built-in endpoints require authentication, and some not. You may enable/disable it for all endpoints using property endpoints.all.enabled. The following configuration inside application.yaml enables all built-in endpoints except stop endpoints using for graceful shutdown of application, and disables authentication for all the enabled endpoints:

endpoints:
  all:
    enabled: true
    sensitive: false
  stop:
    enabled: false

You may use one of the following:

  • GET /beans - returns information about the loaded bean definitions.
  • GET /info - returns static information from the state of the application.
  • GET /health - exposes "healthcheck."
  • POST /refresh - it is refresh the application state, all the beans annotated with @Refreshable will be reloaded.
  • GET /routes - returns information about URIs exposed by the application.
  • GET /logger - returns information about the available loggers.
  • GET /caches - returns information about the caches.
  • POST /stop - it shuts down the application server.

Summary

In this tutorial, we have learned how to:

  • Build a simple application that exposes some HTTP endpoints.
  • Validate input data inside a controller.
  • Test our controller with JUnit 5 on an embedded Netty server using a Micronaut HTTP client.
  • Use built-in API versioning.
  • Generate Swagger API documentation automatically.
  • Using built-in management and monitoring endpoints.

The repository for this series can be found here: https://github.com/piomin/sample-micronaut-applications.git.

Web application unit test Spring Framework API Annotation Spring Boot

Published at DZone with permission of Piotr Mińkowski, DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

Related

  • Spring Microservices RESTFul API Documentation With Swagger Part 1
  • How to Migrate From JUnit 4 to JUnit 5 Step by Step
  • How Spring Boot Starters Integrate With Your Project
  • Day in the Life of a Developer With Google’s Gemini Code Assist: Part 1

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!