Gün 2 / Web Layer & Runtime / Modül 06 — Exception Handling Fundamentals
Ana Portal
Modül 06 Gün 2 Web Layer ~75 dk

Exception Handling Fundamentals

Bu modülde MiniBank API’sinde hata yönetimini standart hale getireceğiz. Amaç sadece exception yakalamak değil; API tüketicisine tutarlı, güvenli ve anlaşılır hata cevabı dönen bir error contract tasarlamak.

Anlatım
45 dk
MiniBank Case
27 dk
Kapanış
5 dk
Checkpoint
lab-06

Hataları rastgele response’lara dönüşmeden, bilinçli bir API davranışına çevireceksin.

1

Hata akışını anlayacaksın

Controller içinde exception oluştuğunda Spring MVC’nin bunu nasıl yakaladığını ve hangi resolver zincirinden geçirdiğini göreceksin.

2

Global handler yazacaksın

@RestControllerAdvice ve @ExceptionHandler ile MiniBank için merkezi hata yönetimi oluşturacaksın.

3

Error contract tasarlayacaksın

Business hata, validation hata ve teknik hata için tutarlı JSON response yapısını MiniBank’a ekleyeceksin.

Önce kavramları sadeleştirelim.

Exception handling konusunu anlamak için önce “hata”, “exception”, “handler”, “contract” gibi kavramları ayırmak gerekir. Bu ayrım yapılmazsa her hata ya 500 döner ya da controller içinde dağınık try-catch blokları oluşur.

Exception

Java’da normal akışın devam edemediğini söyleyen nesnedir. Spring MVC’de controller veya service katmanından fırlayan exception, uygun handler bulunursa HTTP response’a çevrilir.

Business Exception

İş kuralı ihlalini temsil eder. Örneğin hesap bulunamadı, bakiye yetersiz veya müşteri zaten kayıtlı gibi hatalar business exception’dır.

Technical Exception

Sistemsel veya beklenmeyen teknik problemdir. Database bağlantı hatası, null pointer, timeout veya serialization hatası buna örnektir.

@RestControllerAdvice

Tüm REST controller’lar için ortak hata yakalama alanıdır. Controller’lara tek tek try-catch yazmak yerine merkezi davranış sağlar.

@ExceptionHandler

Belirli bir exception tipini hangi method’un handle edeceğini söyler. Örneğin AccountNotFoundException geldiğinde 404 döndürmek için kullanılır.

ResponseEntity

Body, status code ve header gibi HTTP response detaylarını kontrollü şekilde dönmemizi sağlar. Hata cevaplarında doğru status code vermek için sık kullanılır.

Validation Error

@Valid ile request DTO kontrol edilirken oluşan hatadır. Kullanıcıya hangi alanın neden hatalı olduğunu söylemek gerekir.

Error Contract

API’nin hata durumunda döneceği standart JSON yapısıdır. Frontend, mobil uygulama veya başka servisler bu contract’a güvenerek hata davranışı geliştirir.

Exception handling, hatayı saklamak değil; hatayı doğru sınıflandırıp doğru response’a çevirmektir.

REST API’de başarılı cevap kadar hatalı cevap da contract’ın parçasıdır. İyi tasarlanmış bir API, hata olduğunda bile tüketicisine ne olduğunu, hangi isteğin sorunlu olduğunu ve mümkünse nasıl düzeltebileceğini anlaşılır şekilde söyler.

Exception handling neden gerekli?

Bir controller method’u çalışırken üç farklı hata türüyle karşılaşabiliriz. Kullanıcı yanlış veri gönderebilir, iş kuralı izin vermeyebilir ya da sistem beklenmeyen teknik bir problem yaşayabilir. Bunların hepsini aynı şekilde ele alırsak API tüketicisi için karanlık bir kutu oluşur.

Validation hatası

Request formatı veya alanları hatalıdır. Örneğin amount negatif gönderilmiştir. Genellikle 400 Bad Request döner.

Business hata

Request formatı doğrudur ama iş kuralı izin vermez. Örneğin hesap yoktur veya bakiye yetersizdir. Duruma göre 404, 409 veya 422 dönebilir.

Technical hata

Beklenmeyen sistem hatasıdır. Kullanıcıya stack trace verilmez. Kontrollü, kısa ve güvenli bir 500 Internal Server Error cevabı döndürülür.

Basit mental model

Exception handling’i “hata yakalama” olarak değil, exception → HTTP response dönüşüm katmanı olarak düşün. Service katmanı iş kuralı hatasını fırlatır; web katmanı bunu API contract’a uygun JSON response’a çevirir.

