Ketika kami merancang ulang platform perusahaan, kami belajar bahwa hal utama yang membuat pelanggan tidak senang bukanlah kinerja atau antarmuka pengguna; itu adalah kesalahan yang tidak diperbaiki yang menghancurkan proses. Dalam Spring Boot atau pola serupa dalam kerangka kerja lainnya, pastikan bahwa sistem berperilaku seperti yang diharapkan ketika mereka berada di bawah tekanan. @ControllerAdvice Why This Matters Ketika Anda membangun REST APIs di Anda akan dengan cepat mengatasi masalah ini: Spring Boot “ ” How do I handle errors neatly without writing repetitive try-catch blocks everywhere? Bayangkan memiliki Setiap orang bisa gagal dengan , input yang tidak valid, atau sumber daya yang hilang. Alih-alih mengembalikan jejak stack yang membingungkan, Anda ingin . 50+ endpoints Null Pointer Pengecualian consistent, meaningful, and client-friendly error responses Itulah di mana datang – ia menyentralisasikan semua pengolahan kesalahan di satu tempat. Mari kita lihat untuk membangun ini langkah demi langkah, dengan contoh dunia nyata. @ControllerAdvice how Use Case — User Management REST API Kami akan menciptakan a yang mendukung : simple User API Fetching users Creating users Simulating failures for testing Our goal → build a that returns clean JSON responses. unified global error-handling layer Step 1: Project Setup Dependencies (Maven): <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!-- Validation --> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-validation</artifactId> </dependency> This brings in web + validation support. Then create your main app: @SpringBootApplication public class ExceptionHandlerDemoApplication { public static void main(String[] args) { SpringApplication.run(ExceptionHandlerDemoApplication.class, args); } } Step 2: Define the Entity — User.java @Data @AllArgsConstructor @NoArgsConstructor public class User { private int id; @NotBlank(message = "Name cannot be blank") private String name; @Min(value = 18, message = "Age must be at least 18") private int age; } • Mengapa : ** Kami menambahkan validasi sederhana sehingga Spring dapat memicu Metode Argumen Tidak valid Pengecualian secara otomatis ketika entri tidak sah. Step 3: Custom Exception for Missing Data public class ResourceNotFoundException extends RuntimeException { public ResourceNotFoundException(String message) { super(message); } } public class InvalidRequestException extends RuntimeException { public InvalidRequestException(String message) { super(message); } } • Mengapa : ** Untuk mewakili situasi "pengguna tidak ditemukan" dengan jelas daripada pengecualian umum. Step 4: Build the Controller @RestController @RequestMapping("/api/users") public class UserController { // Simple GET that throws ResourceNotFoundException for id > 100 @GetMapping("/{id}") public String getUser(@PathVariable("id") @Min(1) Integer id) { if (id > 100) { throw new ResourceNotFoundException("User with id " + id + " not found"); } return "User-" + id; } // Create user example to demonstrate validation public static record CreateUserRequest( @NotBlank(message = "name is required") String name, @Min(value = 18, message = "age must be >= 18") int age) {} @PostMapping @ResponseStatus(HttpStatus.CREATED) public String createUser(@RequestBody @Valid CreateUserRequest body) { if ("bad".equalsIgnoreCase(body.name())) { throw new InvalidRequestException("Name 'bad' is not allowed"); } return "created:" + body.name(); } // Endpoint to force a server error for demo @GetMapping("/boom") public void boom() { throw new IllegalStateException("simulated server error"); } } • Mengapa : ** Kami membuat skenario yang realistis – tidak ditemukan, kesalahan validasi, dan kesalahan runtime – yang manajer global kami akan mengelola. Step 5: Create a Standard Error Model @Data @AllArgsConstructor @NoArgsConstructor public class ErrorResponse { private OffsetDateTime timestamp; private int status; private String error; private String message; private String path; private List<FieldError> fieldErrors;} • Mengapa : ** Semua API harus mengembalikan kesalahan dalam struktur yang sama - ini meningkatkan pemantauan dan debugging dalam produksi. Step 6: Implement @ControllerAdvice (Global Handler) @ControllerAdvice public class GlobalExceptionHandler { private final Logger log = LoggerFactory.getLogger(GlobalExceptionHandler.class); // Handle custom validation exceptions @ExceptionHandler(InvalidRequestException.class) public ResponseEntity<ErrorResponse> handleInvalidRequest(InvalidRequestException ex, HttpServletRequest req) { log.debug("InvalidRequestException: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(body); } // Resource not found -> 404 @ExceptionHandler(ResourceNotFoundException.class) public ResponseEntity<ErrorResponse> handleNotFound(ResourceNotFoundException ex, HttpServletRequest req) { log.debug("ResourceNotFoundException: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } // Validation errors from @Valid on request bodies @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity<ErrorResponse> handleValidation(MethodArgumentNotValidException ex, HttpServletRequest req) { log.debug("Validation failed: {}", ex.getMessage()); List<ErrorResponse.FieldError> fieldErrors = ex.getBindingResult().getFieldErrors().stream() .map(fe -> new ErrorResponse.FieldError(fe.getField(), fe.getRejectedValue(), fe.getDefaultMessage())) .collect(Collectors.toList()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), "Validation failed", req.getRequestURI()); body.setFieldErrors(fieldErrors); return ResponseEntity.badRequest().body(body); } // Type mismatch for method args (?id=abc) @ExceptionHandler(MethodArgumentTypeMismatchException.class) public ResponseEntity<ErrorResponse> handleTypeMismatch(MethodArgumentTypeMismatchException ex, HttpServletRequest req) { log.debug("Type mismatch: {}", ex.getMessage()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.BAD_REQUEST.value(), HttpStatus.BAD_REQUEST.getReasonPhrase(), ex.getMessage(), req.getRequestURI()); return ResponseEntity.badRequest().body(body); } // No handler found (404 for unmatched endpoints) @ExceptionHandler(NoHandlerFoundException.class) public ResponseEntity<ErrorResponse> handleNoHandler(NoHandlerFoundException ex, HttpServletRequest req) { log.debug("NoHandlerFound: {} {}", ex.getHttpMethod(), ex.getRequestURL()); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.NOT_FOUND.value(), HttpStatus.NOT_FOUND.getReasonPhrase(), "Endpoint not found", req.getRequestURL().toString()); return ResponseEntity.status(HttpStatus.NOT_FOUND).body(body); } // Generic fallback @ExceptionHandler(Exception.class) public ResponseEntity<ErrorResponse> handleAll(Exception ex, HttpServletRequest req) { log.error("Unhandled exception: ", ex); ErrorResponse body = new ErrorResponse(OffsetDateTime.now(), HttpStatus.INTERNAL_SERVER_ERROR.value(), HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase(), "An internal error occurred", req.getRequestURI()); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(body); } } Why: @ControllerAdvice → Membuatnya global di semua kontroler. @ExceptionHandler → Menangkap jenis pengecualian tertentu. Pembantu buildResponse() menjaga kode DRY dan bersih. Step 7: Test Scenarios GET /api/users/1 Kode Tanggapan: 200 -Sukses * yang * yang GET /api/users/999 Kode respon: 404 Resource Not Found * yang * yang Validation Error POST / API / Pengguna {"name":"", "age": 15} Response: * yang * yang Unexpected Error GET /api/pengguna / boom * yang * yang Why This Approach Works in Real Projects Problem Solution Too many try-catch blocks Centralized handling with @ControllerAdvice Inconsistent responses Unified ErrorResponse structure Hard to debug Standardized messages with timestamps and paths Client confusion Clear, meaningful messages for each failure type Terlalu banyak try-catch block Pengolahan terpusat dengan @ControllerAdvice Jawaban yang tidak konsisten Struktur Unified ErrorResponse Sulit untuk debug Pesan standar dengan timestamp dan jalur Pelanggan bingung Pesan yang jelas dan bermakna untuk setiap jenis kegagalan Real-World Usage Scenarios API perbankan: Pastikan kesalahan validasi (seperti nomor akun yang tidak sah) tidak mengganggu layanan. E-commerce: Mengatasi kesalahan produk-tidak ditemukan atau pembayaran dengan sopan. Data Microservices: Mengembalikan pesan terstruktur ketika data input gagal validasi. API Gateway: Jawaban yang konsisten di berbagai microservices. Final Thoughts Dengan menggabungkan @ControllerAdvice, @ExceptionHandler, dan model Error Response sederhana, Anda mendapatkan: Kode yang bersih Pengalaman API yang konsisten Memfasilitasi Debugging dalam Produksi Ini adalah salah satu pola desain yang paling sederhana namun paling kuat dalam pengembangan Spring Boot.