Httpmessagenotreadableexception json parse error cannot deserialize value of type

How to show conversion error along with other validation errors when creating REST API? Spring Boot Spring MVC REST Controller @Data @AllArgsConstructor class DTO { @NotNull @Min(1) p...

How to show conversion error along with other validation errors when creating REST API?

  • Spring Boot
  • Spring MVC
  • REST Controller
@Data
@AllArgsConstructor
class DTO
{
    @NotNull
    @Min(1)
    private Integer id;
}

@RestController
class ExampleController
{
    @PostMapping("/")
    public void createEndpoint(@Valid @RequestBody DTO dto) {
        return "{"message": "OK"}";
    }
}

When I make request to this endpoint,

POST /
{
    "id: "abrakadabra"
}

I would like to get something like this

{
    "errors": {
        "id": [
            { code: "invalid_format", message: "Field must contain only digits" }
        ]
    }
}

What I actually get?

Resolved [org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `int` from String "abrakadabra": not a valid `int` value;
nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `int` from String "abrakadabra": not a valid `int` value
at [Source: (PushbackInputStream); line: 2, column: 24] (through reference chain: com.mylid.back.dtos.CreateCompanyDto["pricing_plan_id"])]

I know that I can create custom annotation for validation.
BUT the problem is that the process does not reach validation. It fails on deserialization step.

[Possible way]

There is one dirty trick, to change Integer type in DTO to String. Create custom annotation to check if String contains only digits. Then manually map this String from DTO to Integer in Entity. Then save to database.

What are other ways how to solve this problem?
It is very weird, that there is a few topics on Google and StackOverFlow for this particular problem. And on almost every page the accepted answer is «it is not our problem, API clients should pass integer».

In PHP we can easily do it with ‘integer’ validator, almost every framework has it, and it will not prevent other fields from validation if I pass «abracadabra» instead of «1».

2021-08-02

This Stackoverflow Topic
comes up with two questions.

  • Q1: how to distinguish exceptions between
    • data binding when conversion http request body to object
    • customized validation defined as annotation on that object
  • Q2: how to display valuable information when data binding fails

All the code can be found in this repo. All test code related to
this topic is under package com.example.demo.validation.

The main data structure to be used:

1
2
3
4
5
6
7
8
9
@Data
public class Person {

@Digits(integer = 200, fraction = 0, message = "code should be number and no larger than 200")
private int ageInt;

@Digits(integer = 200, fraction = 0, message = "code should be number and no larger than 200")
private String ageString;
}

The project can be run with command:

  • gradlew clean bootRun

1 Distinguish Exceptions

It is possible to achieve this by ControllerAdvice. The most important thing is to find the concise exception class
thrown, and in our case, it is org.springframework.http.converter.HttpMessageNotReadableException.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
package com.example.demo.validation;

import org.springframework.http.HttpStatus;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.stereotype.Component;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

@Component
@ControllerAdvice
public class CustomExceptionHandlerResolver {

private static final int COMMON_PARAMETER_ERROR_CODE = 42;

/**
* For errors from data binding, for example, try to attach "hello" to a int field. Treat this as http-level error,
* so response with http code 400.
* <p>
* - Error messages are attached to message field of response body
* - code field of response body is not important and also assigned with 400
* <p>
* Another option is totally use http concept and attach error message to an http head named error, thus no need to
* initiate a DataContainer object.
* <p>
* Example text from exception.getMessage():
* <p>
* - JSON parse error: Cannot deserialize value of type `int` from String "1a": not a valid `int` value....
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.BAD_REQUEST)
public DataContainer<?> handleBindException(
HttpServletRequest request, HttpServletResponse response, HttpMessageNotReadableException exception) {
System.out.println("In handleBindException");
System.out.println(exception);
return new DataContainer(400, exception.getMessage());
}

/**
* For errors from regulation violation defined in annotations such as @NotBlank with @Valid aside the @RequestBody
* object. Treat this as business error, so response with http code 200.
* <p>
* - Error messages defined in validation annotations are attached to message field of response body
* - code field of response body is important and should be defined in the whole API system
*/
@ExceptionHandler
@ResponseBody
@ResponseStatus(HttpStatus.OK)
protected DataContainer handleMethodArgumentNotValidException(
HttpServletRequest request, HttpServletResponse response, MethodArgumentNotValidException ex)
throws IOException {
System.out.println("In handleMethodArgumentNotValidException");
List<FieldError> errors = ex.getBindingResult().getFieldErrors();
String errorMessages = errors.stream()
.map(FieldError::getDefaultMessage)
.collect(Collectors.joining(";"));
return new DataContainer(COMMON_PARAMETER_ERROR_CODE, errorMessages);
}

}

2 Display Valuable Information for Data Binding Exception

The text got from HttpMessageNotReadableException is created by the spring framework and is kinda robotic. We can use
customize json deserializer to make the message more readable. Jackson itself doesn’t support
customized information to be thrown in data binding fail yet.

Add a field to Person:

1
2
@JsonDeserialize(using = MyIntDeserializer.class)
private int ageStringWithCustomizeErrorMessage;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MyIntDeserializer extends JsonDeserializer<Integer> {

@Override
public Integer deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
String text = p.getText();
if (text == null || text.equals("")) {
return 0;
}

int result;
try {
result = Integer.parseInt(text);
} catch (Exception ex) {
throw new RuntimeException("ageStringWithCustomizeErrorMessage must be number");
}

if (result < 0 || result >= 200) {
throw new RuntimeException("ageStringWithCustomizeErrorMessage must in (0, 200)");
}

return result;
}
}

