java 121 lines · 4 tabs

Custom @UniqueEmail Bean Validation Constraint in Spring Boot Signup

Shared by codesnips Jul 2026
4 tabs
package com.example.signup.validation;

import jakarta.validation.Constraint;
import jakarta.validation.Payload;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;

import static java.lang.annotation.ElementType.FIELD;
import static java.lang.annotation.ElementType.PARAMETER;
import static java.lang.annotation.RetentionPolicy.RUNTIME;

@Documented
@Target({FIELD, PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = UniqueEmailValidator.class)
public @interface UniqueEmail {

    String message() default "{signup.email.alreadyTaken}";

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

    Class<? extends Payload>[] payload() default {};
}
4 files · java Explain with highlit

This snippet demonstrates how to enforce database-backed uniqueness during a signup flow using JSR-380 Bean Validation rather than scattering ad-hoc checks through service code. The core idea is that uniqueness is a validity concern of the request payload, so it can be expressed declaratively with a custom constraint annotation and a validator that queries the database.

In UniqueEmail annotation, the marker is defined with @Constraint(validatedBy = UniqueEmailValidator.class), which tells Hibernate Validator which class implements the rule. The mandatory message, groups, and payload members follow the Bean Validation contract; message uses a template key so it can be externalized into ValidationMessages.properties. Declaring @Target({FIELD, PARAMETER}) and @Retention(RUNTIME) keeps the annotation usable on DTO fields and readable via reflection at runtime.

UniqueEmailValidator implements ConstraintValidator<UniqueEmail, String>. Because it is a Spring @Component, the container injects UserRepository through constructor injection — a capability plain JSR-380 lacks, which is precisely why Spring's LocalValidatorFactoryBean wires validators as beans. The isValid method deliberately returns true for null, delegating null-checking to @NotBlank so responsibilities stay single-purpose. It normalizes the address to lowercase and trims it before calling existsByEmailIgnoreCase, avoiding case-variant duplicates. This means the validator performs a real query, so it should be paired with a database unique index to close the race window between validation and insert.

SignupRequest DTO composes the constraints: @NotBlank, @Email, and the custom @UniqueEmail sit together on the email field, layering format and uniqueness rules. AuthController triggers the whole chain simply by annotating the parameter with @Valid; when any constraint fails, Spring throws MethodArgumentNotValidException before the method body runs.

The trade-off worth noting is cost and timing: each validation issues a query, and validation is not transactional, so two concurrent signups can both pass. The pattern's value is clean, declarative, reusable rules with friendly field-level error messages, while the unique index remains the ultimate guarantee of correctness.


Related snips

Share this code

Here's the card — post it anywhere.

Custom @UniqueEmail Bean Validation Constraint in Spring Boot Signup — share card
Link copied