Zum Hauptinhalt springen

Woche 4, Tag 5 — Spring MVC Validation im Detail

Ziel

Heute willst du Validation in Spring MVC REST APIs wirklich verstehen.

Die Kernfragen:

  1. Was ist Bean Validation?
  2. Warum brauchen REST APIs Validation?
  3. Welche Dependency brauchst du?
  4. Was ist der Unterschied zwischen @Valid und @Validated?
  5. Welche Validation-Annotations sind üblich?
  6. Was ist der Unterschied zwischen @NotNull, @NotEmpty und @NotBlank?
  7. Wie validierst du Request Bodies?
  8. Wie validierst du verschachtelte DTOs?
  9. Wie validierst du Listen?
  10. Wie validierst du Query-Parameter?
  11. Wie validierst du Path Variables?
  12. Wie erstellst du einen Custom Validator?
  13. Welche Exceptions entstehen bei fehlgeschlagener Validation?
  14. Welche typischen Prüfungsfallen gibt es?

1. Kurz-Wiederholung aus Woche 4, Tag 4

In Tag 4 hast du gelernt:

  • @ExceptionHandler behandelt Exceptions.
  • @ControllerAdvice gilt für alle Controller.
  • @RestControllerAdvice entspricht @ControllerAdvice plus @ResponseBody.
  • MethodArgumentNotValidException steht häufig für fehlgeschlagenes @Valid @RequestBody.
  • Ungültiges JSON liefert meist 400 Bad Request.
  • Fehlende Ressourcen liefern meist 404 Not Found.
  • Business-Konflikte liefern oft 409 Conflict.
  • Error Responses sollten sicher und strukturiert sein.

Merksatz:


Exception -> Handler -> Error DTO -> JSON-Antwort.

Heute gehst du tiefer in Validation.


2. Was ist Validation?

Validation bedeutet: Eingabedaten prüfen, bevor die Anwendung sie nutzt.

Beispielregeln:


name darf nicht blank sein
email muss gültig sein
age muss mindestens 18 sein
page darf nicht negativ sein
size muss zwischen 1 und 100 liegen
password muss genug Zeichen haben
startDate muss vor endDate liegen
items-Liste darf nicht leer sein

Einfache Definition:

Validation prüft, ob Client-Eingaben den Regeln entsprechen.

Merksatz:

Validation schützt die Anwendung vor ungültigen Eingaben.


3. Warum REST APIs Validation brauchen

Ohne Validation können schlechte Daten ins System gelangen.

Mögliche Probleme:


leere Namen
ungültige E-Mails
negative Preise
zu große Page Sizes
fehlende Pflichtfelder
ungültige Datumsbereiche
verletzte Business Rules
spätere Datenbankfehler
unklare Error Responses
Sicherheitsrisiken

Beispiel für einen schlechten Request:


{
"title": "",
"priority": "",
"dueDate": "not-a-date"
}

Eine gute API sollte das früh ablehnen mit:


400 Bad Request

und einer klaren Error Response.


4. Bean Validation

Bean Validation ist das Standard-Validation-Modell in Java.

Es nutzt Annotations wie:


@NotBlank
@Email
@Size
@Min
@Max
@Positive

Beispiel:


public record CreateUserRequest(

@NotBlank
String name,

@Email
String email
) {
}

Diese Annotations deklarieren Validation-Regeln.

Merksatz:

Bean Validation bedeutet: Validation-Regeln als Annotations auf Java-Objekten.


5. Benötigte Dependency

In Spring Boot fügst du hinzu:


implementation("org.springframework.boot:spring-boot-starter-validation")

Maven:


<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>

Ohne diese Dependency sind Validation-Annotations möglicherweise nicht verfügbar — oder Validation läuft nicht korrekt.

Merksatz:

Für Bean Validation in Spring Boot: spring-boot-starter-validation hinzufügen.


6. Wichtige Imports

Modernes Spring Boot nutzt Jakarta Validation Packages.

Übliche Imports:


import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Positive;

Ältere Projekte nutzen eventuell:


javax.validation.*

Für modernes Spring Boot 3+:


use jakarta.validation.*

Merksatz:

Spring Boot 3+ nutzt jakarta.validation, nicht javax.validation.


7. Request-Body-Validation

Request-DTO:


public record CreateTaskRequest(

@NotBlank
@Size(max = 100)
String title,

@NotBlank
String priority
) {
}

Controller:


@PostMapping("/api/tasks")
public TaskDto create(
@Valid @RequestBody CreateTaskRequest request
) {
return taskService.create(request);
}

Wichtig:


@Valid löst Validation aus.
@RequestBody liest den JSON-Body.

Ohne @Valid werden die Annotations möglicherweise nicht geprüft.

Merksatz:

Validation-Annotations definieren Regeln. @Valid löst die Prüfung aus.


8. Was passiert bei fehlgeschlagener Request-Body-Validation?

Request:


POST /api/tasks
Content-Type: application/json

{
"title": "",
"priority": ""
}

DTO:


public record CreateTaskRequest(
@NotBlank String title,
@NotBlank String priority
) {
}

Controller:


public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Ergebnis:


Der Controller-Methodenrumpf läuft nicht.
Spring liefert 400 Bad Request.

Übliche Exception:


MethodArgumentNotValidException

Diese Exception enthält Field Errors.


9. Übliche Validation-Annotations

