java 108 lines · 3 tabs

Collect Bean Validation Field Errors in a Spring Boot Signup Endpoint

Shared by codesnips Jul 2026
3 tabs
package com.example.auth.dto;

import jakarta.validation.constraints.Email;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;

public class SignupRequest {

    @NotBlank(message = "Username is required")
    @Size(min = 3, max = 30, message = "Username must be between 3 and 30 characters")
    private String username;

    @NotBlank(message = "Email is required")
    @Email(message = "Email must be a valid address")
    private String email;

    @NotBlank(message = "Password is required")
    @Size(min = 8, message = "Password must be at least 8 characters")
    @Pattern(regexp = ".*\\d.*", message = "Password must contain at least one digit")
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getEmail() {
        return email;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}
3 files · java Explain with highlit

This snippet shows the standard way a Spring Boot service validates an incoming signup request declaratively and turns any constraint violations into a clean, structured JSON error payload. The core idea is to keep validation rules on the data itself rather than scattering if checks through controller code, so the request object becomes the single source of truth for what a valid signup looks like.

In SignupRequest, the DTO carries Jakarta Bean Validation annotations — @NotBlank, @Email, @Size, and @Pattern — each with a human-readable message. These are metadata, not executed logic; Hibernate Validator (the reference implementation bundled with spring-boot-starter-validation) reads them at runtime. A subtle but important detail is @Size(min = 8) combined with @NotBlank on the password: @NotBlank guards the empty case while @Size enforces length, because a single annotation cannot express both concerns cleanly.

In AuthController, the @Valid annotation on the @RequestBody parameter is what actually triggers validation. When the bound object fails, Spring does not proceed to the method body — it throws a MethodArgumentNotValidException before register runs. That is why the controller itself contains no validation code at all; the happy path assumes the data is already trustworthy, which keeps business logic readable.

The interesting work happens in ValidationExceptionHandler, a @RestControllerAdvice that centralizes error formatting for every controller in the application. Its @ExceptionHandler for MethodArgumentNotValidException walks getBindingResult().getFieldErrors() and collapses them into a field -> message map. Using a merge function in Collectors.toMap avoids an IllegalStateException when one field has multiple failing constraints, keeping only the first message per field. The response is wrapped in an ApiError record and returned with 400 Bad Request.

This pattern scales well: adding a new rule means adding one annotation, and the error shape stays consistent across endpoints. The main trade-offs are that annotation order does not guarantee message order, cross-field rules (like password confirmation) need a custom class-level constraint instead, and messages are best externalized to a messages.properties file for internationalization. It is the go-to approach whenever request payloads have well-defined, per-field rules.


Related snips

graphql
type User {
    id: ID!
    name: String!
    email: String!
    posts: [Post!]!
    createdAt: String!

GraphQL API with Spring Boot

java graphql spring-boot
by David Kumar 3 tabs
ruby
class SignupForm
  include ActiveModel::Model
  include ActiveModel::Attributes

  attribute :account_name, :string
  attribute :email, :string

Shallow Controller, Deep Params: Form Object Pattern

rails activemodel form-object
by codesnips 3 tabs
javascript
// Creating a Promise
const myPromise = new Promise((resolve, reject) => {
  const success = true;

  setTimeout(() => {
    if (success) {

Promises and async/await patterns for asynchronous JavaScript

javascript promises async-await
by Alex Chang 1 tab
java
package com.example.starter.config;

import com.example.starter.properties.CustomProperties;
import com.example.starter.service.CustomService;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;

Custom Spring Boot starters

java spring-boot starter
by David Kumar 4 tabs
html
<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <title>Form Validation Example</title>
  <style>

HTML forms with validation and accessibility

html forms validation
by Alex Chang 1 tab
ruby
class CreateDeadJobs < ActiveRecord::Migration[7.1]
  def change
    create_table :dead_jobs do |t|
      t.string  :jid, null: false
      t.string  :queue, null: false
      t.string  :klass, null: false

Background Job Dead Letter Queue (DLQ) Table

rails reliability background-jobs
by codesnips 4 tabs

Share this code

Here's the card — post it anywhere.

Collect Bean Validation Field Errors in a Spring Boot Signup Endpoint — share card
Link copied