3 Test Step

3.1 Test Validation Fail

Request:

  • curl -X POST “http://localhost:8080/validationTest» -H “accept: /“ -H “Content-Type: application/json” -d “{ ”ageInt”: ”0”, ”ageString”: ”1a”}”

Response:

1
2
3
4
5
{
"code": 42,
"message": "code should be number and not larger than 200",
"data": null
}

3.2 Test Data Binding Fail

Request:

  • curl -X POST “http://localhost:8080/validationTest» -H “accept: /“ -H “Content-Type: application/json” -d “{ ”ageInt”: ”1a”, ”ageString”: ”0”}”

Response:

1
2
3
4
5
{
"code": 400,
"message": "JSON parse error: Cannot deserialize value of type `int` from String "1a": not a valid `int` value; nested exception is com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `int` from String "1a": not a valid `int` valuen at [Source: (PushbackInputStream); line: 2, column: 13] (through reference chain: com.example.demo.validation.Person["ageInt"])",
"data": null
}

3.3 Test Data Binding in Customized Deserializer

Request:

  • curl -X POST “http://localhost:8080/validationTest» -H “accept: /“ -H “Content-Type: application/json” -d “{ ”ageInt”: 0, ”ageString”: ”0”, ”ageStringWithCustomizeErrorMessage”: ”aa”}”

Response:

1
2
3
4
5
{
"code": 400,
"message": "JSON parse error: ageStringWithCustomizeErrorMessage must be number; nested exception is com.fasterxml.jackson.databind.JsonMappingException: ageStringWithCustomizeErrorMessage must be number (through reference chain: com.example.demo.validation.Person["ageStringWithCustomizeErrorMessage"])",
"data": null
}

Request:

  • curl -X POST “http://localhost:8080/validationTest» -H “accept: /“ -H “Content-Type: application/json” -d “{ ”ageInt”: 0, ”ageString”: ”0”, ”ageStringWithCustomizeErrorMessage”: ”-1”}”

Response:

1
2
3
4
5
{
"code": 400,
"message": "JSON parse error: ageStringWithCustomizeErrorMessage must in (0, 200); nested exception is com.fasterxml.jackson.databind.JsonMappingException: ageStringWithCustomizeErrorMessage must in (0, 200) (through reference chain: com.example.demo.validation.Person["ageStringWithCustomizeErrorMessage"])",
"data": null
}

It’s still a little robotic, but with more concise information offered by code.

3.4 Tips

Open http://localhost:8080/swagger-ui/#/validation-test-controller/validationTestUsingPOST
in browser and all requests can be made in web page easily.

Today We are Going To Solve JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String in Java. Here we will Discuss All Possible Solutions and How this error Occurs So let’s get started with this Article.

Contents

  • 1 How to Fix JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String Error?
    • 1.1 Solution 1 : Use the “yyyy-MM-dd’T’HH:mm:ss.SSS” format
    • 1.2 Solution 2 : Use the below code
  • 2 Conclusion
    • 2.1 Also Read These Solutions
  1. How to Fix JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String Error?

    To Fix JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String Error just Use the “yyyy-MM-dd’T’HH:mm:ss.SSS” format. You should use the “yyyy-MM-dd’T’HH:mm:ss.SSS” format to solve this error. So just use it like below
    @JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S]") private LocalDateTime updatedTime;

  2. JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String

    To Fix JSON parse error: Cannot deserialize value of type `java.time.LocalDateTime` from String Error just Use the below code. You can use the below one code to solve this error @JsonDeserialize(using = LocalDateTimeDeserializer.class) @JsonSerialize(using = LocalDateTimeSerializer.class) private LocalDateTime pickupDate;

Solution 1 : Use the “yyyy-MM-dd’T’HH:mm:ss.SSS” format

You should use the “yyyy-MM-dd’T’HH:mm:ss.SSS” format to solve this error. So just use it like below

