← Kursa Dön
📄 Text · 12 min

Problem Details (RFC 7807) Standardı

Bir doktorun raporunu düşünün. Her doktor hastanın durumunu farklı formatta yazsa ne olur? Birisi "ateş var" yazarken, diğeri "hasta sıcaklığı 38.7°C, 2 gündür devam ediyor" yazsa — eczane hangisini daha iyi anlayabilir? Tabii ki standart formatta yazılmış olanı.

REST API'lerin hata yanıtlarında da tam olarak bu sorun yaşanıyor. Her proje, hatta her takım, farklı formatta hata döndürüyor:

// Proje A
{"error": "User not found", "code": 404}

// Proje B
{"message": "not_found", "statusCode": 404, "details": null}

// Proje C
{"errors": [{"msg": "Not found"}], "success": false}

Frontend geliştiricisi üç farklı API kullanıyorsa, üç farklı error handling mantığı yazması gerekir. RFC 7807 (ve güncellenmiş hali RFC 9457), bu sorunu çözmek için bir endüstri standardı tanımlar.


RFC 7807 Nedir?

RFC 7807 — "Problem Details for HTTP APIs" — IETF (Internet Engineering Task Force) tarafından 2016'da yayınlanmış, HTTP API'lerde hata yanıtlarının standart formatını tanımlayan bir spesifikasyondur. 2023'te RFC 9457 ile güncellenmiştir.

Gerçek Dünya Analojisi

Polis raporlarını düşünün. Dünya genelinde polis raporları belirli bir standart formatı takip eder:

  • Olay türü: Hırsızlık, kaza, kavga...

  • Tarih/Saat: Ne zaman oldu

  • Konum: Nerede oldu

  • Detay: Ne oldu

  • Referans numarası: Benzersiz takip kodu

RFC 7807 de API hataları için aynı şeyi yapar — her hatanın type, title, status, detail, instance alanları zorunludur.

Standart Alanlar

{
    "type": "https://api.example.com/errors/insufficient-balance",
    "title": "Insufficient Balance",
    "status": 422,
    "detail": "Your current balance of 150.00 TL is not sufficient for a 500.00 TL purchase",
    "instance": "/api/orders/12345"
}
AlanZorunlu mu?AçıklamaÖrnek
typeEvetHata türünü tanımlayan URI. Documentation linki olabilirhttps://api.example.com/errors/not-found
titleEvetİnsan tarafından okunabilir kısa başlık. Değişmez — aynı type her zaman aynı title'a sahip olmalı"Resource Not Found"
statusEvetHTTP status kodu (sayı)404
detailHayırBu spesifik hata için detaylı açıklama. Her seferinde farklı olabilir"User not found with id: 42"
instanceHayırHatanın oluştuğu spesifik kaynak URI'si"/api/users/42"

type vs title vs detail Farkı

Bu üç alanı karıştırmak yaygın bir hatadır. Şöyle düşünün:

  • `type`: Hastalığın adı (gripal enfeksiyon) — kategorik, sabit

  • `title`: Hastalığın kısa açıklaması (Üst solunum yolu enfeksiyonu) — sabit

  • `detail`: Bu hastanın durumu (38.7°C ateş, 2 gündür öksürük, boğaz ağrısı) — her hasta farklı

// Aynı type, farklı detail'lar
{
    "type": "https://api.example.com/errors/insufficient-balance",
    "title": "Insufficient Balance",
    "detail": "Current balance: 150 TL, Required: 500 TL"
}

{
    "type": "https://api.example.com/errors/insufficient-balance",
    "title": "Insufficient Balance",
    "detail": "Current balance: 0 TL, Required: 25 TL"
}

Ek (Extension) Alanlar

RFC 7807, standart alanların yanına ek alanlar eklemeye izin verir:

{
    "type": "https://api.example.com/errors/validation",
    "title": "Validation Error",
    "status": 400,
    "detail": "One or more fields failed validation",
    "instance": "/api/users",
    "errors": [
        {
            "field": "email",
            "message": "must be a valid email address",
            "rejectedValue": "not-an-email"
        },
        {
            "field": "name",
            "message": "must not be blank",
            "rejectedValue": ""
        }
    ],
    "errorCode": "VALIDATION_FAILED",
    "timestamp": "2024-01-15T10:30:00Z"
}