AnnotationBedeutung
@NotNullWert darf nicht null sein
@NotEmptyString, Collection, Map oder Array darf nicht null oder leer sein
@NotBlankString darf nicht null, leer oder nur Whitespace sein
@Size(min, max)Größe/Länge muss im Bereich liegen
@Min(value)Zahl muss mindestens value sein
@Max(value)Zahl darf höchstens value sein
@PositiveZahl muss größer als null sein
@PositiveOrZeroZahl muss null oder größer sein
@NegativeZahl muss kleiner als null sein
@EmailString muss wie eine E-Mail aussehen
@PatternString muss dem Regex entsprechen
@PastDatum muss in der Vergangenheit liegen
@PastOrPresentDatum muss Vergangenheit oder Gegenwart sein
@FutureDatum muss in der Zukunft liegen
@FutureOrPresentDatum muss Zukunft oder Gegenwart sein
@AssertTrueBoolean muss true sein
@AssertFalseBoolean muss false sein

10. @NotNull vs @NotEmpty vs @NotBlank

Das ist sehr wichtig.

@NotNull

Der Wert darf nicht null sein.


@NotNull
String name;

Ungültig:


null

Weiterhin gültig:


""
" "

@NotEmpty

Der Wert darf nicht null oder leer sein.


@NotEmpty
String name;

Ungültig:


null
""

Weiterhin gültig:


" "

Funktioniert auch für Collections:


@NotEmpty
List<String> items;

@NotBlank

Für Strings.

Der Wert darf nicht null, leer oder nur Whitespace sein.


@NotBlank
String name;

Ungültig:


null
""
" "

Merksatz:

Für Pflicht-Textfelder nutzt du meist @NotBlank.


11. @Size

@Size prüft Länge oder Collection-Größe.

String:


@Size(min = 3, max = 50)
String username;

Collection:


@Size(min = 1, max = 10)
List<String> tags;

Wichtig:


@Size bedeutet nicht, dass der Wert nicht null sein darf.

Das kann durchgehen:


null

Wenn Pflichtfeld und Größe nötig sind:


@NotBlank
@Size(min = 3, max = 50)
String username;

Merksatz:

@Size prüft die Größe — null lehnt es allein nicht ab.


12. Zahlen-Validation

Beispiel:


public record PageRequest(

@Min(0)
int page,

@Min(1)
@Max(100)
int size
) {
}

Bedeutung:


page muss mindestens 0 sein
size muss zwischen 1 und 100 liegen

Für Preise oder Beträge:


@Positive
BigDecimal price;

Für optionalen positiven Wert:


@Positive
Integer quantity;

Wichtig:


Wenn Integer null ist, schlägt @Positive nicht fehl.
Füge @NotNull hinzu, wenn der Wert Pflicht ist.

Beispiel:


@NotNull
@Positive
Integer quantity;

13. Datums-Validation

Beispiel:


public record CreateTaskRequest(

@FutureOrPresent
LocalDate dueDate
) {
}

Bedeutung:


dueDate muss heute oder in der Zukunft liegen

Für Geburtsdatum:


@Past
LocalDate birthDate;

Wichtig:


Wenn das Datumsfeld Pflicht ist, füge @NotNull hinzu.

Beispiel:


@NotNull
@FutureOrPresent
LocalDate dueDate;

14. E-Mail-Validation


public record RegisterUserRequest(

@NotBlank
@Email
String email
) {
}

Warum beides?


@NotBlank prüft Pflicht-Text.
@Email prüft das E-Mail-Format.

Merksatz:

Kombiniere Annotations, wenn du mehrere Regeln brauchst.


15. Regex-Validation mit @Pattern

Beispiel:


public record CreateClientRequest(

@Pattern(regexp = "^[A-Z]{2}[0-9]{6}$")
String clientCode
) {
}

Das akzeptiert Werte wie:


DE123456

Lehnt aber ab:


de123456
ABC123

Für Lesbarkeit nutze klare Meldungen:


@Pattern(
regexp = "^[A-Z]{2}[0-9]{6}$",
message = "clientCode must look like DE123456"
)
String clientCode;

16. Eigene Validation-Meldungen

Standardmeldungen können generisch sein.

Beispiel:


@NotBlank(message = "Title is required")
@Size(max = 100, message = "Title must not exceed 100 characters")
String title;

Field Errors in der Antwort können zeigen:


{
"field": "title",
"message": "Title is required"
}

Merksatz:

Eigene Meldungen machen Validation-Fehler benutzerfreundlicher.


17. Validation verschachtelter DTOs

Request:


{
"name": "Client A",
"address": {
"street": "",
"city": ""
}
}

DTO:


public record CreateClientRequest(

@NotBlank
String name,

@Valid
@NotNull
AddressRequest address
) {
}

Verschachteltes DTO:


public record AddressRequest(

@NotBlank
String street,

@NotBlank
String city
) {
}

Wichtig:


@Valid auf address löst verschachtelte Validation aus.
@NotNull macht address zum Pflichtfeld.

Ohne @Valid werden verschachtelte Felder möglicherweise nicht geprüft.

Merksatz:

Bei verschachtelten DTOs: @Valid auf das verschachtelte Feld setzen.


18. Prüfungsfalle: verschachteltes DTO

Schlecht:


public record CreateClientRequest(

@NotBlank
String name,

AddressRequest address
) {
}

Verschachteltes DTO:


public record AddressRequest(
@NotBlank String street,
@NotBlank String city
) {
}

