Skip to main content

JSR380 Validation Specification

Introduction

Business framework supports JSR380 validation specification (backed by hibernate-validator), which makes business code cleaner.

Without a validation framework, a common approach is writing many if else checks in business code, which causes messy logic. Below we compare two examples to show JSR380 advantages: one with JSR380 and one without.

Example Source Code

see https://github.com/iohao/ionet-examples

path : ionet-cookbook-code

  • Jsr380Action

Example Without Validation

First, look at an example without JSR380. In this example, we validate email and age fields, including email format and minimum age.

message values:

message.email="a"
message.age = 1;

As shown, even with only two fields, there is already a lot of code. If more fields or validation rules are added, code grows quickly, and most manual validation code is repetitive.

  • code 5~10: validate email.
  • code 12~13: validate age.
@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(ValidMessage message) {
String email = message.email;
ErrorCode.emailChecked.assertNullThrows(email);

Pattern pattern = Pattern.compile("[a-zA-Z0-9_-]+@\\w+\\.[a-z]+(\\.[a-z]+)?");
var result = pattern.matcher(email).find()
ErrorCode.emailChecked.assertTrue(result);

int age = message.age;
ErrorCode.ageChecked.assertTrueThrows(age < 2);

// Your biz code
...
}
}

@ToString
@ProtobufClass
public class ValidMessage {
public String email;
public int age;
}

@Getter
@FieldDefaults(level = AccessLevel.PRIVATE)
public enum ErrorCode implements ErrorInformation {
emailChecked(100, "Email error."),
ageChecked(101, "Age error."),
;

final int code;
final String message;

ErrorCode(int code, String message) {
this.code = code;
this.message = message;
}
}
tip

JSR380 already provides a unified solution for these validation issues. Most frameworks support this specification, and so does this business framework.

How to Install

JSR380 is an on-demand feature, so add the dependencies below in your Maven project.

pom.xml
<!-- https://mvnrepository.com/artifact/jakarta.validation/jakarta.validation-api -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
<version>3.1.1</version>
</dependency>

<!-- hibernate validator -->
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
<version>9.0.1.Final</version>
</dependency>

<!-- EL implementation. In a Java SE environment, you must add the implementation as a dependency to your POM file -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.el</artifactId>
<version>4.0.2</version>
</dependency>

How to Use

Enable validation

class OneApplication {
static void main() {
CoreGlobalConfig.setting.validator = true;
...
new RunOne()
...
.startup();
}
}

We add validation annotations to ValidMessage, which simplifies validation.

@ToString
@ProtobufClass
public class ValidMessage {
@NotNull
@Email
public String email;

@Min(value = 2, message = "Age error")
public int age;

}

action example

In the same example, we validate email and age fields, including email format and minimum age.

tip

We add @Valid to method parameter to tell framework this parameter should be validated.


@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(@Valid ValidMessage message) {
// Your biz code
...
}
}

No validation logic appears in business body (clean business body), because validation logic and error-code response are handled by framework. This code achieves the same validation effect as the previous non-JSR380 example. Besides equivalent validation result, framework also applies a small optimization: if parameter validation fails, action is not executed.

Framework combines JSR380 and exception mechanism so developers can focus on real business logic.

Below, email and age are tested separately. When validation fails, framework returns error code and error message to requester.

Console output

┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:6468190] ━━━━
┣ userId: 1378604058
┣ RequestParam: ValidMessage(email=luoyizhu@gmail.com, age=1)
┣ ErrorCode: -1001
┣ ErrorMessage: age Age error
┣ ExecutionTime: 0 ms
┗━━ [ionet:25.1] ━━ [Thread:User-8-3] ━━ [ConnectionWay:WebSocket] ━━ [traceId:1761984090278] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

┏━━ Error.(Jsr380Action.java:1) ━━ [ValidMessage verifyData(ValidMessage message)] ━━ [8-1] ━━ [tag:HallLogicServer, serverId:6468190] ━━━━
┣ userId: 1378604058
┣ RequestParam: ValidMessage(email=luoyizhu, age=99)
┣ ErrorCode: -1001
┣ ErrorMessage: email must be a well-formed email address
┣ ExecutionTime: 0 ms
┗━━ [ionet:25.1] ━━ [Thread:User-8-3] ━━ [ConnectionWay:WebSocket] ━━ [traceId:1761984090279] ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

Auto Verification

By default, we manually add @Valid to indicate parameter validation. Framework also provides auto-validation setting, so you don't need to configure it on every parameter.

@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(@Valid ValidMessage message) {
// Your biz code
...
}
}

After enabling auto validation, @Valid is not required on parameters.

class OneApplication {
static void main() {
CoreGlobalConfig.setting.validator = true;
CoreGlobalConfig.setting.validatorAutoCall = true;
...
new RunOne()
...
.startup();
}
}

@ActionController(Jsr380Cmd.cmd)
public class Jsr380Action {
@ActionMethod(Jsr380Cmd.verifyData)
private ValidMessage verifyData(ValidMessage message) {
// Your biz code
...
}
}

How to choose?

Personally, I prefer manual annotation because semantics are clearer. Auto verification is mainly provided for compatibility with ioGame code.