0Pricing
Spring Boot 4 Complete Guide · บทเรียน

การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice

รวมการแปลงข้อยกเว้นให้เป็นการตอบกลับ HTTP ที่เป็นระเบียบไว้ที่ศูนย์กลางด้วย @ExceptionHandler และ @ControllerAdvice

การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice เป็นบทเรียน Spring Boot 4 Complete Guide ฟรีบน CoddyKit นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน Spring Boot 4 Complete Guide และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน

บางส่วนของบทเรียนนี้ยังไม่ได้รับการแปล และแสดงเป็นภาษาอังกฤษ

Why Centralize Exception Handling?

When something goes wrong in a Spring Boot REST API, the client should receive a clean, predictable HTTP response, not a raw stack trace.

Without a strategy, you end up scattering try/catch blocks across every controller method, repeating the same JSON-building logic over and over.

  • Inconsistent status codes for the same error type
  • Duplicated error-response shaping
  • Leaked internal details (stack traces, SQL) to clients

Global exception handling solves this by translating exceptions into HTTP responses in one place.

The @ExceptionHandler Building Block

The core annotation is @ExceptionHandler. It marks a method that handles a specific exception type thrown during request processing.

Placed inside a controller, it only catches exceptions thrown by that controller's handler methods.

The method's parameter declares which exception it handles, and its return value becomes the HTTP response.

@RestController
public class ProductController {

    @GetMapping("/products/{id}")
    public Product getProduct(@PathVariable Long id) {
        throw new ProductNotFoundException(id);
    }

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<String> handleNotFound(ProductNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

From Local to Global with @ControllerAdvice

A local @ExceptionHandler only covers one controller. To share handlers across the whole application, move them into a class annotated with @ControllerAdvice.

@ControllerAdvice is a specialization of @Component. Spring registers its @ExceptionHandler methods globally, so any controller that throws a matching exception is handled here.

For REST APIs, prefer @RestControllerAdvice, which combines @ControllerAdvice with @ResponseBody so return values are serialized to JSON automatically.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<String> handleNotFound(ProductNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(ex.getMessage());
    }
}

A Consistent Error Response Body

Returning a raw string is rarely enough. Clients want a structured, predictable JSON body they can parse.

Define a small error response record that every handler returns. This is plain Java, no framework needed, so it is fully standalone.

Spring serializes this record to JSON when returned from a @RestControllerAdvice method.

import java.time.Instant;

public record ApiError(
        Instant timestamp,
        int status,
        String error,
        String message,
        String path) {
}

public class Demo {
    public static void main(String[] args) {
        ApiError err = new ApiError(
                Instant.parse("2026-01-01T10:15:30Z"),
                404,
                "Not Found",
                "Product 42 not found",
                "/products/42");
        System.out.println(err);
    }
}

Returning Structured Errors with ResponseEntity

Now combine the ApiError record with your global handler. Build a ResponseEntity that carries both the correct status code and the structured body.

Use the injected HttpServletRequest to capture the request path, which is helpful for debugging on the client side.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ApiError> handleNotFound(
            ProductNotFoundException ex, HttpServletRequest request) {

        ApiError error = new ApiError(
                Instant.now(),
                HttpStatus.NOT_FOUND.value(),
                HttpStatus.NOT_FOUND.getReasonPhrase(),
                ex.getMessage(),
                request.getRequestURI());

        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
}

Mapping Status with @ResponseStatus

If a handler always returns the same status and you do not need custom headers, you can skip ResponseEntity entirely.

Annotate the handler method with @ResponseStatus(...) and return the body directly. Spring applies the status code for you.

This keeps simple handlers concise, but reach for ResponseEntity when you need dynamic status codes or headers.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    @ResponseStatus(HttpStatus.NOT_FOUND)
    public ApiError handleNotFound(ProductNotFoundException ex, HttpServletRequest req) {
        return new ApiError(Instant.now(), 404, "Not Found",
                ex.getMessage(), req.getRequestURI());
    }
}

Handling Validation Errors

When a @Valid request body fails Bean Validation, Spring throws MethodArgumentNotValidException. By default the JSON response is verbose and noisy.

Override it in your advice to return a clean map of field errors. Each FieldError carries the offending field name and the validation message.

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
public Map<String, String> handleValidation(MethodArgumentNotValidException ex) {
    Map<String, String> errors = new HashMap<>();
    for (FieldError fieldError : ex.getBindingResult().getFieldErrors()) {
        errors.put(fieldError.getField(), fieldError.getDefaultMessage());
    }
    return errors;
}