@JsonFormat(shape=JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd'T'HH:mm:ss[.SSS][.SS][.S]")
private LocalDateTime updatedTime;

Solution 2 : Use the below code

You can use the below one code to solve this error

@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime pickupDate;

Conclusion

So these were all possible solutions to this error. I hope your error has been solved by this article. In the comments, tell us which solution worked? If you liked our article, please share it on your social media and comment on your suggestions. Thank you.

Also Read These Solutions

  • Cannot open local file – Chrome: Not allowed to load local resource
  • OpenCV(4.1.2) error: (-215:Assertion failed) !ssize.empty() in function ‘cv::resize’
  • OpenSSL Error messages: error:1416F086:SSL routines:tls_process_server_certificate:certificate verify failed
  • ESLint: Component definition is missing displayName (react/display-name)
  • “IsADirectoryError: [Errno 21] Is a directory: It is a file”

Mosaic pattern

Photo by Annie Spratt

Spring Boot projects primarily use the JSON library Jackson to serialize and deserialize objects. It is especially useful that Jackson automatically serializes objects returned from REST APIs and deserializes complex type parameters like @RequestBody.

In a Spring Boot project the automatically registered MappingJackson2HttpMessageConverter is usually enough and makes JSON conversions simple, but this may have some issues which need custom configuration. Let’s go over a few good practices for them.

Configuring a Custom Jackson ObjectMapper

In Spring REST projects a custom implementation of MappingJackson2HttpMessageConverter helps to create the custom ObjectMapper, as seen below. Whatever custom implementation you need to add to the custom ObjectMapper can be handled by this custom converter:

public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private ObjectMapper initCustomObjectMapper() {
        ObjectMapper customObjectMapper = new ObjectMapper();
        return customObjectMapper;
    }

    // ...
}

Additionally, some MappingJackson2HttpMessageConverter methods, such as writeInternal, can be useful to override in certain cases. I’ll give a few examples in this article.

In Spring Boot you also need to register a custom MappingJackson2HttpMessageConverter like below:

@Bean
MappingJackson2HttpMessageConverter mappingJackson2HttpMessageConverter() {
    return new CustomHttpMessageConverter();
}

Serialization

Pretty-printing

Pretty-printing in Jackson is disabled by default. By enabling SerializationFeature.INDENT_OUTPUT in the ObjectMapper configuration pretty-print output is enabled (as in the example below). Normally a custom ObjectMapper is not necessary for setting the pretty-print configuration. In some cases, however, like one case of mine in a recent customer project, this configuration might be necessary.

For example, passing a URL parameter can enable pretty-printing. In this case having a custom ObjectMapper with pretty-print enabled and keeping the default ObjectMapper of MappingJackson2HttpMessageConverter as is could be a better option.

public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private ObjectMapper initiatePrettyObjectMapper() {
        ObjectMapper customObjectMapper = new ObjectMapper();
        customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        // additional indentation for arrays
        DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
        pp.indentArraysWith(new DefaultIndenter());
        customObjectMapper.setDefaultPrettyPrinter(pp);

        return customObjectMapper;
    }

}

Conditionally Filtering the Fields

When serializing a response object you may need to include or ignore one or more fields depending on their values. Let’s assume a model class UserResponse like below.

Notice that we used @JsonIgnore which is completely discarding the annotated field from serialization. Conditional filtering is different and it can be done using SimpleBeanPropertyFilter objects set to the filter provider of the ObjectMapper objects. Also notice that @JsonFilter annotation is used for UserResponse which points to which filter will be used by ObjectMapper during the serialization.

@JsonFilter("userCodeFilter")
public class UserResponse {

    public Integer userId;
    public String username;
    public Integer code;

    @JsonIgnore
    public String status;

}

Here we add a filter called userCodeFilter—like the one we added to the custom ObjectMapper of CustomHttpMessageConverter—which will include the UserResponse class’s code field in the serialization if its value is greater than 0. You can add multiple filters to ObjectMapper for different models.

public class CustomHttpMessageConverter extends MappingJackson2HttpMessageConverter {

    private ObjectMapper initiatePrettyObjectMapper() {
        ObjectMapper customObjectMapper = new ObjectMapper();
        customObjectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);

        // additional indentation for arrays
        DefaultPrettyPrinter pp = new DefaultPrettyPrinter();
        pp.indentArraysWith(new DefaultIndenter());
        customObjectMapper.setDefaultPrettyPrinter(pp);

