Validação dos campos de entrada da API com Spring Validation

Validação dos campos de entrada da API com Spring Validation

Table of contents

No heading

No headings in the article.

Olá pessoal,

Hoje vamos adotar mais uma boa prática no nosso projeto: a validação dos campos de entrada enviados para a nossa API.

Essa validação tem como objetivo garantir que as informações recebidas estejam corretas e coerentes com o objetivo da aplicação. Além disso, ela reduz e elimina as validações manuais que são recomendadas antes de salvar os dados no banco.

Outro ponto importante é que essa validação pode evitar erros de segurança, como a injeção de comandos no banco de dados ou a execução de scripts JavaScript maliciosos (por exemplo, ataques XSS). Também ajuda a prevenir ataques de negação de serviço (DoS), onde um usuário mal-intencionado pode enviar um volume excessivo de dados, consumindo muita memória e travando a API.

O primeiro passo é adicionar a dependência spring-boot-starter-validation no seu arquivo pom dentro de dependencies. O Spring Validation é um "starter" do Spring Boot que simplifica e traz automaticamente as dependências necessárias para validar nossos objetos de entrada (DTOs).

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-validation</artifactId>
        </dependency>

O segundo passo é ajustar a classe SeriesController com a anotação @Valid, conforme exemplo abaixo:

    @PatchMapping(path = "/{id}", consumes = APPLICATION_JSON, produces = APPLICATION_JSON)
    public ResponseEntity<SeriesResponseDTO> updateSeries(@PathVariable String id,
                                                          @RequestBody @Valid SeriesUpdateDTO updatedSeriesDTO) {
        log.info("Received request to update series with ID: {}", id);
        return ResponseEntity.ok().body(seriesService.updateSeries(id, updatedSeriesDTO));
    }Segue abaixo as anotações que foram implementadas no projeto. Para clonar o projeto, basta acessar aqui.
  • @NotBlank

    Garante que o valor do campo não seja nulo, vazio ou composto apenas por espaços em branco.

    Não pode ser utilizado com tipos números.

  • @NotEmpty

    Pode ser aplicada em Strings, coleções (List, Set), mapas (Map) e arrays. Seu objetivo é garantir que o objeto não esteja nulo e que contenha pelo menos um elemento (ou caractere, no caso de uma String). Também não é indicada para tipos numéricos.

  • @NotNull

    Pode ser aplicada a qualquer tipo de objeto para assegurar que o valor não seja nulo. Ela é bastante genérica e não impõe restrições quanto ao conteúdo ou tamanho, apenas verifica a nulidade.

  • @Size

    Utilizada para restringir o tamanho (comprimento ou número de elementos) de Strings, coleções, mapas e arrays. Não faz sentido aplicar @Size a tipos numéricos diretamente, pois sua finalidade é medir a "quantidade" de elementos ou caracteres, e não o valor numérico.

  • @Min e @Max

    Utilizada para validar o valor mínimo e máximo permitido para o campo. Pode ser utilizado em campos numéricos.

  • @Valid

    Anotação usada para validar objetos dentro do objeto principal (objetos aninhados).

  • @AssertTrue

    Indicado para propriedades booleanas (tanto o primitivo boolean quanto o wrapper Boolean). Ela exige que o valor seja true, sendo útil para validar condições que devem ser verdadeiras.

    Observe como as classes ficaram:

      @Data
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      public class SeriesCreateDTO {
    
          @NotBlank(message = "Series name is required")
          @Size(min = 3, max = 100, message = "Series name must be between 3 and 100 characters")
          private String name;
    
          @NotEmpty(message = "At least one genre is required")
          @Size(max = 3, message = "Maximum of 3 genres allowed")
          private List<SeriesGenre> genres;
    
          @Min(value = 1900, message = "Release year must be at least 1900")
          private int releaseYear;
    
          @NotEmpty(message = "At least one season is required")
          @Size(max = 50, message = "Maximum of 50 seasons allowed")
          @Valid
          private List<SeasonDTO> seasons;
    
          @NotNull(message = "Completion status is required")
          private Boolean completed;
    
          @AssertTrue(message = "Release year cannot be in the future")
          public boolean isInvalidReleaseYear() {
              return releaseYear <= Year.now().getValue();
          }
      }
    
      @Data
      @Builder
      @NoArgsConstructor
      @AllArgsConstructor
      public class SeasonDTO {
    
          @Positive(message = "Season number must be positive")
          @Max(value = 50, message = "Season number cannot exceed 50")
          private int seasonNumber;
    
          @Positive(message = "Number of episodes must be positive")
          @Max(value = 100, message = "Number of episodes cannot exceed 100")
          private int episodes;
      }
    

    Segue outras anotações que você pode utilizar em sua API.

  • @Email

    Útil para garantir que uma String tenha o formato de um endereço de e-mail.

  • @Pattern

    Permite validar se o valor de uma String atende a um padrão definido por uma expressão regular. Isso é muito usado para formatos específicos, como CPF, CNPJ, telefones, etc.

  • @Past, @Future, @PastOrPresent e @FutureOrPresent

    Essas anotações são comuns para campos que representam datas ou instantes, garantindo que o valor seja, respectivamente, uma data passada, futura ou presente.

  • @Positive (ou @PositiveOrZero) e @Negative (ou @PositiveOrZero)

    Quando se trabalha com números, você pode garantir que o valor seja positivo ou negativo.

Lembrando que em caso de envio de campos no formato incorreto ou que exceda os limites definidos, pode ser lançado dois tipos de exceções: ConstraintViolationException ou MethodArgumentNotValidException.
Para manter um retorno de erro padronizado, utilize a classe anotado com o @RestControllerAdvice para realizar o tratamento. Para consultar um exemplo de implementação, consulte aqui.


Ficou com alguma dúvida? Deixe nos comentários.
Até a próxima 😊