Spring REST - Is there a way to override the character Spring uses to separate a query parameter into a list of values?

Viewed 671

I'm writing a REST API using Spring and have certain clients to the service that cannot or will not change how they call my service.

Normally when sending a query param with a list of values you would just comma delimit the parameter and Spring will do the rest curl http://host.com/api/endpoint?listParam=1,2,3

And the controller

@GetMapping("/api/endpoint")
public ResponseEntity endpoint(@RequestParam("listParam" List<String> listParam){
    // Here, listParam is populated with 1,2,3
}

Unfortunately my clients are going to be passing lists with the bar | delimiter and it simply isn't possible to get them to change that.
Example: curl http://host.com/api/endpoint?listParam=1%7C2%7C3%7C

I would still like to use Spring to break these calls out into lists so I don't have to clutter my code with manual String.split() calls.

What I've already tried:
I found the @InitBinder annotation and wrote the following

@InitBinder
public void initBinder(WebDataBinder dataBinder){
    dataBinder.registerCustomEditor(String[].class, new StringArrayPropertyEditor("|"));
}

However, this code doesn't seem to ever be called (watching with breakpoints) and requests using the bar as the delimiter fail with a 400 BAD REQUEST.

Any suggestions would be much appreciated, thanks!

2 Answers

404 is coming due to URL encoding issue.

You need to encode | then it will work, but it will create another problem, params would not be split.

To work around this you need to create a custom conversion that can convert String to Collection. For the custom conversion, you can check the StringToCollectionConverter class. Once you have custom conversion then you can register that service, in any of the configuration classes add following function


  @Autowired
  void conversionService(GenericConversionService genericConversionService) {
    genericConversionService.addConverter(myStringToCollectionConvert());
  }

  @Bean
  public MyStringToCollectionConvert myStringToCollectionConvert() {
    return new MyStringToCollectionConvert();
  }

In this MyStringToCollectionConvert is class that will parse String and converts to a collection of Strings.

I've accepted Sonus21's answer since his suggestion allowed me to hunt down an example that worked, but my solution was not exactly his.

The class StringToCollectionConverter did in fact exist for me, but it wasn't accessible and I couldn't use it in any way. However, in looking at the interface it implemented (ConditionalGenericConverter) and searching for more examples with Spring converters I eventually settled on the following solution.

The listParam in my question actually refers to a set of Enum values. The first thing I did was rewrite my controller to actually use the Enum values instead of raw Integers.

@GetMapping("/api/endpoint")
public ResponseEntity endpoint(@RequestParam("listParam" List<EnumClass> listParam){
    // ...
}

Next, I wrote a Spring Custom Converter (Baeldung Doc)

public class CustomStringToEnumClassListConverter implements Converter<String, List<EnumClass>> {

    @Override
    public List<EnumClass> convert(String str) {
        return Stream.of(
                str.split("\\|")) // Here is where we manually delimit the incoming string with bars instead of commas
                .map(i -> EnumClass.intToValue(Integer.parseInt(i))) // intToValue is a method I wrote to get the actual Enum for a given int
                .collect(Collectors.toList());
    }
}

Finally, I wrote a Config Bean and registered this Custom Converter with Spring:

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addFormatters(FormatterRegistry registry){
        registry.addConverter(new CustomStringToEnumClassListConverter());
    }
}

Once all of this was done, Spring automatically populated the listParam list with EnumClass objects.

Related