Problem:


street und city werden möglicherweise nicht validiert, weil address nicht mit @Valid annotiert ist.

Besser:


public record CreateClientRequest(

@NotBlank
String name,

@Valid
@NotNull
AddressRequest address
) {
}

19. Listen-Validation

Request:


{
"items": [
{
"productId": 1,
"quantity": 2
}
]
}

DTO:


public record CreateOrderRequest(

@NotEmpty
List<@Valid OrderItemRequest> items
) {
}

Verschachteltes Element:


public record OrderItemRequest(

@NotNull
Long productId,

@Positive
int quantity
) {
}

Bedeutung:


items darf nicht leer sein
jedes Element muss validiert werden
productId darf nicht null sein
quantity muss positiv sein

Merksatz:

Für Listenelemente: List<@Valid ItemDto> nutzen.


20. Constraint für Listenelemente

Du kannst Listenelemente direkt validieren.

Beispiel:


public record TagRequest(

@NotEmpty
List<@NotBlank String> tags
) {
}

Bedeutung:


tags-Liste darf nicht leer sein
jedes Tag darf nicht blank sein

Ungültig:


{
"tags": ["java", "", "spring"]
}

21. Map-Validation

Beispiel:


public record MetadataRequest(

Map<@NotBlank String, @NotBlank String> metadata
) {
}

Bedeutung:


Map-Keys dürfen nicht blank sein
Map-Values dürfen nicht blank sein

In einfachen REST APIs seltener — aber gut zu wissen.


22. @Valid vs @Validated

@Valid

Aus Jakarta Validation.

Übliche Nutzung:


public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
}

Nutze es für:


Request-Body-Validation
verschachtelte Objekt-Validation
einfache Bean Validation

@Validated

Spring-Annotation.

Übliche Nutzung:


@RestController
@Validated
public class TaskController {
}

Nützlich für:


Method-Parameter-Validation
Validation Groups
Spring-spezifische Validation-Unterstützung

Prüfungssicherer Satz:

@Valid ist Standard-Bean-Validation. @Validated ist die Spring-Variante und unterstützt Validation Groups.


23. Request Body: @Valid oder @Validated

Beide können Validation auf Request-Body-Objekten auslösen.

Beispiel mit @Valid:


@PostMapping("/api/tasks")
public TaskDto create(@Valid @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Beispiel mit @Validated:


@PostMapping("/api/tasks")
public TaskDto create(@Validated @RequestBody CreateTaskRequest request) {
return taskService.create(request);
}

Für die meisten Request-DTOs:


@Valid reicht und ist üblich.

24. Query-Parameter-Validation

Beispiel:


@RestController
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
) {
return taskService.findPage(page, size);
}
}

Request:


GET /api/tasks?page=-1&size=500

Ungültig:


page muss mindestens 0 sein
size darf höchstens 100 sein

Modernes Spring MVC hat eingebaute Method-Validation für Controller-Methodenparameter.

Merksatz:

Setze Validation-Annotations direkt auf Query-Parameter.


25. Query-Parameter-Validation mit @Validated

In vielen Spring Boot Anwendungen siehst du:


@RestController
@Validated
@RequestMapping("/api/tasks")
public class TaskController {

@GetMapping
public List<TaskDto> list(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size
) {
return taskService.findPage(page, size);
}
}

Dieser Stil ist üblich.

Wichtiger Hinweis für moderne Versionen:


Spring MVC hat auch eingebaute Method-Validation.
Je nach Spring-Version und Konfiguration kann Parameter-Validation HandlerMethodValidationException oder eine andere Constraint-Exception werfen.

Merksatz für die Prüfung:

Request-Body-Validation wirft häufig MethodArgumentNotValidException; Method-Parameter-Validation kann in modernem Spring MVC HandlerMethodValidationException werfen.


26. Path-Variable-Validation

Beispiel:


@GetMapping("/api/tasks/{id}")
public TaskDto get(
@PathVariable @Positive Long id
) {
return taskService.findById(id);
}

Ungültiger Request:


GET /api/tasks/-1

Erwartet:


400 Bad Request

weil id positiv sein muss.


27. Header-Validation

Beispiel:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestHeader("X-Tenant-Id") @NotBlank String tenantId
) {
return taskService.findForTenant(tenantId);
}

Ungültig, wenn:


X-Tenant-Id ist blank

Tenant- und Auth-Validation passiert oft in Filtern/Security-Layern — hier geht es um das Validation-Konzept.


28. Request-Parameter-Objekt

Statt vieler Query-Parameter:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam(defaultValue = "0") @Min(0) int page,
@RequestParam(defaultValue = "20") @Min(1) @Max(100) int size,
@RequestParam(required = false) String status,
@RequestParam(required = false) String search
) {
}

Du kannst ein Filter-Objekt nutzen:


public class TaskSearchRequest {

@Min(0)
private int page = 0;

@Min(1)
@Max(100)
private int size = 20;

private String status;

private String search;

// Getter und Setter
}

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(@Valid TaskSearchRequest request) {
return taskService.search(request);
}

Bei Query-Parameter-Objekten bindet Spring Request-Parameter an das Objekt.

Merksatz:

Bei vielen Query-Parametern: Request-/Filter-Objekt nutzen.


29. Hinweis zu @ModelAttribute

Für Nicht-Body-Objekte in Spring MVC funktioniert Binding oft wie @ModelAttribute.

Das:


