3

I am new to spring-boot I'm trying to add validation to my DTO class like below.

import javax.validation.constraints.NotBlank;

@Getter
@Setter
public class EmployeeDto {
    private Long id;

    @NotBlank(message = "Employee first name is required")
    private String firstName;

    private String lastName;

    @NotBlank(message = "EmployeeNUM  is required")
    private String employeeNum;

}

Below is my REST endpoint to save employee.

import javax.validation.Valid;
 @PostMapping("/employee")
    public ResponseEntity<?> addEmployee(@Valid @RequestBody EmployeeDto employeeDto) throws ClassNotFoundException {
        return   ResponseEntity.ok(employeeService.saveEmployee(deptId,employeeDto));

    }

I create a Validation class like below to validate the DTO fields.

@ControllerAdvice
@RestController
public class Validation {

    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(MethodArgumentNotValidException.class)
    public Map<String, String> handleValidationExceptions(
            MethodArgumentNotValidException ex) {
        Map<String, String> errors = new HashMap<>();
        ex.getBindingResult().getAllErrors().forEach((error) -> {
            String fieldName = ((FieldError) error).getField();
            String errorMessage = error.getDefaultMessage();
            errors.put(fieldName, errorMessage);
        });
        return errors;

    }
}

expected output is

{ "firstName":"Employee first name is required", "employeeNum":"EmployeeNUM is required" }

But I'm getting only the 400 bad request when hit the endpoint through postman. What is the issue with my code? How to fix and get the expected output as mentioned above?

7
  • Did you pass values with the request? Commented May 17, 2020 at 3:52
  • yes. I passed empty strings for the fileds Commented May 17, 2020 at 3:52
  • @SupunWijerathne Yes. I'm getting the status correctly but not the response body. Commented May 17, 2020 at 5:50
  • @SupunWijerathne still not working. I just followed the below document, doing the same thing for the entity class that class is annotated with the entity and its working I think the issue is DTO is not having entity annotation. we cannot add entity on DTO classes so is there another way to handle validations on DTO classes? baeldung.com/spring-boot-bean-validation Commented May 17, 2020 at 6:08
  • I posted the answer. Found a different way. Commented May 17, 2020 at 7:35

3 Answers 3

3

Try to extend the ResponseEntityExceptionHandler class like this:


import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;

import javax.validation.ConstraintViolationException;
import java.time.Instant;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;


/**
 * * Handle all exceptions and java bean validation errors for all endpoints income data that use the @Valid annotation
 *
 * @author Ehab Qadah
 */
@ControllerAdvice
public class GeneralExceptionHandler extends ResponseEntityExceptionHandler {


    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(MethodArgumentNotValidException exception, HttpHeaders headers,
                                                                  HttpStatus status, WebRequest request) {
        List<String> validationErrors = exception.getBindingResult()
                .getFieldErrors()
                .stream()
                .map(error -> error.getField() + ": " + error.getDefaultMessage())
                .collect(Collectors.toList());
        return getExceptionResponseEntity(HttpStatus.BAD_REQUEST, request, validationErrors);
    }


    @ExceptionHandler({ConstraintViolationException.class})
    public ResponseEntity<Object> handleConstraintViolation(
            ConstraintViolationException exception, WebRequest request) {
        List<String> validationErrors = exception.getConstraintViolations().stream().
                map(violation -> violation.getPropertyPath() + ": " + violation.getMessage())
                .collect(Collectors.toList());
        return getExceptionResponseEntity(HttpStatus.BAD_REQUEST, request, validationErrors);
    }

    private ResponseEntity<Object> getExceptionResponseEntity(final HttpStatus status, WebRequest request, List<String> errors) {
        final Map<String, Object> body = new LinkedHashMap<>();
        final String errorsMessage = CollectionUtils.isNotEmpty(errors) ? errors.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")):status.getReasonPhrase();
        final String path = request.getDescription(false);
        body.put("TIMESTAMP", Instant.now());
        body.put("STATUS", status.value());
        body.put("ERRORS", errorsMessage);
        body.put("PATH", path);
        body.put("MESSAGE", status.getReasonPhrase());
        return new ResponseEntity<>(body, status);
    }
}

Sign up to request clarification or add additional context in comments.

Comments

0

I have copied the answer of @Ehab Qadah. Only one modification is giving error fields with reason in a map of string, instead of a string (fieldErrors variable)

    @ExceptionHandler({ConstraintViolationException.class})
public ResponseEntity<Object> handleConstraintViolation(
        ConstraintViolationException exception, WebRequest request) {
        
    Map<String, String> fieldErrors = ex.getConstraintViolations().stream()
            .collect(Collectors.toMap(cv -> cv.getPropertyPath().toString(), ConstraintViolation::getMessage));
        
    return getExceptionResponseEntity(HttpStatus.BAD_REQUEST, request, fieldErrors);
}

private ResponseEntity<Object> getExceptionResponseEntity(final HttpStatus status, WebRequest request, List<String> errors) {
    final Map<String, Object> body = new LinkedHashMap<>();
    final String errorsMessage = CollectionUtils.isNotEmpty(errors) ? errors.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining(",")):status.getReasonPhrase();
    final String path = request.getDescription(false);
    body.put("TIMESTAMP", Instant.now());
    body.put("STATUS", status.value());
    body.put("ERRORS", errorsMessage);
    body.put("PATH", path);
    body.put("MESSAGE", status.getReasonPhrase());
    return new ResponseEntity<>(body, status);
}   

Comments

-1

I used the below class and now it's working correctly.

@Service
public class MapValidationErrorService {
    public ResponseEntity<?> MapValidationService(BindingResult result){

        if(result.hasErrors()){
            Map<String, String> errorMap = new HashMap<>();

            for(FieldError error: result.getFieldErrors()){
                errorMap.put(error.getField(), error.getDefaultMessage());
            }
            return new ResponseEntity<Map<String, String>>(errorMap, HttpStatus.BAD_REQUEST);
        }

        return null;

    }

}

In COntroller

 @Autowired
    private MapValidationErrorService mapValidationErrorService;

    @PostMapping("/employee/{deptId}")
    public ResponseEntity<?> addEmployee(@PathVariable(name = "deptId") String deptId,@Valid @RequestBody EmployeeDto employeeDto, BindingResult result) throws ClassNotFoundException {
        ResponseEntity<?> errorMap = mapValidationErrorService.MapValidationService(result);
        if(errorMap != null)return errorMap;
        return   ResponseEntity.ok(employeeService.saveEmployee(deptId,employeeDto));

    }

2 Comments

This is pretty much unnecessary manual work. I'm sure your actual problem is something else.
You don't need to validate manually, Sprint Boot validates the request body when you mention @valid. Don't forget to include spring-boot-starter-validation in dependencies.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.