Логотип Workflow

Article

Updated at:

Stage 11: Request Validation with Bean Validation

Stage 11 - Request Validation with Bean Validation

Backend code becomes hard to maintain when important behavior is hidden in random controller methods. This article focuses on Bean Validation and @Valid. The topic looks technical, but the real question is practical: what happens when a real client sends a real request and something is incomplete, too large, forbidden, slow, or inconsistent?

A request can be valid JSON and still be invalid for the business. Quantity 0, blank email, missing product id, or a date in the past should be rejected before the service changes state.

Stage 11 - Request Validation with Bean Validation

The picture to keep in your head

Think about a small online shop. A user opens the frontend, clicks a button, and the browser calls the backend. The backend receives HTTP data, converts it into Java objects, checks rules, touches the database, and returns JSON. If each step has a clear owner, the system is understandable. If every step is mixed in one method, the first production incident becomes painful.

For this topic the sequence is:

  1. JSON.
  2. request DTO.
  3. @Valid.
  4. service only if valid.
PartResponsibility
ControllerReceives HTTP data and returns the public response.
ServiceRuns the business use case and protects business rules.
Repository/adapterTalks to persistence or external systems.
DTO/contractDefines what the outside world may send or receive.

Concrete Spring example

record CreateOrderRequest(
    @NotNull Long productId,
    @Min(1) int quantity,
    @NotBlank String deliveryAddress
) {}

@PostMapping("/api/orders")
OrderResponse create(@Valid @RequestBody CreateOrderRequest request) {
    return orderService.create(request);
}

The code is intentionally small because the important part is the boundary. The controller should not quietly decide business rules that belong in the service. The service should not depend on servlet objects. The repository should not know which JSON field name the frontend expects. When the boundary is clean, the same behavior can be documented, tested, and changed without touching every layer.

Why it matters

In a demo project, many shortcuts look harmless. Returning all records is fine when there are five rows. Returning an entity is fine until it contains a hidden field. Logging without a request id is fine until several users fail at the same time. Running against a manually created database is fine until staging has a different schema. Production work is mostly about removing these hidden assumptions before they become incidents.

The simplest useful rule is this: make the behavior explicit at the edge, enforce it in the right layer, and keep the public contract stable. If the frontend knows the request format, response format, and error behavior, it can work confidently. If tests exercise the same contract, refactoring becomes safer. If logs and configuration reflect the same design, operations become less dependent on guesswork.

Common mistakes

  • Putting the whole use case into a controller method.
  • Letting persistence details leak into the API contract.
  • Handling the happy path but leaving failure behavior undefined.
  • Using a local shortcut that cannot work in staging or production.

Understanding checklist

  • I can draw the sequence for this topic from request to response.
  • I can explain which layer owns the decision.
  • I can name the production problem this topic prevents.

Self-check questions

  1. What happens if this rule is implemented differently in every endpoint?
  2. Which part of the behavior should be documented for API clients?
  3. Which test would prove the rule works, not only that the happy path works?