DEV Community

Cover image for Validate Your Endpoints Using Spring
Abdulcelil Cercenazi
Abdulcelil Cercenazi

Posted on

Validate Your Endpoints Using Spring

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)