跳到正文
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(原文)

安装前可以查看交给 AI 代理的指令内容。

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 / 1000 字

此表单受 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 的全部技能

报告此技能的问题