        PropertyFilter userCodeFilter = new SimpleBeanPropertyFilter() {
            @Override
            public void serializeAsField(Object pojo, JsonGenerator jgen, SerializerProvider provider, PropertyWriter writer)
                    throws Exception {
                if (include(writer)) {
                    if (!writer.getName().equals("code")) {
                        writer.serializeAsField(pojo, jgen, provider);
                        return;
                    }
                    int intValue = ((UserResponse) pojo).code;
                    if (intValue > 0) {
                        writer.serializeAsField(pojo, jgen, provider);
                    }
                } else if (!jgen.canOmitFields()) {
                    writer.serializeAsOmittedField(pojo, jgen, provider);
                }
            }

            @Override
            protected boolean include(BeanPropertyWriter writer) {
                return true;
            }

            @Override
            protected boolean include(PropertyWriter writer) {
                return true;
            }
        };

        FilterProvider filters = new SimpleFilterProvider().addFilter("userCodeFilter", userCodeFilter);
        customObjectMapper.setFilterProvider(filters);

        return customObjectMapper;
    }

}

Deserialization

JSON String Parse Error Handling in Spring Boot

This one is a little tricky. Deserialization of a JSON @RequestParam object can cause parsing errors if the JSON object is not well-formed. The errors thrown in Jackson’s deserialization level just before it’s pushed to Spring Boot occur at that level, so Spring Boot doesn’t catch these errors.

Deserialization of Jackson maps JSON to POJOs and finally returns the expected Java class object. If the JSON is not well-formed, parsing cannot be done and MappingJackson2HttpMessageConverter internally throws a parsing error. Since this exception is not caught by Spring Boot and no object is returned, the REST controller would be unresponsive, having a badly-formed JSON payload.

Here we can override the internal read method of MappingJackson2HttpMessageConverter, hack the ReadJavaType with a customReadJavaType method, and make it return an internal error when the deserialization fails to parse the JSON input, rather than throwing an exception which is not seen or handled by Spring Boot.

@Override
public Object read(Type type, @Nullable Class<?> contextClass, HttpInputMessage inputMessage)
        throws IOException, HttpMessageNotReadableException {

    objectMapper.enable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);

    JavaType javaType = getJavaType(type, contextClass);
    return customReadJavaType(javaType, inputMessage);
}

private Object customReadJavaType(JavaType javaType, HttpInputMessage inputMessage) throws IOException {
    try {
        if (inputMessage instanceof MappingJacksonInputMessage) {
            Class<?> deserializationView = ((MappingJacksonInputMessage) inputMessage).getDeserializationView();
            if (deserializationView != null) {
                return this.objectMapper.readerWithView(deserializationView).forType(javaType).
                        readValue(inputMessage.getBody());
            }
        }
        return this.objectMapper.readValue(inputMessage.getBody(), javaType);
    }
    catch (InvalidDefinitionException ex) {
        //throw new HttpMessageConversionException("Type definition error: " + ex.getType(), ex);
        return "Type definition error";
    }
    catch (JsonProcessingException ex) {
        //throw new HttpMessageNotReadableException("JSON parse error: " + ex.getOriginalMessage(), ex, inputMessage);
        return "JSON parse error";
    }
}

This way you can return errors occurring at the deserialization level to Spring Boot, which expects a deserialized object but gets a String value which can be caught and translated into a ControllerAdvice handled exception. This also makes it easier to catch JSON parsing errors without using any third party JSON libraries like Gson.

json

rest

java

frameworks

spring


TL;DR : Enum deserialization errors are not caught by org.springframework.validation.Errors in a Rest Controller

For reference: we didn’t find a clean solution yet as we finally decided that no one should call us wit a bad enum


I have a rest controller that uses org.springframework.validation.Errors for parameter validations:

@RequestMapping(value = "/vol1/frodo")
public ResponseEntity<Object> simpleMethodUsingPost(
HttpServletRequest httpServletRequest,
@Valid @RequestBody MySimpleObject simpleObject,
Errors errors) {

/* If an error occured, I need to log the object */
if (errors.hasErrors()) {
List<FieldError> fields = errors.getFieldErrors();
doSomething(fields , simpleObject);
}
}

My class MySimpleObject looks like this:

public class MySimpleObject {
@Valid
@NotNull(message = "anObjectField is a mandatory field")
private EmbeddedObject anObjectField = null;

@Valid
@NotNull(message = "aStringField is a mandatory field")
private String aStringField = null;

@Valid
private MySimpleEnum aSimpleEnum = null;
}

And my enum class MySimpleEnum is basically a class with two values:

