Master Java Bean Validation: Reduce Redundant Code with JSR‑380 Annotations

This tutorial shows how to use Javax Validator annotations to simplify backend service interfaces, covering basic, collection, date, enum, conditional, group, and custom validations with complete Maven dependencies and example code, plus handling of validation exceptions.

Lin is Dream
Lin is Dream
Lin is Dream
Master Java Bean Validation: Reduce Redundant Code with JSR‑380 Annotations

In backend service interface development, using a validation framework with JavaBeans can eliminate a lot of redundant if‑else code. This article explains the basic usage and common techniques of Javax Validator annotations, providing complete example code. Before use, you need to add the required dependencies and a global exception handler; when validation fails, a MethodArgumentNotValidException is thrown, which should be handled uniformly.

Quick Start

<dependency>
    <groupId>javax.validation</groupId>
    <artifactId>validation-api</artifactId>
    <version>2.0.1.Final</version>
</dependency>
<dependency>
    <groupId>org.hibernate.validator</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.7.Final</version>
</dependency>

Basic Type Validation

1.1 Text

@NotEmpty(message = "文本字段 必传")
@Size(min = 1, max = 5, message = "文本长度必须在1到5之间")
@Length(min = 1, max = 5, message = "文本长度必须在1到5之间")
private String text;

1.2 Number

@NotNull(message = "数字 必传")
@Min(value = 1, message = "数字不能小于1")
@Max(value = 5, message = "数字不能大于5")
@Range(min = 1, max = 5, message = "数字范围 1-5")
@Positive(message = "数字必须大于0")
@PositiveOrZero(message = "数字必须为非负数")
@Negative(message = "数字必须小于0")
@NegativeOrZero(message = "数字必须非正数")
private Integer number;

1.3 Amount

@NotNull(message = "金额 必传")
@DecimalMin(value = "0.01", message = "金额不能小于0.01")
@DecimalMax(value = "10.99", message = "金额不能大于10.99")
@Digits(integer = 8, fraction = 4, message = "金额超出了允许范围(只允许在8位整数和4位小数范围内)")
private BigDecimal price;

1.4 Boolean

@NotNull(message = "布尔值 必传")
private Boolean isTrue;

1.5 Collection

@NotNull(message = "集合 必传")
@Size(min = 1, message = "集合至少有一个元素")
@Size(max = 3, message = "集合至多有三个元素")
private List<Integer> objs;

1.6 Date

LocalDateTime and Date parameters use the format "2025-12-01T12:00:00".

@NotNull(message = "LocalDateTime/Date 时间 必传")
@Past(message = "LocalDateTime/Date 时间必须是过去的时间")
@PastOrPresent(message = "LocalDateTime/Date 时间必须是当前或者过去的时间")
@Future(message = "LocalDateTime/Date 时间必须是未来的时间")
@FutureOrPresent(message = "LocalDateTime/Date 时间必须是当前或者未来的时间")
private LocalDateTime endTime;

1.7 Email

@NotEmpty(message = "邮箱 必传")
@Email(message = "邮箱格式不正确")
private String email;

1.8 Enum (custom validation)

This uses a custom validator to ensure a field value exists in an enum list. The enumClass attribute specifies the enum type and the field attribute specifies the enum field to compare.

@NotNull(message = "状态 必传")
@EnumValue(enumClass = OrderStatusEnum.class, field = "status", message = "状态 输入错误")
private Integer status;

1.9 Regular Expressions

Regular expressions can validate many scenarios such as phone numbers, ID numbers, URLs, IPs, ports, lowercase letters, uppercase letters, digits, and emoji.

@NotEmpty(message = "手机号码 必传")
@Pattern(regexp = "^[0-9]*$", message = "手机号码格式不正确")
@Size(max = 20, min = 11, message = "手机号码长度不正确")
private String phone;
@NotEmpty(message = "URL不能为空")
@Pattern(regexp = "^(http|https):\/\/([\w.]+\/?)\S*", message = "URL格式不正确")
private String url;
@NotNull(message = "身份证号码不能为空")
@Pattern(regexp = "^(\\d{15}|\\d{18}|\\d{17}[Xx])$", message = "身份证号码格式不正确")
private String idNumber;
@NotEmpty(message = "必须是纯英文小写字母")
@Pattern(regexp = "^[a-z]+$", message = "必须是纯英文小写字母")
private String lowercaseLetters;
@NotEmpty(message = "必须是纯英文大写字母")
@Pattern(regexp = "^[A-Z]+$", message = "必须是纯英文大写字母")
private String uppercaseLetters;
@NotEmpty(message = "不能包含特殊字符")
@Pattern(regexp = "^[a-zA-Z0-9_]+$", message = "不能包含特殊字符")
private String noSpecialChars;
@NotEmpty(message = "必须是纯数字")
@Pattern(regexp = "^[0-9]+$", message = "必须是纯数字")
private String digits;

