Woche 4, Tag 5 — Spring MVC Validation im Detail
Ziel
Heute willst du Validation in Spring MVC REST APIs wirklich verstehen.
Die Kernfragen:
- Was ist Bean Validation?
- Warum brauchen REST APIs Validation?
- Welche Dependency brauchst du?
- Was ist der Unterschied zwischen
@Validund@Validated? - Welche Validation-Annotations sind üblich?
- Was ist der Unterschied zwischen
@NotNull,@NotEmptyund@NotBlank? - Wie validierst du Request Bodies?
- Wie validierst du verschachtelte DTOs?
- Wie validierst du Listen?
- Wie validierst du Query-Parameter?
- Wie validierst du Path Variables?
- Wie erstellst du einen Custom Validator?
- Welche Exceptions entstehen bei fehlgeschlagener Validation?
- Welche typischen Prüfungsfallen gibt es?
1. Kurz-Wiederholung aus Woche 4, Tag 4
In Tag 4 hast du gelernt:
@ExceptionHandlerbehandelt Exceptions.@ControllerAdvicegilt für alle Controller.@RestControllerAdviceentspricht@ControllerAdviceplus@ResponseBody.MethodArgumentNotValidExceptionsteht 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-validationhinzufü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, nichtjavax.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.
@Validlö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
| Annotation | Bedeutung |
|---|---|
@NotNull | Wert darf nicht null sein |
@NotEmpty | String, Collection, Map oder Array darf nicht null oder leer sein |
@NotBlank | String 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 |
@Positive | Zahl muss größer als null sein |
@PositiveOrZero | Zahl muss null oder größer sein |
@Negative | Zahl muss kleiner als null sein |
@Email | String muss wie eine E-Mail aussehen |
@Pattern | String muss dem Regex entsprechen |
@Past | Datum muss in der Vergangenheit liegen |
@PastOrPresent | Datum muss Vergangenheit oder Gegenwart sein |
@Future | Datum muss in der Zukunft liegen |
@FutureOrPresent | Datum muss Zukunft oder Gegenwart sein |
@AssertTrue | Boolean muss true sein |
@AssertFalse | Boolean 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:
@Sizeprü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:
@Validauf 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:
@Validist Standard-Bean-Validation.@Validatedist 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 MVCHandlerMethodValidationExceptionwerfen.
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
@Validund@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:
- Was validiert
name? - Was validiert
email? - Was löst Validation für den Request Body aus?
- Was löst verschachtelte Adress-Validation aus?
- Welchen Status sollte ungültige Eingabe üblicherweise liefern?
Antworten:
@NotBlankund@Size@Email@Validauf dem Controller-Parameter@Validauf dem Feldaddress400 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. @Validlöst Validation aus.@Validatedist die Spring-Variante und unterstützt Groups.@NotNulllehnt nur null ab.@NotEmptylehnt null und leer ab.@NotBlanklehnt null, leer und nur-Whitespace-Text ab.@Sizelehnt null allein nicht ab.@Valid @RequestBodyfür Request-Body-Validation nutzen.@Validauf verschachtelte DTO-Felder setzen.List<@Valid ItemDto>für Listen-Element-Validation nutzen.- Constraints auf
@RequestParamund@PathVariablefür Method-Parameter-Validation nutzen. - Request-Body-Validation wirft häufig
MethodArgumentNotValidException. - Moderne Method-Validation kann
HandlerMethodValidationExceptionwerfen. - 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.