public enum MySimpleEnum{

ORC("ORC"),
URUK("URUK");

private String value;

MySimpleEnum(String value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

The validation of this object (and the injection of errors in the springframework Error object) works well when it’s on a String or an Object, but it will fail validation of an enum (hence an object containing a valid-annoted enum will fail too).

It fails when trying to cast the JSON String to an enum when the value is not valid:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: 
Cannot deserialize value of type 'lotr.middleearth.model.MySimpleEnum' from String "HOBBIT"

This deserialization error is caught if I use a ResponseEntityExceptionHandler and override handleHttpMessageNotReadable, but then I don’t have access to the different other parameters and can’t use them.

How can I configure either a Validator, enum or springframework Error so that this exception is caught and usable in my controller body?

java spring error-handling enums exception-handling

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

TL;DR : Enum deserialization errors are not caught by org.springframework.validation.Errors in a Rest Controller

For reference: we didn’t find a clean solution yet as we finally decided that no one should call us wit a bad enum


I have a rest controller that uses org.springframework.validation.Errors for parameter validations:

@RequestMapping(value = "/vol1/frodo")
public ResponseEntity<Object> simpleMethodUsingPost(
HttpServletRequest httpServletRequest,
@Valid @RequestBody MySimpleObject simpleObject,
Errors errors) {

/* If an error occured, I need to log the object */
if (errors.hasErrors()) {
List<FieldError> fields = errors.getFieldErrors();
doSomething(fields , simpleObject);
}
}

My class MySimpleObject looks like this:

public class MySimpleObject {
@Valid
@NotNull(message = "anObjectField is a mandatory field")
private EmbeddedObject anObjectField = null;

@Valid
@NotNull(message = "aStringField is a mandatory field")
private String aStringField = null;

@Valid
private MySimpleEnum aSimpleEnum = null;
}

And my enum class MySimpleEnum is basically a class with two values:

public enum MySimpleEnum{

ORC("ORC"),
URUK("URUK");

private String value;

MySimpleEnum(String value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

The validation of this object (and the injection of errors in the springframework Error object) works well when it’s on a String or an Object, but it will fail validation of an enum (hence an object containing a valid-annoted enum will fail too).

It fails when trying to cast the JSON String to an enum when the value is not valid:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: 
Cannot deserialize value of type 'lotr.middleearth.model.MySimpleEnum' from String "HOBBIT"

This deserialization error is caught if I use a ResponseEntityExceptionHandler and override handleHttpMessageNotReadable, but then I don’t have access to the different other parameters and can’t use them.

How can I configure either a Validator, enum or springframework Error so that this exception is caught and usable in my controller body?

java spring error-handling enums exception-handling

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

0

TL;DR : Enum deserialization errors are not caught by org.springframework.validation.Errors in a Rest Controller

For reference: we didn’t find a clean solution yet as we finally decided that no one should call us wit a bad enum


I have a rest controller that uses org.springframework.validation.Errors for parameter validations:

@RequestMapping(value = "/vol1/frodo")
public ResponseEntity<Object> simpleMethodUsingPost(
HttpServletRequest httpServletRequest,
@Valid @RequestBody MySimpleObject simpleObject,
Errors errors) {

/* If an error occured, I need to log the object */
if (errors.hasErrors()) {
List<FieldError> fields = errors.getFieldErrors();
doSomething(fields , simpleObject);
}
}

My class MySimpleObject looks like this:

public class MySimpleObject {
@Valid
@NotNull(message = "anObjectField is a mandatory field")
private EmbeddedObject anObjectField = null;

@Valid
@NotNull(message = "aStringField is a mandatory field")
private String aStringField = null;

@Valid
private MySimpleEnum aSimpleEnum = null;
}

And my enum class MySimpleEnum is basically a class with two values:

public enum MySimpleEnum{

ORC("ORC"),
URUK("URUK");

private String value;

MySimpleEnum(String value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

The validation of this object (and the injection of errors in the springframework Error object) works well when it’s on a String or an Object, but it will fail validation of an enum (hence an object containing a valid-annoted enum will fail too).

It fails when trying to cast the JSON String to an enum when the value is not valid:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: 
Cannot deserialize value of type 'lotr.middleearth.model.MySimpleEnum' from String "HOBBIT"

This deserialization error is caught if I use a ResponseEntityExceptionHandler and override handleHttpMessageNotReadable, but then I don’t have access to the different other parameters and can’t use them.

How can I configure either a Validator, enum or springframework Error so that this exception is caught and usable in my controller body?

java spring error-handling enums exception-handling

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

TL;DR : Enum deserialization errors are not caught by org.springframework.validation.Errors in a Rest Controller

For reference: we didn’t find a clean solution yet as we finally decided that no one should call us wit a bad enum


I have a rest controller that uses org.springframework.validation.Errors for parameter validations:

@RequestMapping(value = "/vol1/frodo")
public ResponseEntity<Object> simpleMethodUsingPost(
HttpServletRequest httpServletRequest,
@Valid @RequestBody MySimpleObject simpleObject,
Errors errors) {

/* If an error occured, I need to log the object */
if (errors.hasErrors()) {
List<FieldError> fields = errors.getFieldErrors();
doSomething(fields , simpleObject);
}
}

My class MySimpleObject looks like this:

public class MySimpleObject {
@Valid
@NotNull(message = "anObjectField is a mandatory field")
private EmbeddedObject anObjectField = null;

@Valid
@NotNull(message = "aStringField is a mandatory field")
private String aStringField = null;

@Valid
private MySimpleEnum aSimpleEnum = null;
}

And my enum class MySimpleEnum is basically a class with two values:

public enum MySimpleEnum{

ORC("ORC"),
URUK("URUK");

private String value;

MySimpleEnum(String value) {
this.value = value;
}

@Override
public String toString() {
return String.valueOf(value);
}
}

The validation of this object (and the injection of errors in the springframework Error object) works well when it’s on a String or an Object, but it will fail validation of an enum (hence an object containing a valid-annoted enum will fail too).

It fails when trying to cast the JSON String to an enum when the value is not valid:

org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: 
Cannot deserialize value of type 'lotr.middleearth.model.MySimpleEnum' from String "HOBBIT"

This deserialization error is caught if I use a ResponseEntityExceptionHandler and override handleHttpMessageNotReadable, but then I don’t have access to the different other parameters and can’t use them.

How can I configure either a Validator, enum or springframework Error so that this exception is caught and usable in my controller body?

java spring error-handling enums exception-handling

java spring error-handling enums exception-handling

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

edited Dec 12 ’18 at 9:40

edited Dec 12 ’18 at 9:40

edited Dec 12 ’18 at 9:40

asked Nov 28 ’18 at 17:52

asked Nov 28 ’18 at 17:52

asked Nov 28 ’18 at 17:52

The problem that is occurring is that in the enum MySimpleEnum there is no constant «HOBBIT» the possibilities are «ORC» and «URUK», in the validation question can be used simply as in the example:

@NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

I ended up doing something like that to extract the problematic field in the request :

int start = ex.getMessage().indexOf("["");
int end = ex.getMessage().indexOf(""]");
String fieldName = exception.getMessage().substring(start + 2, end)

The field happens to be at the end of the message between the brackets.

I’m not really proud of that one, it’s messy, but it seems to be the only way with enums.

I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

Your Answer

StackExchange.ifUsing(«editor», function () {
StackExchange.using(«externalEditor», function () {
StackExchange.using(«snippets», function () {
StackExchange.snippets.init();
});
});
}, «code-snippets»);

StackExchange.ready(function() {
var channelOptions = {
tags: «».split(» «),
id: «1»
};
initTagRenderer(«».split(» «), «».split(» «), channelOptions);

StackExchange.using(«externalEditor», function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using(«snippets», function() {
createEditor();
});
}
else {
createEditor();
}
});

function createEditor() {
StackExchange.prepareEditor({
heartbeatType: ‘answer’,
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: «»,
imageUploader: {
brandingHtml: «Powered by u003ca class=»icon-imgur-white» href=»https://imgur.com/»u003eu003c/au003e»,
contentPolicyHtml: «User contributions licensed under u003ca href=»https://creativecommons.org/licenses/by-sa/3.0/»u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href=»https://stackoverflow.com/legal/content-policy»u003e(content policy)u003c/au003e»,
allowUrls: true
},
onDemand: true,
discardSelector: «.discard-answer»
,immediatelyShowMarkdownHelp:true
});

}
});

Sign up or log in

StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave(‘#login-link’);
});

Sign up using Email and Password

Post as a guest

Email

Required, but never shown

StackExchange.ready(
function () {
StackExchange.openid.initPostLogin(‘.new-post-login’, ‘https%3a%2f%2fstackoverflow.com%2fquestions%2f53525359%2fvalidate-an-enum-with-springframework-validation-errors%23new-answer’, ‘question_page’);
}
);

Post as a guest

Email

Required, but never shown

The problem that is occurring is that in the enum MySimpleEnum there is no constant «HOBBIT» the possibilities are «ORC» and «URUK», in the validation question can be used simply as in the example:

@NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

The problem that is occurring is that in the enum MySimpleEnum there is no constant «HOBBIT» the possibilities are «ORC» and «URUK», in the validation question can be used simply as in the example:

@NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

0

The problem that is occurring is that in the enum MySimpleEnum there is no constant «HOBBIT» the possibilities are «ORC» and «URUK», in the validation question can be used simply as in the example:

@NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

The problem that is occurring is that in the enum MySimpleEnum there is no constant «HOBBIT» the possibilities are «ORC» and «URUK», in the validation question can be used simply as in the example:

@NotNull(message = "Custom message")
private MySimpleEnum aSimpleEnum

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

answered Nov 28 ’18 at 21:01

Jeremias SantosJeremias Santos

I ended up doing something like that to extract the problematic field in the request :

int start = ex.getMessage().indexOf("["");
int end = ex.getMessage().indexOf(""]");
String fieldName = exception.getMessage().substring(start + 2, end)

The field happens to be at the end of the message between the brackets.

I’m not really proud of that one, it’s messy, but it seems to be the only way with enums.

I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

I ended up doing something like that to extract the problematic field in the request :

int start = ex.getMessage().indexOf("["");
int end = ex.getMessage().indexOf(""]");
String fieldName = exception.getMessage().substring(start + 2, end)

The field happens to be at the end of the message between the brackets.

I’m not really proud of that one, it’s messy, but it seems to be the only way with enums.

I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

0

I ended up doing something like that to extract the problematic field in the request :

int start = ex.getMessage().indexOf("["");
int end = ex.getMessage().indexOf(""]");
String fieldName = exception.getMessage().substring(start + 2, end)

The field happens to be at the end of the message between the brackets.

I’m not really proud of that one, it’s messy, but it seems to be the only way with enums.

I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

I ended up doing something like that to extract the problematic field in the request :

int start = ex.getMessage().indexOf("["");
int end = ex.getMessage().indexOf(""]");
String fieldName = exception.getMessage().substring(start + 2, end)

The field happens to be at the end of the message between the brackets.

I’m not really proud of that one, it’s messy, but it seems to be the only way with enums.

I guess it would be better to use strings and proper Spring validation instead, since it depends too much on the implementation and may break with future updates.

Thanks for contributing an answer to Stack Overflow!

  • Please be sure to answer the question. Provide details and share your research!

But avoid

  • Asking for help, clarification, or responding to other answers.
  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.

Sign up or log in

StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave(‘#login-link’);
});

Sign up using Email and Password

Post as a guest

Email

Required, but never shown

StackExchange.ready(
function () {
StackExchange.openid.initPostLogin(‘.new-post-login’, ‘https%3a%2f%2fstackoverflow.com%2fquestions%2f53525359%2fvalidate-an-enum-with-springframework-validation-errors%23new-answer’, ‘question_page’);
}
);

Post as a guest

Email

Required, but never shown

Sign up or log in

StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave(‘#login-link’);
});

Sign up using Email and Password

Post as a guest

Email

Required, but never shown

Sign up or log in

StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave(‘#login-link’);
});

Sign up using Email and Password

Post as a guest

Email

Required, but never shown

Sign up or log in

StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave(‘#login-link’);
});

Sign up using Email and Password

Sign up using Email and Password

Post as a guest

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

Email

Required, but never shown

У меня есть приложение для весенней загрузки с java-клиентом, созданным через плагин gradle:

openApiGenerate {
    generatorName = "java"
    inputSpec = specsYml
    outputDir = "$buildDir/generated".toString()
    apiPackage = "com.customapi.api"
    invokerPackage = "com.customapi.invoker"
    modelPackage = "com.customapi.model"
    configOptions = [
        dateLibrary: "java8",
        library    : "resttemplate"
    ]
}

Я выбрал "java8" как dateLibrary, поскольку он кажется предпочтительным для проекта с java 1.8.

С помощью этого сгенерированного клиента я выполняю запрос, который возвращает объект, содержащий временную метку. Я получаю следующую ошибку:

java.lang.IllegalStateException: Failed to execute CommandLineRunner
    ...
Caused by: org.springframework.web.client.RestClientException: Error while extracting response for type [class com.customapi.model.Info] and content type [application/json];
    ...
Caused by: org.springframework.http.converter.HttpMessageNotReadableException: JSON parse error: Cannot deserialize value of type `java.time.OffsetDateTime` from String "2020-07-21T12:12:23.000+0200": ...
   ...
...
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `java.time.OffsetDateTime` from String "2020-07-21T12:12:23.000+0200": Failed to deserialize java.time.OffsetDateTime: (java.time.format.DateTimeParseException) Text '2020-07-21T12:12:23.000+0200' could not be parsed at index 23
 at [Source: (ByteArrayInputStream); line: 1, column: 84] (through reference chain: com.customapi.model.Info["buildTimestamp"])
    at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:1679) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:935) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.JSR310DeserializerBase._handleDateTimeException(JSR310DeserializerBase.java:86) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:218) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:50) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:129) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:369) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:159) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4218) ~[jackson-databind-2.10.3.jar:2.10.3]
    at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3267) ~[jackson-databind-2.10.3.jar:2.10.3]
    at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:269) ~[spring-web-5.2.7.RELEASE.jar:5.2.7.RELEASE]
    ... 17 common frames omitted
