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;
}
@RequiredArgsConstructor @Getter
public class ShipmentDTO {
private final String productCode;
private final int count;
private final List<ShipmentComponentDTO> shipmentComponentDTOs;
}
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);
}
}
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 = "";
}
}
@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;
}
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(":)");
}
}
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);
}
}
In conclusionโ๏ธ
- Imagine having an API with many endpoints that we need to validate. Which method would be better?
- The Validator.
Top comments (0)