public List<TaskDto> list(@Valid TaskSearchRequest request)

wird oft ähnlich behandelt wie:


public List<TaskDto> list(@Valid @ModelAttribute TaskSearchRequest request)

In REST APIs schreibst du @ModelAttribute nicht immer — das Konzept ist aber nützlich.

Merksatz:

Query-/Form-Parameter können an ein Objekt gebunden werden — nicht nur einzelne Parameter.


30. Eigene Cross-Field-Validation

Manchmal reicht ein Feld nicht.

Beispielregel:


startDate must be before endDate

DTO:


@ValidDateRange
public record CreateProjectRequest(

@NotNull
LocalDate startDate,

@NotNull
LocalDate endDate
) {
}

Du brauchst eine eigene Annotation und einen Validator.


31. Eigene Validation-Annotation

Annotation erstellen:


@Target({ ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = DateRangeValidator.class)
public @interface ValidDateRange {

String message() default "startDate must be before or equal to endDate";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

Wichtige Teile:


@Constraint verbindet Annotation mit Validator
message definiert die Standard-Fehlermeldung
groups unterstützt Validation Groups
payload ist von der Bean Validation API vorgeschrieben

32. Eigener Constraint Validator


public class DateRangeValidator
implements ConstraintValidator<ValidDateRange, CreateProjectRequest> {

@Override
public boolean isValid(CreateProjectRequest value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}

if (value.startDate() == null || value.endDate() == null) {
return true;
}

return !value.startDate().isAfter(value.endDate());
}
}

Warum true zurückgeben, wenn Felder null sind?


@NotNull soll fehlende Daten behandeln.
Dieser Validator prüft nur die Beziehung zwischen den Daten.

Merksatz:

Einzelfeld-Regeln: Built-in-Annotations. Cross-Field-Regeln brauchen oft Custom Validators.


33. Eigener Field-Level Validator

Beispielregel:


priority must be LOW, MEDIUM, or HIGH

Bessere Option:


public enum Priority {
LOW, MEDIUM, HIGH
}

DTO:


public record CreateTaskRequest(
@NotNull Priority priority
) {
}

Wenn der Wert aber String sein muss, erstelle eine eigene Annotation:


@Target({ ElementType.FIELD, ElementType.PARAMETER })
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = TaskPriorityValidator.class)
public @interface ValidTaskPriority {

String message() default "priority must be LOW, MEDIUM, or HIGH";

Class<?>[] groups() default {};

Class<? extends Payload>[] payload() default {};
}

Validator:


public class TaskPriorityValidator
implements ConstraintValidator<ValidTaskPriority, String> {

private static final Set<String> ALLOWED =
Set.of("LOW", "MEDIUM", "HIGH");

@Override
public boolean isValid(String value, ConstraintValidatorContext context) {
if (value == null) {
return true;
}

return ALLOWED.contains(value);
}
}

DTO:


public record CreateTaskRequest(

@NotBlank
String title,

@NotBlank
@ValidTaskPriority
String priority
) {
}

34. Enum bevorzugen, wenn möglich

Statt:


@NotBlank
@ValidTaskPriority
String priority;

Oft besser:


@NotNull
Priority priority;

Warum?


Typsicherheit
weniger Custom Code
klare erlaubte Werte
Compiler-Unterstützung
saubererer Service-Code

Merksatz:

Enum für feste Wertemengen bevorzugen.


35. Validation Groups

Manchmal unterscheidet sich Validation bei Create und Update.

Beispiel:


Create: title ist Pflicht.
Update: title kann optional sein.

Groups definieren:


public interface CreateValidation {
}

public interface UpdateValidation {
}

DTO:


public record TaskRequest(

@NotBlank(groups = CreateValidation.class)
String title,

@Size(max = 100, groups = { CreateValidation.class, UpdateValidation.class })
String description
) {
}

Controller:


@PostMapping("/api/tasks")
public TaskDto create(
@Validated(CreateValidation.class)
@RequestBody TaskRequest request
) {
return taskService.create(request);
}

@PatchMapping("/api/tasks/{id}")
public TaskDto update(
@PathVariable Long id,
@Validated(UpdateValidation.class)
@RequestBody TaskRequest request
) {
return taskService.update(id, request);
}

Merksatz:

Validation Groups erlauben unterschiedliche Regeln in verschiedenen Situationen.


36. Validation Groups vorsichtig nutzen

Validation Groups können kompliziert werden.

Oft einfacher:


CreateTaskRequest
UpdateTaskRequest

Beispiel:


public record CreateTaskRequest(
@NotBlank String title,
@Size(max = 100) String description
) {
}

public record UpdateTaskRequest(
@Size(max = 100) String description
) {
}

Das ist oft leichter lesbar.

Merksatz:

Separate DTOs sind oft einfacher als Validation Groups.


37. Validation vs. Business Rules

Validation prüft grundlegende Eingaberegeln.

Beispiele:


nicht blank
gültige E-Mail
Größenbereich
positive Zahl
Datumsformat
Pflichtfelder

Business Rules gehören in den Service Layer.

Beispiele:


User darf Task eines anderen Tenants nicht löschen
Rechnung darf nicht zweimal gesendet werden
Task darf nicht abgeschlossen werden, bevor alle Subtasks fertig sind
Subscription muss aktiv sein
Client muss zur aktuellen Firma gehören

Merksatz:

Validation prüft die Eingabeform. Services prüfen Business Rules.


