Validation failed for object error count 1

Normally, if a BindingResult exception is thrown, BasicErrorController returns an errors JSON property that contains a very helpful list of errors in the BindingResult. However, when a request hand...

Normally, if a BindingResult exception is thrown, BasicErrorController returns an errors JSON property that contains a very helpful list of errors in the BindingResult.

However, when a request handler has a @RequestBody @Valid parameter, and the parameter coming from the client is not valid, a MethodArgumentNotValidException is thrown. While it is technically not a BindingResult, it has a method getBindingResult() that returns one. The problem is that BasicErrorController doesn’t know that, and so doesn’t return the «errors» JSON property.

Can BasicErrorController be modified to check if an exception has inner BindingResult and use that to return the «errors» property? Or perhaps make MethodArgumentNotValidException implement BindingResult?

I’ve looked at the code and this seems to be the check that happens:
https://github.com/spring-projects/spring-boot/blob/master/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/web/DefaultErrorAttributes.java#L133

Demo Boot application that demonstrates the issue:

@SpringBootApplication
@RestController
public class DemoApplication {

    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }

    @RequestMapping(path = "/", produces = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
    public Map<String, Object> test(@RequestBody @Valid Z postObject) {
        Map<String, Object> result = new HashMap<>();
        result.put("test", postObject.getA());
        return result;
    }

    public static class Z {
        @NotBlank
        private String a;

        public String getA() {
            return a;
        }

        public void setA(String a) {
            this.a = a;
        }
    }
}

Request:

curl -X POST -H "Content-Type: application/json" -d '{"a":""}' 'http://localhost:8080/'

Response:

{
  "timestamp": 1444810157161,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "message": "Validation failed for argument at index 0 in method: public java.util.Map<java.lang.String, java.lang.Object> com.example.DemoApplication.test(com.example.DemoApplication$Z), with 1 error(s): [Field error in object 'z' on field 'a': rejected value []; codes [NotBlank.z.a,NotBlank.a,NotBlank.java.lang.String,NotBlank]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [z.a,a]; arguments []; default message [a]]; default message [may not be empty]] ",
  "path": "/"
}

Expected Response:

{
  "timestamp": 1444810157161,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.validation.BindException",
  "errors": [
    {
      "codes": [
        "NotBlank.z.a",
        "NotBlank.a",
        "NotBlank.java.lang.String",
        "NotBlank"
      ],
      "arguments": [
        {
          "codes": [
            "z.a",
            "a"
          ],
          "arguments": null,
          "defaultMessage": "a",
          "code": "a"
        }
      ],
      "defaultMessage": "may not be empty",
      "objectName": "z",
      "field": "a",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotBlank"
    }
  ],
  "message": "Validation failed for object='z'. Error count: 1",
  "path": "/"
}

I’ve been learning Spring MVC and Thymeleaf to produce a basic administerative web client. My starting point was this page from Spring: https://spring.io/guides/gs/serving-web-content/. After getting comfortable with basic page rendering with Thymeleaf and model and controller development with Spring MVC I ran into an issue with dropdown lists.

