Validasi Bean Pada Spring
Melakukan validasi input user merupakan hal yang pasti dan wajib dilakukan pada setiap aplikasi. Pada bahasa pemrograman Java terdapat standar framework validasi bernama JSR 380 atau biasa disebut bean validation 2.0. JSR 380 berisi spesifikasi validasi API, dan merupakan bagian dari Jakarta EE. Pada versi terbaru ini banyak memanfaatkan penggunakan annotations sehingga dibutuhkan Java versi 8 keatas.
Dependencies
Library JSR 380 hanya berisi interface saja dan membutuhkan implementasinya, disini implementasi yang digunakan adalah hibernate-validator
. Selain itu kita bisa menambahkan library javax.el
untuk memproses Expressions Language yang bisa disisipkan pada violation messages, jika dibutuhkan.
Sebagai catatan, library hibernate-validator sepenuhnya terpisah dari aspek persistensi Hibernate. Jadi dengan menambahkan library hibernate-validator tidak ikut membawa aspek persistensi pada project anda.
<dependencies>
...
<dependency>
<groupId>javax.validation</groupId>
<artifactId>validation-api</artifactId>
<version>2.0.1.Final</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>6.1.5.Final</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>javax.el</artifactId>
<version>3.0.0</version>
</dependency>
</dependencies>
Validation Annotations
JSR 380 memiliki banyak sekali standar annotations yang bisa digunakan untuk mendeskripsikan spesifikasi suatu bean.
@NotNull
validasi value property tidak null.@AssertTrue
validasi value property harus true.@Size
validasi value property berada antara atribut min dan max, selain itu bisa digunakan juga pada String, Collections, Map, dan Array.@Min
validasi value property tidak lebih kecil dari value atribut.@Max
validasi value property tidak lebih besar dari value atribut.@Email
validasi value property memiliki format email yang valid.@NotEmpty
validasi value property tidak null dan kosong. Bisa digunakan juga pada String, Collections, Map, dan Array.@NotBlank
validasi value property tidak null dan tidak hanya berisi whitespace.@Positive
dan@PositiveOrZero
diterapkan pada value numeric harus bernilai positif atau termasuk angka 0.@Negative
dan@NegativeOrZero
diterapkan pada value numeric harus bernilai negatif atau termasuk angka 0.@Past
dan@pastOrPresent
diterapkan pada value date harus waktu lampau atau waktu saat ini.@Future
dan@FutureOrPresent
diterapkan pada value date harus waktu yang akan datang atau waktu saat ini.
Berikut contoh penerapannya:
public class Input {
@Size(min = 3, message = "Invalid name")
@NotEmpty(message = "Name cannot be empty")
private String name;
@Email(message = "Please input a valid format email")
private String email;
@Max(value = 50, message = "Age should not be greater than {value}.")
@Min(value = 17, message = "Age should not be less than 17")
@Positive
private int age;
@Past(message = "birthday is past")
private LocalDate birthDay;
@AssertTrue(message = "You should do something")
private boolean working;
}
Custom Annotation Validation
Kita juga bisa membuat annotation validasi sesuai kebutuhan kita. Caranya yaitu pertama dengan membuat class yang mengimplementasikan ConstraintValidator
dimana class ini digunakan untuk mendeklarasikan rule dari validasi yang akan dibuat. selanjutnya class tersebut kita daftarkan pada annotation yang akan kita buat juga. Sebagai contoh kita akan membuat validasi IP address menggunakan regex pattern, berikut kodenya:
public class IpAddressValidator implements ConstraintValidator<IpAddress, String> {
@Override
public boolean isValid(String value, ConstraintValidatorContext constraintValidatorContext) {
if(value == null){
return false;
}
Pattern pattern = Pattern.compile("^([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})\\.([0-9]{1,3})$");
Matcher matcher = pattern.matcher(value);
try {
if (!matcher.matches()) {
return false;
}
else {
for (int i = 1; i <= 4; i++) {
int octet = Integer.parseInt(matcher.group(i));
if (octet > 255) {
return false;
}
}
return true;
}
} catch (Exception e) {
return false;
}
}
}
@Target({ FIELD })
@Retention(RUNTIME)
@Constraint(validatedBy = IpAddressValidator.class)
@Documented
public @interface IpAddress {
String message() default "Invalid IP address";
Class<?>[] groups() default { };
Class<? extends Payload>[] payload() default { };
}
Untuk menggunakan annotation tersebut cukup tambahkan pada suatu property:
public class Input {
...
@IpAddress(message = "Invalid IP address")
private String ipAddress;
}
Validate
Langkah terakhir adalah membuat validator, dimana validator ini digunakan untuk menguji object/bean tersebut valid atau tidak. Berikut contoh programnya:
public class BasicValidator {
public <B> boolean isValid(B b){
ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();
//Validate bean
Set<ConstraintViolation<B>> violations = validator.validate(b);
return violations.isEmpty();
}
}
@Test
public void testValidateSuccess(){
Input input = Input.builder()
.name("Happy Indra Wijaya")
.email("me@hiwijaya.com")
.age(29)
.birthDay(LocalDate.of(1992, Month.JULY, 10))
.working(true)
.ipAddress("192.168.0.1")
.build();
BasicValidator validator = new BasicValidator();
var result = validator.isValid(input);
assertTrue(result);
}
Spring Web Validation
Kita bisa menggunakan library bean validation ini pada ekosistem spring terutama spring-web, dimana sangat membantu sekali ketika kita ingin menvalidasi request yang dikirim oleh user/client. Spring menyediakan library bean validation yang sudah "dibungkus" agar menyesuaikan dengan ekosistem Spring.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
Validasi Request Body
Untuk menvalidasi request body pada Rest API yang kita buat pada project Spring, caranya cukup dengan menambahkan annotation @Valid
pada parameter method. Berikut contohnya:
@RestController
public class WebController {
@PostMapping("/validate-body")
public ResponseEntity<String> validateRequestBody(@Valid @RequestBody Input input){
return ResponseEntity.ok("valid");
}
...
}
Lantas apa yang terjadi jika request tersebut tidak valid? Secara otomatis akan mentrigger MethodArgumentNotValidException
, dan secara default Spring akan mengembalikan response dengan HTTP status 400
(Bad Request).
Validasi Path Variable dan Request Parameter
Selain bisa untuk menvalidasi suatu request body, tentu saja spring validation bisa digunakan untuk memvalidasi path variable maupun request parameter. Berikut contohnya:
@RestController
@Validated
public class WebController {
...
@GetMapping("/validate-path-variable/{id}")
public ResponseEntity<String> validatePathVariable(@PathVariable("id") @Min(1) int id){
return ResponseEntity.ok("valid");
}
@GetMapping("/validate-request-parameter")
public ResponseEntity<String> validateRequestParameter(@RequestParam("id") @Max(10) int id){
return ResponseEntity.ok("valid");
}
}
Bila pada contoh sebelumnya kita mendeklarasikan spesifikasi validasi pada suatu bean Input
, disini kita langsung mendeklarasikan spesifikasi validasinya pada parameter method. Untuk itu diperlukan satu annotation lagi yang perlu ditambahkan pada class level yaitu @Validated
.
Berbeda dengan validasi request body yang jika tidak valid mengaktifkan exception MethodArgumentNotValidException
, pada kasus ini jika validasi path variable maupun request parameter tidak valid, maka Spring akan mengaktifkan ConstraintViolationException
yang secara default akan mengembalikan response dengan HTTP status 500
(Internal Server Error). Tentu akan sangat membingungkan jika yang dikembalikan status 500
, oleh karena itu jika kita ingin tetap mengembalikan status 400
, kita perlu membuat custom exception handler.
Untuk membuat custom exception handler, akan dibahas pada post lain.
Semua kode program lengkap dari contoh diatas dapat diakses disini.
Ref: