Spring Webflux URI Validator


Spring Framework Validated Is designed for convenient use with Spring’s JSR-303 and allows to express and validate application constraints, in this technical post we will see how to validate a path variable using Spring Validated annotation in a Spring Webflux application, if you want to know more about how to create Spring Webflux please go to my previous post getting started with Spring Webflux here. Then, let’s create a new Spring Boot project with Webflux and Lombok as dependencies:

spring init --dependencies=webflux,lombok --language=java --build=gradle spring-webflux-uri-validator

Here is the complete build.gradle file generated:

plugins {
  id 'org.springframework.boot' version '2.1.9.RELEASE'
  id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'com.jos.dem.springboot.uri.encoding'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.11'

configurations {
  compileOnly {
    extendsFrom annotationProcessor
  }
}

repositories {
	mavenCentral()
}

dependencies {
  implementation 'org.springframework.boot:spring-boot-starter-webflux'
  compileOnly 'org.projectlombok:lombok'
  annotationProcessor 'org.projectlombok:lombok'
  testImplementation 'org.springframework.boot:spring-boot-starter-test'
  testImplementation 'io.projectreactor:reactor-test'
}

Now add Junit 5 and Apache commons lang dependencies to your build.gradle file:

implementation('org.apache.commons:commons-lang3:3.8.1')
testImplementation "org.junit.jupiter:junit-jupiter-api:$junitJupiterVersion"
testRuntime "org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion"

Let’s start by creating a service to send a request to the controller

package com.jos.dem.springboot.uri.validator.service;

import reactor.core.publisher.Mono;

public interface WebClientService {

  Mono<String> send(String path);

}

This is the service implementation

package com.jos.dem.springboot.uri.validator.service.impl;

import reactor.core.publisher.Mono;

import org.springframework.stereotype.Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.reactive.function.client.WebClient;

import com.jos.dem.springboot.uri.validator.service.WebClientService;

@Service
public class WebClientServiceImpl implements WebClientService {

  @Autowired
  private WebClient webClient;

  public Mono<String> send(String path) {
    return webClient.get()
      .uri(path)
      .retrieve()
    .bodyToMono(String.class);
  }

}

This service implementation is receiving path as a variable and send it to a controller for validation.

package com.jos.dem.springboot.uri.validator.controller;

import reactor.core.publisher.Mono;

import org.apache.commons.lang3.builder.ToStringBuilder;

import javax.validation.constraints.Pattern;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.validation.annotation.Validated;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Validated
@RestController
public class ValidatorController {

  private Logger log = LoggerFactory.getLogger(this.getClass());

  @GetMapping("/{mnemonic}")
  public Mono<String> index(
      @PathVariable
        @Pattern(
          regexp = "^\\d{4}[-]\\d{2}[-]\\d{2}[-]\\d{6}$",
          message = "Invalid mnemonic format") String mnemonic) {
    log.info("INDEX: Mneminc is valid");
    return Mono.just("valid");
  }

}

Where:

  • regexp Is a regular expresion to validate this format: YYYY-MM-DD- plus six digits, if you want to know more about regular expression in Java, please go here
  • @PathVariable We are reading mnemonic as path variable
  • @Validated With this annotation we are enabling Hibernate validator

WebClient is an bean instance and it is defined in our validator Spring application

package com.jos.dem.springboot.uri.validator;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.reactive.function.client.WebClient;

@SpringBootApplication
public class UriValidatorApplication {

  public static void main(String[] args) {
    SpringApplication.run(UriValidatorApplication.class, args);
  }

  @Bean
  WebClient webClient() {
    return WebClient.create("http://localhost:8080");
  }

}

Finally, let’s create a test case to validate our mnemonic value

package com.jos.dem.springboot.uri.validator;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Date;
import java.time.LocalTime;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.beans.factory.annotation.Autowired;

import com.jos.dem.springboot.uri.validator.service.WebClientService;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@RunWith(SpringRunner.class)
@SpringBootTest
public class ValidatorTest {

  @Autowired
  private WebClientService service;

  private Logger log = LoggerFactory.getLogger(this.getClass());

  @Test
  public void shouldValidateMnemonic() throws Exception {
    log.info("Running: Validating mnemonic at {}", new Date());

    String path = "2019-05-19-888123";
    assertEquals("valid", service.send(path).block());
  }

}

If you want to see the error description when you are sending an invalid mnemonic, add an exception handler to the validator controller

package com.jos.dem.springboot.uri.validator.controller;

import reactor.core.publisher.Mono;

import org.apache.commons.lang3.builder.ToStringBuilder;

import javax.validation.constraints.Pattern;
import javax.validation.ConstraintViolationException;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.validation.annotation.Validated;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@Validated
@RestController
public class ValidatorController {

  private Logger log = LoggerFactory.getLogger(this.getClass());

  @GetMapping("/{mnemonic}")
  public Mono<String> index(
      @PathVariable
        @Pattern(
          regexp = "^\\d{4}[-]\\d{2}[-]\\d{2}[-]\\d{6}$",
          message = "Invalid mnemonic format") String mnemonic) {
    log.info("INDEX: Mneminc is valid");
    return Mono.just("valid");
  }

  @ExceptionHandler(ConstraintViolationException.class)
  Mono<String> handleException(ConstraintViolationException cve){
    log.info("Violations: " + ToStringBuilder.reflectionToString(cve.getConstraintViolations()));
    cve.getConstraintViolations().forEach(v -> log.info(ToStringBuilder.reflectionToString(v)));
    return Mono.just(cve.getMessage());
  }

}

To run the project:

gradle bootRun

To test the project:

gradle test

To browse the project go here, to download the project:

git clone git@github.com:josdem/spring-webflux-uri-validator.git

Return to the main article