Empresas
Empleos
  • Sobre nosotros
  • Soluciones
    • Publicación de vacantes
      Publica tu vacante y recibe candidatos calificados en 48h.
    • Evaluación de candidatos
      500+ pruebas técnicas y psicológicas, más anti-fraude.
    • Headhunting
      Búsqueda ejecutiva a la medida de principio a fin.
    • Nómina + EOR
      Dispersión de nómina y EOR en más de 15 países de LATAM.
  • Precios
  • Empleos

0

1K
Vistas
Springboot: Better handling of error messages

I'm developing an API with Spring Boot and currently, I'm thinking about how to handle error messages in an easily internationalizable way. My goals are as follows:

  1. Define error messages in resource files/bundles
  2. Connect constraint annotation with error messages (e.g., @Length) in a declarative fashion
  3. Error messages contain placeholders, such as {min}, that are replaced by the corresponding value from the annotation, if available, e.g., @Length(min = 5, message = msg) would result in something like msg.replace("{min}", annotation.min()).replace("{max}", annotation.max()).
  4. The JSON property path is also available as a placeholder and automatically inserted into the error message when a validation error occurs.
  5. A solution outside of an error handler is preferred, i.e., when the exceptions arrive in the error handler, they already contain the desired error messages.
  6. Error messages from a resource bundle are automatically registered as constants in Java.

Currently, I customized the methodArgumentNotValidHandler of my error handler class to read ObjectErrors from e.getBindingResult().getAllErrors() and then try to extract their arguments and error codes to decide which error message to choose from my resource bundle and format it accordingly. A rough sketch of my code looks as follows:

Input:

@Data
@RequiredArgsConstructor
public class RequestBody {
  @NotNull
  @NotBlank(message = ErrorConstants.NOT_BLANK)
  @Length(min = 5, max = 255, message = ErrorConstants.LENGTH_MIN_MAX) // LENGTH_MIN_MAX = validation.length.min-max
  private String greeting;
}

Error handler:

@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseStatus(HttpStatus.BAD_REQUEST)
ErrorMessage methodArgumentNotValidHandler(MethodArgumentNotValidException e) {
  ObjectError objectError = e.getBindingResult().getAllErrors().get(0);
  Object[] arguments = objectError.getArguments();
  String messageCode = objectError.getDefaultMessage(); // e.g., "validation.length.min-max" (key in resource bundle)
  ResourceBundle errMsgBundle = ResourceBundle.getBundle("errorMsg");
  String message;
  if (objectError.getCode().equals("Length")) {
    String messageTemplate = errMsgBundle.getString(messageCode);
    message = String.format(messageTemplate, arguments[2], arguments[1]);
  } else {
    message = "Bad input, but I cannot tell you the problem because the programmer hasn't handled this yet. Sorry :'(";
  }
  return new ErrorMessage(message);
}

Unfortunately, I suppose this approach is not maintainable. In the error handler, I will end up with a huge if-else block that has to probe several different situations (error codes, number of arguments, ...) and format error messages accordingly. Changing error messages will possibly result in having to change the code (e.g., the order of arguments). Each property key must be present as a constant in ErrorConstants, which I find undesirable. This code also doesn't query the name or path of the faulty property, e.g., "name".

Hence,

  1. is there a solution that can satisfy some or all of the above-mentioned requirements?
  2. At which place would I implement this?
  3. Is there at least a better solution to the above one?
  4. Are there recipes or patterns in SpringBoot to handle validation errors (I'm definitely not the first one thinking about this)?
over 4 years ago · Santiago Trujillo
2 Respuestas
Responde la pregunta

0

May be you can have

@ExceptionHandler(ConstraintViolationException.class)
protected ResponseEntity<Object> handleConstraintViolation(ConstraintViolationException e, WebRequest request){
   return Optional.ofNullable(e).map(ConstraintViolationException::getConstraintViolations).map(this::createException).orElseGet(this::generateGenericError);
}

From which you can have

private ErrorBody createException(FieldError fieldError) {
    return ErrorBody.builder()
            .code(fieldError.getCode())
            .message(fieldError.getDefaultMessage())
            .field(fieldError.getField())
            .value(fieldError.getRejectedValue())
            .build();
}

So that you can use

fieldError.getCode()

for mapping key value from properties file

over 4 years ago · Santiago Trujillo Denunciar

0

I'm not a big fan of javax.validation annotations. Mostly because objects whose classes are annotated with these cannot be unit tested easily.

What I recommend is registering a org.springframework.validation.Validator implementation in your @RestController annotated handler class as follows:

@InitBinder
void initBinder(WebDataBinder binder) {
    if (binder.getTarget() == null) {
        return;
    }
    final var validator1 = // your validator1 instance

    //check if specific validator is eligible to validate request and its body
    if (validator1.supports(binget.getTarget().getClass()) {
        binder.setValidator(validator);
    }
}

After such registration, Spring invokes such validator for matching request and it's body and throws MethodArgumentNotValidException if validator rejected any of the given object fields.

In your exception handler annotated with @ControllerAdvice (keep in mind that it's scope is only for http requests) you can handle such exception as follows:

@ExceptionHandler(MethodArgumentNotValidException.class)
@ResponseBody
ErrorMessage handleMethodArgumentNotValidException(MethodArgumentNotValidException e) {
    final var errors = e.getAllErrors()

    return new ErrorMessage(/* populate your error message based on given errors */);
}

While validator implementation could have looked like that:

@Override
public void validate(Object target, Errors errors) {
    final var credentials = (Credentials) target;

    //reject username field with given c000 code if it's null
    if (isNull(credentials.getUsername())) {
        errors.rejectValue("username", "c000", "username cannot be null");
        return;
    }

    if (credentials.getUsername().trim().isEmpty()) {
        errors.rejectValue("username", "c001", "username cannot be empty");
        return;
    }

    if (credentials.getUsername().length() > 256) {
        errors.rejectValue("username", "c002", "username cannot be longer than 256 characters");
        return;
    }
}

The advantage of such solution is:

  • you can unit-test such validator without setting up application's context - which is fast
  • when validator rejects reuqest body it(actually you provide that ;)) provides an error code and message so you can map it directly to your ErrorMessage response without digging any further.
  • you externalise validation logic to dedicated class - that corresponds to S in SOLID ([S]ingle object responsibility principle), which is desired by some developers

If you have any questions or doubts - just ask.

over 4 years ago · Santiago Trujillo Denunciar
Responde la pregunta
Encuentra empleos remotos

¡Descubre la nueva forma de encontrar empleo!

Top de empleos
Top categorías de empleo
Empresas
Publicar vacante Precios Comercial
Legal
Términos y condiciones Política de privacidad
© 2026 PeakU Inc. All Rights Reserved.
Andres GPT
Recomiéndame algunas ofertas
Necesito ayuda