errors, errorCode, timestamp — bunların hepsi ek alanlardır. İstediğiniz kadar ekleyebilirsiniz.


Content-Type: application/problem+json

RFC 7807, hata yanıtlarının application/problem+json content type'ı ile dönmesini önerir (zorunlu değil ama best practice):

HTTP/1.1 404 Not Found
Content-Type: application/problem+json

{
    "type": "https://api.example.com/errors/not-found",
    "title": "Resource Not Found",
    "status": 404,
    "detail": "User not found with id: 42",
    "instance": "/api/users/42"
}

Bu content type, client'a "bu bir hata yanıtıdır ve Problem Details formatındadır" diye sinyal verir. Client buna göre özel error handling mantığı çalıştırabilir.


Spring Boot 3+ ile ProblemDetail

Spring Framework 6 (Spring Boot 3) native RFC 7807 desteği sunar. ProblemDetail sınıfı, standart alanları kapsüller:

Etkinleştirme

# application.properties
spring.mvc.problemdetails.enabled=true

Bu ayar aktifken, Spring'in varsayılan exception handler'ları (TypeMismatchException, MethodArgumentNotValidException vb.) otomatik olarak Problem Details formatında yanıt döner.

Temel Kullanım

@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // ─── İş Mantığı Exception'ları ───

    @ExceptionHandler(ResourceNotFoundException.class)
    public ProblemDetail handleNotFound(ResourceNotFoundException ex,
                                         HttpServletRequest request) {
        log.warn("Resource not found: {}", ex.getMessage());

        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND,
            ex.getMessage()
        );
        pd.setTitle("Resource Not Found");
        pd.setType(URI.create("https://api.example.com/errors/not-found"));
        pd.setInstance(URI.create(request.getRequestURI()));

        // Ek alanlar
        pd.setProperty("errorCode", ex.getErrorCode());
        pd.setProperty("timestamp", Instant.now());

        return pd;
    }

    @ExceptionHandler(ResourceAlreadyExistsException.class)
    public ProblemDetail handleConflict(ResourceAlreadyExistsException ex,
                                          HttpServletRequest request) {
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.CONFLICT,
            ex.getMessage()
        );
        pd.setTitle("Resource Already Exists");
        pd.setType(URI.create("https://api.example.com/errors/conflict"));
        pd.setInstance(URI.create(request.getRequestURI()));
        pd.setProperty("errorCode", ex.getErrorCode());
        pd.setProperty("timestamp", Instant.now());
        return pd;
    }

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

    // ─── Genel Hata ───

    @ExceptionHandler(Exception.class)
    public ProblemDetail handleGeneral(Exception ex, HttpServletRequest request) {
        log.error("Unhandled exception: ", ex);

        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.INTERNAL_SERVER_ERROR,
            "Beklenmedik bir hata oluştu"
        );
        pd.setTitle("Internal Server Error");
        pd.setType(URI.create("https://api.example.com/errors/internal"));
        pd.setInstance(URI.create(request.getRequestURI()));
        pd.setProperty("timestamp", Instant.now());
        return pd;
    }
}

Validation Hataları için Override

ResponseEntityExceptionHandler'ı extend ettiğinizde, Spring'in varsayılan exception handler'larını override edebilirsiniz:

@RestControllerAdvice
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

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

        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.BAD_REQUEST,
            "Bir veya daha fazla alan doğrulama kuralını ihlal ediyor"
        );
        pd.setTitle("Validation Failed");
        pd.setType(URI.create("https://api.example.com/errors/validation"));
        pd.setProperty("timestamp", Instant.now());

        // Validation hatalarını ek alan olarak ekle
        Map<String, String> fieldErrors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            fieldErrors.put(error.getField(), error.getDefaultMessage())
        );
        pd.setProperty("errors", fieldErrors);

        return ResponseEntity
            .status(HttpStatus.BAD_REQUEST)
            .contentType(MediaType.APPLICATION_PROBLEM_JSON)  // RFC 7807 content type
            .body(pd);
    }

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

        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.METHOD_NOT_ALLOWED,
            String.format("'%s' metodu bu endpoint için desteklenmiyor. Desteklenen: %s",
                ex.getMethod(), Arrays.toString(ex.getSupportedMethods()))
        );
        pd.setTitle("Method Not Allowed");
        pd.setType(URI.create("https://api.example.com/errors/method-not-allowed"));
        pd.setProperty("timestamp", Instant.now());

        return ResponseEntity
            .status(HttpStatus.METHOD_NOT_ALLOWED)
            .contentType(MediaType.APPLICATION_PROBLEM_JSON)
            .body(pd);
    }
}

ResponseEntityExceptionHandler Nedir?

ResponseEntityExceptionHandler, Spring MVC'nin standart exception'larını (validation, method not allowed, media type vs.) ele alan abstract bir sınıftır. Bu sınıfı extend ettiğinizde:

  1. Spring'in ~15 standart exception'ı otomatik olarak Problem Details formatında döner

  2. Override ederek bunları customize edebilirsiniz

  3. Kendi custom exception handler'larınızı da ekleyebilirsiniz

// ResponseEntityExceptionHandler'ın handle ettiği exception'lar:
// - HttpRequestMethodNotSupportedException → 405
// - HttpMediaTypeNotSupportedException → 415
// - HttpMediaTypeNotAcceptableException → 406
// - MissingPathVariableException → 500
// - MissingServletRequestParameterException → 400
// - MissingServletRequestPartException → 400
// - ServletRequestBindingException → 400
// - MethodArgumentNotValidException → 400
// - HandlerMethodValidationException → 400
// - NoHandlerFoundException → 404
// - NoResourceFoundException → 404
// - AsyncRequestTimeoutException → 503
// - ErrorResponseException → dinamik
// - MaxUploadSizeExceededException → 413
// - ConversionNotSupportedException → 500
// - TypeMismatchException → 400
// - HttpMessageNotReadableException → 400
// - HttpMessageNotWritableException → 500
// - BindException → 400

⚠️ Dikkat: ResponseEntityExceptionHandler'ı extend etmeden sadece @RestControllerAdvice kullanırsanız, yukarıdaki Spring exception'ları varsayılan (ve genellikle yeterli olmayan) formatta döner.


ProblemDetail API Detaylı

ProblemDetail Oluşturma Yöntemleri

// 1. Status ve detail ile
ProblemDetail pd = ProblemDetail.forStatusAndDetail(
    HttpStatus.NOT_FOUND,
    "User not found with id: 42"
);

// 2. Sadece status ile
ProblemDetail pd = ProblemDetail.forStatus(HttpStatus.NOT_FOUND);
pd.setDetail("User not found with id: 42");

// 3. Builder-style zincirleme
ProblemDetail pd = ProblemDetail.forStatusAndDetail(HttpStatus.NOT_FOUND, "Not found");
pd.setTitle("Resource Not Found");
pd.setType(URI.create("https://api.example.com/errors/not-found"));
pd.setInstance(URI.create("/api/users/42"));
pd.setProperty("errorCode", "RESOURCE_NOT_FOUND");
pd.setProperty("timestamp", Instant.now());
pd.setProperty("traceId", MDC.get("traceId"));

Ek Alanlar (Extension Members)

setProperty() ile istediğiniz kadar ek alan ekleyebilirsiniz:

ProblemDetail pd = ProblemDetail.forStatusAndDetail(
    HttpStatus.UNPROCESSABLE_ENTITY,
    "Yetersiz bakiye"
);
pd.setProperty("errorCode", "INSUFFICIENT_BALANCE");
pd.setProperty("currentBalance", 150.00);
pd.setProperty("requiredAmount", 500.00);
pd.setProperty("timestamp", Instant.now());
pd.setProperty("traceId", MDC.get("traceId"));
pd.setProperty("documentation", "https://docs.example.com/errors/insufficient-balance");