Dağınık yaklaşım — controller içinde try/catch
@PostMapping("/transfers")
public ResponseEntity<?> transfer(@Valid @RequestBody TransferRequest request) {
    try {
        transferService.transfer(request);
        return ResponseEntity.status(HttpStatus.CREATED).build();
    } catch (InsufficientBalanceException ex) {
        return ResponseEntity.status(HttpStatus.CONFLICT)
            .body(Map.of("message", "Insufficient balance"));
    } catch (AccountNotFoundException ex) {
        return ResponseEntity.status(HttpStatus.NOT_FOUND)
            .body(Map.of("message", "Account not found"));
    }
}

Bu kod çalışır ama büyüdükçe problem çıkarır. Her controller aynı try/catch bloklarını tekrarlar, hata response formatı controller’dan controller’a değişebilir ve teknik detayların dışarı sızma riski artar. Daha temiz olan yaklaşım, controller’ı sade tutup hata dönüşümünü merkezi hale getirmektir.

Spring MVC exception akışı nasıl işler?

Controller method’u exception fırlattığında Spring MVC request’i hemen kaybetmez. DispatcherServlet, oluşan exception’ı çözebilecek bir handler arar. Bu noktada devreye exception resolver zinciri girer.

01
Request gelir
DispatcherServlet doğru controller method’unu çağırır.
02
Exception oluşur
Controller veya service katmanı normal akışı durdurur.
03
Resolver aranır
Spring bu exception için uygun handler var mı diye bakar.
04
Handler çalışır
@ExceptionHandler veya default resolver response üretir.
05
JSON döner
Status code ve body API tüketicisine gönderilir.

Bu mekanizmanın önemli tarafı şudur: Hata oluştu diye response mutlaka 500 olmak zorunda değildir. Sen doğru exception tipini doğru handler ile eşlersen, iş kuralı hatalarını anlamlı status code ve body ile döndürebilirsin.

Senior dikkat noktası

Hata yönetimi sadece “hangi exception hangi status code?” meselesi değildir. Aynı zamanda güvenlik meselesidir. Stack trace, class adı, SQL detayı, internal servis URL’i veya exception message içindeki hassas veri dışarı sızmamalıdır.

@RestControllerAdvice ve @ExceptionHandler

@RestControllerAdvice, REST controller’lar için global bir hata yakalama katmanı sağlar. Burada yazdığımız @ExceptionHandler method’ları, uygulamanın farklı controller’larından gelen exception’ları tek merkezde karşılar.

Bu yaklaşım controller’ları sadeleştirir. Controller sadece request’i alır, service’i çağırır ve başarılı response’u döner. İş kuralı ihlalinde service exception fırlatır; global handler bunu API cevabına çevirir.

GlobalExceptionHandler.java — temel yapı
@RestControllerAdvice
public class GlobalExceptionHandler {

