Mastering Spring Parameter Validation: A Flexible SpEL‑Based Validator
This article introduces a powerful, extensible Spring validation component built on SpEL that handles simple annotations, enum checks, multi‑field logic, and Spring Bean integration, providing step‑by‑step setup, usage examples, custom constraints, and performance considerations for Java backend developers.
Problem addressed
The component extends javax.validation to handle scenarios that standard annotations such as @NotNull and @Size cannot cover, including enum value validation, conditional multi‑field checks, complex logic via static methods, and invocation of Spring beans.
Enum field validation:
@SpelAssert(assertTrue = "T(cn.sticki.enums.UserStatusEnum).getByCode(#this.userStatus) != null", message = "用户状态不合法")
private Integer userStatus;Multi‑field joint validation:
@NotNull
private Integer contentType;
@SpelNotNull(condition = "#this.contentType == 1", message = "语音内容不能为空")
private Object audioContent;
@SpelNotNull(condition = "#this.contentType == 2", message = "视频内容不能为空")
private Object videoContent;Complex logic using a static method:
// 中文算两个字符,英文算一个字符,要求总长度不超过 10
@SpelAssert(assertTrue = "T(cn.sticki.util.StringUtil).getLength(#this.userName) <= 10", message = "用户名长度不能超过10")
private String userName;Calling a Spring Bean (requires @EnableSpelValidatorBeanRegistrar ):
@SpelAssert(assertTrue = "@userService.getById(#this.userId) != null", message = "用户不存在")
private Long userId;Key Features
Supports virtually all validation scenarios without modifying existing javax.validation APIs.
Built on Spring Expression Language (SpEL), enabling complex expressions and conditional validation.
Allows invocation of Spring beans inside validation expressions.
Object‑level validation enables cross‑field checks.
Customizable constraint annotations for business‑specific rules.
Integrates seamlessly with the standard validation exception flow.
Usage pattern mirrors native javax.validation annotations.
Environment
Tested on JDK 8; it should work on any JDK 8+ runtime.
Quick Start
Add the Maven dependencies (latest version 0.0.2‑beta):
<dependency>
<groupId>cn.sticki</groupId>
<artifactId>spel-validator</artifactId>
<version>0.0.2-beta</version>
</dependency>
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>${hibernate-validator.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<version>${spring-boot-starter-web.version}</version>
</dependency>Use on controller method parameters with @Valid or @Validated :
@RestController
@RequestMapping("/example")
public class ExampleController {
/** Simple validation example */
@PostMapping("/simple")
public Resp<Void> simple(@RequestBody @Valid SimpleExampleParamVo param) {
return Resp.ok(null);
}
}Annotate entity classes with @SpelValid and apply field‑level constraints such as @SpelNotNull :
@Data
@SpelValid
public class SimpleExampleParamVo {
@NotNull
private Boolean switchAudio;
/** When switchAudio is true, audioContent must not be null */
@SpelNotNull(condition = "#this.switchAudio == true", message = "语音内容不能为空")
private Object audioContent;
}Add a global exception handler to translate validation errors:
@RestControllerAdvice
public class ControllerExceptionAdvice {
@ExceptionHandler({BindException.class, MethodArgumentNotValidException.class})
public Resp<Void> handleBindException(BindException ex) {
String msg = ex.getFieldErrors().stream()
.map(error -> error.getField() + " " + error.getDefaultMessage())
.reduce((s1, s2) -> s1 + "," + s2)
.orElse("");
return new Resp<>(400, msg);
}
}Usage Guide
Validation occurs only when both conditions are satisfied:
Apply @Valid or @Validated on the controller method parameter.
Annotate the target class with @SpelValid.
If only the first condition is present, only standard constraints ( @NotNull, @NotEmpty, @NotBlank, etc.) are processed. The custom SpEL‑based constraints require the second condition because @SpelValid implements javax.validation.Constraint and is activated only when the standard validation annotations are in effect.
Supported constraint annotations
@SpelAssert– logical assertion validation (no direct javax.validation counterpart). @SpelNotNull – non‑null validation (maps to @NotNull). @SpelNotEmpty – collection/string/array non‑empty validation (maps to @NotEmpty). @SpelNotBlank – string non‑blank validation (maps to @NotBlank). @SpelNull – must be null validation (maps to @Null). @SpelSize – length/size validation for collections, strings, arrays (maps to @Size).
Each constraint annotation provides three default attributes: message: error message when validation fails. group: validation group, supports SpEL expressions. condition: activation condition, also a SpEL expression; validation runs only when the expression is empty or evaluates to true.
Enabling Spring Bean support
To use Spring beans inside SpEL expressions, add @EnableSpelValidatorBeanRegistrar to the application entry point:
@EnableSpelValidatorBeanRegistrar
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}Creating custom constraint annotations
Define a new annotation and mark it with @SpelConstraint, specifying the validator class.
Add the mandatory fields message, group, and condition to the annotation.
Implement the corresponding validator logic. See cn.sticki.validator.spel.SpelConstraint for a reference implementation.
Example Project
Minimal example repository: https://github.com/stick-i/spel-validator-example
Performance Notes
The current implementation relies heavily on reflection, which introduces some overhead. Future versions will add caching to mitigate the performance impact.
Observed IDE Behavior
When using SpEL in a project, IntelliJ IDEA 2024.1 only recognizes the condition attribute for annotation processing, while other @Language("SpEL") annotations are ignored. This may be an IDE bug.
Repository
GitHub address: https://github.com/stick-i/spel-validator
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Code Ape Tech Column
Former Ant Group P8 engineer, pure technologist, sharing full‑stack Java, job interview and career advice through a column. Site: java-family.cn
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
