Partner – Orkes – NPI EA (cat=Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Partner – LambdaTest – NPI EA (cat=Testing)
announcement - icon

Browser testing is essential if you have a website or web applications that users interact with. Manual testing can be very helpful to an extent, but given the multiple browsers available, not to mention versions and operating system, testing everything manually becomes time-consuming and repetitive.

To help automate this process, Selenium is a popular choice for developers, as an open-source tool with a large and active community. What's more, we can further scale our automation testing by running on theLambdaTest cloud-based testing platform.

Read more through our step-by-step tutorial on how to set up Selenium tests with Java and run them on LambdaTest:

>> Automated Browser Testing With Selenium

Partner – Orkes – NPI EA (cat=Java)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Partner – Orkes – NPI (tag=Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

1. Overview

Error handling is one of the main concerns when developing systems. On a code level, error handling handles exceptions thrown by the code we write. On a service level, by errors, we mean all the non-successful responses we return.

It’s a good practice in large systems, to handle similar errors in a consistent way. For example, in a service with two controllers, we want the authentication error responses to be similar, so that we can debug issues easier. Taking a step back, we probably want the same error response from all services of a system, for simplicity. We can implement this approach by using global exception handlers.

In this tutorial, we’ll focus on the error handling in Micronaut. Similar to most of the Java frameworks, Micronaut provides a mechanism to handle errors commonly. We’ll discuss this mechanism and we’ll demonstrate it in examples.

2. Error Handling in Micronaut

In coding, the only thing we can take for granted is that errors will happen. No matter how good code we write and well-defined tests and test coverage we have, we can’t avoid errors. So, how we’re handling them in our system should be one of our main concerns. Error handling in Micronaut comes easier, by using some of the framework features like status handlers and exception handlers.

If we’re familiar with error handling in Spring, then it’s easy to onboard on Micronaut ways. Micronaut provides handlers to tackle exceptions thrown, but also handlers that deal with specific response statuses. In error status handling, we can set a local scope or a global one. Exception handling is on a global scope only.

One thing worth mentioning is that, if we take advantage of Micronaut environments capabilities, we can set different global error handlers for different active environments. If we have, for example, an error handler that publishes an event message, we can make use of active environments and skip the message publishing functionality on the local environment.

3. Error Handling in Micronaut Using the @Error Annotation

In Micronaut, we can define error handlers using the @Error annotation. This annotation is defined on a method level and it should be inside @Controller annotated classes. It has some functionalities similar to other controller methods, like that it can use request binding annotation on parameters to access request headers, the request body, etc.

By using the @Error annotation for error handling in Micronaut, we can either handle exceptions or response status codes. This is something different from other popular Java frameworks, which only provide handlers per exception.

One feature of the error handlers is that we can set a scope for them. We can have one handler that handles 404 responses for the whole service, by setting the scope to global. If we don’t set a scope, then the handler only handles the specified errors thrown in the same controller.

3.1. Using the @Error Annotation to Handle Response Error Codes

The @Error annotation provides a way for us to handle errors per error response status. This way, we can define a common way to handle all HttpStatus.NOT_FOUND responses, for example. The error statuses we can handle should be one defined in the io.micronaut.http.HttpStatus enum:

@Controller("/notfound")
public class NotFoundController {
    @Error(status = HttpStatus.NOT_FOUND, global = true)
    public HttpResponse<JsonError> notFound(HttpRequest<?> request) {
        JsonError error = new JsonError("Page Not Found")
          .link(Link.SELF, Link.of(request.getUri()));

        return HttpResponse.<JsonError> notFound().body(error);
    }
}

public class CustomException extends RuntimeException {
    public CustomException(String message) {
        super(message);
    }
}

In this controller, we define a method annotated with @Error that handles the HttpStatus.NOT_FOUND responses. The scope is set to global, so all 404 errors should go through this method. After handling, all such errors should return a status code of 404, with a modified body that contains the error message “Page Not Found” and a link.

Notice that even though we use the @Controller annotation, this controller doesn’t specify any HttpMethod, so it doesn’t work as a conventional controller exactly, but it has some implementation similarities, as we mentioned earlier.

Now let’s assume we have an endpoint that gives a NOT_FOUND error response:

@Get("/not-found-error")
public HttpResponse<String> endpoint1() {
    return HttpResponse.notFound();
}

The “/not-found-error” endpoint should always return 404. If we hit this endpoint, the NOT_FOUND error handler should be triggered:

@Test
public void whenRequestThatThrows404_thenResponseIsHandled(
    RequestSpecification spec
) {
    spec.given()
      .basePath(ERRONEOUS_ENDPOINTS_PATH)
      .when()
      .get("/not-found-error")
      .then()
      .statusCode(404)
      .body(Matchers.containsString("\"message\":\"Page Not Found\",\"_links\":"));
}

This Micronaut test makes a GET request to the “/not-found-error” endpoint and gets back the expected 404 status code. However, by asserting the response body, we can verify that the response came through the handler since the error message is the one we added to the handler.

One thing to clear up is that, if we change the base path and path to point to NotFoundController, because there is no GET defined in this controller, only the error, then the server is the one that throws a 404 and the handler still handles it.

3.2. Using @Error Annotation to Handle Exceptions

In a web service, if an exception is not caught and handled anywhere, then the controller returns an internal server error by default. Error handling in Micronaut offers the @Error annotation for such cases.

Let’s create an endpoint that throws an exception and a handler that handles those specific exceptions:

@Error(exception = UnsupportedOperationException.class)
public HttpResponse<JsonError> unsupportedOperationExceptions(HttpRequest<?> request) {
    log.info("Unsupported Operation Exception handled");
    JsonError error = new JsonError("Unsupported Operation")
      .link(Link.SELF, Link.of(request.getUri()));

    return HttpResponse.<JsonError> notFound().body(error);
}

@Get("/unsupported-operation")
public HttpResponse<String> endpoint5() {
    throw new UnsupportedOperationException();
}

The “/unsupported-operation” endpoint only throws an UnsupportedOperationException exception. The unsupportedOperationExceptions method uses the @Error annotation to handle these exceptions. It returns a 404 error code since this resource is not supported and a response body with the message “Unsupported Operation”. Note that the scope in this example is local since we don’t set it to global.

If we hit this endpoint, we should see the handler handling it and giving back the response as defined in the unsupportedOperationExceptions method:

@Test
public void whenRequestThatThrowsLocalUnsupportedOperationException_thenResponseIsHandled(
    RequestSpecification spec
) {
    spec.given()
      .basePath(ERRONEOUS_ENDPOINTS_PATH)
      .when()
      .get("/unsupported-operation")
      .then()
      .statusCode(404)
      .body(containsString("\"message\":\"Unsupported Operation\""));
}

@Test
public void whenRequestThatThrowsExceptionInOtherController_thenResponseIsNotHandled(
    RequestSpecification spec
) {
    spec.given()
      .basePath(PROBES_ENDPOINTS_PATH)
      .when()
      .get("/readiness")
      .then()
      .statusCode(500)
      .body(containsString("\"message\":\"Internal Server Error\""));
}

In the first example, we request the “/unsupported-operation” endpoint, which throws the UnsupportedOperationException exception. Since the local handler is in the same controller, then we get the response we expect from the handler, with the modified response error message “Unsupported Operation”.

In the second example, we request the “/readiness” endpoint, from a different controller, which also throws an UnsupportedOperationException exception. Because this endpoint is defined on a different controller, the local handler is not going to handle the exception, so the response we get is the default with error code 500.

4. Error Handling in Micronaut Using the ExceptionHandler Interface

Micronaut also offers the option to implement an ExceptionHandler interface, to handle specific exceptions in a global scope. This approach requires one class per exception, which means that by default they have to be on a global scope.

Micronaut provides some default exception handlers, for example:

  • jakarta.validation.ConstraintViolationException
  • com.fasterxml.jackson.core.JsonProcessingException
  • UnsupportedMediaException
  • and more

These handlers can of course be overridden on our service, if needed.

One thing to consider is the exception hierarchy. When we create a handler for a specific exception A, an exception B that extends A will also fall under the same handler, unless we implement one more handler for this specific exception B. More detail on that is in the following sections.

4.1. Handling an Exception

As described earlier, we can use the ExceptionHandler interface to handle a specific type of exception globally:

@Slf4j
@Produces
@Singleton
@Requires(classes = { CustomException.class, ExceptionHandler.class })
public class CustomExceptionHandler implements ExceptionHandler<CustomException, HttpResponse<String>> {
    @Override
    public HttpResponse<String> handle(HttpRequest request, CustomException exception) {
        log.info("handling CustomException: [{}]", exception.getMessage());

        return HttpResponse.ok("Custom Exception was handled");
    }
}

In this class, we implement the interface, which uses generics to define which exception we’ll be handling. In this case, it is the CustomException we defined earlier. The class needs to be annotated with @Requires and include the exception class, but also the interface. The handle method takes as parameters the request that triggered the exception and also the exception object. Then, we simply add our custom message in the response body, giving back a 200 response status code.

Now let’s assume we have an endpoint that throws a CustomException:

@Get("/custom-error")
public HttpResponse<String> endpoint3(@Nullable @Header("skip-error") String isErrorSkipped) {
    if (isErrorSkipped == null) {
        throw new CustomException("something else went wrong");
    }
    return HttpResponse.ok("Endpoint 3");
}

The “/custom-error” endpoint accepts an isErrorSkipped header, to enable/disable the exception thrown. If we don’t include the header, then the exception is thrown:

@Test
public void whenRequestThatThrowsCustomException_thenResponseIsHandled(
    RequestSpecification spec
) {
    spec.given()
      .basePath(ERRONEOUS_ENDPOINTS_PATH)
      .when()
      .get("/custom-error")
      .then()
      .statusCode(200)
      .body(is("Custom Exception was handled"));
}

In this test, we request the “/custom-error” endpoint, without including the header. So, a CustomException exception is thrown. Then, we verify that the handler has handled this exception, by asserting on the response code and response body that we expect from the handler.

4.2. Handling Exceptions Based on Hierarchy

In the case of exceptions that are not explicitly handled, if they extend an exception that has a handler, they are implicitly handled by the same handler. Let’s assume we have a CustomeChildException that extends our CustomException:

public class CustomChildException extends CustomException {
    public CustomChildException(String message) {
        super(message);
    }
}

And there’s an endpoint that throws this exception:

@Get("/custom-child-error")
public HttpResponse<String> endpoint4(@Nullable @Header("skip-error") String isErrorSkipped) {
    log.info("endpoint4");
    if (isErrorSkipped == null) {
        throw new CustomChildException("something else went wrong");
    }

    return HttpResponse.ok("Endpoint 4");
}

The “/custom-child-error” endpoint accepts an isErrorSkipped header, to enable/disable the exception thrown. If we don’t include the header, then the exception is thrown:

@Test
public void whenRequestThatThrowsCustomChildException_thenResponseIsHandled(
    RequestSpecification spec
) {
    spec.given()
      .basePath(ERRONEOUS_ENDPOINTS_PATH)
      .when()
      .get("/custom-child-error")
      .then()
      .statusCode(200)
      .body(is("Custom Exception was handled"));
}

This test hits the “/custom-child-error” endpoint and triggers the CustomChildException exception. From the response, we can verify that the handler has handled this child exception too, by asserting on the response code and response body that we expect from the handler.

5. Conclusion

In this article, we went through the error handling in Micronaut. There are different ways to handle errors, by handling exceptions or by handling error response status codes. We also saw how we can apply our handlers on different scopes, local and global. Last, we demonstrated all options discussed, with some code examples, and used Micronaut tests to verify the outcomes.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

Partner – Orkes – NPI EA (cat = Spring)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

Partner – Orkes – NPI EA (tag = Microservices)
announcement - icon

Modern software architecture is often broken. Slow delivery leads to missed opportunities, innovation is stalled due to architectural complexities, and engineering resources are exceedingly expensive.

Orkes is the leading workflow orchestration platform built to enable teams to transform the way they develop, connect, and deploy applications, microservices, AI agents, and more.

With Orkes Conductor managed through Orkes Cloud, developers can focus on building mission critical applications without worrying about infrastructure maintenance to meet goals and, simply put, taking new products live faster and reducing total cost of ownership.

Try a 14-Day Free Trial of Orkes Conductor today.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)