From dd567fa21d98f57e9596ca8fd37c45a01bc1018d Mon Sep 17 00:00:00 2001 From: Stelios Anastasakis Date: Fri, 6 Sep 2024 15:40:24 +0300 Subject: [PATCH] [BAEL-6574] Introduced code examples for error handling in micronaut * Examples for error annotation, both for exceptions and status codes * Examples for ExceptionHandler implementation for specific exceptions --- .../ServerApplication.java | 10 ++ .../controller/ErroneousController.java | 72 ++++++++++ .../controller/ProbesController.java | 25 ++++ .../error/CustomChildException.java | 7 + .../error/CustomException.java | 7 + .../handlers/CustomExceptionHandler.java | 25 ++++ .../error/handlers/NotFoundController.java | 24 ++++ .../ServerApplicationUnitTest.java | 131 ++++++++++++++++++ 8 files changed, 301 insertions(+) create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplication.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ErroneousController.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ProbesController.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomChildException.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomException.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/CustomExceptionHandler.java create mode 100644 microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/NotFoundController.java create mode 100644 microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplicationUnitTest.java diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplication.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplication.java new file mode 100644 index 000000000000..2c48d71c3d2c --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplication.java @@ -0,0 +1,10 @@ +package com.baeldung.micronaut.globalexceptionhandler; + +import io.micronaut.runtime.Micronaut; + +public class ServerApplication { + + public static void main(String[] args) { + Micronaut.run(ServerApplication.class, args); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ErroneousController.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ErroneousController.java new file mode 100644 index 000000000000..93201f11ff7a --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ErroneousController.java @@ -0,0 +1,72 @@ +package com.baeldung.micronaut.globalexceptionhandler.controller; + +import com.baeldung.micronaut.globalexceptionhandler.error.CustomChildException; +import com.baeldung.micronaut.globalexceptionhandler.error.CustomException; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Error; +import io.micronaut.http.annotation.Get; +import io.micronaut.http.annotation.Header; +import io.micronaut.http.hateoas.JsonError; +import io.micronaut.http.hateoas.Link; +import jakarta.annotation.Nullable; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Controller("/erroneous-endpoint") +public class ErroneousController { + + @Get("/not-found-error") + public HttpResponse endpoint1() { + log.info("endpoint1"); + + return HttpResponse.notFound(); + } + + @Get("/internal-server-error") + public HttpResponse endpoint2(@Nullable @Header("skip-error") String isErrorSkipped) { + log.info("endpoint2"); + if (isErrorSkipped == null) { + throw new RuntimeException("something went wrong"); + } + + return HttpResponse.ok("Endpoint 2"); + } + + @Get("/custom-error") + public HttpResponse endpoint3(@Nullable @Header("skip-error") String isErrorSkipped) { + log.info("endpoint3"); + if (isErrorSkipped == null) { + throw new CustomException("something else went wrong"); + } + + return HttpResponse.ok("Endpoint 3"); + } + + @Get("/custom-child-error") + public HttpResponse 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"); + } + + @Error(exception = UnsupportedOperationException.class) + public HttpResponse unsupportedOperationExceptions(HttpRequest request) { + log.info("Unsupported Operation Exception handled"); + JsonError error = new JsonError("Unsupported Operation").link(Link.SELF, Link.of(request.getUri())); + + return HttpResponse. notFound() + .body(error); + } + + @Get("/unsupported-operation") + public HttpResponse endpoint5() { + log.info("endpoint5"); + throw new UnsupportedOperationException(); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ProbesController.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ProbesController.java new file mode 100644 index 000000000000..fb4baa7666b4 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/controller/ProbesController.java @@ -0,0 +1,25 @@ +package com.baeldung.micronaut.globalexceptionhandler.controller; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Controller("/probe") +public class ProbesController { + + @Get("/liveness") + public HttpResponse endpoint1() { + log.info("endpoint1"); + + return HttpResponse.ok(); + } + + @Get("/readiness") + public HttpResponse endpoint2() { + log.info("endpoint2"); + + throw new UnsupportedOperationException(); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomChildException.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomChildException.java new file mode 100644 index 000000000000..09a1a725e464 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomChildException.java @@ -0,0 +1,7 @@ +package com.baeldung.micronaut.globalexceptionhandler.error; + +public class CustomChildException extends CustomException { + public CustomChildException(String message) { + super(message); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomException.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomException.java new file mode 100644 index 000000000000..aa135d95c2bd --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/CustomException.java @@ -0,0 +1,7 @@ +package com.baeldung.micronaut.globalexceptionhandler.error; + +public class CustomException extends RuntimeException { + public CustomException(String message) { + super(message); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/CustomExceptionHandler.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/CustomExceptionHandler.java new file mode 100644 index 000000000000..82a09e05007f --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/CustomExceptionHandler.java @@ -0,0 +1,25 @@ +package com.baeldung.micronaut.globalexceptionhandler.error.handlers; + +import com.baeldung.micronaut.globalexceptionhandler.error.CustomException; + +import io.micronaut.context.annotation.Requires; +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; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@Produces +@Singleton +@Requires(classes = { CustomException.class, ExceptionHandler.class }) +public class CustomExceptionHandler implements ExceptionHandler> { + + @Override + public HttpResponse handle(HttpRequest request, CustomException exception) { + log.info("handling CustomException: [{}]", exception.getMessage()); + + return HttpResponse.ok("Custom Exception was handled"); + } +} diff --git a/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/NotFoundController.java b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/NotFoundController.java new file mode 100644 index 000000000000..47ee29a27ad5 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/main/java/com/baeldung/micronaut/globalexceptionhandler/error/handlers/NotFoundController.java @@ -0,0 +1,24 @@ +package com.baeldung.micronaut.globalexceptionhandler.error.handlers; + +import io.micronaut.http.HttpRequest; +import io.micronaut.http.HttpResponse; +import io.micronaut.http.HttpStatus; +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 lombok.extern.slf4j.Slf4j; + +@Slf4j +@Controller("/notfound") +public class NotFoundController { + + @Error(status = HttpStatus.NOT_FOUND, global = true) + public HttpResponse notFound(HttpRequest request) { + log.info("not found error handled"); + JsonError error = new JsonError("Page Not Found").link(Link.SELF, Link.of(request.getUri())); + + return HttpResponse. notFound() + .body(error); + } +} diff --git a/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplicationUnitTest.java b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplicationUnitTest.java new file mode 100644 index 000000000000..28ec8981c414 --- /dev/null +++ b/microservices-modules/micronaut-configuration/src/test/java/com/baeldung/micronaut/globalexceptionhandler/ServerApplicationUnitTest.java @@ -0,0 +1,131 @@ +package com.baeldung.micronaut.globalexceptionhandler; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.emptyOrNullString; +import static org.hamcrest.Matchers.is; + +import org.hamcrest.Matchers; +import org.junit.jupiter.api.Test; + +import io.micronaut.test.extensions.junit5.annotation.MicronautTest; +import io.restassured.specification.RequestSpecification; + +@MicronautTest +class ServerApplicationUnitTest { + + private static final String ERRONEOUS_ENDPOINTS_PATH = "micronaut-configuration-tutorials/erroneous-endpoint"; + private static final String PROBES_ENDPOINTS_PATH = "micronaut-configuration-tutorials/probe"; + + @Test + public void givenNotFoundStatusHandler_whenRequestTheHandlerEndpoint_thenResponseIsNotFound(RequestSpecification spec) { + spec.given() + .basePath("micronaut-configuration-tutorials") + .header("some-header", "some-value") + .when() + .get("/notfound") + .then() + .statusCode(404) + .body(Matchers.containsString("\"message\":\"Page Not Found\",\"_links\":")); + } + + @Test + public void givenNotFoundStatusHandler_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\":")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatThrowsDifferentError_thenResponseIsNotHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .when() + .get("/internal-server-error") + .then() + .statusCode(500) + .body(containsString("\"message\":\"Internal Server Error\"")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatThrowsDifferentErrorIsSkipped_thenResponseIsNotHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .header("skip-error", true) + .when() + .get("/internal-server-error") + .then() + .statusCode(200) + .body(is("Endpoint 2")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatThrowsCustomException_thenResponseIsHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .when() + .get("/custom-error") + .then() + .statusCode(200) + .body(is("Custom Exception was handled")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatThrowsCustomExceptionIsSkipped_thenResponseIsNotHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .header("skip-error", true) + .when() + .get("/custom-error") + .then() + .statusCode(200) + .body(is("Endpoint 3")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatThrowsCustomChildException_thenResponseIsHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .when() + .get("/custom-child-error") + .then() + .statusCode(200) + .body(is("Custom Exception was handled")); + } + + @Test + public void givenCustomExceptionHandler_whenRequestThatIsOK_thenResponseIsNotHandled(RequestSpecification spec) { + spec.given() + .basePath(PROBES_ENDPOINTS_PATH) + .when() + .get("/liveness") + .then() + .statusCode(200) + .body(emptyOrNullString()); + } + + @Test + public void givenLocalUnsupportedOperationExceptionHandler_whenRequestThatThrowsThisException_thenResponseIsHandled(RequestSpecification spec) { + spec.given() + .basePath(ERRONEOUS_ENDPOINTS_PATH) + .when() + .get("/unsupported-operation") + .then() + .statusCode(404) + .body(containsString("\"message\":\"Unsupported Operation\"")); + } + + @Test + public void givenLocalUnsupportedOperationExceptionHandler_whenRequestThatThrowsThisExceptionInOtherController_thenResponseIsNotHandled(RequestSpecification spec) { + spec.given() + .basePath(PROBES_ENDPOINTS_PATH) + .when() + .get("/readiness") + .then() + .statusCode(500) + .body(containsString("\"message\":\"Internal Server Error\"")); + } +}