38. Beispiel: Validation + Business Rule

DTO-Validation:


public record CompleteTaskRequest(

@NotNull
Long taskId
) {
}

Business Rule im Service:


public TaskDto completeTask(Long taskId, Long currentUserId) {
Task task = taskRepository.findById(taskId)
.orElseThrow(() -> new ResourceNotFoundException("Task", taskId));

if (!task.canBeCompletedBy(currentUserId)) {
throw new ForbiddenOperationException("User cannot complete this task");
}

task.complete();
return toDto(task);
}

Validation prüft:


taskId ist vorhanden

Service prüft:


ob der aktuelle User den Task abschließen darf

39. Validation Error Response

Aus Tag 4:


public record ValidationErrorResponse(
Instant timestamp,
int status,
String error,
String code,
String message,
String path,
List<FieldErrorDto> fieldErrors
) {
}

public record FieldErrorDto(
String field,
String message
) {
}

Beispiel:


{
"timestamp": "2026-07-07T18:30:00Z",
"status": 400,
"error": "Bad Request",
"code": "VALIDATION_ERROR",
"message": "Request validation failed",
"path": "/api/tasks",
"fieldErrors": [
{
"field": "title",
"message": "Title is required"
}
]
}

40. MethodArgumentNotValidException behandeln


@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ValidationErrorResponse> handleValidationError(
MethodArgumentNotValidException ex,
HttpServletRequest request
) {
List<FieldErrorDto> fieldErrors = ex.getBindingResult()
.getFieldErrors()
.stream()
.map(error -> new FieldErrorDto(
error.getField(),
error.getDefaultMessage()
))
.toList();

ValidationErrorResponse response = new ValidationErrorResponse(
Instant.now(),
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
"VALIDATION_ERROR",
"Request validation failed",
request.getRequestURI(),
fieldErrors
);

return ResponseEntity.badRequest().body(response);
}

Nutze das bei fehlgeschlagenem @Valid @RequestBody.


41. Method-Validation-Fehler behandeln

Modernes Spring MVC kann werfen:


HandlerMethodValidationException

bei Method-Parameter-Validation.

Beispiel ungültig:


GET /api/tasks?page=-1

Controller:


@GetMapping("/api/tasks")
public List<TaskDto> list(
@RequestParam @Min(0) int page
) {
return taskService.findPage(page);
}

Prüfungssichere Handler-Idee:


@ExceptionHandler(HandlerMethodValidationException.class)
public ResponseEntity<ApiErrorResponse> handleMethodValidation(
HandlerMethodValidationException ex,
HttpServletRequest request
) {
ApiErrorResponse error = new ApiErrorResponse(
Instant.now(),
HttpStatus.BAD_REQUEST.value(),
HttpStatus.BAD_REQUEST.getReasonPhrase(),
"METHOD_VALIDATION_ERROR",
"Request parameter validation failed",
request.getRequestURI()
);

return ResponseEntity.badRequest().body(error);
}

Für die Zertifizierung wichtig:


Request-Body-Validation und Method-Parameter-Validation können unterschiedliche Exception-Typen erzeugen.

42. BindingResult-Alternative

Diesen Stil siehst du manchmal:


@PostMapping("/api/tasks")
public ResponseEntity<?> create(
@Valid @RequestBody CreateTaskRequest request,
BindingResult bindingResult
) {
if (bindingResult.hasErrors()) {
return ResponseEntity.badRequest().body("Validation failed");
}

return ResponseEntity.ok(taskService.create(request));
}

Validation wird lokal in der Controller-Methode behandelt.

In REST APIs oft besser:


Validation Exception werfen lassen.
Global in @RestControllerAdvice behandeln.

Merksatz:

BindingResult = lokale Behandlung. @ControllerAdvice = globale Behandlung.


43. Gutes Validation-Design für REST APIs

Empfohlener Stil:


Request-DTOs nutzen.
Validation-Annotations auf DTO-Felder setzen.
@Valid auf @RequestBody nutzen.
@Valid für verschachtelte DTOs nutzen.
Query-Parameter-Constraints für Pagination nutzen.
Globales Exception Handling nutzen.
Strukturierte Validation-Fehler zurückgeben.
Business Rules in Services belassen.

44. Häufige Validation-Fehler

Fehler 1

Validation-Annotations sind da, aber @Valid fehlt.


public TaskDto create(@RequestBody CreateTaskRequest request)

Lösung:


public TaskDto create(@Valid @RequestBody CreateTaskRequest request)

Fehler 2

Verschachteltes Objekt hat Validation-Annotations, aber das Parent-Feld hat kein @Valid.


AddressRequest address

Lösung:


@Valid
AddressRequest address

Fehler 3

@NotNull für Pflicht-Text nutzen.


@NotNull
String title

Das erlaubt:


""
" "

Besser:


@NotBlank
String title

Fehler 4

Nur @Size für ein Pflichtfeld nutzen.


@Size(min = 3)
String title

Das kann erlauben:


null

Besser:


@NotBlank
@Size(min = 3)
String title

Fehler 5

Business Rules nur in DTO-Validation packen.

Schlecht:


@ValidTenantAccess

auf Request-DTO, obwohl das von Datenbank/aktuellem User/Security-Kontext abhängt.

Besser:


Service Layer prüft Tenant-Zugriff

45. Praxisbeispiel für eine Klarsync-ähnliche App

Request:


