Interview question
Build a validated Spring create endpoint with ProblemDetail errors
Implements request validation, a clean service boundary, a 201 response, and stable RFC 9457-style validation details.
TL;DR
Implements request validation, a clean service boundary, a 201 response, and stable RFC 9457-style validation details.
Spring MVC, Bean Validation, request DTOs, ResponseEntity, ProblemDetail, error contracts, and boundary ownership.
Practice the problem like a real interview: restate, reason, implement, and test.
Implement POST /orders. The request requires a product ID and a quantity from 1 to 100. A valid request returns 201 Created with a location header. Invalid input returns a ProblemDetail with status 400 and a stable errors map keyed by field name. Do not expose persistence entities or raw exception messages.
Next in Java & Spring Practical Labs: Persisted idempotent POST
{ "productId": null, "quantity": 0 }
{
"type": "https://aporeon.com/problems/validation",
"title": "Request validation failed",
"status": 400,
"errors": { "productId": "must not be null", "quantity": "must be greater than or equal to 1" }
}
@Valid to the request body and validation annotations to fields.ResponseEntity.created.MethodArgumentNotValidException in @RestControllerAdvice.ProblemDetail and attach a field-to-message map.record CreateOrderRequest(
@jakarta.validation.constraints.NotNull java.util.UUID productId,
@jakarta.validation.constraints.Min(1)
@jakarta.validation.constraints.Max(100) int quantity) {}
record OrderResponse(java.util.UUID id, java.util.UUID productId, int quantity) {}
@org.springframework.web.bind.annotation.RestController
class OrderController {
private final OrderService service;
OrderController(OrderService service) { this.service = service; }
@org.springframework.web.bind.annotation.PostMapping("/orders")
org.springframework.http.ResponseEntity<OrderResponse> create(
@jakarta.validation.Valid
@org.springframework.web.bind.annotation.RequestBody CreateOrderRequest request) {
var created = service.create(request);
var location = java.net.URI.create("/orders/" + created.id());
return org.springframework.http.ResponseEntity.created(location).body(created);
}
}
@org.springframework.web.bind.annotation.RestControllerAdvice
class ApiErrors {
@org.springframework.web.bind.annotation.ExceptionHandler(
org.springframework.web.bind.MethodArgumentNotValidException.class)
org.springframework.http.ProblemDetail validation(
org.springframework.web.bind.MethodArgumentNotValidException ex) {
var errors = new java.util.TreeMap<String, String>();
ex.getBindingResult().getFieldErrors().forEach(error ->
errors.putIfAbsent(error.getField(), error.getDefaultMessage()));
var problem = org.springframework.http.ProblemDetail.forStatus(400);
problem.setType(java.net.URI.create("https://aporeon.com/problems/validation"));
problem.setTitle("Request validation failed");
problem.setProperty("errors", errors);
return problem;
}
}
Validation is O(f) in the number of constrained fields. Error collection is O(e log e) here because TreeMap provides deterministic key ordering; for this small boundary the cost is negligible.
int distinguishes an omitted quantity only through its default zero; use Integer if omission must differ from zero.@Valid, annotations exist but request validation never runs.ex.getMessage() leaks framework detail and creates an unstable client contract.Spot a weak answer, missing edge case, or clearer explanation? Send it in.
Say thanks with a standalone, one-time $5 contribution. No account required.