본문으로 건너뛰기
cccskills
무료GitHub에 공개

problem-details-rfc9457

Use when implementing error handling, exception mappers, or error response formatting. Enforces RFC 9457 (Problem Details for HTTP APIs) using Spring's built-in ProblemDetail.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

설치 방법 보기

포함된 파일(5)

  • SKILL.md5.8 KB
  • examples/bad-exception-handler.java1.6 KB
  • examples/good-exception-handler.java2.8 KB
  • templates/DomainException.java1.6 KB
  • templates/ProblemDetailExceptionHandler.java3.6 KB

SKILL.md(원문)

설치하기 전에 에이전트에게 전달되는 지시 내용을 확인할 수 있습니다.

Problem Details — RFC 9457

Spring Boot 3.x includes native RFC 9457 support via ProblemDetail.

Enable in application.yml

spring:
  mvc:
    problemdetails:
      enabled: true  # enables Spring's built-in RFC 9457 handler

ProblemDetail Response Shape

{
  "type": "https://api.example.com/errors/order-not-found",
  "title": "Order Not Found",
  "status": 404,
  "detail": "No order found with id: 550e8400-e29b-41d4-a716-446655440000",
  "instance": "/api/v1/orders/550e8400-e29b-41d4-a716-446655440000",
  "errorCode": "ORDER_NOT_FOUND",
  "timestamp": "2026-04-13T10:00:00Z"
}

Global Exception Handler

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    @ExceptionHandler(EntityNotFoundException.class)
    public ProblemDetail handleNotFound(EntityNotFoundException ex, HttpServletRequest request) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, ex.getMessage());
        problem.setType(URI.create("https://api.example.com/errors/not-found"));
        problem.setTitle("Resource Not Found");
        problem.setInstance(URI.create(request.getRequestURI()));
        problem.setProperty("errorCode", "NOT_FOUND");
        problem.setProperty("timestamp", Instant.now());
        return problem;
    }

    @ExceptionHandler(BusinessRuleViolationException.class)
    public ProblemDetail handleBusinessRule(BusinessRuleViolationException ex, HttpServletRequest request) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(HttpStatus.UNPROCESSABLE_ENTITY, ex.getMessage());
        problem.setType(URI.create("https://api.example.com/errors/business-rule"));
        problem.setTitle("Business Rule Violation");
        problem.setInstance(URI.create(request.getRequestURI()));
        problem.setProperty("errorCode", ex.getErrorCode());
        problem.setProperty("timestamp", Instant.now());
        return problem;
    }

    @Override
    protected ResponseEntity<Object> handleMethodArgumentNotValid(
        MethodArgumentNotValidException ex, HttpHeaders headers,
        HttpStatusCode status, WebRequest request
    ) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
            HttpStatus.BAD_REQUEST, "Request validation failed");
        problem.setType(URI.create("https://api.example.com/errors/validation"));
        problem.setTitle("Validation Failed");
        problem.setProperty("timestamp", Instant.now());
        problem.setProperty("violations", ex.getBindingResult().getFieldErrors().stream()
            .map(e -> Map.of("field", e.getField(), "message", e.getDefaultMessage()))
            .toList());
        return ResponseEntity.badRequest().body(problem);
    }

    @ExceptionHandler(Exception.class)
    public ProblemDetail handleGeneric(Exception ex, HttpServletRequest request) {
        ProblemDetail problem = ProblemDetail.forStatusAndDetail(
            HttpStatus.INTERNAL_SERVER_ERROR, "An unexpected error occurred");
        problem.setType(URI.create("https://api.example.com/errors/internal"));
        problem.setTitle("Internal Server Error");
        problem.setInstance(URI.create(request.getRequestURI()));
        problem.setProperty("timestamp", Instant.now());
        // Don't expose ex.getMessage() in production — log it instead
        log.error("Unhandled exception at {}", request.getRequestURI(), ex);
        return problem;
    }
}

Custom Domain Exceptions

// Base exception
public abstract class DomainException extends RuntimeException {
    private final String errorCode;

    protected DomainException(String errorCode, String message) {
        super(message);
        this.errorCode = errorCode;
    }

    public String getErrorCode() { return errorCode; }
}

// Specific exceptions
public class OrderNotFoundException extends DomainException {
    public OrderNotFoundException(UUID orderId) {
        super("ORDER_NOT_FOUND", "Order not found: " + orderId);
    }
}

public class InsufficientInventoryException extends DomainException {
    public InsufficientInventoryException(UUID productId, int requested, int available) {
        super("INSUFFICIENT_INVENTORY",
            "Insufficient inventory for product %s: requested %d, available %d"
                .formatted(productId, requested, available));
    }
}

Content Negotiation

Clients can request problem details explicitly with Accept: application/problem+json. Spring handles this automatically when problemdetails.enabled: true — your handler returns ProblemDetail and Spring sets the correct Content-Type: application/problem+json.

ProblemDetail vs ApiResponse Envelope

Use CaseApproach
Error responses onlyProblemDetail (RFC 9457)
All responses (success + error)ApiResponse envelope
Public API with diverse clientsProblemDetail — RFC standard
Internal microservicesEither — be consistent across services

Choose one approach per project. Don't mix ApiResponse for success and ProblemDetail for errors — it confuses API consumers who see two different shapes.

Gotchas

  • Agent returns Map<String, Object> for errors — use ProblemDetail
  • Agent exposes raw exception messages in 500 errors — log it, return generic message
  • Agent uses custom error envelope alongside ProblemDetail — pick one standard
  • Agent forgets to set type URI — required for RFC 9457 compliance
  • Agent returns 200 with error in body — always use the correct HTTP status code
  • Agent creates handler without extending ResponseEntityExceptionHandler — extend it to get Spring's built-in exception handling for free

리뷰

아직 리뷰가 없습니다. 사용해 본 소감을 남겨 주세요.

댓글

로그인 없이 쓸 수 있습니다. 사용 후기, 질문, 작동하지 않은 환경 등을 편하게 남겨 주세요.

아직 댓글이 없습니다. 첫 댓글을 남겨 보세요.

보내면 바로 공개됩니다. 이메일 주소나 전화번호 같은 개인 정보는 쓰지 마세요. URL은 2개까지입니다.

0 / 1,000자

이 양식은 reCAPTCHA로 보호되며 Google 개인정보처리방침 및 서비스 약관이 적용됩니다.

같은 저장소의 스킬

Use when adding monitoring, metrics, logging, or tracing to Spring AI or LLM integration code. Covers token tracking, latency measurement, cost estimation, and prompt/response logging. Use when user mentions AI monitoring, token costs, or LLM observability.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

android

무료

Provides expert knowledge for building modern native Android apps using Kotlin, Jetpack Compose, Material Design 3, MVI architecture, Hilt dependency injection, and Media3/ExoPlayer.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

Design, implement, review, and audit Android Jetpack Compose UI/UX for phone interfaces. Use when a request concerns concrete Compose layout, components, interaction, accessibility, or visual behavior. Do not activate for framework or dependency presence alone; non-UI Android work is excluded.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

Provides expert knowledge for designing, implementing, and managing REST API versioning strategies with deprecation workflows.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

astro

무료

Provides expert knowledge for building Astro 5+ sites with Islands Architecture, Content Collections, TypeScript, and performance-first rendering strategies.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

Audits token storage mechanisms to prevent XSS-based JWT theft.

한국어 요약은 준비 중입니다. 원문 설명을 표시하고 있습니다.

lgzarturo/springboot-course32026년 10월 20일 업데이트

lgzarturo의 스킬 모두 보기

이 스킬의 문제 신고