{
"title": "Prepare payroll documents",
"clientId": 10,
"dueDate": "2026-08-01",
"priority": "HIGH",
"assigneeEmail": "user@example.com"
}

DTO:


public record CreateWorkTaskRequest(

@NotBlank(message = "Title is required")
@Size(max = 120, message = "Title must not exceed 120 characters")
String title,

@NotNull(message = "Client id is required")
@Positive(message = "Client id must be positive")
Long clientId,

@NotNull(message = "Due date is required")
@FutureOrPresent(message = "Due date must not be in the past")
LocalDate dueDate,

@NotNull(message = "Priority is required")
Priority priority,

@Email(message = "Assignee email must be valid")
String assigneeEmail
) {
}

Controller:


@PostMapping("/api/work-tasks")
public ResponseEntity<WorkTaskDto> create(
@Valid @RequestBody CreateWorkTaskRequest request
) {
WorkTaskDto created = workTaskService.create(request);
URI location = URI.create("/api/work-tasks/" + created.id());

return ResponseEntity.created(location).body(created);
}

46. Validation-Ablauf


1. Client sendet JSON.
2. @RequestBody sagt Spring, den Body zu lesen.
3. Jackson konvertiert JSON zu DTO.
4. @Valid löst Bean Validation aus.
5. Wenn gültig, läuft die Controller-Methode.
6. Wenn ungültig, wirft Spring eine Validation-Exception.
7. @RestControllerAdvice behandelt die Exception.
8. Client erhält 400 mit Field Errors.

Merksatz:

JSON -> DTO -> Validation -> Controller oder 400-Fehler.


47. Typische Prüfungsfallen

Falle 1

Validation-Annotations allein reichen für Request Bodies nicht.

Nötig:


@Valid @RequestBody

Falle 2

@NotNull lehnt leere Strings nicht ab.

Nutze @NotBlank für Pflicht-Text.


Falle 3

@Size impliziert nicht @NotNull.

Nutze beides, wenn nötig.


Falle 4

Verschachtelte DTO-Validation braucht @Valid auf dem verschachtelten Feld.


Falle 5

Listen-Element-Validation braucht List<@Valid ItemDto> oder List<@NotBlank String>.


Falle 6

Für feste erlaubte Werte ist ein Enum oft besser als Custom String-Validation.


Falle 7

Request-Body-Validation und Query-Parameter-Validation können unterschiedliche Exception-Typen werfen.


Falle 8

Validation prüft die Eingabeform.

Business Rules gehören in Service-/Domain-Logik.


Falle 9

@Validated unterstützt Validation Groups.


Falle 10

Globales Exception Handling für konsistente Validation-Error-Responses nutzen.


48. Prüfungsfrage: Bean Validation

Frage:

Was ist Bean Validation?

Antwort:

Bean Validation ist das Standard-Java-Validation-Modell. Es nutzt Annotations wie @NotBlank, @Email, @Size, @Min und @Max, um Validation-Regeln auf Java-Objekten zu deklarieren.


49. Prüfungsfrage: Dependency

Frage:

Welcher Spring Boot Starter wird für Validation üblicherweise benötigt?

Antwort:


implementation("org.springframework.boot:spring-boot-starter-validation")

50. Prüfungsfrage: @Valid

Frage:

Was macht @Valid mit @RequestBody?

Antwort:

@Valid löst Bean Validation für das aus dem Request Body erzeugte Objekt aus.


51. Prüfungsfrage: @Validated

Frage:

Was ist @Validated?

Antwort:

@Validated ist die Spring-Validation-Annotation. Sie kann Validation auslösen und unterstützt Validation Groups. Oft für Method-Parameter-Validation oder gruppenbasierte Validation genutzt.


52. Prüfungsfrage: @NotNull vs @NotBlank

Frage:

Was ist der Unterschied zwischen @NotNull und @NotBlank?

Antwort:

@NotNull lehnt nur null ab. @NotBlank lehnt null, leere Strings und nur-Whitespace-Strings ab.


53. Prüfungsfrage: Nested Validation

Frage:

Wie validiere ich ein verschachteltes DTO?

Antwort:

Setze Validation-Annotations auf die Felder des verschachtelten DTOs und @Valid auf das verschachtelte Feld im Parent-DTO.


54. Prüfungsfrage: List Validation

Frage:

Wie validiere ich jedes Objekt in einer Liste?

Antwort:

Nutze:


List<@Valid ItemRequest> items

und füge meist @NotEmpty hinzu, damit die Liste nicht leer ist.


55. Prüfungsfrage: Query-Parameter-Validation

Frage:

Wie validiere ich Query-Parameter?

Antwort:

Setze Constraint-Annotations direkt auf die Methodenparameter, z. B.:


@RequestParam @Min(0) int page

Je nach Spring-Version/Konfiguration kann Method-Parameter-Validation @Validated erfordern oder Spring MVCs eingebaute Method-Validation nutzen.


56. Prüfungsfrage: MethodArgumentNotValidException

Frage:

Wann tritt MethodArgumentNotValidException üblicherweise auf?

Antwort:

Das passiert üblicherweise, wenn Validation für ein Objekt wie @Valid @RequestBody fehlschlägt.


57. Prüfungsfrage: HandlerMethodValidationException

Frage:

Wann kann HandlerMethodValidationException auftreten?

Antwort:

