Consider a simple RestController that requires a @Valid object as show in the example below.

@RestController
public class ErrorProne {
 @PostMapping("/customers")
  Customer customerEcho(@Valid @RequestBody Customer c) {
    return c;
  }
}

class Customer {
  @NotBlank(message = "name can't be blank")
  private String name;

  public String getName() {
    return name;
  }

  public void setName(String name) {
    this.name = name;
  }
}

Executing an http POST :8080/customers name= results in a lot of superfluous details about the validation error including internal spring state.

{
    "error": "Bad Request",
    "errors": [
        {
            "arguments": [
                {
                    "arguments": null,
                    "code": "name",
                    "codes": [
                        "customer.name",
                        "name"
                    ],
                    "defaultMessage": "name"
                }
            ],
            "bindingFailure": false,
            "code": "NotBlank",
            "codes": [
                "NotBlank.customer.name",
                "NotBlank.name",
                "NotBlank.java.lang.String",
                "NotBlank"
            ],
            "defaultMessage": "name can't be blank",
            "field": "name",
            "objectName": "customer",
            "rejectedValue": ""
        }
    ],
    "message": "Validation failed for object='customer'. Error count: 1",
    "path": "/customers",
    "status": 400,
    "timestamp": "2020-03-08T16:19:45.828+0000"
}

It would be nicer to only send back errors field with only the essential details for example.

{
    "error": "Bad Request",
    "errors": [
        {
            "defaultMessage": "name can't be blank",
            "field": "name",
            "objectName": "customer",
            "rejectedValue": ""
        }
    ],
    "message": "Validation failed for object='customer'. Error count: 1",
    "path": "/customers",
    "status": 400,
    "timestamp": "2020-03-08T16:19:45.828+0000"
}

While it's possible to implement a custom bean of DefaultErrorAttributes I think improving the default behavior is the better thing for users.

Comment From: asaikali

The following Jackson Serializer can reduce the amount of noise in the output

@JsonComponent
public class ItemSerializer extends StdSerializer<FieldError> {

    public ItemSerializer() {
        this(null);
    }

    public ItemSerializer(Class<FieldError> t) {
        super(t);
    }

    @Override
    public void serialize(
      FieldError value, JsonGenerator jgen, SerializerProvider provider)
      throws IOException, JsonProcessingException {

        jgen.writeStartObject();
        jgen.writeStringField("field", value.getField());
        jgen.writeStringField("defaultMessage",value.getDefaultMessage());
        jgen.writeStringField("objectName",value.getObjectName());
        jgen.writeObjectField("rejectedValue",value.getRejectedValue());
        jgen.writeEndObject();
    }
} 

for http POST :8080/customers name= produces

{
    "error": "Bad Request",
    "errors": [
        {
            "defaultMessage": "name can't be blank",
            "field": "name",
            "objectName": "customer",
            "rejectedValue": ""
        }
    ],
    "message": "Validation failed for object='customer'. Error count: 1",
    "path": "/customers",
    "status": 400,
    "timestamp": "2020-03-08T20:33:11.716+0000"
}

Comment From: wilkinsona

Thanks for the suggestion.

What is superfluous from your perspective may be required by someone else. I don't think we can make this change to the defaults without running the risk of breaking existing applications. For example, they may be using the values in errors.[].codes to look up some localized text that is displayed in a web UI.

If the information that is returned by default exceeds your needs, customization using your own ErrorAttributes implementation or a Jackson serialiser as you have shown above is the way to go.