DEV Community

loading...
Cover image for Validate Your Endpoints Using Spring

Validate Your Endpoints Using Spring

Abdulcelil Cercenazi
I enjoy learning and teaching, I am currently working as a Software developer using Spring and Vanilla JavaScript.
・3 min read

What Do We Want To Do? 🤔

Apply validation to the objects we receive at the endpoint controllers from clients.

What Is The First Solution That Comes To Mind? 🧠

it's simple, right? write functions that validate those objects.

Say we have an endpoint to add new Shipments. 🚚

Let's start with the DTO classes (Data Transfer Object)

@RequiredArgsConstructor @Getter  
public class ShipmentComponentDTO {  
    private final String productCode;  
}
Enter fullscreen mode Exit fullscreen mode
@RequiredArgsConstructor @Getter  
public class ShipmentDTO {  
    private final String productCode;  
    private final int count;  
    private final List<ShipmentComponentDTO> shipmentComponentDTOs;  
}
Enter fullscreen mode Exit fullscreen mode

Now, the controller

@RestController  
public class ShipmentController {  
    @PostMapping("/shipment/add")  
    public ResponseEntity<String> addShipment(@RequestBody ShipmentDTO shipmentDTO){  
        if (shipmentDTO.getProductCode() == null)  
            return ResponseEntity.badRequest().body("Product code can't be null");  

        if (shipmentDTO.getCount() <= 0)  
            return ResponseEntity.badRequest().body("Count can't be negative or zero");  

        if (!allComponentsAreValid(shipmentDTO))  
            return ResponseEntity.badRequest().body("Component product code can't be null");  

        return ResponseEntity.ok(":)");  
    }  

    private boolean allComponentsAreValid(ShipmentDTO shipmentDTO) {  
        List<ShipmentComponentDTO> shipmentComponentDTOs = shipmentDTO.getShipmentComponentDTOs();  

        return shipmentComponentDTOs != null &&  
                shipmentComponentDTOs.size() > 0 &&  
                shipmentComponentDTOs.  
                stream().  
                allMatch(shipmentComponentDTO -> shipmentComponentDTO.getProductCode() != null);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

Notice all the manual tedious work we had to do.🤕

Can't anyone help us make this task more fun?😏

Please meet the Javax validator.🥳

  • It provides a set of annotations that are used with class fields.
  • They act as constraints.
    • this field should not be null
    • this list should have a minimum size of 5
    • and many more...

How to do it?👀

For the object we want to validate

  • We annotate the fields we want to validate with annotations from javax.validation.constraints
  • Then, we annotate the parameter of the controller where we want to validate.

  • We then have to catch the error thrown by the validator, and return a proper response to the user.

Let's code it 🦾

Here are the DTO objects

@RequiredArgsConstructor @Getter  
public class ShipmentComponentDTO {  
    @NotBlank(message = "Component product code can't be null")  
    private final String productCode;  
    // required by the javax validation code  
  public ShipmentComponentDTO() {  
        this.productCode = "";  
    }  
}
Enter fullscreen mode Exit fullscreen mode
@RequiredArgsConstructor @Getter  
public class ShipmentDTO {  
    @NotBlank(message = "Product code can't be null")  
    private final String productCode;  
    @Min(1)  
    private final int count;  

    @Size(min = 1, message = "Component product code can't be empty")  
    @NotNull(message = "Component product code can't be null")  
    @Valid  
  private final List<ShipmentComponentDTO> shipmentComponentDTOs;  
}
Enter fullscreen mode Exit fullscreen mode

Ok, what were those annotations?👇

  • NotBlank
    • This field can't be null or empty string
  • Min
    • this integer should have a min value we specify
  • Size
    • this array's size should be within the boundaries we specify
  • NotNull
    • This field must not be null
  • Valid
    • Validate the fields in this object

Now, here is the controller👇

@RestController  
public class ShipmentController_Validation {  
    @PostMapping("validation/shipment/add")  
    public ResponseEntity<String> addShipment(@RequestBody @Validated ShipmentDTO shipmentDTO){  
        return ResponseEntity.ok(":)");  
    }  
}
Enter fullscreen mode Exit fullscreen mode

What was that @Validated annotation?🤨

  • it tells Spring to run the validation mechanism on this object according to its validation annotations(NotBlank, etc..)

Finally, let's catch the exception thrown by the validator and generate a proper response

@Order(Ordered.HIGHEST_PRECEDENCE)  
@ControllerAdvice  
public class ValidationAdvice{  
    @ResponseStatus(BAD_REQUEST)  
    @ResponseBody  
 @ExceptionHandler(MethodArgumentNotValidException.class)  
    public ResponseEntity<?> methodArgumentNotValidException(MethodArgumentNotValidException ex) {  
        BindingResult result = ex.getBindingResult();  
        List<FieldError> fieldErrors = result.getFieldErrors();  
        String errorMessage = fieldErrors.get(0).getDefaultMessage();  
        return ResponseEntity.badRequest().body(errorMessage);  
    }  
}
Enter fullscreen mode Exit fullscreen mode

In conclusion✍️

  • Imagine having an API with many endpoints that we need to validate. Which method would be better?
  • The Validator.

Code on GitHub

Discussion (0)