In modernem Spring MVC kann das passieren, wenn Method-Validation auf Controller-Methodenparameter angewendet wird — z. B. Constraints direkt auf @RequestParam, @PathVariable oder Rückgabewerten.


58. Prüfungsfrage: Validation vs. Business Rules

Frage:

Was ist der Unterschied zwischen Validation und Business Rules?

Antwort:

Validation prüft grundlegende Eingabeform und Constraints — z. B. Pflichtfelder, Größe, E-Mail-Format oder positive Zahlen. Business Rules prüfen anwendungsspezifische Logik — z. B. ob ein User auf einen Tenant zugreifen darf oder ob eine Rechnung zweimal gesendet werden kann.


59. Interview-Antwort

Frage:

Wie validierst du einen Request Body in Spring Boot?

Gute Antwort:

Ich erstelle ein Request-DTO und füge Bean-Validation-Annotations wie @NotBlank, @Size oder @Email hinzu. Ich füge spring-boot-starter-validation hinzu und annotiere den Controller-Parameter mit @Valid @RequestBody. Schlägt Validation fehl, liefert Spring meist 400 Bad Request — und ich behandle MethodArgumentNotValidException in einem globalen @RestControllerAdvice, um eine strukturierte Error Response zurückzugeben.


60. Interview-Antwort

Frage:

Was ist der Unterschied zwischen @Valid und @Validated?

Gute Antwort:

@Valid ist die Standard-Jakarta-Bean-Validation-Annotation — üblich für Request Body und verschachtelte Objekte. @Validated ist die Spring-Variante. Sie kann Validation ebenfalls auslösen und unterstützt Validation Groups. Oft für Method-Parameter-Validation oder gruppenbasierte Validation.


61. Interview-Antwort

Frage:

Wie validierst du verschachtelte DTOs?

Gute Antwort:

Ich setze Validation-Annotations auf die Felder des verschachtelten DTOs und @Valid auf das verschachtelte Feld im Parent-DTO. Ist das verschachtelte Objekt Pflicht, füge ich auch @NotNull hinzu. Ohne @Valid auf dem verschachtelten Feld laufen dessen Validation-Regeln möglicherweise nicht.


62. Interview-Antwort

Frage:

Wie validierst du Query-Parameter?

Gute Antwort:

Ich setze Constraint-Annotations direkt auf die Controller-Methodenparameter — z. B. @RequestParam @Min(0) int page und @RequestParam @Max(100) int size. In vielen Spring Boot Anwendungen steht @Validated auf der Controller-Klasse für Method-Parameter-Validation. In modernem Spring MVC kann Method-Validation auch über eingebaute Unterstützung laufen und HandlerMethodValidationException werfen.


63. Interview-Antwort

Frage:

Wann würdest du einen Custom Validator erstellen?

Gute Antwort:

Ich erstelle einen Custom Validator, wenn Built-in-Annotations nicht reichen. Muss ich z. B. prüfen, dass startDate vor endDate liegt, erstelle ich eine eigene Constraint-Annotation und einen ConstraintValidator. Für feste Werte wie priority bevorzuge ich ein Enum — einfacher und typsicher.


64. Kleine Code-Übung

DTO erstellen:


public record CreateClientRequest(

@NotBlank
@Size(max = 100)
String name,

@Email
String email,

@Valid
@NotNull
AddressRequest address
) {
}

public record AddressRequest(

@NotBlank
String street,

@NotBlank
String city
) {
}

Controller:


@PostMapping("/api/clients")
public ResponseEntity<ClientDto> create(
@Valid @RequestBody CreateClientRequest request
) {
ClientDto created = clientService.create(request);
URI location = URI.create("/api/clients/" + created.id());

return ResponseEntity.created(location).body(created);
}

Fragen:

  1. Was validiert name?
  2. Was validiert email?
  3. Was löst Validation für den Request Body aus?
  4. Was löst verschachtelte Adress-Validation aus?
  5. Welchen Status sollte ungültige Eingabe üblicherweise liefern?

Antworten:

  1. @NotBlank und @Size
  2. @Email
  3. @Valid auf dem Controller-Parameter
  4. @Valid auf dem Feld address
  5. 400 Bad Request

65. Kleine Bug-Übung 1

Problem:


public record CreateClientRequest(

@NotBlank
String name,

AddressRequest address
) {
}

public record AddressRequest(
@NotBlank String street,
@NotBlank String city
) {
}

Verschachtelte Adressfelder werden nicht validiert.

Frage:

Was fehlt?

Antwort:

@Valid fehlt auf dem verschachtelten Feld.

Lösung:


public record CreateClientRequest(

@NotBlank
String name,

@Valid
AddressRequest address
) {
}

66. Kleine Bug-Übung 2

Problem:


public record CreateTaskRequest(

@Size(min = 3)
String title
) {
}

Request:


{
"title": null
}

Validation läuft unerwartet durch.

Frage:

Warum?

Antwort:

@Size prüft die Länge, wenn ein Wert existiert — lehnt aber null nicht ab. Füge @NotBlank oder @NotNull hinzu.

Lösung:


public record CreateTaskRequest(

@NotBlank
@Size(min = 3)
String title
) {
}

67. Kleine Bug-Übung 3

Problem:


public record CreateTaskRequest(

@NotNull
String title
) {
}

Request:


{
"title": " "
}

Validation läuft durch.

Frage:

Warum?

Antwort:

@NotNull prüft nur, dass der Wert nicht null ist. Leere oder Whitespace-Strings lehnt es nicht ab. Nutze @NotBlank.


