Springfox Java Bean Validations not displaying in Swagger Output - spring-boot

I'm trying to follow the documentation for Springfox Swagger to get Java Bean Validation to work (http://springfox.github.io/springfox/docs/current/#springfox-support-for-jsr-303), but they are not showing up in the Swagger UI.
This is my Spring Configuration:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.service.ApiInfo;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2;
#EnableSwagger2
#Import({springfox.bean.validators.configuration.BeanValidatorPluginsConfiguration.class})
#Configuration
public class SwaggerConfig {
#Bean
public Docket docket() {
return new Docket(DocumentationType.SWAGGER_2)
.apiInfo(apiInfo())
.select()
.build();
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("My API")
.build();
}
}
This is my request mapping:
#ApiOperation(value = "Use to get token for internal applications")
#PostMapping(value = AuthUris.TOKEN)
public AuthResponse token(#Valid #RequestBody AuthRequest authRequest) {
// implementation omitted
}
This is my POJO:
#ApiModel
public class AuthRequest {
#ApiModelProperty
#NotNull
#Size(min = 4, max = 50)
private String username;
#NotNull
private String password;
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
I expected the NotNull and Size annotations to be captured in the Swagger UI but they are not. Please help me understand how this should work. Thank you.
.
So thanks to https://stackoverflow.com/users/8012379/indra-basak I do see that they were working. However, I have to hover over the field to thinks like #Size. See the screenshot below.

If you are using springfox version 2.7.0, both #NotNull and #Size annotations should work.
Your #NotNull annotation is already working in the password field.
If #ApiModelProperty annotation is present for a field, it takes precedence over #NotNull annotation. It is the case with the username field. It shows up as optional because the required attribute of #ApiModelProperty annotation is set to false by default.
If you use springfox version 2.7.0 and don't use #ApiModelProperty annotation, the model will show up as:
Validation
For example, if you enter a username less than the minimum size of 4, you will get the following exception:
{
"timestamp": 1511550365198,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"Size.authRequest.username",
"Size.username",
"Size.java.lang.String",
"Size"
],
"arguments": [
{
"codes": [
"authRequest.username",
"username"
],
"arguments": null,
"defaultMessage": "username",
"code": "username"
},
50,
4
],
"defaultMessage": "size must be between 4 and 50",
"objectName": "authRequest",
"field": "username",
"rejectedValue": "s",
"bindingFailure": false,
"code": "Size"
}
],
"message": "Validation failed for object='authRequest'. Error count: 1",
"path": "/tokens"
}

Related

with #SpringBootTest (or #WebfluxTest), the errors attribute disappears

I made very simple controller like below.
#PostMapping("/books")
public void create(#Valid #RequestBody BookPayload bookPayload) {
}
#Getter
#Setter
public class BookPayload {
#NotBlank
private String name;
#NotBlank
private String author;
}
When I call this api without name. It responses like below.
{
"timestamp": "2022-03-26T14:06:43.564+00:00",
"path": "/books",
"status": 400,
"error": "Bad Request",
"requestId": "654248ee-5",
"errors": [
{
"codes": [
"NotBlank.bookPayload.name",
"NotBlank.name",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"bookPayload.name",
"name"
],
"arguments": null,
"defaultMessage": "name",
"code": "name"
}
],
... omit ...
}
]
}
You can see errors attribute in the response body.
But If I test this api with #SpringBootTest or #WebfluxTest, There is no errors attribute.
#Slf4j
#SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class CommonErrorResponseTest {
private final WebClient web;
public CommonErrorResponseTest(#LocalServerPort Integer port) {
web = WebClient.create("http://localhost:" + port);
}
#Test
void _400_badRequest_violation() {
BookPayload bookPayload = new BookPayload();
bookPayload.setAuthor("John");
Mono<String> stringMono = web.post().uri("/books")
.header("Content-Type", MediaType.APPLICATION_JSON_VALUE)
.bodyValue(bookPayload)
.exchangeToMono(response -> response.bodyToMono(String.class));
String body = stringMono.block();
log.info("body: {}", body);
}
}
console
body: {"timestamp":"2022-03-26T14:19:21.981+00:00","path":"/books","status":400,"error":"Bad Request","requestId":"68df2a79-1"}
I'd like to know why I'm getting different results.
Spring Boot’s DevTools enables the inclusion of binding errors in the error response to ease problem solving during development. You can configure the same behaviour in your tests by setting server.error.include-binding-errors to always.
You can see a complete list of the properties that DevTools sets in the reference documentation.

#Valid | response very verbose, how to customize?