One Handler for Many Exceptions

@ExceptionHandler accepts an array of exception classes, so a single method can handle several related types that map to the same status.

This avoids duplicate code when, say, several distinct "bad input" exceptions should all become 400 Bad Request.

@ExceptionHandler({
        IllegalArgumentException.class,
        IllegalStateException.class,
        ConstraintViolationException.class
})
@ResponseStatus(HttpStatus.BAD_REQUEST)
public ApiError handleBadRequest(RuntimeException ex, HttpServletRequest req) {
    return new ApiError(Instant.now(), 400, "Bad Request",
            ex.getMessage(), req.getRequestURI());
}

Exception Resolution Order

When an exception is thrown, Spring picks the most specific matching @ExceptionHandler.

  • An exact-type handler wins over a handler for its superclass.
  • Local handlers in the controller take precedence over global ones in @ControllerAdvice.
  • If two handlers are equally specific, Spring raises an ambiguity error at startup.

This ordering lets you provide a broad fallback while still overriding specific cases.

A Safe Catch-All Fallback

Always provide a last-resort handler for Exception.class. It catches anything you did not explicitly map and prevents internal details from leaking.

Return a generic 500 Internal Server Error with a safe message, and log the real exception server-side for investigation.

Never put the raw exception message or stack trace into the client response for unexpected errors.

@ExceptionHandler(Exception.class)
@ResponseStatus(HttpStatus.INTERNAL_SERVER_ERROR)
public ApiError handleAny(Exception ex, HttpServletRequest req) {
    log.error("Unhandled exception at {}", req.getRequestURI(), ex);
    return new ApiError(Instant.now(), 500, "Internal Server Error",
            "An unexpected error occurred", req.getRequestURI());
}

Scoping and Extending the Framework Base

You can scope an advice to a subset of controllers using attributes like @RestControllerAdvice(basePackages = "com.shop.api") or assignableTypes = {...}. Unscoped advice applies everywhere.

For full control over Spring MVC's built-in exceptions (missing parameters, unreadable bodies, unsupported media types), extend ResponseEntityExceptionHandler and override its protected methods. It already declares handlers for the standard MVC exceptions.

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ResponseEntity<ApiError> handleNotFound(
            ProductNotFoundException ex, HttpServletRequest req) {
        ApiError error = new ApiError(Instant.now(), 404, "Not Found",
                ex.getMessage(), req.getRequestURI());
        return ResponseEntity.status(HttpStatus.NOT_FOUND).body(error);
    }
}

Quick Check

You have a REST API. You want validation errors and not-found errors to produce consistent JSON bodies and correct status codes across every controller, in one place. What is the idiomatic Spring Boot approach?

Recap

You learned how to centralize exception handling in Spring Boot:

  • @ExceptionHandler maps an exception type to an HTTP response; alone it is controller-local.
  • @RestControllerAdvice registers handlers globally and serializes return values to JSON.
  • Return a consistent error record (status, message, path, timestamp) for predictable clients.
  • Use ResponseEntity for dynamic status/headers, or @ResponseStatus for fixed ones.
  • Handle MethodArgumentNotValidException for clean validation errors, group related exceptions, and always add an Exception.class catch-all that hides internals.

The result is consistent, secure, and maintainable error responses across your whole API.

คำถามที่พบบ่อย

บทเรียน “การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Spring Boot 4 Complete Guide ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice”

รวมการแปลงข้อยกเว้นให้เป็นการตอบกลับ HTTP ที่เป็นระเบียบไว้ที่ศูนย์กลางด้วย @ExceptionHandler และ @ControllerAdvice คุณปฏิบัติ Spring Boot 4 Complete Guide ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Spring Boot 4 Complete Guide หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Spring Boot 4 Complete Guide บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 3 จากทั้งหมด 4 บทเรียน

บทเรียน “การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน Spring Boot 4 Complete Guide นี้ได้ไหม

ได้ บทเรียน Spring Boot 4 Complete Guide ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. ข้อจำกัดการตรวจสอบ Bean และกลุ่มข้อจำกัด
  2. การสร้างคำอธิบายประกอบข้อจำกัดแบบกำหนดเอง
  3. การจัดการข้อยกเว้นทั่วโลกด้วย @ControllerAdvice
  4. การตอบกลับรายละเอียดปัญหา RFC 7807
← กลับไปที่ Spring Boot 4 Complete Guide