How to process hibernate exception using Spring #ControllerAdvice and problem-spring-web - java

Hello Stackoverflow Experts,
how can I process org.hibernate.exception.ConstraintViolationException (and all other hibernate exceptions) and RuntimeException to return the following json to the frontend ?
{
"timestamp": "2020-08-18T21:03:36.174Z",
"code": "CODE_20500",
"status": 500,
"details": "An internal error occurred."
}
Actually I have declared my ControllerDevice and followed the steps of the Baeldung Guide at https://www.baeldung.com/problem-spring-web :
#ControllerAdvice
public class MyErrorHandlingControllerAdvice implements ProblemHandling {
}
I set also theses properties in the file "application.properties"
spring.resources.add-mappings=false
spring.mvc.throw-exception-if-no-handler-found=true
servlet.http.encoding.force=true
However I receive the following message when a duplicated primary key is trying to be saved in the database:
{
"timestamp": "2020-08-20T08:44:11.342+02:00",
"status": 400,
"detail": "could not execute statement; SQL [n/a]; constraint [uq_mykatalog_name]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement"
}
For security reasons we cannot expose the database fields to the public.
I am using these library versions:
springboot version = 2.3.3.RELEASE
implementation "org.zalando:problem-spring-web:0.23.0"
Please, help !
Thank you.

This is because the problem library will, by default, print out the exception message included in the exception class.
What you could do is either:
Wrap the calls to the database in a try/catch clause (to catch Hibernate exceptions) and rethrow a custom exception that will hide the unsecure details.
Implement an ExceptionHandler for the hibernate exceptions and also rethrow a custom ones that will hide details - see the guide here https://www.baeldung.com/exception-handling-for-rest-with-spring solution 1

Solution:
Create a class "CustomException" that extends AbstractThrowableProblem and put your custom fields there. For me was the field "code".
put the code below in your ControllerDevice:
#ControllerAdvice
public class MyErrorHandlingControllerAdvice implements ProblemHandling {
#ExceptionHandler({RuntimeException.class})
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
#ResponseBody CustomException onUnexpectedRuntimeException(RuntimeException e) {
return new CustomException("CODE_20500","An Internal error occurred.", INTERNAL_SERVER_ERROR);
}
}
It will handle any exception that extends RuntimeException, including the org.hibernate.exception.ConstraintViolationException

Related

How to handle 404 exceptions using #ControllerAdvice and #ExceptionHandler?

