/blog/*

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: