0

I'm unable to successfully bind request parameters to a command object having a generic list property items:

public class Holder<T> {
  private List<T> items = new ArrayList<>();

  public List<T> getItems() {
    return items;
  }

  public void setItems(List<T> items) {
    this.items = items;
  }
}

Setting items via request parameters appears to work but the values remain Strings just waiting to trigger ClassCastExceptions.

For example, the following code throws a ClassCastException given the invocation

/test?items=1

and Controller handler:

@GetMapping("/test")
public String test(Holder<Integer> holder) {
  List<Integer> items = holder.getItems();

  System.out.println(items.size()); // 1
  System.out.println(items); // [1] 
  if (!items.isEmpty()) {
    System.out.println(items.get(0).getClass()); // java.lang.ClassCastException: java.lang.String incompatible with java.lang.Integer
  }
  return "test";
}

How can I configure Spring MVC to convert the item parameter values to the correct (generic) type?

I'm using Spring Boot 2.3.2.

1 Answer 1

2

You can implement your own HandlerMethodArgumentResolver like this:

@Component
public class HolderMethodArgumentResolver implements HandlerMethodArgumentResolver {

    @Override
    public boolean supportsParameter(MethodParameter methodParameter) {
        return Holder.class.isAssignableFrom(methodParameter.getParameterType());
    }

    @Override
    public Holder resolveArgument(MethodParameter methodParameter, ModelAndViewContainer modelAndViewContainer,
            NativeWebRequest nativeWebRequest, WebDataBinderFactory webDataBinderFactory) {
        Class<?> genericClass = (Class<?>) ((ParameterizedType) methodParameter.getGenericParameterType()).getActualTypeArguments()[0];
        if (Integer.class.isAssignableFrom(genericClass)) {
            String[] values = nativeWebRequest.getParameterValues("items");
            List<Integer> convertedValues = Stream.of(values).map(Integer::valueOf).collect(Collectors.toList());
            return new Holder<>(convertedValues);
        }
        if (String.class.isAssignableFrom(genericClass)) {
            String[] values = nativeWebRequest.getParameterValues("items");
            return new Holder<>(Arrays.asList(values));
        }
        throw new IllegalArgumentException("Generic type " + genericClass + " doesn't support");
    }

}

And after that you can define in contoller different methods:

    @GetMapping("/test")
    public String test(Holder<Integer> holder) {
        List<Integer> items = holder.getItems();

        System.out.println(items.size());
        System.out.println(items);
        if (!items.isEmpty()) {
            System.out.println(items.get(0).getClass());
        }
        return "test";
    }

    @GetMapping("/test2")
    public String test2(Holder<String> holder) {
        List<String> items = holder.getItems();

        System.out.println(items.size());
        System.out.println(items);
        if (!items.isEmpty()) {
            System.out.println(items.get(0).getClass());
        }
        return "test2";
    }
Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.