javaspringvalidation

Spring validation with jakarta @AssertTrue returns some very long message with too much info


I use jakarta's @AssertTrue on my object when I save it on the page to check if the phone number matches the mask:

@AssertTrue(message = "{validation.phone.incorrect}")
public boolean isCorrectPhone() {
    String phoneRegex = "^(\\+\\d{1,3}( )?)?((\\(\\d{1,3}\\))|\\d{1,3})[- .]?\\d{3,4}[- .]?\\d{4}$";
    return phone == null || phone.matches(phoneRegex);
}

The problem is that when the phone has incorrect format I get not the message that I expect, but a very detailed one with packages and classes used. And the message that I need is available only at the end of this message.

My expected message is: "Incorrect phone number".

What I get in exception looks like this:

Validation failed for argument [0] in public void com.my.user.UserController.editUser(com.my.entity.user.User): [Field error in object 'user' on field 'correctPhone': rejected value [false]; codes [AssertTrue.user.correctPhone,AssertTrue.correctPhone,AssertTrue]; arguments [org.springframework.context.support.DefaultMessageSourceResolvable: codes [user.correctPhone,correctPhone]; arguments []; default message [correctPhone]]; default message [Incorrect phone number]]

.getCause() on the exception returns null, so there's no any nested exceptions with cleaner message. How do I get the message I want without parsing?


Solution

  • The problem was partly with our custom exception handlers one of which intercepted MethodArgumentNotValidException and wrapped it into Error (our custom, not java.lang) like this:

    return new Error(ErrorCode.NOT_VALID, ex.getMessage());
    

    So, this was the message I got. But as I said before I examined the exception there and saw that it doesn't have any root causes with clean message. I actually even tried to remove this handler at all to get a response like first snippet here but got even worse result:

    {
        "timestamp": "2025-03-07T09:05:40.792+00:00",
        "status": 400,
        "error": "Bad Request",
        "path": "/api/users/edit"
    }
    

    No message at all! Just some generic error. I double checked: no other exception handlers intercepted this exception, so it was "clean" output.

    But then I found out that actually MethodArgumentNotValidException has a method getFieldErrors() (I don't know why I didn't check for all available methods beforehand). This method contains a list of errors for all fields that are marked with either validation annotation. So, all I had to do is to loop through all field errors and get what I need, smth like:

    return ex.getFieldErrors().stream()
            .map(error -> new FieldValidationError(error.getField(), error.getDefaultMessage(), error.getRejectedValue()))
            .toList();