Caused by: java.time.format.DateTimeParseException: Text '2020-07-21T12:12:23.000+0200' could not be parsed at index 23
    at java.time.format.DateTimeFormatter.parseResolved0(DateTimeFormatter.java:1949) ~[na:1.8.0_151]
    at java.time.format.DateTimeFormatter.parse(DateTimeFormatter.java:1777) ~[na:1.8.0_151]
    at com.fasterxml.jackson.datatype.jsr310.deser.InstantDeserializer.deserialize(InstantDeserializer.java:212) ~[jackson-datatype-jsr310-2.10.3.jar:2.10.3]
    ... 24 common frames omitted

Соответствующие части рассматриваемого класса Info:

...
@javax.annotation.Generated(value = "org.openapitools.codegen.languages.JavaClientCodegen", date = "2020-07-26T14:09:54.137+02:00[Europe/Berlin]")
public class Info {
    ...
    public static final String JASON_PROPERTY_BUILD_TIMESTAMP = "buildTimestamp";
    private OffsetDateTime buildTimestamp;
    ...
    public Info buildTimestamp(OffsetDateTime buildTimestamp) {
        this.buildTimestamp = buildTimestamp;
        return this;
    }

    public void setBuildTimestamp(OffsetDateTime buildTimestamp) {
        this.buildTimestamp = buildTimestamp;
    }
    ...
}