Übungsfragen

Frage 1

Was ist Validation?

Antwort:

Validation bedeutet: Client-Eingaben prüfen, ob sie den Regeln entsprechen, bevor die Anwendung sie nutzt.


Frage 2

Was ist Bean Validation?

Antwort:

Bean Validation ist das Standard-Java-Validation-Modell mit Annotations auf Java-Objekten — z. B. @NotBlank, @Email, @Size und @Min.


Frage 3

Welche Dependency brauche ich für Validation in Spring Boot?

Antwort:

Nutze:


implementation("org.springframework.boot:spring-boot-starter-validation")

Frage 4

Was macht @Valid?

Antwort:

@Valid löst Bean Validation für ein Objekt aus — z. B. ein Request-DTO oder verschachteltes DTO.


Frage 5

Was macht @Validated?

Antwort:

@Validated ist die Spring-Validation-Annotation. Sie kann Validation auslösen und unterstützt Validation Groups.


Frage 6

Was ist der Unterschied zwischen @NotNull, @NotEmpty und @NotBlank?

Antwort:

@NotNull lehnt nur null ab. @NotEmpty lehnt null und leere Werte ab. @NotBlank lehnt null, leere Strings und nur-Whitespace-Strings ab.


Frage 7

Lehnt @Size null ab?

Antwort:

Nein. @Size lehnt null allein nicht ab.


Frage 8

Wie validiere ich einen Request Body?

Antwort:

Setze Validation-Annotations auf das Request-DTO und nutze @Valid @RequestBody in der Controller-Methode.


Frage 9

Wie validiere ich ein verschachteltes DTO?

Antwort:

Setze Validation-Annotations auf die Felder des verschachtelten DTOs und @Valid auf das verschachtelte Feld im Parent-DTO.


Frage 10

Wie validiere ich jedes Objekt in einer Liste?

Antwort:

Nutze List<@Valid ItemRequest> für Objekte oder Constraints wie List<@NotBlank String> für einfache Werte.


Frage 11

Wie validiere ich Query-Parameter?

Antwort:

Setze Constraint-Annotations direkt auf Query-Parameter-Methodenparameter — z. B. @RequestParam @Min(0) int page.


Frage 12

Wie validiere ich Path Variables?

Antwort:

Setze Constraint-Annotations direkt auf Path-Variable-Parameter — z. B. @PathVariable @Positive Long id.


Frage 13

Wann sollte ich einen Custom Validator erstellen?

Antwort:

Erstelle einen Custom Validator, wenn Built-in-Annotations nicht reichen — besonders für Cross-Field-Validation wie Startdatum vor Enddatum.


Frage 14

Warum sollte ich für feste Werte ein Enum bevorzugen?

Antwort:

Enums sind typsicher, klar — und vermeiden Custom String-Validation für feste Wertemengen.


Frage 15

Was sind Validation Groups?

Antwort:

Validation Groups erlauben unterschiedliche Validation-Regeln in verschiedenen Situationen — z. B. Create vs. Update.


Frage 16

Wann sind separate DTOs besser als Validation Groups?

Antwort:

Separate DTOs sind oft besser, wenn Create- und Update-Requests unterschiedliche Formen haben — einfacher und leichter verständlich.


Frage 17

Was ist der Unterschied zwischen Validation und Business Rules?

Antwort:

Validation prüft grundlegende Eingabeform und Constraints. Business Rules prüfen anwendungsspezifische Logik und gehören meist in den Service- oder Domain Layer.


Frage 18

Welche Exception tritt üblicherweise bei fehlgeschlagenem @Valid @RequestBody auf?

Antwort:

MethodArgumentNotValidException.


Frage 19

Welche Exception kann bei moderner Method-Parameter-Validation auftreten?

Antwort:

HandlerMethodValidationException.


Frage 20

Welcher Stil wird für Validation-Error-Responses empfohlen?

Antwort:

Strukturierte 400 Bad Request-Antwort mit stabilem Error Code, Message, Path und Field-Level-Validation-Fehlern zurückgeben.

Merksätze zum Mitnehmen

  • Validation schützt die Anwendung vor ungültigen Eingaben.
  • Bean Validation nutzt Annotations auf Java-Objekten.
  • Spring Boot Validation braucht spring-boot-starter-validation.
  • Spring Boot 3+ nutzt jakarta.validation.
  • @Valid löst Validation aus.
  • @Validated ist die Spring-Variante und unterstützt Groups.
  • @NotNull lehnt nur null ab.
  • @NotEmpty lehnt null und leer ab.
  • @NotBlank lehnt null, leer und nur-Whitespace-Text ab.
  • @Size lehnt null allein nicht ab.
  • @Valid @RequestBody für Request-Body-Validation nutzen.
  • @Valid auf verschachtelte DTO-Felder setzen.
  • List<@Valid ItemDto> für Listen-Element-Validation nutzen.
  • Constraints auf @RequestParam und @PathVariable für Method-Parameter-Validation nutzen.
  • Request-Body-Validation wirft häufig MethodArgumentNotValidException.
  • Moderne Method-Validation kann HandlerMethodValidationException werfen.
  • Enums für feste Werte bevorzugen.
  • Custom Validators für Regeln, die Built-in-Annotations nicht abbilden.
  • Validation prüft die Eingabeform.
  • Business Rules gehören in Service-/Domain-Logik.