I have a Spring Boot microservice and I want to valide the incoming requestBody of an endpoint.
By using #Valid with #NotBlank I have noticed that the answer is very verbose and my customized error message is deep into the object; here is an example:
{
"timestamp": "2020-12-17T09:28:26.529+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.createUserRequest.username",
"NotBlank.username",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"createUserRequest.username",
"username"
],
"arguments": null,
"defaultMessage": "username",
"code": "username"
}
],
"defaultMessage": "USERNAME IS REQUIRED",
"objectName": "createUserRequest",
"field": "username",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='createUserRequest'. Error count: 1",
"path": "/api/user/create"
}
How can I customize this object returned? I would like the response to simply be something like this:
{
"timestamp": "2020-12-17T09:28:26.529+0000",
"status": 400,
"error": "Bad Request",
"message": "USERNAME IS REQUIRED"
}
Here is my code:
Request
#Data
public class CreateUserRequest {
#NotBlank(message = "username is required")
private String username;
#Size(min = 3, max = 64)
#NotBlank(message = "password is required")
private String password;
#NotBlank(message = "confirmPassword is required")
#Size(min = 3, max = 64)
private String confirmPassword;
}
Controller
#PostMapping("/create")
public ResponseEntity<User> createUser(#Valid #RequestBody CreateUserRequest request) {
User user = appService.createUserAndCart(request);
return ResponseEntity.ok(user);
}
Thank you for your experience
You can use #ControllerAdvice/#RestControllerAdvice
it allows you to handle exceptions across the whole application. You can think of it as an interceptor of exceptions thrown by methods annotated with #RequestMapping and similar.
And add a method like this,
#ExceptionHandler(Exception.class)
public ResponseEntity<Object> handleException(Exception ex)
//your custom body
return new ResponseEntity<>(body, HttpStatus.XXXXX);
}
You can specify a specific Exception type (I think it's InvalidArgumentException in your case)
Define a return class
import org.springframework.http.HttpStatus;
import java.util.HashMap;
/**
* #description:
* #author: 582895699#qq.com
* #time: 2020/12/20 下午 01:50
*/
public class Resp extends HashMap {
private static final long serialVersionUID = 1L;
public static final String TIMESTAMP = "timestamp";
public static final String STATUS = "status";
public static final String ERROR = "error";
public static final String MESSAGE = "message";
public static Resp fail(String message) {
Resp resp = new Resp();
resp.put(TIMESTAMP, System.currentTimeMillis());
resp.put(STATUS, HttpStatus.BAD_REQUEST.value());
resp.put(ERROR, HttpStatus.BAD_REQUEST.getReasonPhrase());
resp.put(MESSAGE, message);
return resp;
}
#Override
public Object put(Object key, Object value) {
return super.put(key, value);
}
}
Define global exception handling class and obtain exception information
import org.springframework.validation.BindingResult;
import org.springframework.validation.ObjectError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**
* #description:
* #author: 582895699#qq.com
* #time: 2020/12/20 下午 01:55
*/
#RestControllerAdvice
public class GlobalExceptionHandler {
#ExceptionHandler(value = MethodArgumentNotValidException.class)
public Resp methodArgumentNotValidExceptionHandler(MethodArgumentNotValidException e) {
BindingResult bindingResult = e.getBindingResult();
ObjectError objectError = bindingResult.getAllErrors().get(0);
String message = objectError.getDefaultMessage();
return Resp.fail(message);
}
}

Want to give custom annotation at RequestBody Pojo class

We can give #valid annotation but I want to give custom annotation at #RequestBody.
Use case: In my person Pojo class, I have two fields firstname and lastname. so I want to validate pojo class in that way that if user has given value for any field (like given for lastname) then it's good. but Both field should not be empty. User should give value for at least one field (it is either or condition)
My Pojo class:
class Person {
private String firstName;
private String lastName;
}
we can't give #NotNull for both fields. so I want to give custom annotation at the class level.
In that validator, I will check both fields and will send the proper error message to User.
You can try Custom ConstraintValidator, simple example #ValidatePerson:
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.validation.Constraint;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.validation.Payload;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
#RestController
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#PostMapping
public Person doSomeThingPerson(#Validated #RequestBody Person person) {
return person;
}
#ValidatePerson
public static class Person {
private String firstName;
private String lastName;
public String getFirstName() {
return firstName;
}
public String getLastName() {
return lastName;
}
}
#Target({TYPE})
#Retention(RUNTIME)
#Constraint(validatedBy = {PersonValidator.class}) // you can use multiply validators
public #interface ValidatePerson {
String message() default "Invalid Person, firstName and lastName can't be null";
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
}
public static class PersonValidator implements ConstraintValidator<ValidatePerson, Person> {
#Override
public boolean isValid(Person person, ConstraintValidatorContext context) {
return person.getFirstName() != null || person.getLastName() != null;
}
}
}
If firstName and lastName both null then:
{
"timestamp": 1560456328285,
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"ValidatePerson.person",
"ValidatePerson"
],
"arguments": [
{
"codes": [
"person.",
""
],
"arguments": null,
"defaultMessage": "",
"code": ""
}
],
"defaultMessage": "Invalid Person, firstName and lastName can't be null",
"objectName": "person",
"code": "ValidatePerson"
}
],
"message": "Validation failed for object='person'. Error count: 1",
"path": "/"
}
Also you can customize exception with #ControllerAdvice

How to return a custom response pojo when request body fails validations that are defined using Bean Validation/Hibernate Validator?