    @ExceptionHandler(AccountNotFoundException.class)
    public ResponseEntity<ApiErrorResponse> handleAccountNotFound(
            AccountNotFoundException ex,
            HttpServletRequest request
    ) {
        ApiErrorResponse response = ApiErrorResponse.of(
            "ACCOUNT_NOT_FOUND",
            ex.getMessage(),
            request.getRequestURI()
        );

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

Burada dikkat edilmesi gereken şey, handler method’unun sadece response üretmesidir. Business karar burada verilmez. “Hesap gerçekten var mı?” kararı service katmanındadır. Handler sadece “bu exception API’de nasıl temsil edilecek?” sorusuna cevap verir.

Business exception ile technical exception ayrımı

Business exception, beklenen ve domain tarafından anlamı olan hatadır. Kullanıcı hatalı bir işlem yapmış olabilir veya iş kuralı bu işleme izin vermiyor olabilir. Technical exception ise çoğu zaman beklenmeyen ve kullanıcıya detaylı açıklanmaması gereken sistem hatasıdır.

Business exception

  • Domain dilinde anlamlıdır.
  • API tüketicisine kontrollü mesaj dönebilir.
  • Status code iş kuralına göre seçilir.
  • Örnek: InsufficientBalanceException

Technical exception

  • Sistemsel veya beklenmeyen problemdir.
  • Detayı dışarı verilmez.
  • Loglanır, kullanıcıya güvenli mesaj döner.
  • Örnek: DataAccessException

MiniBank için örnek hata kodları

404
ACCOUNT_NOT_FOUND

İstenen hesap bulunamadı.

409
INSUFFICIENT_BALANCE

Transfer için bakiye yeterli değil.

400
VALIDATION_ERROR

Request alanları geçersiz.

500
INTERNAL_ERROR

Beklenmeyen teknik hata.

Validation hatalarını özel ele almak

Bir önceki modülde request DTO ve validation ekledik. Şimdi bu validation hatalarının dışarı nasıl döneceğini standart hale getireceğiz. Çünkü validation hatasında sadece “Bad Request” demek çoğu zaman yetmez; hangi alanın neden hatalı olduğunu da söylemek gerekir.

Validation error response örneği
{
  "code": "VALIDATION_ERROR",
  "message": "Request validation failed",
  "path": "/api/v1/accounts",
  "fieldErrors": [
    {
      "field": "iban",
      "message": "IBAN boş olamaz"
    },
    {
      "field": "balance",
      "message": "Bakiye negatif olamaz"
    }
  ]
}

Bu format frontend ve mobil ekip için çok değerlidir. Çünkü kullanıcıya alan bazlı mesaj gösterebilirler. API tüketicisi sadece 400 status code’a bakmak zorunda kalmaz; response body üzerinden hangi input’u düzeltmesi gerektiğini bilir.

Standard error response modeli

Error contract tasarlarken temel amaç tutarlılıktır. Aynı uygulamada bazı hataların message, bazılarının error, bazılarının detail alanıyla dönmesi API tüketicisini yorar. MiniBank’ta tek bir standart response modeli kullanacağız.

ApiErrorResponse.java
public record ApiErrorResponse(
    String code,
    String message,
    String path,
    Instant timestamp,
    List<FieldErrorResponse> fieldErrors
) {
    public static ApiErrorResponse of(String code, String message, String path) {
        return new ApiErrorResponse(
            code,
            message,
            path,
            Instant.now(),
            List.of()
        );
    }

    public static ApiErrorResponse validation(
            String message,
            String path,
            List<FieldErrorResponse> fieldErrors
    ) {
        return new ApiErrorResponse(
            "VALIDATION_ERROR",
            message,
            path,
            Instant.now(),
            fieldErrors
        );
    }
}
MiniBank tercihi

Bu eğitimde anlaşılır olması için custom ApiErrorResponse modeli kullanacağız. Spring Boot 3 projelerinde ProblemDetail yaklaşımı da değerlendirilebilir; ancak eğitimde önce error contract mantığını net oturtmak daha değerli.

MiniBank Case 06 — Global Exception Handling & Standard Error Contract

Bu case’de MiniBank API’si için merkezi hata yönetimi ekleyeceğiz. Controller içinde dağınık try/catch yazmayacağız; business exception’ları ve validation hatalarını tek bir global handler üzerinden standart response’a çevireceğiz. Görev listesi, doğrulama komutları, kod adımları ve kabul kriterleri Lab Dashboard'da.

Başlangıç
lab-06-start
Tamamlanmış
lab-06-complete
Checkpoint kuralı

Lab sırasında geride kalırsan lab-06-complete branch'ine geçip herkesle aynı checkpoint'ten devam edebilirsin.

Bu modülde akılda kalması gerekenler

1
Exception handling, API contract’ın parçasıdır.

Başarılı response kadar hatalı response da tutarlı ve tahmin edilebilir olmalıdır.

2
Controller içinde try/catch büyüyen projede sürdürülebilir değildir.

Merkezi hata yönetimi controller’ları sade, response formatını tutarlı tutar.

3
Business ve technical exception ayrımı yapılmalıdır.

İş kuralı hatası kullanıcıya anlamlı dönebilir; teknik hata güvenli ve sınırlı bilgiyle dönmelidir.

4
Validation hatalarında alan bazlı bilgi vermek gerekir.

Frontend ve mobil ekipler hangi alanın neden hatalı olduğunu response üzerinden okuyabilmelidir.

5
Standart error response modeli entegrasyon kalitesini artırır.

Servis tüketicileri her endpoint’te aynı hata formatını bekleyebilirse entegrasyon daha güvenli olur.

5 kısa kontrol sorusu

1. @RestControllerAdvice ne işe yarar?

Tüm REST controller’lar için global hata yakalama ve response üretme mekanizması sağlar.

2. Business exception ile technical exception arasındaki temel fark nedir?

Business exception iş kuralı ihlalidir ve kullanıcıya kontrollü mesaj dönebilir. Technical exception sistemsel problemdir ve detayı dışarı sızdırılmamalıdır.

3. Validation hatalarında neden field bazlı response önemlidir?

API tüketicisi hangi alanın neden hatalı olduğunu anlayabilir ve kullanıcı arayüzünde doğru mesajı gösterebilir.

4. Controller içinde try/catch yazmak neden uzun vadede problem olabilir?

Tekrar üretir, response formatını tutarsızlaştırır ve controller’ın sorumluluğunu artırır.

5. Standart error contract neden gereklidir?

Frontend, mobil ve servis entegrasyonlarının hata response’larını tahmin edilebilir şekilde işlemesini sağlar.