Sonuç:

{
    "type": "about:blank",
    "title": "Unprocessable Entity",
    "status": 422,
    "detail": "Yetersiz bakiye",
    "errorCode": "INSUFFICIENT_BALANCE",
    "currentBalance": 150.00,
    "requiredAmount": 500.00,
    "timestamp": "2024-01-15T10:30:00Z",
    "traceId": "abc-123-def",
    "documentation": "https://docs.example.com/errors/insufficient-balance"
}

ErrorResponse Interface

Spring 6'da ErrorResponse interface'i, ResponseEntity-uyumlu ProblemDetail oluşturmanızı sağlar. Custom exception'ınızı ErrorResponse implement edecek şekilde yazabilirsiniz:

public class ResourceNotFoundException extends RuntimeException implements ErrorResponse {

    private final ProblemDetail problemDetail;

    public ResourceNotFoundException(String resourceName, String field, Object value) {
        super(String.format("%s not found with %s: %s", resourceName, field, value));

        this.problemDetail = ProblemDetail.forStatusAndDetail(
            HttpStatus.NOT_FOUND,
            getMessage()
        );
        this.problemDetail.setTitle("Resource Not Found");
        this.problemDetail.setType(
            URI.create("https://api.example.com/errors/not-found"));
        this.problemDetail.setProperty("resourceName", resourceName);
        this.problemDetail.setProperty("field", field);
        this.problemDetail.setProperty("value", value);
    }

    @Override
    public HttpStatusCode getStatusCode() {
        return HttpStatus.NOT_FOUND;
    }

    @Override
    public ProblemDetail getBody() {
        return problemDetail;
    }
}

Bu şekilde exception'ı fırlattığınızda Spring otomatik olarak ProblemDetail yanıtı oluşturur — @ExceptionHandler yazmaya bile gerek kalmaz!


Gerçek Dünya Projesi: Tam Entegrasyon

Tüm parçaları bir araya getirelim:

// ─── application.properties ───
// spring.mvc.problemdetails.enabled=true

// ─── Base Exception ───
public abstract class BusinessException extends RuntimeException implements ErrorResponse {

    private final ProblemDetail problemDetail;

    protected BusinessException(HttpStatus status, String type,
                                  String title, String detail) {
        super(detail);
        this.problemDetail = ProblemDetail.forStatusAndDetail(status, detail);
        this.problemDetail.setTitle(title);
        this.problemDetail.setType(URI.create(
            "https://api.example.com/errors/" + type));
        this.problemDetail.setProperty("timestamp", Instant.now());
    }

    protected void addProperty(String key, Object value) {
        this.problemDetail.setProperty(key, value);
    }

    @Override
    public HttpStatusCode getStatusCode() {
        return HttpStatusCode.valueOf(problemDetail.getStatus());
    }

    @Override
    public ProblemDetail getBody() {
        return problemDetail;
    }
}

// ─── Spesifik Exception'lar ───
public class ResourceNotFoundException extends BusinessException {
    public ResourceNotFoundException(String resource, String field, Object value) {
        super(HttpStatus.NOT_FOUND, "not-found", "Resource Not Found",
            String.format("%s not found with %s: %s", resource, field, value));
        addProperty("resource", resource);
        addProperty("field", field);
        addProperty("value", value);
    }
}

public class InsufficientBalanceException extends BusinessException {
    public InsufficientBalanceException(BigDecimal current, BigDecimal required) {
        super(HttpStatus.UNPROCESSABLE_ENTITY, "insufficient-balance",
            "Insufficient Balance",
            String.format("Mevcut: %s TL, Gereken: %s TL", current, required));
        addProperty("currentBalance", current);
        addProperty("requiredAmount", required);
        addProperty("deficit", required.subtract(current));
    }
}

// ─── Global Handler (sadece özelleştirme gereken kısım) ───
@RestControllerAdvice
@Slf4j
public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {

    // ErrorResponse implement eden exception'lar OTOMATIK handle edilir!
    // Sadece genel Exception ve validation override'ı yazıyoruz.

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

        ProblemDetail pd = ex.getBody();
        pd.setType(URI.create("https://api.example.com/errors/validation"));
        pd.setProperty("timestamp", Instant.now());

        Map<String, String> fieldErrors = new LinkedHashMap<>();
        ex.getBindingResult().getFieldErrors().forEach(error ->
            fieldErrors.put(error.getField(), error.getDefaultMessage())
        );
        pd.setProperty("errors", fieldErrors);

        return ResponseEntity.status(status)
            .contentType(MediaType.APPLICATION_PROBLEM_JSON)
            .body(pd);
    }

    @ExceptionHandler(Exception.class)
    public ProblemDetail handleUnexpected(Exception ex, HttpServletRequest request) {
        log.error("Unhandled: ", ex);
        ProblemDetail pd = ProblemDetail.forStatusAndDetail(
            HttpStatus.INTERNAL_SERVER_ERROR, "Beklenmedik bir hata oluştu");
        pd.setTitle("Internal Server Error");
        pd.setInstance(URI.create(request.getRequestURI()));
        pd.setProperty("timestamp", Instant.now());
        return pd;
    }
}

// ─── Controller ───
@RestController
@RequestMapping("/api/users")
public class UserController {

    @GetMapping("/{id}")
    public UserResponse getUser(@PathVariable Long id) {
        // Bulamazsa → ResourceNotFoundException → ProblemDetail (otomatik!)
        return userService.findById(id);
    }
}

API yanıtları:

404 — User Not Found:

{
    "type": "https://api.example.com/errors/not-found",
    "title": "Resource Not Found",
    "status": 404,
    "detail": "User not found with id: 42",
    "resource": "User",
    "field": "id",
    "value": 42,
    "timestamp": "2024-01-15T10:30:00Z"
}

422 — Insufficient Balance:

{
    "type": "https://api.example.com/errors/insufficient-balance",
    "title": "Insufficient Balance",
    "status": 422,
    "detail": "Mevcut: 150.00 TL, Gereken: 500.00 TL",
    "currentBalance": 150.00,
    "requiredAmount": 500.00,
    "deficit": 350.00,
    "timestamp": "2024-01-15T10:30:00Z"
}

400 — Validation Error:

{
    "type": "https://api.example.com/errors/validation",
    "title": "Bad Request",
    "status": 400,
    "detail": "One or more fields failed validation",
    "errors": {
        "email": "Geçerli bir email adresi giriniz",
        "name": "İsim boş olamaz"
    },
    "timestamp": "2024-01-15T10:30:00Z"
}

Custom ErrorResponse vs ProblemDetail — Hangisini Seçmeliyim?

KriterCustom ErrorResponseProblemDetail (RFC 7807)
StandartProjeye özelEndüstri standardı (RFC)
Spring desteğiManuelNative (Spring Boot 3+)
Content-Typeapplication/jsonapplication/problem+json
InteroperabilitySadece sizin client'larHerhangi bir RFC 7807 client
Öğrenme eğrisiDüşükOrta
EsneklikTam kontrolStandart + ek alanlar
Best practiceKüçük projelerBüyük / public API projeleri

💡 İpucu: Yeni başlayan bir proje için ProblemDetail kullanın. Spring Boot 3+ ile zaten native desteği var, ekstra bir şey yapmanıza gerek yok. Mevcut bir projenin error formatını değiştiremiyorsanız (backward compatibility), custom ErrorResponse devam edebilir.


type URI Stratejileri

type alanı için üç yaygın strateji:

1. about:blank (Varsayılan)

{"type": "about:blank", "title": "Not Found", "status": 404}

RFC 7807'nin varsayılan değeri. "Ek bilgi yok" anlamına gelir. title, HTTP status'un standart adını taşır.

2. Documentation URI

{
    "type": "https://api.example.com/docs/errors/insufficient-balance",
    "title": "Insufficient Balance"
}

En iyi practice — type URI'si tıklandığında hata hakkında detaylı documentation'a ulaşılabilir.

3. URN (Namespace)

{
    "type": "urn:example:error:insufficient-balance",
    "title": "Insufficient Balance"
}