Is it possible to override the default response POJO of Spring Hibernate validator?
Currently when a validation gets failed then a very big response returned to the client as shown below. But I don't want the client to provide the full error response of hibernate validator and instead to send some key-value pair regarding the error message.
{
"timestamp": "2018-05-28T18:12:56.705+0000",
"status": 400,
"error": "Bad Request",
"errors": [
{
"codes": [
"NotBlank.abc.xyz",
"NotBlank.xyz",
"NotBlank.java.lang.String",
"NotBlank"
],
"arguments": [
{
"codes": [
"abc.xyz",
"xyz"
],
"arguments": null,
"defaultMessage": "transactionId",
"code": "transactionId"
}
],
"defaultMessage": "xyz is mandatory parameter , please provide appropriate value",
"objectName": "abc",
"field": "xyz",
"rejectedValue": "",
"bindingFailure": false,
"code": "NotBlank"
}
],
"message": "Validation failed for object='xyz'. Error count: 1",
"path": "/path/create/1"
}
A BindException is thrown when the request body fails validation. You can define your own ControllerAdvice that construct an appropriate error message from the details in the BindException.
import java.util.ArrayList;
import java.util.List;
import org.springframework.http.HttpStatus;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
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 com.example.demo.ErrorResponse.ErrorDetails;
#ControllerAdvice
public class CustomExceptionHandler {
#ExceptionHandler(BindException.class)
#ResponseStatus(HttpStatus.BAD_REQUEST)
#ResponseBody
public ErrorResponse handleException(BindException ex) {
List<FieldError> errors = ex.getBindingResult().getFieldErrors();
List<ErrorDetails> errorDetails = new ArrayList<>();
for (FieldError fieldError : errors) {
ErrorDetails error = new ErrorDetails();
error.setFieldName(fieldError.getField());
error.setMessage(fieldError.getDefaultMessage());
errorDetails.add(error);
}
ErrorResponse errorResponse = new ErrorResponse();
errorResponse.setErrors(errorDetails);
return errorResponse;
}
}
Pojo for the error response:
#Data
public class ErrorResponse {
private List<ErrorDetails> errors;
#Data
public static class ErrorDetails {
private String fieldName;
private String message;
}
}
Sample error message
{
"errors": [
{
"fieldName": "firstName",
"message": "must not be null"
},
{
"fieldName": "lastName",
"message": "must not be null"
}
]
}

Handling of MethodArgumentNotValidException overridden by DefaultErrorAttributes

My goal is to have a custom response body for validation errors. This is a very common case and I've read lots of posts/blogs/articles and I've even implemented this myself in the past. For some reason, I cannot figure this out.
I have this #RestControllerAdvice
#Slf4j
#RequiredArgsConstructor
#RestControllerAdvice
public class ErrorHandler extends ResponseEntityExceptionHandler {
private final MessageSource messageSource;
#Override
protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException ex,
HttpHeaders headers, HttpStatus status, WebRequest request) {
ValidationErrorDTO validationError = ValidationErrorDTO.builder()
.fieldErrors(ex.getBindingResult().getFieldErrors().stream()
.map(fieldError -> FieldErrorDTO.builder()
.field(fieldError.getField())
.message(messageSource.getMessage(fieldError, LocaleContextHolder.getLocale()))
.build())
.collect(Collectors.toList()))
.build();
LOGGER.debug("W3MnsZ validation error: {}", validationError);
return new ResponseEntity<Object>(validationError, HttpStatus.BAD_REQUEST);
}
}
====
#ToString
#Builder
public class ValidationErrorDTO {
private final List<FieldErrorDTO> fieldErrors;
}
====
#ToString
#Builder
#Getter
#JsonInclude(Include.NON_NULL)
public class FieldErrorDTO {
private final String field;
private final String message;
}
My ErrorHandler.handleMethodArgumentNotValid() gets hit, but the actual response body returned to the client is not from my ValidationErrorDTO.
{
"timestamp": 1523115887261,
"status": 400,
"error": "Bad Request",
"exception": "org.springframework.web.bind.MethodArgumentNotValidException",
"errors": [
{
"codes": [
"NotEmpty.myDTO.field.another.lastly",
"NotEmpty.field.another.lastly",
"NotEmpty.lastly",
"NotEmpty.java.lang.String",
"NotEmpty"
],
"arguments": [
{
"codes": [
"myDTO.field.another.lastly",
"field.another.lastly"
],
"arguments": null,
"defaultMessage": "field.another.lastly",
"code": "field.another.lastly"
}
],
"defaultMessage": "may not be empty",
"objectName": "myDTO",
"field": "field.another.lastly",
"rejectedValue": null,
"bindingFailure": false,
"code": "NotEmpty"
}
],
"message": "Validation failed for object='myDTO'. Error count: 1",
"path": "/myPath"
}
I've figured out that what's happening is org.springframework.boot.autoconfigure.web.DefaultErrorAttributes is getting hit and somehow overriding my custom response body.
What do I do to allow my custom response body to be returned to the client?
Just figured it out.
ValidationErrorDTO.fieldErrors had no getter.
#ToString
#Builder
#Getter
public class ValidationErrorDTO {
#Singular
private final List<FieldErrorDTO> fieldErrors;
}
Now it works and we get response:
{
"fieldErrors": [
{
"field": "field.another.lastly",
"message": "may not be empty"
}
]
}

Resources