I have a problem with Spring's exception handling for controllers. I have a class annotated with #RestControllerAdvice with a couple of #ExceptionHandler's, like this:
#ExceptionHandler(HttpRequestMethodNotSupportedException::class)
fun methodNotSupportedException(
exception: HttpRequestMethodNotSupportedException,
request: HttpServletRequest
): ResponseEntity<ApiError> {
logger().error("Method not supported: {}", exception.message)
val methodNotAllowed = HttpStatus.METHOD_NOT_ALLOWED
val apiError = logAndBuildApiError(request, methodNotAllowed, exception)
return ResponseEntity(apiError, methodNotAllowed)
}
and they work perfectly fine. In this case, when I'm trying to use an non-implemented HTTP method like POST:
{
"requestUri": "/api/v1/items",
"status": 405,
"statusText": "Method Not Allowed",
"createdAt": "2023-01-12T16:50:36.55422+02:00",
"errorMessage": "Request method 'POST' not supported"
}
What I would like to achieve is to handle situations when someone is trying to reach an non-existing endpoint, i.e. the correct one is GET http://localhost:8080/api/v1/items.
But when I'm trying to reach http://localhost:8080/api/v1/itemss, which is of course nonexistent, I recieve a regular Spring whitelabel error page, but I would like to receive a JSON like in the former example:
{
"requestUri": "/api/v1/itemss",
"status": 404,
"statusText": "Not Found",
"createdAt": "2023-01-12T16:52:06.932108+02:00",
"errorMessage": "Some error message"
}
How do I implement a #ExceptionHandler so it could handle exceptions related to non-existing resources?
spring.mvc.throw-exception-if-no-handler-found works in conjunction with
spring.mvc.static-path-pattern. By default, the static path pattern is /**, which includes the whitelabel error pages that you're seeing.
See https://github.com/spring-projects/spring-boot/pull/31660
and https://gitter.im/spring-projects/spring-boot?at=62ba1378568c2c30d30790af
and https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#web.servlet.spring-mvc.static-content
Option one is to set these two properties in your configuration.
spring:
mvc:
throw-exception-if-no-handler-found: true
static-path-pattern: /static
Option 2 is to add #EnableWebMvc to your spring boot application, and set the spring.mvc.throw-exception-if-no-handler-found property to true. By adding EnableWebMvc you'll be getting the WebMvcConfigurationSupport bean, which will cause Spring not to initialize the WebMvcAutoConfiguration and thereby not set the static-path-pattern.

Logback does not log ConstraintViolationException in Spring Boot's #ExceptionHandler

I am doing some validation on a query parameter in my Spring Boot web service. In this case it is a parameter that does not match the regex [0-9]{3}. So in the service method, there is a validation:
#Pattern(regexp="[0-9]{3}") #Valid #RequestParam(value = "AngivelseFrekvensForholdUnderkontoArtKode", required = false) String angivelseFrekvensForholdUnderkontoArtKode
(angivelseFrekvensForholdUnderkontoArtKode is just the name of the query parameter)
I am working on a log manager that basically just prints log messages using logback and slf4j.
I have a writeInternalError(exception) in my log manager class which nicely logs an exception when told to:
public void writeInternalError(Exception exception) {
logger.error(exception.getClass().getName(), kv("LogType", exception), kv("LogMessage", exception));
}
except for when the ConstraintViolationException is caught by the #ExceptionHandler in my #ControllerAdvice. No errors are shown, and a Spring log is outputted instead of my expected custom log. When I debug, the logger.error() seems to be executed and no errors are shown.
I have made a quick fix method where I manually extract the information of the exception, but I want to use the same logging method for all exceptions:
public void writeTracelog(Exception exception) {
logger.error(exception.getClass().getName(), kv("LogType", "exception"), kv("ErrorMessage", exception.getMessage()), kv("StackTrace", exception.getStackTrace()));
}
The expected and unexpected logs I get are:
// The Spring log message shown instead of my custom error message:
{
"#timestamp": "2021-06-10T12:13:40.730+02:00",
"#version": "1",
"message": "Resolved [javax.validation.ConstraintViolationException: call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]{3}\"]",
"logger_name": "org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver",
"thread_name": "http-nio-8082-exec-1",
"level": "WARN",
"level_value": 30000
}
// How the log is supposed to look like
{
"#timestamp": "2021-06-10T14:35:18.257+02:00",
"#version": "1",
"message": "javax.validation.ConstraintViolationException",
"logger_name": "ClsLogManager",
"thread_name": "http-nio-8082-exec-1",
"level": "ERROR",
"level_value": 40000,
"LogType": "exception",
"LogMessage": {
"cause": null,
"stackTrace": [...],
"constraintViolations": null,
"message": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]",
"suppressed": [],
"localizedMessage": "call29f0dab4A3094a30A1cdE29c01f28af8.angivelseFrekvensForholdUnderkontoArtKode: must match \"[0-9]"
}
}
When I call writeInternalError() with any other exception, the log is nicely output. I have tried different ways of logging to see what works and what does not as you can see in the handler in the #ControllerAdvice
#ControllerAdvice
public class RestResponseEntityExceptionHandler extends ResponseEntityExceptionHandler {
#ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConflict(ConstraintViolationException ex, HttpServletRequest request) {
...
// Get the invalid parameter from the ConstraintViolationException
if (invalidParameter.equalsIgnoreCase("angivelseFrekvensForholdUnderkontoArtKode")) {
errorMessage = setErrorMessage(request, "422.9", HttpStatus.UNPROCESSABLE_ENTITY.value(), invalidValue);
clsLogManager.writeTracelog(ex); // Outputs customized unwanted log
clsLogManager.writeInternalError(new ConstraintViolationException(null)); // Outputs exception in the format I want
clsLogManager.writeInternalError(ex); // Outputs nothing
responseEntity = writeToAuditlog(request, inputHeaders, errorMessage); // Outputs an info log as it supposed to
return responseEntity; // Outputs the ExceptionHandlerExceptionResolver message after the return
}
// Do something else in case of another error
}
}
It looks like the logger cannot handle the exception, but why doesn't it tell me why, in case that is true, and why is the ExceptionHandlerExceptionResolver doing it instead?
Update:
I looked into ExceptionHandlerExceptionResolver as saver suggested, and found out that the log comes from AbstractHandlerExceptionResolver's logException(). My custom logger class' method gets called before logException(), but it still doesn't print anything. Can it be because it is a ConstraintViolationException that contains the field constraintViolations and that the logger does not know how to handle this?
There is a setWarnLogCategory method that I guess I can switch off if I don't want the Spring log. I just can't find out how. The javadocs for logException in AbstractHandlerExceptionResolver indicate that there is a property for this, but I don't know how to set it.
Update:
The issue is in method subAppend(E event) in OutputStreamAppender that class is parent of ConsoleAppender and in line of encoding:
byte[] byteArray = this.encoder.encode(event);
Encoder tried serialize ConstraintViolationException exception and jackson fails with error: HV000116: getParameterIndex() may only be invoked for nodes of type ElementKind.PARAMETER.
And as result of encoding is empty byte array in case of exception and this is reason why nothing is logged in console. See below:
I don't have a quick fix for that right now.
Old proposal:
I recommend to debug method doResolveHandlerMethodException in ExceptionHandlerExceptionResolver class and you can see there is only one place when spring boot logger can log message with warning level and that logger will work only in case when something happened during invoking handler method for exception (For example: incorrect type of parameter in handler method and so on). You will see the reason why your handler method wasn't called.
Please pay attention to case when class ConstraintViolationException can be located in two different packages:
javax.validation.ConstraintViolationException
org.hibernate.exception.ConstraintViolationException
of course in our case we should use ConstraintViolationException from javax.validation package
Those exceptions are handled in one of the base methods of ResponseEntityExceptionHandler. You need to override it.

How could we use #ExceptionHandler with spring web flux?

In spring web we could use annotation #ExceptionHandler for handling server and client errors for controllers.
I've tried to use this annotation with web-flux controller and it still worked for me, but after some investigation I've found out here
The situation with Spring Web Reactive is more complicated. Because
the reactive streams are evaluted by a different thread than the one
that executes the controllers method, the exceptions won’t be
propagated to the controller thread automatically. This means that the
#ExceptionHandler method will work only for exceptions that are thrown
in the thread that handles the request directly. Exceptions thrown in
the stream will have to be propagated back to the thread if we want to
use the #ExceptionHandler feature. This seems like a bit of a let down
but at the time of writing this Spring 5 is still not released so
error handling might still get better.
So my question is how to propagate back exception to the thread. Is there a good example or article about using #ExceptionHandler and Spring web flux?
Updated:
From spring.io it looks like it's supported, but still lack general understanding
Thanks,
Now it is possible to use the #ExceptionHandler as well as #RestControllerAdvice or even #ControllerAdvice in Spring WebFlux.
Example:
Add the webflux dependency
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
Create your class ExceptionHandler
#RestControllerAdvice
public class ExceptionHandlers {
private static final Logger LOGGER = LoggerFactory.getLogger(ExceptionHandlers.class);
#ExceptionHandler(Exception.class)
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public String serverExceptionHandler(Exception ex) {
LOGGER.error(ex.getMessage(), ex);
return ex.getMessage();
}
}
Create a Controller
#GetMapping(value = "/error", produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public Mono<String> exceptionReturn() {
return Mono.error(new RuntimeException("test error"));
}
Example extracted here:
https://ddcode.net/2019/06/21/spring-5-webflux-exception-handling/
You can use #ExceptionHandler annotated methods to handle errors that happen within the execution of a WebFlux handler (e.g., your controller method). With MVC you can indeed also handle errors happening during the mapping phase, but this is not the case with WebFlux.
Back to your exception propagation question, the article you're sharing is not accurate.
In reactive applications, the request processing can indeed hop from one thread to another at any time, so you can't rely on the "one thread per request" model anymore (think: ThreadLocal).
You don't have to think about exception propagation or how threads are managed, really. For example, the following samples should be equivalent:
#GetMapping("/test")
public Mono<User> showUser() {
throw new IllegalStateException("error message!");
}
#GetMapping("/test")
public Mono<User> showUser() {
return Mono.error(new IllegalStateException("error message!"));
}
Reactor will send those Exceptions as error signals as expected in the Reactive Streams contract (see the "error handling" documentation section for more on that).
not an exact answer to the original question, but a quick way to map your exceptions to http response status is to throw org.springframework.web.server.ResponseStatusException / or create your own subclasses...
Full control over http response status + spring will add a response body with the option to add a reason.
{
"timestamp": 1529138182607,
"path": "/api/notes/f7b.491bc-5c86-4fe6-9ad7-111",
"status": 400,
"error": "Bad Request",
"message": "For input string: \"f7b.491bc\""
}
The following global error handler did the trick for me:
import org.springframework.web.server.ResponseStatusException;
#Slf4j
#RestControllerAdvice
#ConditionalOnWebApplication(type = ConditionalOnWebApplication.Type.REACTIVE)
public class MyCustomReactiveErrorHandling {
#ExceptionHandler(MyCustomNotFoundException.class)
public void handleMyCustomException(MyCustomNotFoundException ex) {
throw new ResponseStatusException(404, "Data not found!", ex);
}
}
Throwing my exceptions returns the correct http status code at the rest service.

Spring multipart file upload size

My application has following properties set
spring.http.multipart.max-file-size = 10MB
and
spring.http.multipart.max-request-size = 10MB
But instead of throwing the Springs default exception below
{ "message": "Could not parse multipart servlet request; nested exception is java.lang.IllegalStateException: org.apache.tomcat.util.http.fileupload.FileUploadBase$SizeLimitExceededException: the request was rejected because its size (12780451) exceeds the configured maximum (10485760)", "type": "MultipartException" }
I would like to throw CustomException with message like FILE_SIZE_TOO_BIG. Is this possible?
Can I use #ControllerAdvice for this ?
There're a lot of ways to handle exceptions in Spring application. See exception handling in Spring MVC topic. I think GlobalControllerExceptionHandler taken from the provided article will fit your requirements. Try this handler:
#ControllerAdvice
class GlobalControllerExceptionHandler {
#ResponseStatus(value = HttpStatus.PAYLOAD_TOO_LARGE)
#ExceptionHandler(MultipartException.class)
public void handleMultipart(MultipartException exception) {
// to do
}
}
If you get this class picked up by component scan, it should handle MultipartExceptions thrown from any controller.

Where does Spring Data REST build Exception JSON reply?

I'm using Spring Boot 1.5.3, Spring Data REST, Spring HATEOAS, Hibernate.
Spring Data REST manage in a pretty way exceptions, returning a well formatted JSON object like this:
{
"timestamp": "2017-06-24T16:08:54.107+0000",
"status": 500,
"error": "Internal Server Error",
"exception": "org.springframework.dao.InvalidDataAccessApiUsageException",
"message": "org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved beforeQuery current operation : com.test.server.model.workflows.WorkSession.checkPoint -> com.test.server.model.checkpoints.CheckPoint",
"path": "/api/v1/workSessions/start"
}
I need to localize exception messages and I'd like to keep the same JSON format of Spring Data REST and take a look how they create the exception object.
I'm looking for the code where the exception is created in source code but I am not able to find that. Maybe ExceptionMessage is useful but it has not the structure of the object that at the end arrive to the user.
Where is the point where the exception is created?
Finally I found a useful link before I didn't see: Modify default JSON error response from Spring Boot Rest Controller.
So the object I was looking for is here: https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/servlet/error/DefaultErrorAttributes.java
there is an answer , from useful link
As described in the documentation on error handling, you can provide
your own bean that implements ErrorAttributes to take control of the
content.
here is example from documentation :
#ControllerAdvice(basePackageClasses = FooController.class)
public class FooControllerAdvice extends ResponseEntityExceptionHandler {
#ExceptionHandler(YourException.class)
#ResponseBody
ResponseEntity<?> handleControllerException(HttpServletRequest request, Throwable ex) {
HttpStatus status = getStatus(request);
return new ResponseEntity<>(new CustomErrorType(status.value(), ex.getMessage()), status);
}
private HttpStatus getStatus(HttpServletRequest request) {
Integer statusCode = (Integer) request.getAttribute("javax.servlet.error.status_code");
if (statusCode == null) {
return HttpStatus.INTERNAL_SERVER_ERROR;
}
return HttpStatus.valueOf(statusCode);
}
}
just inject Locale into handleControllerException method and MessageSource into advice , and in handleControllerException get localize exception messages that you need

Categories

Resources