Spring Webflux - Custom HandlerMethodArgumentResolver Examples

This tutorial shows you how to create a custom argument resolver in Spring WebFlux using SyncHandlerMethodArgumentResolver.

If you create an endpoint using Spring Boot and you want to store the values from a request to an object, there are various ways to do it. For example, you can create a class and add it as a parameter of the controller method, then Spring will inject the values from query parameters to an instance of the class. If the values come from the request body, just add @RequestBody annotation. The @RequestHeader annotation can be used if you want to get a value from the request header. What if you want to combine the values from the request params, body, headers, or cookies to an object. A possible solution is by creating a class that implements HandlerMethodArgumentResolver.

You may already know the Pageable interface in Spring. If you add certain query parameters in the request such as sort and size, the values will be injected to a Pageable instance. The resolver of the Pageable interface also implements HandlerMethodArgumentResolver. In this tutorial, I am going to explain how to create a custom argument resolver in Spring WebFlux by implementing HandlerMethodArgumentResolver

Create Custom Interface

For example, we want to get the name, score and version values from a request. First, we need to create the interface.

  package com.woolha.handlermethodargumentresolver.param;
  
  public interface MyParameter {
  
    String getName();
  
    Integer getScore();
  
    String getVersion();
  }

Since we cannot create an object using only the interface, we need to create an instantiable class. Below is a class that implements the interface. To make the code short, I use Project Lombok in this tutorial.

  package com.woolha.handlermethodargumentresolver.param;
  
  import lombok.AllArgsConstructor;
  import lombok.Builder;
  import lombok.Getter;
  import lombok.NoArgsConstructor;
  import lombok.Setter;
  
  @AllArgsConstructor
  @NoArgsConstructor
  @Builder
  @Getter
  @Setter
  public class MyParameterImpl implements MyParameter {
  
    private String name;
  
    private Integer score;
  
    private String version;
  }

Create Custom Argument Resolver Using SyncHandlerMethodArgumentResolver

In Spring WebFlux, you can create a custom argument resolver by creating a class that implements SyncHandlerMethodArgumentResolver interface. There are two methods to be implemented: supportsParameter and resolveArgumentValue.

The supportsParameter is used to check whether a method parameter should be handled using this argument resolver. Since this resolver is only for parameters whose type is MyParameter, just check whether the type of the parameter equals to MyParameter.

The SyncHandlerMethodArgumentResolver has a method named resolveArgument that returns a Mono. If you look into the source code, the method has a default implementation that calls resolveArgumentValue. Therefore, if you don't have any asynchronous operation to resolve the argument value, it's possible to override the resolveArgumentValue method. If there is an asynchronous operation, it's also necessary to override the resolveArgument method. In this example, we extract the values from headers and query parameters and return an object whose type is MyParameter.

  package com.woolha.handlermethodargumentresolver.support;
  
  import com.woolha.handlermethodargumentresolver.param.MyParameter;
  import com.woolha.handlermethodargumentresolver.param.MyParameterImpl;
  import lombok.extern.slf4j.Slf4j;
  import org.springframework.core.MethodParameter;
  import org.springframework.http.HttpHeaders;
  import org.springframework.lang.NonNull;
  import org.springframework.util.MultiValueMap;
  import org.springframework.web.reactive.BindingContext;
  import org.springframework.web.reactive.result.method.SyncHandlerMethodArgumentResolver;
  import org.springframework.web.server.ServerWebExchange;
  
  @Slf4j
  public class MyParameterArgumentResolver implements SyncHandlerMethodArgumentResolver {
  
    @Override
    public boolean supportsParameter(MethodParameter parameter) {
      return MyParameter.class.equals(parameter.getParameterType());
    }
  
    @NonNull
    @Override
    public MyParameter resolveArgumentValue(
        @NonNull MethodParameter parameter,
        @NonNull BindingContext bindingContext,
        @NonNull ServerWebExchange exchange
    ) {
      final MultiValueMap<String, String> params = exchange.getRequest().getQueryParams();
      final HttpHeaders headers = exchange.getRequest().getHeaders();
      final String name = this.getNameValue(params);
      final Integer score = this.getScoreValue(params);
      final String version = this.getVersionValue(headers);
  
      return MyParameterImpl.builder()
          .name(name)
          .score(score)
          .version(version)
          .build();
    }
  
    private String getNameValue(MultiValueMap<String, String> params) {
      return params.get("name")
          .stream()
          .findFirst().orElse(null);
    }
  
    private Integer getScoreValue(MultiValueMap<String, String> params) {
      try {
        final String sizeParam = params.get("score").stream()
            .findFirst().orElse(null);
  
        if (sizeParam != null) {
          return Integer.parseInt(sizeParam);
        }
      } catch (Exception e) {
        log.warn("Unable to parse size param", e);
      }
  
      return null;
    }
  
    private String getVersionValue(HttpHeaders headers) {
      return headers.getFirst("x-version-id");
    }
  }

The next thing you need to do is registering the custom argument resolver. It can be done in a @Configuration-annotated class that implements WebFluxConfigurer. You have to override the configureArgumentResolvers method. The method has a parameter of type ArgumentResolverConfigurer, which has addCustomResolver method. To register the custom argument resolver above, call the addCustomResolver method by passing the custom argument resolver bean.

  package com.woolha.handlermethodargumentresolver.config;
  
  import com.woolha.handlermethodargumentresolver.support.MyParameterArgumentResolver;
  import org.springframework.context.annotation.Bean;
  import org.springframework.context.annotation.Configuration;
  import org.springframework.web.reactive.config.WebFluxConfigurer;
  import org.springframework.web.reactive.result.method.annotation.ArgumentResolverConfigurer;
  
  @Configuration
  public class MyParameterConfig implements WebFluxConfigurer {
  
    @Override
    public void configureArgumentResolvers(ArgumentResolverConfigurer configurer) {
      configurer.addCustomResolver(myParameterArgumentResolver());
    }
  
    @Bean
    public MyParameterArgumentResolver myParameterArgumentResolver() {
      return new MyParameterArgumentResolver();
    }
  }

Summary

You can create a custom argument resolver in Spring WebFlux by creating a class that extends SyncHandlerMethodArgumentResolver. The class needs to override the supportsParameter method which checks whether a parameter should be handled using the custom argument resolver. You also need to override the resolveArgumentValue method. If there is an asynchronous operation for resolving the value, you have to override the resolveArgument method which returns a Mono. Finally, register the resolver using the addCustomResolver method of ArgumentResolverConfigurer in a configuration class by overriding the configureArgumentResolvers method.