Generics of SuperClass

I’m trying to using generics with the reactive webclient, so far I’ve create a class that holds the webClient and some methods

    public class CustomWebClient<T> {
            
    private final WebClient webClient;
        
            public CustomWebClient(WebClient.Builder builder, ClientProperties properties) {
                String rootUrl = properties.getHttp().getRootUrl();
                webClient = builder
                        .baseUrl(rootUrl)
                        .defaultHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE)
                        .defaultHeaders(httpHeaders -> httpHeaders.setAll(properties.getHeaders()))
                        .build();
            }

     public ​Mono<ResponseEntity<T>> setRequest(final String uri,
                                               ​final HttpMethod httpMethod,
                                               ​final Class<T> classToMap) {
     WebClient.RequestHeadersSpec<?> requestHeadersSpec = webClient.method(httpMethod).uri(uri);
     ​WebClient.ResponseSpec responseSpec = setRetrievingOptions(requestHeadersSpec);
       ​return setEntity(responseSpec, classToMap);
   
  }  

private WebClient.ResponseSpec setRetrievingOptions(final WebClient.RequestHeadersSpec<?> requestHeadersSpec) {
    return requestHeadersSpec.retrieve()
            .onStatus(HttpStatus::is4xxClientError,
                    clientResponse -> Mono.error(new ResponseStatusException(clientResponse.statusCode(), NOT_FOUND_MSG)))
            .onStatus(HttpStatus::is5xxServerError,
                    clientResponse -> Mono.error(new ResponseStatusException(clientResponse.statusCode(), INTERNAL_SERVER_ERROR_MSG)));
}

private Mono<ResponseEntity<T>> setEntity(final WebClient.ResponseSpec responseSpec, Class<T> classToMap) {
    return responseSpec.toEntity(classToMap);
}
 } 

then I inject the CustomWebClient into another class which is a service

      public class ABCServiceImpl implements ABCService {
    
         private final CustomWebClient<? extends BaseResponse> customClient;
        
         private Mono<PageResponse> invokeGetPageFromABC(final String storeId, final String site, final MultiValueMap<String, String> queryParams) {
          Mono<ResponseEntity<PageResult>> responseEntityMono = customClient.setRequest(String.format(PAGE_ENDPOINT, site), HttpMethod.GET, queryParams, PageResult.class);
    
      return responseEntityMono.map(content ->
                    new ContentResponse(content.getStatusCode(), customWebClient.SUCCESS_MSG, createContent(content.getBody())));
        }

    private ContentDTO createContent(ContentResult contentResult){
        return Optional.ofNullable(contentResult)
                .filter(c -> Objects.nonNull(c.getContent()))
                .map(ContentResult::getContent)
                .orElse(new ContentDTO());
    }
}

at this point a get a compile time error which is

Required type: Class <capture of ? extends BaseResponse>
Provided: Class <ContentResult>

Here is the object I’m passing

public class ContentResult extends BaseResponse {

    private ContentDTO content;
}

Since I’m new with generics probably there is something I’m missing or I didn’t fully understand. Any clue about what could be the problem?

Honestly I’ve expected to work given the fact that I’ve actually specified the super class while injecting the customWebClient.

Thank you in advance!

Answer

I’ve combed through this, and I think I understand what you’re trying to do. I recommend not using the wildcard (“?”) in this way. When you call customClient.setRequest(..), the compiler can’t figure out what version of customClient you have (i.e. what subclass of BaseResponse), so it throws an error when you try to pass in the parameterized type Class object (which looks to be PageResult.class)

You could fix this by turning ABCServiceImpl into a parameterized class i.e. ABCServiceImpl<T extends BaseResponse> with a field property of private final CustomWebClient<T extends BaseResponse> customClient;

But I still wouldn’t do that either, because it looks like your custom client does not need to be a parameterized type at all!

So I’d recommend this:

  1. Remove the parameterized type from CustomWebClient. (i.e. public class CustomWebClient instead of public class CustomWebClient<T>)

  2. Make the public ​Mono<ResponseEntity<T>> setRequest(..) method a parameterized method instead: public <T> ​Mono<ResponseEntity<T>> setRequest(..)

  3. Call the method on customWebClient like so:

    customWebClient.<ContentResult>setRequest(ContentResult.class, ...etc);

Leave a Reply

Your email address will not be published. Required fields are marked *