I was trying to select an item from a dropdown (select tag), but I received an error when trying to post the selection. The item was a custom object and a member variable of another custom object. So I wasn’t using basic object types here. Selecting from dropdown lists using Spring MVC and Thymeleaf seems pretty easy when the item is some sort of ordinal (e.g. an int or an enum – see http://itutorial.thymeleaf.org/exercise/12) but using your own objects is a little harder. The error and solution follow.

Error:

There was an unexpected error (type=Bad Request, status=400).
Validation failed for object='thing'. Error count: 2

This error is due to the members of thing that are selected from option inputs:


<select th:field="*{item}">
    <option th:each="i, iter : ${itemSet}" th:value="${i}" th:text="${i.description}"></option>
</select>

Solution:

I managed to get this working, but it seems that the select th:field must be an ordinal (e.g. an int or enum). For example:

<select th:field=”*{item.id}”>

    <option th:each=”i, ${itemSet}” th:value=”${i.id}” th:text=”${i.description}”></option>

</select>

It’s important to understand that the information required to rebuild the list of options must be stored in the view (i.e. the html page) otherwise the model can’t be rebuilt – I’m relatively new to Spring MVC, but I’m pretty sure this is the case. So rather than store all that info, it’s probably best to just use the IDs as I have here, and then the model will be rebuilt with an empty object and just the ID set. The full object can then be looked up in your controller method. For example:

@RequestMapping(params = "save", value="/items.html", method=RequestMethod.POST)
public String save(
@RequestParam Map<String,String> allRequestParams,
@ModelAttribute(value="thing") Thingthing,
Model model)
{
int id = thing.getItem().getId();
thing.setItem(loadItem(id));
...
}

Here, Thing is the parent object class and item is one of its members. The model holds the Thing but only fills in the id of the item. The ID is then used to load the full item and overwrite the otherwise empty item.

у вас импорт неправильный был вместо

import javax.validation.Valid;
@RestController
public class MainController
{
    @PostMapping("/proxy/add")
    public void saveProxy(@Valid @RequestBody ProxyDto dto) {

надо

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;
@RestController
@RequestMapping("/proxy")

public class MainController
{
	@RequestMapping(value = "add", method = RequestMethod.POST)
	public Map<String, Object> addBook(@Validated @RequestBody ProxyDto dto ) {
....

и

import javax.validation.constraints.NotNull;
public class ProxyDto {

	@NotNull(message = "idbn is missing")
	@NotEmpty(message = "idbn is empty")


 private String proxy;

в зависимости от версии фрамеворка скажкт типа

{
  "timestamp": 1612029070201,
  "status": 400,
  "error": "Bad Request",
  "exception": "org.springframework.web.bind.MethodArgumentNotValidException",
  "errors": [
    {
      "codes": [
        "NotNull.book.isbn",
        "NotNull.isbn",
        "NotNull.java.lang.String",
        "NotNull"
      ],
      "arguments": [
        {
          "codes": [
            "book.isbn",
            "isbn"
          ],
          "arguments": null,
          "defaultMessage": "isbn",
          "code": "isbn"
        }
      ],
      "defaultMessage": "idbn is missing",
      "objectName": "book",
      "field": "isbn",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotNull"
    },
    {
      "codes": [
        "NotEmpty.book.isbn",
        "NotEmpty.isbn",
        "NotEmpty.java.lang.String",
        "NotEmpty"
      ],
      "arguments": [
        {
          "codes": [
            "book.isbn",
            "isbn"
          ],
          "arguments": null,
          "defaultMessage": "isbn",
          "code": "isbn"
        }
      ],
      "defaultMessage": "idbn is empty",
      "objectName": "book",
      "field": "isbn",
      "rejectedValue": null,
      "bindingFailure": false,
      "code": "NotEmpty"
    }
  ],
  "message": "Validation failed for object='book'. Error count: 2",
  "path": "/book/add"
}

In this tutorial, you will learn how to validate the request body of an HTTP Post request sent to a RESTful Web Service endpoint built with Spring Boot.

Let’s say you have a RESTful Web Service endpoint that accepts HTTP post requests with the following JSON payload:

{
    "firstName": "Sergey",
    "lastName": "Kargopolov",
    "password":"1234765432",
    "email":"[email protected]"
}

You would like to validate this request body and make sure that required information is not missing and that, for example, the email address is of a correct format.

Web Service Endpoint

For you to be able to receive the above-mentioned JSON payload in a RESTful Web Service, your Rest Controller, and the request mapping will look something like this:

@RestController
@RequestMapping("/users")
public class UserController {

    @PostMapping(produces = {MediaType.APPLICATION_XML_VALUE, MediaType.APPLICATION_JSON_VALUE})
    public UserRest createUser(@Valid @RequestBody UserDetailsRequestModel requestUserDetails) {
        
        UserRest returnValue = new UserRest();
        returnValue.setFirstName("Sergey");
        returnValue.setLastName("Kargopolov");

        return returnValue;
    }

}

Please have a look at the method signature that handles the HTTP Post Request. It contains three important pieces of information:

  • @Valid annotation which is required to kick in the validation of the information received,
  • @RequestBody annotation which signifies that the payload in the body of the request will be mapped to a model class which it annotates,
  • and the UserDetailsRequestModel model class which can be named differently but what is important is that this model class contains getters and setters methods for class fields which should match the JSON payload keys. An example of this model class is below:

Model class

For the framework to be able to map the above-mentioned JSON payload to a Java class, we need to create a Java bean class which contains class fields for each of the key in the JSON payload. Like so:

package com.appsdeveloperblog.app.ws.ui.request;

public class UserDetailsRequestModel {
    private String firstName;
    private String lastName;
    private String email;
    private String password;

  
    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }
 
    public String getLastName() {
        return lastName;
    }
 
    public void setLastName(String lastName) {
        this.lastName = lastName;
    }
 
    public String getEmail() {
        return email;
    }
 
    public void setEmail(String email) {
        this.email = email;
    }

 
    public String getPassword() {
        return password;
    }

 
    public void setPassword(String password) {
        this.password = password;
    }
    
}

The above Java bean class does not have any validation yet. But it can be used to accept information which the JSON payload sent in the body of HTTP POST request carries. Now let’s validate the fields and make sure that firstName and lastName are not empty and that the email address is of a valid format.

Validate Java Bean Fields with Validation Constraints

To validate the JSON payload sent in the HTTP POST request body and then mapped to the above Java bean class, we will use Hibernate Validator Constraints which are already included in our project if you have used Spring Boot to create it and you have the spring-boot-starter-web in your pom.xml file.

Note: If your project does not compile because the below validation annotations could not be found, then add the following dependencies to the pom.xml file of your project.

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Additionally to adding the above validation dependency, add the following two configuration properties to an application.properties file.

server.error.include-message=always
server.error.include-binding-errors=always

There are many validation constraints available to use but I will use just the ones needed for this example:

  1. @NotNull
  2. @Size
    1. @Size(min=8,  message=” Password must be greater than 8 characters “)
    2. @Size(max=16, message=” Password must be greater than 16 characters”)
    3. Or you can use min and max together like so:
      @Size(min=8, max=16, message=” Password must be equal to or greater than 8 characters and less than 16 characters”)
  3. @Email

Have a look at the below model class now annotated with validation constraints.

package com.appsdeveloperblog.app.ws.ui.request;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

 
public class UserDetailsRequestModel {
    @NotNull(message="First name cannot be missing or empty")
    @Size(min=2, message="First name must not be less than 2 characters")
    private String firstName;

    @NotNull(message="Last name cannot be missing or empty")
    @Size(min=2, message="Last name must not be less than 2 characters")
    private String lastName;

    @Email
    private String email;

    @NotNull(message="Password is a required field")
    @Size(min=8, max=16, message="Password must be equal to or greater than 8 characters and less than 16 characters")
    private String password;

    public String getFirstName() {
        return firstName;
    }
 
    public void setFirstName(String firstName) {
        this.firstName = firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public void setLastName(String lastName) {
        this.lastName = lastName;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

}

If one of the fields fails the validation, a 400 Bad Request HTTP status code will be returned in Response. For example, if the email address was not properly formatted in the above JSON Payload the following response will be sent back:

{
    "timestamp": "2018-09-21T14:52:14.853+0000",
    "status": 400,
    "error": "Bad Request",
    "errors": [
        {
            "codes": [
                "Email.userDetailsRequestModel.email",
                "Email.email",
                "Email.java.lang.String",
                "Email"
            ],
            "arguments": [
                {
                    "codes": [
                        "userDetailsRequestModel.email",
                        "email"
                    ],
                    "arguments": null,
                    "defaultMessage": "email",
                    "code": "email"
                },
                [],
                {
                    "arguments": null,
                    "defaultMessage": ".*",
                    "codes": [
                        ".*"
                    ]
                }
            ],
            "defaultMessage": "must be a well-formed email address",
            "objectName": "userDetailsRequestModel",
            "field": "email",
            "rejectedValue": "test [email protected]",
            "bindingFailure": false,
            "code": "Email"
        }
    ],
    "message": "Validation failed for object='userDetailsRequestModel'. Error count: 1",
    "path": "/users"
}

@NotNull, @NotEmpty and @NotBlank

You could also use @NotNull, @NotEmpty or @NotBlank to validate if a Bean property is not empty. Although these three annotations sound like they all do the same thing there is a difference between them.

  • @NotNull – Checks that the annotated value is not null. But it can be empty. An annotated value can be of any type(String or an Array or a Collection …),
  • @NotEmpty – Checks whether the annotated element is not null nor empty. An annotated value can be CharSequence, Collection, Map, and arrays,
  • @NotBlank – Checks that the annotated character sequence is not null and the trimmed length is greater than 0. The difference to @NotEmpty is that this constraint can only be applied on strings and that trailing whitespaces are ignored.

Video Tutorial

I hope this tutorial was helpful to you.

If you are interested to learn more about building RESTful Web Services with Spring Boot, check the below video courses. One of them might be exactly what you are looking for.

Понравилась статья? Поделить с друзьями:
  • Validation error text
  • Validation error template wpf
  • Validation error response
  • Validation error python
  • Validation error please try again if this error persists please contact the site administrator