Core Java

Spring ParameterizedTypeReference Example

Deserializing generic types in Java can be challenging because of type erasure, which removes generic type information at runtime. This often leads to incorrect data mapping, especially when working with List<T> or nested structures. Spring’s ParameterizedTypeReference offers a clean and reliable solution by preserving type details, ensuring accurate deserialization of complex API responses in both RestTemplate and WebClient.

This article will explore how to use ParameterizedTypeReference with practical examples.

1. Use Case Overview

When working with Java, one frequent challenge is type erasure—the process where the compiler removes generic type information at runtime. Although generics like List<Product> or Map<String, Product> enforce type safety during compilation, the actual type arguments (e.g., Product) vanish once the application runs. This becomes problematic when consuming REST APIs, as JSON parsing libraries such as Jackson need the exact target type to correctly map JSON data to objects.

Without explicit type information, attempting to deserialize an API response containing a JSON array of products into a List<Product> might cause Spring (or Jackson) to treat it as a List<Object>, resulting in the loss of the intended mapping and leading to casting issues or manual conversions.

ParameterizedTypeReference addresses this by retaining full generic type details at runtime, enabling Spring’s RestTemplate or WebClient to correctly interpret and deserialize responses into fully typed objects. It is especially valuable when:

  • Working with collections like List<T> or Set<T>
  • Deserializing nested generic structures such as Map<String, List<T>>
  • Handling paginated or wrapped responses like Page<T> or ApiResponse<T>

In this article’s example, we’ll use it to ensure that the /products endpoint, which returns a List<Product>, is accurately deserialized without losing type details. The application will:

  • Expose a list of products via /products.
  • Consume that endpoint using RestTemplate and WebClient.
  • Deserialize the response into List<Product> using ParameterizedTypeReference.

Without ParameterizedTypeReferenceProblem Example

List<Product> products = webClient.get()
    .uri("/products")
    .retrieve()
    .bodyToMono(List.class) // Loses type information!
    .block();

// This might not work as expected — items could be LinkedHashMap, not Product
products.forEach(System.out::println); // May cause ClassCastException

2. Define the Model

Let’s start by creating a simple Product class that we will send and receive as JSON data.

public class Product {

    private String name;
    private double price;

    public Product() {
    }

    public Product(String name, double price) {
        this.name = name;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

}

This class models a basic product with a name and a price. It includes a no-args constructor (required for Jackson), a parameterized constructor, and getters/setters.

Create a REST Controller

Here’s a REST endpoint that returns a list of Product objects in JSON format.

@RestController
public class ProductController {

    @GetMapping("/products")
    public List<Product> getProducts() {
        return List.of(
            new Product("Laptop", 1200.00),
            new Product("Smartphone", 850.50)
        );
    }
}

This controller defines an endpoint /products that returns a list of two product instances.

3. Use RestTemplate with ParameterizedTypeReference

This is where the actual challenge and solution lie. We’ll create a client that fetches this product list and deserializes it properly using ParameterizedTypeReference.

@Service
public class ProductClient {

    private final RestTemplate restTemplate = new RestTemplate();

    public List<Product> getAllProducts() {
        ResponseEntity<List<Product>> response = restTemplate.exchange(
                "http://localhost:8080/products",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<Product>>() {
        }
        );
        return response.getBody();
    }
}

In this example, the getAllProducts() method sends a GET request to /products, using an anonymous subclass of ParameterizedTypeReference<List<Product>> to capture the generic type at runtime. This ensures that Spring can correctly deserialize the JSON array into a fully typed List<Product>. Without this explicit type reference, type erasure would prevent Spring from knowing the intended target type, leading to improper deserialization.

Finally, we tie everything together in the main application class and call the client to verify the result.

@SpringBootApplication
public class ParameterizedTypeReferenceDemoApplication {

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

        @Bean
    public CommandLineRunner run(ProductClient client) {
        return args -> {
            List<Product> products = client.getAllProducts();
            products.forEach(product -> System.out.println(product.getName() + " - " + product.getPrice()));
        };
    }
}

When the application runs, it calls the /products endpoint, fetches the product list, and prints the result to the console.

4. Use ParameterizedTypeReference with Spring’s WebClient

WebClient is Spring’s non-blocking, reactive HTTP client, unlike the synchronous RestTemplate. It’s ideal for scalable, high-throughput applications. Generics like List<Product> still require ParameterizedTypeReference to avoid issues with type erasure and ensure proper deserialization. Let’s now use WebClient instead of RestTemplate.

@Service
public class ProductWebClient {

    private final WebClient webClient;

    public ProductWebClient(@Value("${app.base-url}") String baseUrl) {
        this.webClient = WebClient.builder()
                .baseUrl(baseUrl)
                .build();
    }

    public Mono<List<Product>> fetchProducts() {
        return this.webClient.get()
                .uri("/products")
                .retrieve()
                .bodyToMono(new ParameterizedTypeReference<List<Product>>() {
                })
                .onErrorMap(throwable -> {
                    if (throwable instanceof WebClientResponseException ex) {
                        return new ServiceException("Error: " + ex.getResponseBodyAsString());
                    }
                    return throwable;
                });
    }

}

In this code, the ProductWebClient service uses Spring’s WebClient to perform a GET request to the /products endpoint, with the base URL injected from the application’s configuration using @Value("${app.base-url}").

The key part is the use of an anonymous subclass of ParameterizedTypeReference<List<Product>> inside bodyToMono(), which preserves the generic type information at runtime and ensures that the JSON array response is correctly deserialized into a List<Product> despite Java’s type erasure. Without this, Spring would be unable to infer the list’s element type and would fail to deserialize properly.

Supporting Exception Class

public class ServiceException extends RuntimeException {
    public ServiceException(String message) {
        super(message);
    }
}

We will now run the WebClient clients when the app starts:

@SpringBootApplication
public class ParameterizedTypeReferenceDemoApplication {

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

    @Bean
    public CommandLineRunner run(ProductWebClient webClient) {
        return args -> {
            webClient.fetchProducts().subscribe(
                    list -> list.forEach(
                        p -> System.out.printf("Product: %s | Price: %.2f%n", p.getName(), p.getPrice())),
                    error -> System.err.println("Error occurred: " + error.getMessage())
            );
        };
    }
}

Observe that the JSON response is successfully deserialized into the intended List<Product> type, and its contents are printed to the console.

5. Conclusion

ParameterizedTypeReference is a powerful utility in Spring for resolving type erasure when working with generics in REST clients. It plays a crucial role in accurately deserializing JSON arrays or nested structures like List<Product>, which would otherwise be difficult due to Java’s runtime limitations. Understanding and applying this concept helps you build more type-safe client-server applications.

6. Download the Source Code

This article covered Spring Parameterized Type Reference for generic type deserialization.

Download
You can download the full source code of this example here: spring parameterized type reference

Omozegie Aziegbe

Omos Aziegbe is a technical writer and web/application developer with a BSc in Computer Science and Software Engineering from the University of Bedfordshire. Specializing in Java enterprise applications with the Jakarta EE framework, Omos also works with HTML5, CSS, and JavaScript for web development. As a freelance web developer, Omos combines technical expertise with research and writing on topics such as software engineering, programming, web application development, computer science, and technology.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Back to top button