Оба метода установки принимают объекты OffsetDateTime и не имеют аннотаций, поэтому преобразование должно происходить в другом месте. Входная строка снова будет «2020-07-21T12: 12: 23.000 + 0200». соответствующие зависимости

ext {
    swagger_annotations_version = "1.5.22"
    jackson_version = "2.10.3"
    jackson_databind_version = "2.10.3"
    jackson_databind_nullable_version = "0.2.1"
}

dependencies {
    compile "io.swagger:swagger-annotations:$swagger_annotations_version"
    compile "com.fasterxml.jackson.core:jackson-core:$jackson_version"
    compile "com.fasterxml.jackson.core:jackson-annotations:$jackson_version"
    compile "com.fasterxml.jackson.core:jackson-databind:$jackson_databind_version"
    compile "com.fasterxml.jackson.jaxrs:jackson-jaxrs-json-provider:$jackson_version"
    compile "org.openapitools:jackson-databind-nullable:$jackson_databind_nullable_version"
    compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jackson_version"
}

Кажется, существует много проблем с jackson и java 8, и большинство решений на этом сайте, похоже, добавляют аннотации. Но я сомневаюсь, что изменение сгенерированного кода — правильное решение. Не упустил ли я важный параметр при создании клиента? Сервер предоставляет неправильный формат? Как я могу это выяснить?

