When building web applications or REST APIs, things can go wrong:
- Client sends invalid data.
- Requested resource is not found.
- Server encounters an unexpected error.
✅ Handling errors gracefully is extremely important.
Micronaut provides simple yet powerful ways to:
- Handle HTTP errors.
- Customize error responses.
- Create user-friendly error pages.
In this post, you’ll learn HTTP Errors and Custom Error Pages in Micronaut step-by-step with simple examples.

Why Proper Error Handling Matters
- Better User Experience: Users get clear messages instead of cryptic errors.
- Consistent API responses: Clients (mobile apps, frontends, etc.) know what to expect.
- Easier Debugging: Well-structured error info helps you fix issues faster.
- Security: Avoid exposing sensitive details accidentally.
Types of Errors
| Type | Example |
|---|---|
| Client Errors (4xx) | 400 Bad Request, 404 Not Found |
| Server Errors (5xx) | 500 Internal Server Error |
| Custom Business Errors | Validation failed, Resource already exists |
Micronaut allows you to handle all of these easily.
Default Error Handling in Micronaut
Micronaut automatically handles many HTTP errors.
For example:
- If you access a non-existing route → 404 Not Found
- If a request body is invalid → 400 Bad Request
By default, Micronaut returns a JSON response like:
{
"message": "Page Not Found"
}
But usually, you want custom error responses that fit your app.
Scenario Example: School Management System
We are building a simple REST API for schools.
If a student with a given ID is not found, we want to return a custom error message.
Step 1: Create a Micronaut Project
mn create-app com.kscodes.micronaut.errorhandling --build=maven --lang=java
Step 2: Create a Student Class
package com.kscodes.micronaut.errorhandling;
public class Student {
private Long id;
private String name;
private String grade;
public Student() {
}
public Student(Long id, String name, String grade) {
this.id = id;
this.name = name;
this.grade = grade;
}
// Getters and setters omitted for brevity
}
Step 3: Create a Custom Exception
We’ll throw this exception when a student is not found.
package com.kscodes.micronaut.errorhandling;
public class StudentNotFoundException extends RuntimeException {
public StudentNotFoundException(Long id) {
super("Student with id " + id + " not found");
}
}
Step 4: Create a Controller
package com.kscodes.micronaut.errorhandling;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
@Controller("/students")
public class StudentController {
@Get("/{id}")
public Student getStudent(Long id) {
if (id == 1) {
return new Student(1L, "John Doe", "5th");
} else {
throw new StudentNotFoundException(id);
}
}
}
✅ If student is not found, we throw our custom exception.
Step 5: Handle the Exception Globally
Now let’s create a global exception handler.
package com.kscodes.micronaut.errorhandling;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.server.exceptions.ExceptionHandler;
import jakarta.inject.Singleton;
@Produces
@Singleton
public class StudentNotFoundExceptionHandler implements ExceptionHandler> {
@Override
public HttpResponse handle(HttpRequest request, StudentNotFoundException exception) {
ErrorResponse error = new ErrorResponse(404, exception.getMessage());
return HttpResponse.notFound(error);
}
}
✅ Explanation:
- We implement
ExceptionHandler. - Return a custom JSON error response.
Step 6: Create ErrorResponse Class
This class defines the structure of our custom error message.
package com.kscodes.micronaut.errorhandling;
public class ErrorResponse {
private int status;
private String message;
public ErrorResponse() {
}
public ErrorResponse(int status, String message) {
this.status = status;
this.message = message;
}
// Getters and setters omitted for brevity
}
Step 7: Test the Error Handling
Run your app:
mvn mn:run
Success case:
http://localhost:8080/students/1
{
"id": 1,
"name": "John Doe",
"grade": "5th"
}
Error case:
http://localhost:8080/students/2
{
"status": 404,
"message": "Student with id 2 not found"
}
🎯 Custom error handling works perfectly!
Bonus: Handle All Other Exceptions Globally
You can even create a global fallback handler:
package com.kscodes.micronaut.errorhandling;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.annotation.Produces;
import io.micronaut.http.server.exceptions.ExceptionHandler;
import jakarta.inject.Singleton;
@Produces
@Singleton
public class GlobalExceptionHandler implements ExceptionHandler> {
@Override
public HttpResponse handle(HttpRequest request, Exception exception) {
ErrorResponse error = new ErrorResponse(500, "Internal Server Error");
return HttpResponse.serverError(error);
}
}
Optional: Custom Error Pages (For Web Apps)
For web applications (not APIs), you can also define custom HTML error pages.
Example:
- Create a file:
src/main/resources/views/errors/404.html
Page Not Found
Oops! Page Not Found.
✅ Micronaut will automatically serve this page on 404 errors for browser clients.
Summary Table
| Feature | Micronaut |
|---|---|
| Global Exception Handling | ExceptionHandler interface |
| Custom JSON Error | Create response DTO |
| Handle 404, 500, etc. | Fully supported |
| Custom Error Pages | Serve HTML files under /views/errors |
Conclusion
- ✅ HTTP Errors and Custom Error Pages in Micronaut are easy to handle
- ✅ Use
ExceptionHandlerto catch and handle exceptions globally. - ✅ Customize both API responses and web error pages.
- ✅ Your users and API clients will love your clean error messages!
Further Reading
- Micronaut Exception Handling Official Docs
👉 https://docs.micronaut.io/latest/guide/#exceptionHandler - Micronaut Views and Error Pages
👉 https://docs.micronaut.io/latest/guide/#views