Nested Object Validation

@Valid
@NotNull(message = "用户集合 必传")
@Size(min = 1, message = "用户集合至少有一个元素")
@Size(max = 3, message = "用户集合至多有三个元素")
private List<User> users;
@Data
public static class User {
    @NotEmpty(message = "name 必传")
    private String name;
    @NotNull(message = "age 必传")
    private Integer age;
}

Conditional Parameter Validation

Use @AssertTrue or @AssertFalse to implement conditional validation, e.g., when type = 1, name must be provided.

private String type;
private String name;

@AssertTrue(message = "type=1时,name 必传")
public boolean isNameValid() {
    if (!StringUtils.hasLength(type)) {
        return true;
    }
    if ("1".equals(type)) {
        return StringUtils.hasLength(name);
    }
    return true;
}

Group Validation

Assign a validation group to each annotation and specify the group in the controller to achieve different validation scenarios with the same JavaBean.

@Data
public class BeanForm {
    @NotEmpty(message = "文本字段 必传", groups = {CreateGroup.class})
    @Size(min = 1, max = 5, message = "文本长度必须在1到5之间", groups = {UpdateGroup.class})
    @Length(min = 1, max = 5, message = "文本长度必须在1到5之间", groups = {UpdateGroup.class})
    private String text;
}

public interface CreateGroup extends Default {}
public interface UpdateGroup extends Default {}

@PostMapping(value = "/test1")
public ResUtil<Object> test1(@RequestBody @Validated(BeanForm.UpdateGroup.class) BeanForm beanForm) {
    return ResUtil.success(JSONObject.toJSONString(beanForm));
}

Custom Annotation Validation

1️⃣ Enum value validation annotation @EnumValue.

package com.su4j.annotion;

import com.su4j.annotion.impl.EnumValueValidatorForInteger;
import com.su4j.annotion.impl.EnumValueValidatorForString;
import javax.validation.Constraint;
import javax.validation.Payload;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.FIELD, ElementType.PARAMETER, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = {EnumValueValidatorForString.class, EnumValueValidatorForInteger.class})
public @interface EnumValue {
    String message() default "Invalid value. Must be a valid enum value.";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
    Class<? extends Enum<?>> enumClass();
    String field();
}
package com.su4j.annotion.impl;

import com.su4j.annotion.EnumValue;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import java.lang.reflect.Field;

public class EnumValueValidatorForInteger implements ConstraintValidator<EnumValue, Integer> {
    private Enum<?>[] enumValues;
    private String fieldName;

    @Override
    public void initialize(EnumValue constraintAnnotation) {
        this.enumValues = constraintAnnotation.enumClass().getEnumConstants();
        this.fieldName = constraintAnnotation.field();
    }

    @Override
    public boolean isValid(Integer value, ConstraintValidatorContext context) {
        if (value == null) {
            return true;
        }
        for (Enum<?> enumValue : enumValues) {
            try {
                Field field = enumValue.getClass().getDeclaredField(fieldName);
                field.setAccessible(true);
                Object fieldValue = field.get(enumValue);
                if (value.equals(fieldValue)) {
                    return true;
                }
            } catch (NoSuchFieldException | IllegalAccessException e) {
                throw new RuntimeException("Failed to access field: " + fieldName);
            }
        }
        return false;
    }
}

2️⃣ Date range validation annotation @DateRange (not yet implemented).

Import Data Validation

Use javax.validation.Validator to validate imported data.

@Resource
private Validator validator;

public void list(List<Object> excelList) {
    // Parameter filtering
    for (Object obj : excelList) {
        Set<ConstraintViolation<Object>> violations = validator.validate(obj);
        if (violations.size() > 0) {
            String msg = violations.stream()
                .map(ConstraintViolation::getMessage)
                .collect(Collectors.joining(","));
            throw new RuntimeException(msg);
        }
    }
}

Q&A

Q1: What is the difference between validation-api and hibernate-validator?

validation-api is the JSR 303/380 specification defining validation annotations such as @NotNull and @Pattern. hibernate-validator is an implementation of this specification that provides the actual validation engine and additional features like @Length.

Q2: Who defines the JSR 303 and JSR 380 standards and what do they contain?

Both standards are defined by the Java Community Process (JCP). JSR 303 (Bean Validation 1.0) introduced the basic validation annotations and API in 2009. JSR 380 (Bean Validation 2.0) updated the API in 2017, adding support for @Valid, @Validated, and enhanced ConstraintValidator for custom constraints.

Q3: What is the difference between @Valid and @Validated?

@Valid is a standard JSR annotation that enables cascading validation of nested objects. @Validated is a Spring-specific annotation used on controller method parameters to trigger validation with support for validation groups.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

BackendJavaspringBean ValidationAnnotationsJSR-380
Lin is Dream
Written by

Lin is Dream

Sharing Java developer knowledge, practical articles, and continuous insights into computer engineering.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.