Episode ini membahas Jakarta Bean Validation di Quarkus: @Valid dan constraint annotations, validasi payload, request dan response filter, interceptor, serta exception translation untuk respons error yang konsisten.

API yang menerima data dari luar tanpa memvalidasi input adalah bom waktu. Di episode 5 dan 6 kalian sudah membangun REST API dan persistence. Sekarang saatnya memasang pengaman: validasi input di setiap lapisan.
Episode 7 membahas Jakarta Bean Validation di Quarkus — annotation constraint seperti @NotBlank dan @Email pada DTO, aktivasi validasi dengan @Valid, filter request dan response, interceptor, serta exception translation agar error validasi mengembalikan format yang konsisten.
Hibernate Validator adalah implementasi referensi Jakarta Bean Validation. Tambahkan dengan ./mvnw quarkus:add-extension -Dextensions=hibernate-validator.
Constraint dipasang pada field atau parameter DTO:
import jakarta.validation.constraints.*;
public class CreateItemCommand {
@NotBlank(message = "Nama tidak boleh kosong")
@Size(min = 2, max = 100, message = "Nama harus 2-100 karakter")
public String nama;
@Size(max = 500)
public String deskripsi;
@NotNull(message = "Harga wajib diisi")
@Positive(message = "Harga harus lebih dari nol")
public double harga;
}Constraint umum: @NotBlank, @NotNull, @Size, @Positive, @Email, @Min, @Max, @Pattern. Message kustom memudahkan client memahami alasan penolakan.
Tanpa @Valid, constraint pada DTO diabaikan. Pasang @Valid pada parameter method resource:
import jakarta.validation.Valid;
import jakarta.ws.rs.*;
import jakarta.ws.rs.core.MediaType;
@Path("/api/items")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ItemResource {
@POST
public Item create(@Valid CreateItemCommand command) {
return new Item(command.nama, command.deskripsi, command.harga);
}
public record Item(String nama, String deskripsi, double harga) {}
}Dengan @Valid CreateItemCommand command, RESTEasy Reactive mengeksekusi validasi sebelum method dipanggil. Jika gagal, request ditolak dengan status 400.
Constraint juga bisa dipasang langsung pada parameter method: @PathParam("id") @Min(1) long id menolak nilai di bawah minimum, dan @QueryParam("limit") @Max(100) int limit membatasi query. Perintah curl http://localhost:8080/api/items/0 akan mengembalikan 400 karena nilai id di bawah minimum — validasi berlaku untuk seluruh jenis parameter.
Filter dijalankan sebelum resource dipanggil. Cocok untuk logging request atau menambahkan header:
import jakarta.ws.rs.container.*;
import jakarta.ws.rs.ext.Provider;
@Provider
public class RequestLogFilter implements ContainerRequestFilter {
@Override
public void filter(ContainerRequestContext ctx) {
System.out.println("Request: " + ctx.getMethod()
+ " " + ctx.getUriInfo().getPath());
}
}Response filter dijalankan setelah resource menghasilkan respons:
import jakarta.ws.rs.container.*;
import jakarta.ws.rs.core.HttpHeaders;
import jakarta.ws.rs.ext.Provider;
@Provider
public class HeaderResponseFilter implements ContainerResponseFilter {
@Override
public void filter(ContainerRequestContext req,
ContainerResponseContext res) {
res.getHeaders().putSingle(HttpHeaders.X_FRAME_OPTIONS, "DENY");
}
}res.getHeaders().putSingle(...) menambahkan header ke setiap respons. Filter adalah tempat yang tepat untuk keamanan, logging, dan korrelasi log.
Selain filter, kalian bisa memakai interceptor CDI untuk membungkus logika bisnis. Pola ini sudah dibahas di episode 4 — misalnya mencatat durasi eksekusi method service.
Saat validasi gagal, Hibernate Validator melempar exception. Quarkus memberikan respons 400 bawaan, tapi kalian bisa menyesuaikan formatnya dengan exception mapper:
import jakarta.validation.ConstraintViolationException;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.ext.ExceptionMapper;
import jakarta.ws.rs.ext.Provider;
import java.util.stream.Collectors;
@Provider
public class ValidationExceptionMapper
implements ExceptionMapper<ConstraintViolationException> {
@Override
public Response toResponse(ConstraintViolationException ex) {
String detail = ex.getConstraintViolations().stream()
.map(v -> v.getPropertyPath() + ": " + v.getMessage())
.collect(Collectors.joining(", "));
return Response.status(400)
.entity(new ErrorResponse("validasi-gagal", detail))
.build();
}
public record ErrorResponse(String code, String detail) {}
}Mapper ini mengubah ConstraintViolationException menjadi JSON dengan status 400 — format yang mudah diparsing client.
Mulai aplikasi lalu kirim payload tidak valid dengan curl -X POST http://localhost:8080/api/items -H "Content-Type: application/json" -d '{"nama":"","harga":-5}'. Request dengan nama kosong dan harga negatif ditolak dengan status 400 dan body error yang menjelaskan constraint mana yang dilanggar.
Episode 7 memasang pengaman validasi di aplikasi kalian: memahami Jakarta Bean Validation dengan constraint annotations, mengaktifkan validasi payload dengan @Valid, memakai request dan response filter, serta menerjemahkan exception validasi menjadi respons error yang konsisten lewat exception mapper.
Inti yang harus dibawa pulang:
hibernate-validator untuk mengaktifkan Jakarta Bean Validation.@NotBlank, @Size, @Email pada field DTO.@Valid wajib dipasang pada parameter method agar constraint dieksekusi.@PathParam dan @QueryParam.ConstraintViolationException bisa dipetakan ke format error kustom.Di episode 8 selanjutnya kita akan membahas configuration dan profiles — application.properties dan application.yaml, profile spesifik environment, externalized config dari env var dan secrets, serta best practice manajemen konfigurasi untuk produksi.