Enterprise Java

Micronaut Error Handling

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 consistently. 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 because error handling is important. We’ll discuss micronaut error handling and we’ll demonstrate it in examples.

1. Overview

Error handling in Micronaut is pivotal for building resilient applications. Micronaut offers several robust mechanisms to manage exceptions and ensure your application handles errors gracefully. One of the core tools is the @Error annotation, which allows developers to define custom error handlers within their controllers. This enables precise control over how specific exceptions and HTTP status codes are managed, ensuring consistent and predictable responses.

Furthermore, Micronaut supports global exception handling, capturing exceptions not caught at the controller level. This centralized approach ensures that even unexpected errors are managed uniformly across the application. By leveraging these features, developers can enhance their applications’ stability and user experience, making error handling not just a backend concern but a crucial aspect of overall application quality.

Now let us look at how to handle error and exception handling in Micronaut.

2. Getting Started on  Error Handling in Micronaut

Error handling is a critical aspect of developing robust and reliable applications. Micronaut provides a range of features to effectively manage errors, ensuring that applications can gracefully handle exceptions and unexpected issues. This section will guide you through the essential steps for implementing error handling in Micronaut, helping you build resilient systems.

First, you need to set up Java 17. You can download Java and install it.

Then , You can download or set up the Gradle. The gradle command for setup is :

 
$ mkdir /opt/gradle
$ unzip -d /opt/gradle gradle-8.10.2-bin.zip
$ ls /opt/gradle/gradle-8.10.2

After setting up, you can run the gradle command to check the version. The gradle command is shown below:

 
	gradle -v

The output of the executed command is shown below:

 
bhagvanarch@Bhagvans-MacBook-Air micronaut % gradle -v         
 
------------------------------------------------------------
Gradle 8.10.2
------------------------------------------------------------
 
Build time:    2024-09-23 21:28:39 UTC
Revision:      415adb9e06a516c44b391edff552fd42139443f7
 
Kotlin:        1.9.24
Groovy:        3.0.22
Ant:           Apache Ant(TM) version 1.10.14 compiled on August 16 2023
Launcher JVM:  23 (Homebrew 23)
Daemon JVM:    /opt/homebrew/Cellar/openjdk/23/libexec/openjdk.jdk/Contents/Home (no JDK specified, using current Java home)
OS:            Mac OS X 14.6.1 aarch64
 
bhagvanarch@Bhagvans-MacBook-Air micronaut %

Now you can create Application java file as shown below:

package org.javacodegeeks.micronaut;

import io.micronaut.runtime.Micronaut;

public class Application {

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

Then, you can create Customer Controller java class. The code is shown below:

package org.javacodegeeks.micronaut;

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

@Controller("/customer") 
public class CustomerController {
    @Get 
    @Produces(MediaType.TEXT_PLAIN) 
    public String index() {
        return "There are no customers yet"; 
    }
}

You can build the application using the command below:

 
./gradlew run

The output for the above commands when executed is shown below:

 
bhagvanarch@Bhagvans-MacBook-Air micronaut % ./gradlew run

> Task :run
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
19:21:59.389 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 261ms. Server Running: http://localhost:8080
 80% EXECUTING [5m 47s]
> :run

You can see the micronaut API running. The output for customer call is shown below:

output

Micronaut Example

2.1 Logging and Monitoring : Micronaut Error handling

Effective error handling also involves logging and monitoring to detect and resolve issues promptly. Micronaut integrates seamlessly with logging frameworks like Logback, allowing you to log information, errors, and exceptions for further analysis because the logs will help in root cause analysis.

Here’s an example of adding logging to the customer API:

package org.javacodegeeks.micronaut;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller("/customer") 
public class CustomerController {
	private static final Logger LOG = LoggerFactory.getLogger(CustomerController.class);
    @Get 
    @Produces(MediaType.TEXT_PLAIN) 
    public String index() {
		LOG.info("in the customer api {}");
        return "There are no customers yet"; 
    }
}

The output when built and executed will show the logging on the console as shown below:

 
bhagvanarch@Bhagvans-MacBook-Air micronaut % ./gradlew run


> Task :run
 __  __ _                                  _   
|  \/  (_) ___ _ __ ___  _ __   __ _ _   _| |_ 
| |\/| | |/ __| '__/ _ \| '_ \ / _` | | | | __|
| |  | | | (__| | | (_) | | | | (_| | |_| | |_ 
|_|  |_|_|\___|_|  \___/|_| |_|\__,_|\__,_|\__|
19:43:03.872 [main] INFO  io.micronaut.runtime.Micronaut - Startup completed in 268ms. Server Running: http://localhost:8080
19:43:17.203 [default-nioEventLoopGroup-1-2] INFO  o.j.micronaut.CustomerController - in the customer api {}
 80% EXECUTING [3m 28s]
> :run


In this example, informational logging for customer API is shown helping you track and investigate issues efficiently.

Now let us look at Error Handling in Micronaut using @Error annotation.

3.  Error Handling in Micronaut – @Error Annotation

Error handling is a fundamental aspect of developing reliable applications. Micronaut, a modern JVM-based framework, offers a sophisticated way to handle errors using @Error annotation. This annotation simplifies the process of managing exceptions and providing meaningful responses, ensuring a smooth user experience.

3.1 Setting Up Error Handlers Using the @Error Annotation

The @Error annotation allows you to define custom error handlers within your controllers. These handlers can manage specific exceptions or HTTP status codes, providing a tailored response. Here’s a basic example:

package org.javacodegeeks.micronaut;

import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Error;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller("/customer") 
public class CustomerController {
	private static final Logger LOG = LoggerFactory.getLogger(CustomerController.class);
    @Get 
    @Produces(MediaType.TEXT_PLAIN) 
    public String index() {
		LOG.info("in the customer api {}");
        return "There are no customers yet"; 
    }
	@Get("/examerror")
	public String errorEndpoint() {
	    throw new RuntimeException("An unexpected error occurred");
	}

	@Error(exception = RuntimeException.class)
	public HttpResponse handleRuntimeException(RuntimeException exception) {
	    return HttpResponse.serverError(" The error message is: " + exception.getMessage());
	}

}

In this example, any RuntimeException thrown within the CustomerController is caught by the handleRuntimeException method, which returns a custom error message “The error message is: An unexpected error occurred”, when called from the browser – http://localhost:8080/customer/examerror

3.2 Handling Specific HTTP Status Codes

Micronaut also allows you to handle specific HTTP status codes using the @Error annotation. This is useful when you need to provide custom responses for different error conditions. Here’s how you can do it:

package org.javacodegeeks.micronaut;

import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.HttpStatus;
import io.micronaut.http.MediaType;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Error;
import io.micronaut.http.hateoas.JsonError;
import io.micronaut.http.hateoas.Link;
import io.micronaut.views.ViewsRenderer;

import java.util.Collections;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Controller("/newresource") 
public class NewResourceController {
	private static final Logger LOG = LoggerFactory.getLogger(NewResourceController.class);
  private final ViewsRenderer viewsRenderer;

   public NewResourceController(ViewsRenderer viewsRenderer) { 
       this.viewsRenderer = viewsRenderer;
   }

   @Error(status = HttpStatus.NOT_FOUND, global = true)  
   public HttpResponse notFound(HttpRequest request) {
	   LOG.error("there is no new resource");
       if (request.getHeaders()
               .accept()
               .stream()
               .anyMatch(mediaType -> mediaType.getName().contains(MediaType.TEXT_HTML))) { 
           return HttpResponse.ok(viewsRenderer.render("notFound", Collections.emptyMap(), request))
                   .contentType(MediaType.TEXT_HTML);
       }

       JsonError error = new JsonError("New Resource is not Found")
               .link(Link.SELF, Link.of(request.getUri()));

       return HttpResponse.notFound()
               .body(error); 
   }

}

In this example, Invoking /customer/newresource triggers the request that results in a 404 Not Found status will be handled by the notFound method, returning a custom error message defined in the resource notFound.vm

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Not Found</title>
</head>
<body>
<h1>NOT FOUND</h1>

<p><b>The page you were looking is moved, deleted or does not exist.</b></p>

<p>This is can be because:</p>

<ul>
    <li>An outdated resource on another site</li>
    <li>A typing error in the address / URL</li>
</ul>
</body>
</html>

Error handling in Micronaut using the @Error annotation is a powerful and flexible way to manage exceptions and provide meaningful responses. By leveraging controller-specific and global error handlers, along with customizing error responses, you can ensure your application handles errors gracefully and maintains a positive user experience. This section has covered the essential steps for implementing effective error handling in Micronaut, equipping you with the tools to manage exceptions seamlessly.

4. Error Handling in Micronaut – using the Exception Handler

Handling errors effectively is a cornerstone of building resilient applications. Micronaut, a modern JVM-based framework, provides a structured way to manage errors using the ExceptionHandler interface. This interface allows developers to create custom error-handling logic, ensuring that exceptions are caught and managed gracefully, enhancing the user experience and maintaining the stability of the application.

4.1 Creating Custom Exception Handlers

To utilize the ExceptionHandler interface, you need to create a class that implements it. This class will define the logic for handling specific exceptions. Here’s a step-by-step guide:

First define the custom Exception:

package org.javacodegeeks.micronaut;

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

Now Implement the ExceptionHandler Interface
Next, create a class that implements the ExceptionHandler interface for your custom exception. This class will contain the logic for handling the exception and returning a meaningful response to the client:

package org.javacodegeeks.micronaut;
@Singleton
public class CustomExceptionHandler implements ExceptionHandler { 
@Override
public HttpResponse handle(HttpRequest request, CustomException exception) {
Map errorResponse = new HashMap();
errorResponse.put("message", exception.getMessage());
errorResponse.put("status", "error");
return HttpResponse.badRequest(errorResponse);
}

4.2 Registering the Exception Handler

Micronaut automatically discovers and registers exception handlers that are annotated with @Singleton. By default, Micronaut scans for these handlers at runtime and applies them to the corresponding exceptions.

4.2.1 Testing the Exception Handler

To see your custom exception handler in action, you can create a simple controller that throws the custom exception:

@Controller("/api")
public class TestController {

@Get("/test")
public String testEndpoint() {
    throw new CustomException("This is a custom error message.");
}
}

When you access the /api/test endpoint, the CustomException will be thrown, and the CustomExceptionHandler will catch it, returning the defined error response – This is a custom error message.

4.2.2 Enhancing Exception Handling Logic

You can further enhance your exception handler by adding more detailed error information, logging the exception, or even notifying external monitoring systems. Here’s an example with added logging:

package org.javacodegeeks.micronaut;
@Singleton
public class CustomExceptionHandler ExceptionHandler { 

private static final Logger LOG = LoggerFactory.getLogger(EnhancedExceptionHandler.class);
@Override
public HttpResponse handle(HttpRequest request, CustomException exception) {
LOG.error("Handling custom exception: {}", exception.getMessage(), exception);
Map errorResponse = new HashMap();
errorResponse.put("message", exception.getMessage());
errorResponse.put("status", "error");
return HttpResponse.badRequest(errorResponse);
}

In this enhanced example, the handler logs the exception details before returning the error response, aiding in troubleshooting and monitoring.

Using the ExceptionHandler interface in Micronaut provides a robust and flexible approach to managing errors. By creating custom exception handlers, you can ensure that your application handles errors in a consistent and meaningful way, enhancing both stability and user experience. This section has covered the essential steps for implementing error handling with the ExceptionHandler interface, equipping you with the knowledge to manage exceptions effectively in your Micronaut applications.

4.3 Global Error Handling

While controller-specific error handling is effective, you may also want to handle exceptions at a global level. This ensures a consistent error handling strategy across your entire application. To create a global error handler, define a new class and annotate it with @ControllerAdvice:

@ControllerAdvice
public class GlobalErrorHandler {

    @Error(global = true, exception = RuntimeException.class)
    public HttpResponse handleGlobalRuntimeException(RuntimeException e) {
        return HttpResponse.serverError("Global error message: " + e.getMessage());
    }
}

The GlobalErrorHandler class catches any RuntimeException not specifically handled within a controller, providing a standardized error response.Micronaut allows further customization of error responses to ensure they are informative and user-friendly. You can include additional details such as error codes, timestamps, and more:

@ControllerAdvice
public class DetailedErrorHandler {

    @Error(global = true, exception = RuntimeException.class)
    public HttpResponse<Map> handleDetailedRuntimeException(RuntimeException e) {
        Map errorDetails = new HashMap();
        errorDetails.put("message", e.getMessage());
        errorDetails.put("timestamp", LocalDateTime.now());
        errorDetails.put("status", HttpStatus.INTERNAL_SERVER_ERROR.getCode());

        return HttpResponse.serverError(errorDetails);
    }
}

In this example, the error response includes a detailed message, the current timestamp, and the HTTP status code, providing more context for debugging and user communication.

5.Conclusion

In summary, effective error handling in Micronaut is vital for developing resilient and user-friendly applications. By leveraging the powerful tools and annotations provided by the framework, such as @Error and the ExceptionHandler interface, developers can create custom and global error handlers tailored to their application’s needs. These mechanisms ensure exceptions are managed gracefully, providing informative and consistent responses to end-users.

Additionally, integrating logging and monitoring enhances the ability to track and address errors promptly, maintaining application stability. Ultimately, mastering error handling in Micronaut equips developers with the skills to build robust systems that can handle unexpected issues seamlessly, ensuring a positive user experience and reliable performance. This comprehensive approach to error management underscores the importance of thoughtful and proactive development practices in the Micronaut ecosystem.

6. Download

Download
You can download the full source code of this example here: Error Handling in Micronaut

Bhagvan Kommadi

Bhagvan Kommadi is the Founder of Architect Corner & has around 19 years experience in the industry, ranging from large scale enterprise development to helping incubate software product start-ups. He has done Masters in Industrial Systems Engineering at Georgia Institute of Technology (1997) and Bachelors in Aerospace Engineering from Indian Institute of Technology, Madras (1993). He is member of IFX forum,Oracle JCP and participant in Java Community Process. He founded Quantica Computacao, the first quantum computing startup in India. Markets and Markets have positioned Quantica Computacao in ‘Emerging Companies’ section of Quantum Computing quadrants. Bhagvan has engineered and developed simulators and tools in the area of quantum technology using IBM Q, Microsoft Q# and Google QScript.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button