Spring Boot 4 Complete Guide · 강의

RFC 7807 Problem Detail 응답

Spring의 ProblemDetail 및 ErrorResponse 지원을 사용해 기계가 읽을 수 있는 오류 데이터를 표준화합니다.

레슨 4/413개 단계

RFC 7807 Problem Detail 응답은(는) CoddyKit의 무료 Spring Boot 4 Complete Guide 강의입니다. 이것은 4개 중 4번째 강의입니다. 아래에서 전체 강의를 무료로 읽을 수 있으며, 내장 코드 에디터와 24/7 AI 튜터와 함께 브라우저에서 직접 실습할 수 있습니다. 이 강의는 Spring Boot 4 Complete Guide 학습 경로의 일부이며, 진행 상황이 웹과 CoddyKit 앱에 동기화됩니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

이 강의의 일부는 아직 번역되지 않았으며 영어로 표시됩니다.

Why Standardize Error Responses?

When an API fails, clients need a predictable, machine-readable error body. Hand-rolled JSON like {"error": "bad"} varies per endpoint and breaks integrations.

RFC 7807 ("Problem Details for HTTP APIs") defines a single, standard shape for error payloads. Spring Boot has first-class support for it through the ProblemDetail class and the ErrorResponse contract.

  • Consistent across every endpoint
  • Self-documenting via a type URI
  • Extensible with custom properties

The RFC 7807 Media Type and Fields

A Problem Detail response uses the media type application/problem+json and contains these standard members:

  • type — a URI identifying the problem category (defaults to about:blank)
  • title — a short, human-readable summary
  • status — the HTTP status code (e.g. 404)
  • detail — a human-readable explanation specific to this occurrence
  • instance — a URI identifying the specific occurrence (often the request path)

You may also add custom extension members like errorCode or timestamp.

A Sample Problem+JSON Body

Here is what a client actually receives. Note the application/problem+json content type and the standard fields, plus an extension member errorCode.

{
  "type": "https://api.shop.com/problems/out-of-stock",
  "title": "Out of Stock",
  "status": 409,
  "detail": "Product 42 has only 3 units left, but 5 were requested.",
  "instance": "/api/orders",
  "errorCode": "INVENTORY_SHORTAGE"
}

Building a ProblemDetail Programmatically

Spring's ProblemDetail is a simple value object. Use the static factory forStatus() or forStatusAndDetail(), then enrich it with setters.

You can attach custom members via setProperty(name, value). This object serializes directly to application/problem+json.

ProblemDetail problem = ProblemDetail.forStatusAndDetail(
        HttpStatus.NOT_FOUND,
        "Product 42 was not found");
problem.setTitle("Product Not Found");
problem.setType(URI.create("https://api.shop.com/problems/not-found"));
problem.setInstance(URI.create("/api/products/42"));
problem.setProperty("errorCode", "PRODUCT_NOT_FOUND");

Returning ProblemDetail from a Controller

A controller method can return a ProblemDetail directly, or wrap it in a ResponseEntity for full control of headers. Spring sets the status from the ProblemDetail and serializes the body as problem+json.

@GetMapping("/products/{id}")
public ResponseEntity<ProblemDetail> getProduct(@PathVariable Long id) {
    return productRepo.findById(id)
        .map(p -> ResponseEntity.ok().<ProblemDetail>build())
        .orElseGet(() -> {
            ProblemDetail pd = ProblemDetail.forStatusAndDetail(
                HttpStatus.NOT_FOUND, "No product with id " + id);
            pd.setTitle("Product Not Found");
            return ResponseEntity.status(404).body(pd);
        });
}

The ErrorResponse Contract

Returning ProblemDetail manually works, but Spring prefers exceptions to carry their own problem detail. The ErrorResponse interface couples an HTTP status, headers, and a ProblemDetail body.

Spring MVC automatically renders any thrown exception that implements ErrorResponse as problem+json. The convenience class ErrorResponseException is a ready-made implementation you can throw directly.

ProblemDetail pd = ProblemDetail.forStatusAndDetail(
        HttpStatus.CONFLICT, "Email already registered");
pd.setTitle("Duplicate Email");
pd.setProperty("errorCode", "DUPLICATE_EMAIL");

throw new ErrorResponseException(HttpStatus.CONFLICT, pd, null);

Custom Exceptions Implementing ErrorResponse

For domain errors, implement ErrorResponse on your own exception. By extending ErrorResponseException, you inherit status, headers, and body handling, and just supply a configured ProblemDetail.

This keeps error definitions next to the domain logic and out of controllers.

public class OutOfStockException extends ErrorResponseException {

    public OutOfStockException(long productId, int available, int requested) {
        super(HttpStatus.CONFLICT, asProblemDetail(productId, available, requested), null);
    }

    private static ProblemDetail asProblemDetail(long id, int avail, int req) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.CONFLICT,
            "Product " + id + " has only " + avail + " left, but " + req + " requested.");
        pd.setType(URI.create("https://api.shop.com/problems/out-of-stock"));
        pd.setTitle("Out of Stock");
        pd.setProperty("errorCode", "INVENTORY_SHORTAGE");
        return pd;
    }
}

Centralizing with @ControllerAdvice

The cleanest pattern: throw plain domain exceptions, then convert them to ProblemDetail in one place using @RestControllerAdvice and @ExceptionHandler.

The handler returns a ProblemDetail; Spring applies the status and problem+json content type automatically.

@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(ProductNotFoundException.class)
    public ProblemDetail handleNotFound(ProductNotFoundException ex) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND, ex.getMessage());
        pd.setTitle("Product Not Found");
        pd.setType(URI.create("https://api.shop.com/problems/not-found"));
        pd.setProperty("errorCode", "PRODUCT_NOT_FOUND");
        return pd;
    }
}