Обновить:

Когда я переключаю dateLibrary на legacy, он работает, поэтому я думаю, что получаю правильные данные.

Ошибка в генераторе серверов (jaxrs) https: / /github.com/swagger-api/swagger-codegen/issues/3648#issuecomment-244056314, при котором сервер отправляет сообщения в неверном формате (без двоеточия) date-time. Мое решение заключалось в использовании устаревшей библиотеки dateLibrary для клиента, которая может обрабатывать неправильный формат.

1 ответ

Лучший ответ

Следуя моему комментарию в вопросе, я понимаю, что вам не нужна аннотация Джексона. Вам просто нужно настроить сеттер. Вот базовая демонстрация:

Предположим следующий класс:

import java.time.OffsetDateTime;
//import com.fasterxml.jackson.annotation.JsonSetter;
import java.time.format.DateTimeFormatter;

public class MyOdt {
    
    private OffsetDateTime odt;

    public OffsetDateTime getOdt() {
        return odt;
    }

    //@JsonSetter("odt")
    public void setOdt(String odtString) {
        final String pattern = "yyyy-MM-dd'T'HH:mm:ss.SSSxx";
        DateTimeFormatter dtfB = DateTimeFormatter.ofPattern(pattern);
        this.odt = OffsetDateTime.parse(odtString, dtfB);
    }
    
}

Класс будет создан из такого фрагмента JSON:

String jsonTest = "{ "odt" : "2020-07-21T12:12:23.000+0200" }";

Картограф объектов:

ObjectMapper objectMapper = new ObjectMapper()
        .registerModule(new JavaTimeModule())
        .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);

MyOdt odtTest = objectMapper.readValue(jsonTest, MyOdt.class);

Для справки, вот исходный комментарий в вопросе:

Замечание: это недопустимая строка для анализа с помощью OffsetDateTime.parse(), поскольку формат даты и времени по умолчанию предполагает, что смещение будет содержать двоеточие: +02:00. Итак, это работает: OffsetDateTime.parse("2020-07-21T12:12:23.000+02:00")


2

andrewjames
27 Июл 2020 в 14:43

Понравилась статья? Поделить с друзьями:
  • Http ошибки серверные
  • Http ошибка при загрузке файла 413
  • Http validation error status code
  • Http transport error java net socketexception connection reset
  • Http sys error codes