URI çözümlenebilir (tıklanabilir) olmak zorunda değildir. URN kullanarak sadece tanımlayıcı olarak kullanabilirsiniz.


Yaygın Hatalar

1. ❌ type Alanını HTTP Status Olarak Kullanmak

// ❌ YANLIŞ
{"type": "404", "title": "Not Found"}

// ✅ DOĞRU — URI olmalı
{"type": "https://api.example.com/errors/not-found", "title": "Not Found"}

2. ❌ title'ı Her Seferinde Farklı Yazmak

// ❌ YANLIŞ — aynı type, farklı title'lar
{"type": "https://api.example.com/errors/not-found", "title": "User bulunamadı"}
{"type": "https://api.example.com/errors/not-found", "title": "Kayıt yok"}

// ✅ DOĞRU — aynı type = aynı title, farklılık detail'da
{"type": ".../not-found", "title": "Resource Not Found", "detail": "User not found with id: 42"}
{"type": ".../not-found", "title": "Resource Not Found", "detail": "Product not found with slug: iphone"}

3. ❌ Stack Trace'i detail'a Koymak

// ❌ GÜVENLİK AÇIĞI
{
    "detail": "java.lang.NullPointerException at com.example.UserService.findById(UserService.java:42)"
}

// ✅ DOĞRU
{
    "detail": "Beklenmedik bir hata oluştu. Lütfen daha sonra tekrar deneyin."
}

4. ❌ problemdetails.enabled Ayarını Açmayı Unutmak

# Bu olmadan Spring varsayılan (non-RFC-7807) format kullanır
spring.mvc.problemdetails.enabled=true

Test Etme

@WebMvcTest(UserController.class)
class ProblemDetailTest {

    @Autowired
    private MockMvc mockMvc;

    @MockitoBean
    private UserService userService;

    @Test
    void shouldReturnProblemDetailFormat() throws Exception {
        when(userService.findById(999L))
            .thenThrow(new ResourceNotFoundException("User", "id", 999L));

        mockMvc.perform(get("/api/users/999")
                .accept(MediaType.APPLICATION_JSON))
            .andExpect(status().isNotFound())
            .andExpect(content().contentType(MediaType.APPLICATION_PROBLEM_JSON))
            .andExpect(jsonPath("$.type").value("https://api.example.com/errors/not-found"))
            .andExpect(jsonPath("$.title").value("Resource Not Found"))
            .andExpect(jsonPath("$.status").value(404))
            .andExpect(jsonPath("$.detail").value("User not found with id: 999"))
            .andExpect(jsonPath("$.resource").value("User"))
            .andExpect(jsonPath("$.timestamp").exists());
    }

    @Test
    void shouldReturnValidationErrorsInProblemDetail() throws Exception {
        mockMvc.perform(post("/api/users")
                .contentType(MediaType.APPLICATION_JSON)
                .content("""
                    {"name": "", "email": "invalid"}
                    """))
            .andExpect(status().isBadRequest())
            .andExpect(jsonPath("$.type").value("https://api.example.com/errors/validation"))
            .andExpect(jsonPath("$.errors.name").exists())
            .andExpect(jsonPath("$.errors.email").exists());
    }
}

Özet

  • RFC 7807 / 9457, HTTP API hata yanıtları için endüstri standardı formatıdır — type, title, status, detail, instance

  • Spring Boot 3+, ProblemDetail sınıfı ile native destek sunar — spring.mvc.problemdetails.enabled=true

  • `ResponseEntityExceptionHandler` extend ederek Spring'in standart exception'larını da RFC 7807 formatına çevirebilirsiniz

  • ErrorResponse interface'ini implement eden exception'lar otomatik olarak ProblemDetail formatında döner

  • `type` URI'si sabit ve tanımlayıcıdır; `title` aynı type için değişmez; `detail` her hata için farklı olabilir

  • setProperty() ile ek alanlar ekleyebilirsiniz — errorCode, timestamp, traceId...

  • Yeni projelerde ProblemDetail kullanın — standart, interoperable ve Spring tarafından destekleniyor