Play 2.6中JSR-303与Validatable验证执行顺序问题咨询
Got it, let's figure out how to fix this. Play 2.6 changed the default validation execution behavior—instead of running your annotation-based checks (like @Constraints.Required) first and then your Validatable logic sequentially, it now runs all validators in parallel. That’s why your validate() method is firing even when annotation rules fail, forcing you to add extra null checks and workarounds. Here are two reliable ways to get back the old sequential flow:
Option 1: Manually trigger bean validation inside your Validatable class
This keeps your controller code clean, but requires updating each Validatable implementation to check annotation-based rules first. Here's how to implement it:
import play.data.validation.ValidationError; import javax.validation.Validation; import javax.validation.Validator; import javax.validation.ValidatorFactory; import java.util.ArrayList; import java.util.List; import java.util.Set; public class YourForm implements Validatable<List<ValidationError>> { @Constraints.Required private String requiredField; @Override public List<ValidationError> validate() { List<ValidationError> errors = new ArrayList<>(); // Step 1: Run annotation-based bean validation first ValidatorFactory factory = Validation.buildDefaultValidatorFactory(); Validator validator = factory.getValidator(); Set<javax.validation.ConstraintViolation<YourForm>> violations = validator.validate(this); if (!violations.isEmpty()) { // Convert bean validation errors to Play's ValidationError format for (javax.validation.ConstraintViolation<YourForm> violation : violations) { errors.add(new ValidationError( violation.getPropertyPath().toString(), violation.getMessage() )); } // Exit early if annotation checks fail—skip custom logic entirely return errors; } // Step 2: Run your custom validation logic only if annotations pass if (customValidationFails(requiredField)) { errors.add(new ValidationError("requiredField", "Custom error message")); } return errors; } private boolean customValidationFails(String fieldValue) { // Replace with your actual custom validation logic return fieldValue.length() < 5; } }
Option 2: Split validation steps in your controller
If you don't want to modify every Validatable class, adjust your controller code to run annotation validation first, then trigger the Validatable logic manually:
import play.data.Form; import play.data.FormFactory; import play.mvc.Controller; import play.mvc.Result; import javax.inject.Inject; import java.util.List; public class YourController extends Controller { private final FormFactory formFactory; @Inject public YourController(FormFactory formFactory) { this.formFactory = formFactory; } public Result submitForm() { Form<YourForm> form = formFactory.form(YourForm.class).bindFromRequest(); // First: Check if annotation-based validation failed if (form.hasErrors()) { return badRequest(form.errorsAsJson()); } // Second: Run custom Validatable logic only if annotations passed YourForm formData = form.get(); List<ValidationError> customErrors = formData.validate(); if (!customErrors.isEmpty()) { // Attach custom errors to the form and return a bad request form = form.withErrors(customErrors); return badRequest(form.errorsAsJson()); } // All validation passed—proceed with your business logic return ok("Form submitted successfully!"); } }
Why this works
Play 2.6 switched to parallel validation to boost performance, but this broke the sequential dependency your code relied on. Both options above enforce the original order: annotation checks complete first, and your custom Validatable logic only runs if those checks pass. Pick Option 1 if you want validation logic encapsulated in the form class, or Option 2 if you prefer keeping form classes clean and handling the order in controllers.
内容的提问来源于stack exchange,提问作者Florian Schaetz

