Spring Web MVC

Diese Seite bleibt auch ohne JavaScript lesbar. Suche und Buttons sind Zusatzkomfort.

Spring Web MVC und REST APIs

Controller als Adapter, DTOs als Vertrag, Services als Use-Case-Grenze.

HTTP Request in Spring MVC ClientJSON Filter ChainSecurity/CORS DispatcherHandler Mapping ControllerDTO/Validation ServiceUse Case Enterprise-Regel: Controller übersetzen Protokoll, Services tragen Fachlogik.Exceptions werden zentral über @ControllerAdvice gemappt.
1. Controller sind Adapter, keine Fachschicht

Ein sauberer REST-Controller nimmt HTTP entgegen, validiert Protokoll- und DTO-Regeln, ruft einen Use Case auf und übersetzt das Ergebnis in HTTP. Er sollte keine Transaktionslogik, keine SQL-Details und keine komplexen fachlichen Entscheidungen enthalten.

DTOs sind nicht dasselbe wie Entities. Entities direkt als REST-Vertrag zu veröffentlichen führt häufig zu Lazy-Loading-Problemen, ungewollten Feldern, Versionskopplung und Security-Lecks.

REST Controller mit DTO und Validierung
@RestController
@RequestMapping("/api/orders")
class OrderController {
    private final PlaceOrderUseCase placeOrder;

    OrderController(PlaceOrderUseCase placeOrder) {
        this.placeOrder = placeOrder;
    }

    @PostMapping
    ResponseEntity<OrderResponse> place(@Valid @RequestBody PlaceOrderRequest request) {
        OrderId id = placeOrder.place(request.toCommand());
        return ResponseEntity.created(URI.create("/api/orders/" + id.value()))
                .body(new OrderResponse(id.value(), "ACCEPTED"));
    }
}

record PlaceOrderRequest(@NotBlank String customerId, @NotEmpty List<OrderLineDto> lines) {
    PlaceOrder toCommand() {
        return new PlaceOrder(customerId, lines);
    }
}
2. Fehlerdesign mit Problem Details

Enterprise-APIs brauchen ein vorhersehbares Fehlerformat. Ein fachlicher Konflikt sollte nicht als zufällige 500-Exception enden. Validierungsfehler, Berechtigungsfehler, nicht gefundene Ressourcen und fachliche Konflikte bekommen eigene Statuscodes und strukturierte Antworten.

Zentrales Error Mapping
@RestControllerAdvice
class ApiExceptionHandler {
    @ExceptionHandler(OrderLimitExceededException.class)
    ResponseEntity<ProblemDetail> orderLimit(OrderLimitExceededException ex) {
        ProblemDetail detail = ProblemDetail.forStatus(HttpStatus.CONFLICT);
        detail.setTitle("Order limit exceeded");
        detail.setDetail(ex.getMessage());
        detail.setProperty("errorCode", "ORDER_LIMIT_EXCEEDED");
        return ResponseEntity.status(HttpStatus.CONFLICT).body(detail);
    }
}
3. Versionierung und API-Verträge

Versionierung sollte nicht reflexartig über /v1, /v2 passieren. Wichtig ist, welche Änderung kompatibel ist: neue optionale Felder sind meist kompatibel, entfernte oder semantisch geänderte Felder nicht. Für Enterprise-Teams sind OpenAPI, Contract Tests und Deprecation-Regeln wichtiger als eine hübsche URL.

Enterprise-Prüffragen

  • Keine Entity direkt als REST Response?
  • Controller dünn und Use-Case-orientiert?
  • Zentrales Fehlerformat vorhanden?
  • OpenAPI/Contract Tests für kritische APIs?