Answer
The BindException implements Spring’s org.springframework.validation.Errors interface. Calling getFieldErrors() (or getFieldError(String field)) returns FieldError objects whose codes[] array contains the constraint‑based error codes (e.g., "blank", "size.min", "range") that are resolved before any message‑source lookup, making them independent of the user’s locale.
Confirmed facts
- The
Errors API (getFieldErrors(), getFieldError()) is identical in Grails 3.x and 4.x.
- Each
FieldError provides:
field – the name of the bound property.
codes[] – ordered list of error codes (constraint name first, then fallback codes).
arguments[] – values used by the constraint (e.g., min/max for size).
rejectedValue – the value that failed binding.
FieldError implements java.io.Serializable, so the object can be serialized, but it holds references to the target bean and may include large object graphs.
Likely explanation of version differences
In Grails 3.x, each constraint violation on a field creates a separate FieldError entry. Grails 4.x (using Hibernate Validator 6 and Spring Boot’s MethodValidationPostProcessor) may aggregate multiple violations into a single FieldError when constraint composition is used, which can cause getFieldErrors().size() to differ between versions while the codes[] content for each violation remains the same.
Steps to obtain locale‑independent error information
- Catch the
BindException in your controller or service:
try {
// grails data binding & validation happens here
} catch (BindException be) {
// proceed with error extraction
}
- Retrieve the field errors:
List<FieldError> fieldErrors = be.getFieldErrors();
- Build a lightweight map (or DTO) containing only the data you need for the client:
Map<String, List<String>> errorMap = new HashMap<>();
for (FieldError fe : fieldErrors) {
errorMap.put(fe.getField(), Arrays.asList(fe.getCodes()));
}
// optionally add arguments:
// errorMap.put(fe.getField() + "\.args", Arrays.asList(fe.getArguments()));
return errorMap;
- Serialize the map as JSON and send it to the client (e.g., via
render JSON or a REST response).
Serializability note
While you could serialize the FieldError objects directly, doing so may expose internal domain state and produce larger payloads. The recommended approach is to extract the field name, error codes, and (if needed) constraint arguments into a simple DTO or map before serialization.
Do you need to include constraint arguments (e.g., min/max values) in the client payload? Let me know if that changes the recommendation.