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.
Related
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.
In my Spring Boot application, I specified my API using OpenApi 3.0.0. When I test its response to bad input, I'm not happy with some of the error messages. The messages are useful when Hibernate can't handle my input. They include the class, field, and even the illegal value. But when Spring Boot rejects my input without even entering my code, I just get the vague message The request cannot be fulfilled due to bad syntax. There's no information about what field is bad, or what object holds the bad field value.
When I specify my DTO in the .yaml file, two fields are required:
MenuItemOptionDto:
type: object
description: Option for a MenuItem
properties:
name:
type: string
deltaPrice:
type: number
description: Floating point price. Strings are easier to work with.
id:
type: integer
format: int32
required:
- name
- deltaPrice
But suppose I submit a DTO with a missing deltaPrice, like this: {"name": "onions"} The error message just says The request cannot be fulfilled due to bad syntax. I want the error message to say which DTO is incorrect, and which field is missing.
I have specified three relevant application properties. Any one of these will give me Hibernate validation error messages, but none give me spring-boot validation messages:
server.error.include-message=always
server.error.include-binding-errors=always
server.error.include-exception=true
And I've received advise to add a validator bean to my main application, which didn't help:
#ComponentScan(basePackages = {"com.myWork.dummy","org.openapitools",})
#EnableCaching
#SpringBootApplication
public class ServerMaster implements CommandLineRunner {
private static final Logger log = LoggerFactory.getLogger(ServerMaster.class);
public static void main(String[] args) {
new SpringApplication(ServerMaster.class).run(args);
}
#Override
public void run(String... arg0) { ... }
// This was suggested at https://stackoverflow.com/questions/49538896/spring-boot-error-message-doesnt-work
// in order to give me better error messages when OpenAPI validations are triggered, but it doesn't help.
#Bean public Validator validator() {
return new LocalValidatorFactoryBean();
}
}
When I generate the code, it doesn't matter if I turn on the performBeanValidation or useBeanValidation options. The generated code doesn't change. Either way, the #NotNull annotations are applied to the getters for the name and deltaPrice fields, and these are getting honored by the server, but without useful error messages.
Finally, I'm using Spring-Boot 2.3.4, and I declare a dependency on Spring Boot annotations:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Spring-Boot correctly rejects the input because the OpenAPI generator puts #NotNull annotations on the getters of the generated MenuItemOptionDTO, but since the code is generated, I can't customize them with an error message, and I don't want to turn off the generator. How can I get Spring or OpenAPI to give me better error messages?
Test Case
To see these messages in action, check out the code at https://github.com/SwingGuy1024/SpringBootDemo.22.05.25
The default SpringBoot error-handler does not provide a response body for MethodArgumentNotValidException:
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return this.handleExceptionInternal(ex, (Object)null, headers, status, request);
}
The good news: you can override this in your GlobalResponseExceptionHandler class:
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) {
return handleExceptionInternal(ex, ex.getBindingResult(), headers, status, request);
}
In the above code, we simply return the entire binding-result as the response body. If you want to, you can tweak this (e.g. only include the errors).
When you call the controller with the invalid payload, you'll now get the following response:
{
"timestamp": "2022-05-28T19:40:47.295+00:00",
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"message": "Validation failed for object='menuItemOptionDto'. Error count: 1",
"errors": [
{
"codes": [
"NotNull.menuItemOptionDto.deltaPrice",
"NotNull.deltaPrice",
"NotNull.java.math.BigDecimal",
"NotNull"
],
"arguments": [
{
"codes": [
"menuItemOptionDto.deltaPrice",
"deltaPrice"
],
"arguments": null,
"defaultMessage": "deltaPrice",
"code": "deltaPrice"
}
],
"defaultMessage": "must not be null",
"objectName": "menuItemOptionDto",
"field": "deltaPrice",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotNull"
}
],
"path": "/demo/admin/menuItem/addOption/1"
}
Based on OpenAPI generator to spring-boot with custom java validations
You can add some another validation layer in your code, which is independent of OpenAPI generator. This layer will be called from
PetsController and PetsController will validate only basic OpenAPI
known constraints.
You can add you validations not via annotations, but via xml config as shown here.
maybe something else.
Hack it a bit. I was looking for a solution in which my custom validation will be defined in OpenAPI spec same way as “required”.
Naturally I decided not to use solutions 1 or 2 (even thought it
might be the right way for a lot of cases). I found out the
openapi-generator actually provides a way of modifying the way the
code is generated. That means that I can actually define custom
constraint in OpenAPI specs as my own made up properties.
Please follow instructions in the above link for implementing last method.
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
Error stacktrace is not printed in console for the custom exception that is annotated with #ResponseStatus
#ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public class InternalErrorException extends RuntimeException {
public InternalErrorException(String message) {
super(message);
}
public InternalErrorException(String message, Throwable throwable) {
super(message, throwable);
}
}
Throwing exception like throw new InternalErrorException("error", e), never get the stacktrace printed in the console unlesss I remove the annotation #ResponseStatus
How could I get it printed while keeping the annotation #ResponseStatus?
See Annotation Type ResponseStatus API doc.
Warning: when using this annotation on an exception class, or when setting the reason attribute of this annotation, the HttpServletResponse.sendError method will be used.
With HttpServletResponse.sendError, the response is considered complete and should not be written to any further. Furthermore, the Servlet container will typically write an HTML error page therefore making the use of a reason unsuitable for REST APIs. For such cases it is preferable to use a ResponseEntity as a return type and avoid the use of #ResponseStatus altogether.
HttpServletResponse.sendError does not throw your error and I guess it is never logged because of that.
Maybe you want to implement exception handler for that exception to get it logged.
Related question
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.