Extending ResponseEntityExceptionHandler

Spring's built-in exceptions (validation failures, unreadable bodies, missing parameters) are handled by ResponseEntityExceptionHandler. Since Spring Boot 3, these already produce ProblemDetail bodies.

Extend it in your advice to override or enrich the defaults while keeping framework error handling consistent.

@RestControllerAdvice
public class ApiExceptionHandler extends ResponseEntityExceptionHandler {

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
            MethodArgumentNotValidException ex, HttpHeaders headers,
            HttpStatusCode status, WebRequest request) {

        ProblemDetail pd = ex.getBody();
        pd.setTitle("Validation Failed");
        pd.setProperty("errorCode", "VALIDATION_ERROR");
        return handleExceptionInternal(ex, pd, headers, status, request);
    }
}

Adding Standard Extension Properties

Common extensions include a timestamp and a traceId for log correlation. Add them in one place so every error carries them.

Use setProperty with serializable values; Instant serializes to an ISO-8601 string by default.

ProblemDetail pd = ProblemDetail.forStatusAndDetail(
        HttpStatus.BAD_REQUEST, "Quantity must be positive");
pd.setTitle("Invalid Quantity");
pd.setProperty("timestamp", Instant.now());
pd.setProperty("traceId", MDC.get("traceId"));
pd.setProperty("errorCode", "INVALID_QUANTITY");

Configuring Defaults in application.yml

Spring can include extra context automatically. Enabling these properties makes the framework attach standard details without code changes.

  • spring.mvc.problemdetails.enabled=true turns on problem+json for built-in exceptions (default in Boot 3+)
  • server.error.include-message and include-binding-errors control how much detail is exposed

Keep sensitive details out of production responses by tuning these flags.

spring:
  mvc:
    problemdetails:
      enabled: true
server:
  error:
    include-message: on_param
    include-binding-errors: on_param
    include-stacktrace: never

Quick Check: Choosing the Right Pattern

You have several domain exceptions (not-found, conflict, forbidden) and want every error across all controllers to emit consistent application/problem+json with shared extensions like traceId. What is the recommended Spring approach?

Recap: RFC 7807 in Spring Boot

You learned how to standardize error payloads with RFC 7807:

  • ProblemDetail is the value object for application/problem+json, with type, title, status, detail, instance plus custom properties.
  • ErrorResponse / ErrorResponseException let exceptions carry their own problem detail.
  • @RestControllerAdvice centralizes conversion of domain exceptions into ProblemDetail.
  • ResponseEntityExceptionHandler already renders Spring's built-in exceptions as problem+json, and can be extended.
  • Add extensions like errorCode, timestamp, and traceId, and tune exposure via application.yml.

The result: a single, predictable, machine-readable error contract across your whole API.

무료로 시작

AI 튜터와 함께 Java을(를) 배우세요 — 무료

브라우저에서 실제 코드를 작성하고 실행하며, 24/7 AI 튜터로부터 즉각적인 도움을 받고, 웹이나 앱에서 중단한 부분부터 계속 학습하세요.

코스
21
레슨
84

자주 묻는 질문

“RFC 7807 Problem Detail 응답” 강의는 무료인가요?

네 — “RFC 7807 Problem Detail 응답” 전체 내용을 이 웹사이트에서 무료로 읽을 수 있습니다. 인터랙티브하게 실습하려면(내장 코드 에디터와 24/7 AI 튜터), CoddyKit PRO로 업그레이드하면 Spring Boot 4 Complete Guide 강의 전체를 잠금 해제할 수 있습니다. Spring Boot 4 Complete Guide 강의에는 총 4개의 강의가 포함되어 있습니다.

“RFC 7807 Problem Detail 응답”에서 뭘 배우나요?

Spring의 ProblemDetail 및 ErrorResponse 지원을 사용해 기계가 읽을 수 있는 오류 데이터를 표준화합니다. 브라우저에서 직접 실행하는 실습 코드로 Spring Boot 4 Complete Guide을(를) 배우며, 24/7 AI 튜터가 강의를 진행하면서 질문에 답변해줍니다.

Spring Boot 4 Complete Guide을(를) 시작하는 데 경험이 필요한가요?

사전 경험은 필요하지 않습니다. CoddyKit의 Spring Boot 4 Complete Guide은(는) 초급자부터 고급 학습자까지를 위해 구성되어 있으므로, 여기서 시작하거나 처음부터 시작할 수 있으며 자신의 속도대로 진행할 수 있습니다. 이것은 4개 중 4번째 강의입니다.

“RFC 7807 Problem Detail 응답” 강의는 얼마나 걸리나요?

대부분의 CoddyKit 강의는 약 5~10분이 소요됩니다. 각 강의는 간결하고 인터랙티브하여 꾸준한 진행이 가능하며, 웹과 앱에서 중단한 부분부터 바로 시작할 수 있습니다.

이 Spring Boot 4 Complete Guide 강의에서 코드를 작성하고 실행할 수 있나요?

네. 모든 Spring Boot 4 Complete Guide 강의에는 내장 코드 에디터가 포함되어 있으므로, 브라우저에서 바로 실제 코드를 작성하고 실행한 후 즉시 AI 피드백을 받을 수 있습니다 — 로컬 설정이 필요 없습니다.

이 강의의 모든 강의

  1. Bean Validation 제약 조건과 제약 조건 그룹
  2. 사용자 지정 제약 조건 주석 구축
  3. @ControllerAdvice를 활용한 전역 예외 처리
  4. RFC 7807 Problem Detail 응답
← Spring Boot 4 Complete Guide(으)로 돌아가기