Нажмите "Enter" для перехода к содержанию

Validation of @PathVariable in Spring MVC

Problem

One can’t use validation annotations on @PathVariable annotations with i.e. @Pattern annotation like this:

@RequestMapping(value = "/bank/{id}", method = RequestMethod.GET)
public ResponseEntity method_name(
    @Pattern(regexp = "\\d{9}", message = "Bank shoud be identified by exactly 9 digits")
    @PathVariable String id) {
    /// Some code
}

Solution

For validation to work we should make a few things

Declare method validation bean

In one of your @Configuration classes you should declare @Bean of type MethodValidationPostProcessor

@Bean
public MethodValidationPostProcessor methodValidationPostProcessor() {
         return new MethodValidationPostProcessor();
}

MethodValidationPostProcessor:

A convenient BeanPostProcessor implementation that delegates to a JSR-303 provider for performing method-level validation on annotated methods.

Target classes with such annotated methods need to be annotated with Spring’s Validated annotation at the type level, for their methods to be searched for inline constraint annotations. Validation groups can be specified through @Validated as well. By default, JSR-303 will validate against its default group only.

Create exception handler

Declare @ControllerAdvice-annotated class with @ExceptionHandler-annotated method inside

@ControllerAdvice
class GlobalExceptionHandler {
    @ExceptionHandler(value = { ConstraintViolationException.class })
    @ResponseStatus(value = HttpStatus.BAD_REQUEST)
    public String handle(ConstraintViolationException e) {
         Set<ConstraintViolation<?>> violations = e.getConstraintViolations();
         StringBuilder strBuilder = new StringBuilder();
         for (ConstraintViolation<?> violation : violations ) {
              strBuilder.append(violation.getMessage() + "\n");
         }
         return strBuilder.toString();
    }
}

Several important lines:

  • 1: @ControllerAdvice annotation on class
  • 3: declaration of `@ExceptionHandler` for `ConstraintViolationException`
  • 4: declaration of status, which will be returned (as usual it’s considered to be a good practice to return `HttpStatus.BAD_REQUEST` on requests with wrong attributes)
  • 5: declaration of method, capable of handling  `ConstraintViolationException`

Add validation annotation on controller

@Validated
@RestController
class BankController {
    @RequestMapping(value = "/bank/{id}", method = RequestMethod.GET)
    public ResponseEntity<BankInfo> bankInfo(
       @Pattern(regexp = "\\d{9}", message = "Bank shoud be identified by exactly 9 digits")
       @PathVariable String id) {
     /// Some code
    }
}

Add @Validated annotation to your controller class for PostProcessor to be able to handle requests to your method.

That’s all folks

комментариев 12

  1. Сергей Калмыков Сергей Калмыков

    Вроде же http://docs.spring.io/spring/docs/current/spring-framework-reference/html/mvc.html#mvc-ann-requestmapping-uri-templates-regex
    И вообще, зачем нужна валидация частей урлов с сообщениями? Урлы не очень подходят для передачи сложной информации, которую необходимо валидировать. Или это для тех, кто хочет userID = «123/account/money»?

    • asm0dey asm0dey

      А потому что если запрос будет не соответствовать паттерну — тебе вывалится 404 ошибка — метод не найден.
      То есть для запроса по БИКу если ты выставишь паттерн `\d{9}` и обратишься по `12345678` — получишь 404. И обойти это нельзя

      • Сергей Калмыков Сергей Калмыков

        Я к тому, что по канону кроме ID в path вообще ничего не должно быть переменного, а ID’шников с сложными валидационными правилами тоже не должно быть.
        Типа, /bank?bik=123456789. А там уже обвалидируйся, + спринг из коробки будет поддерживать.

        • asm0dey asm0dey

          Ну а как ты предлагаешь валидировать запрос информации о банке по БИКу? В принципе-то PathVariable читается лучше чем QueryParam. БИК можно было бы считать идентификатором банка, но валидировать его ткаи надо чтобы не гонять данные туда-сюда лишний раз.

        • asm0dey asm0dey

          Ну и в REST лучше выглядит когда у тебя id — это часть пути. И ИМХО это дикий баг что @PathVariable валидировать нельзя. Не вижу никаких причин почему это надо запрещать. Может я, например, не хочу в БД ходить чтобы убедиться что отрицательных id не бывает.

  2. Marcin Kosmowski Marcin Kosmowski

    Thanks for good idea, but still after unsuccessful validation (when the method parameter was wrong) I’m getting Spring’s default message, not the one I specified in @Pattern annotation. Exception handler works as expected and returns my message, but it somehow gets «lost» after it:

    @RequestMapping(
    value = «/{countryName}»,
    produces = MediaType.APPLICATION_JSON_VALUE,
    method = RequestMethod.GET)
    public ResponseEntity getInfo(
    @Pattern(regexp = «[a-zA-Z+]+», flags = Pattern.Flag.CASE_INSENSITIVE, message = «Country name is invalid.») @PathVariable String countryName) {
    // objectFound here or not
    return (objectFound == null)
    ? new ResponseEntity(HttpStatus.NOT_FOUND)
    : ResponseEntity.ok(objectFound);
    );
    }
    }

    And the message I’m getting:

    {
    «timestamp»: 1500652041032,
    «status»: 404,
    «error»: «Not Found»,
    «exception»: «javax.validation.ConstraintViolationException»,
    «message»: «No message available»,
    «path»: «/123»
    }

    Do you have any idea why it works like this?

    • asm0dey asm0dey

      Sorry, but no. I have used patterns in mappings themselves, but it generates the same 404 error. I think there is no _correct_ way to workaround this, but you always can fall back to injecting Validator into you bean and using it.

      • Marcin Kosmowski Marcin Kosmowski

        It turned out that it’s sufficient to add ‘reason’ attribute to exception handler’s @ResponseStatus annotation, remove the ‘message’ from the controller’s method and it started to work 🙂

        @ResponseStatus(value = HttpStatus.BAD_REQUEST, reason = «Bad request parameters were given. No numbers and/or punctuation in geographic names, please.»)

        {
        «timestamp»: 1500656751192,
        «status»: 400,
        «error»: «Bad Request»,
        «exception»: «javax.validation.ConstraintViolationException»,
        «message»: «Bad request parameters were given. No numbers and/or punctuation in geographic names, please.»,
        «path»: «/123»
        }

        Thanks!

        • asm0dey asm0dey

          Thank you for information! Looks pretty cool!

    • asm0dey asm0dey

      Another dirty hack is to use @Valid-annotated object as you request param, which contains only one field with @Pattern validation

  3. karthik karthik

    response status is coming as 404 instead of 400

    • asm0dey asm0dey

      Does comment below works for you?

Добавить комментарий для Marcin Kosmowski Отменить ответ

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.