1

I am struggling with tailoring javax.validation.ConstraintValidator and javax.validation.ConstraintValidatorContext to my needs. The response message that I am receiving from a malformed request body always takes this shape:

  • <controller method name>.<input parameter>: <default message>, <controller method name>.<input parameter>: <specific validation message>

This message also is returned as a 500, rather than a 400 Bad Request. I have not been able to get a working to solution to do the following:

  1. Only include the <specific validation message>, i.e. exclude the method + input names
  2. Exclude the default message
  3. Change 500 to 400 always, or potentially allow for custom Error responses

I have the following code:

Controller

import org.path.validation.ValidCreateThingRequest;
import org.path.service.ThingService;
// ... various other imports

  @PostMapping("/things")
  @ApiOperation(value = "Create a new thing")
  @ApiResponse(code = 201, message = "Newly created thing", response = Thing.class)
  @ResponseStatus(HttpStatus.CREATED)
  public ThingResponseProto createThing(
      @RequestBody @ValidCreateThingRequest final CreateThingRequestProto thingDto,
      final HttpServletRequest httpServletRequest) {
    final Context context = new RequestContext(httpServletRequest);
    final Thing createdThing = thingService.createThing(thingDto);
    return mapObjToProtoUtils.map(createdThing, ThingResponseProto.class);
  }

Interface to create validation interface

@Constraint(validatedBy = CreateThingRequestValidator.class)
@Target({ METHOD,PARAMETER })
@Retention(RUNTIME)
@Documented
public @interface ValidCreateThingRequest {
    String message() default "Request must be well-formed.";

    Class<?>[] groups() default {};

    Class<? extends Payload>[] payload() default {};
}

Validator used in interface

public class CreateCodeRequestValidator
    implements ConstraintValidator<ValidCreateThingRequest, CreateThingRequestProto> {

  private static final Pattern FORBIDDEN_NAME_CHARACTERS_REGEX =
      Pattern.compile("[\\\\/\\t\\n\\r\\f?;]");

  @Override
  public void initialize(ValidCreateCodeRequest constraintAnnotation) {};

  @Override
  public boolean isValid(final CreateThingRequestProto thingDto, ConstraintValidatorContext context) {
    return isValidCharacters(thingDto, context)
        && isValidNameExists(thingDto, context)
  }

  boolean isValidCharacters(final CreateThingRequestProto thingDto, ConstraintValidatorContext context) {
    final String name = thingDto.getName();

    if (FORBIDDEN_NAME_CHARACTERS_REGEX.matcher(name).find()) {
      context
          .buildConstraintViolationWithTemplate("Name must not contain forbidden characters.")
          .addConstraintViolation();
      return false;
    } else {
      return true;
    }
  }

  boolean isValidNameExists(final CreateThingRequestProto thingDto, ConstraintValidatorContext context) {
    final String name = thingDto.getName();

    if (name != null && !name.trim().isEmpty()) {
      context
          .buildConstraintViolationWithTemplate("Name must not be null or empty.")
          .addConstraintViolation();
      return false;
    } else {
      return true;
    }
  }
}

Sending a malformed payload to the code above would result in a message that looks like this:

{
  error: "Internal Server Error",
  message: "createThing.thingDto: Request must be well-formed., createThing.thingDto: Name must not be null or empty.",
  path: "/things"
  status: 500
  timestamp: 1607110364124
}

I would love to be able to receive this instead:

{
  error: "Bad Request",
  message: "Name must not be null or empty.",
  path: "/things"
  status: 400
  timestamp: 1607110364124
}

Is this even possible based on buildConstraintViolationWithTemplate()??

Torc
  • 1,148
  • 6
  • 20
  • 43
  • 1
    If anyone else is searching for an answer regarding the custom ConstraintValidator message part, I found https://stackoverflow.com/questions/6048556/can-you-change-an-annotation-message-at-run-time thread to be useful and was able to return any message I wanted. – lisymcaydnlb Oct 03 '21 at 14:06

1 Answers1

0

Maybe you can use @ControllerAdvice annotation like

@ControllerAdvice
public class ConstraintValidatorExceptionHandler {

    @ExceptionHandler(ConstraintViolationException.class)
    public void handleConstraintViolationException(ConstraintViolationException exception,
            ServletWebRequest webRequest) throws IOException {
        webRequest.getResponse().sendError(HttpStatus.BAD_REQUEST.value(), exception.getMessage());
    }
}