Fixing MapStruct ClassCastException with DTOs and Custom Expressions
Fixing MapStruct ClassCastException with DTOs and Custom Expressions

Convert Map to Class in Java Using MapStruct Without ClassCastException

Solve MapStruct ClassCastException during JSON map-to-object mapping clearly with intermediate DTOs & custom expressions.6 min


Mapping API responses to Java objects can sometimes feel straightforward—until you encounter unexpected issues like a ClassCastException. One common scenario pops up when developers attempt to convert JSON responses structured as maps into Java classes. Thankfully, solutions like MapStruct help smooth the mapping process significantly by automating object-to-object mappings and reducing boilerplate code. But even the best tools can bring head-scratching moments when complexities arise.

Understanding the API Response Structure Clearly

Before jumping into the actual mapping, let’s first grasp the structure of a typical API JSON response you’d want to map. Suppose your application interacts with an external API that returns data similar to this:

{
  "successRequestList": [
    {
      "requestId": 123,
      "cars": {
        "BMW": {
          "model": "X5",
          "year": 2021
        },
        "Tesla": {
          "model": "Model S",
          "year": 2023
        }
      }
    }
  ]
}

Here, you see clearly identified elements like successRequestList—a list containing several failed or successful API requests. Inside each successRequestList entry, notice the nested cars object, representing a key-value structure with car manufacturers as keys, and their respective data as nested objects. Some responses can become complex, making manual data mapping cumbersome, error-prone, and unsustainable for large-scale systems.

Defining Your Target Object Clearly: Assets and Drive Classes

To cleanly represent your data within Java, a common practice involves defining domain-tailored classes. Let’s say you have two classes: Assets (representing your general response data) and Drive (representing car data specifically):

The Assets class may look something like this:

public class Assets {
    private int requestId;
    private Map<String, Drive> cars;

    // Getters and setters
}

Then, the Drive class designed to represent the details of each car can be defined as follows:

public class Drive {
    private String model;
    private int year;

    // Getters and setters
}

Your goal now becomes straightforward—mapping each entry under cars into a Java Drive object clearly and reliably.

The Common Roadblock: ClassCastException with MapStruct

For simpler cases, you’d immediately reach for the MapStruct library, which provides a clean, annotation-driven way to map objects without manual intervention. But here’s a challenge: attempting a straightforward mapping from your API response (which contains a Map structure) to a custom object (Drive class) can throw a ClassCastException error.

Let’s illustrate with a quick MapStruct mapping attempt:

@Mapper
public interface RideMapper {
    RideMapper INSTANCE = Mappers.getMapper(RideMapper.class);

    Assets map(ResponseDTO source);
}

If MapStruct tries a default mapping on this, you’ll likely see something like this error pop up in your Java logs:

ClassCastException: class java.util.LinkedHashMap cannot be cast to class your.package.Drive

Why does that happen? Because MapStruct is trying to apply straightforward, implicit mapping between incompatible types—a Map structure (coming from JSON deserialization) and your custom Java object (Drive)—causing the Java runtime to complain aggressively.

You might attempt these solutions first, thinking they’d fix the situation:

  • Introducing custom default methods with MapStruct.
  • Explicit casts in your mapper classes.
  • Mapping manually via intermediate layers.

Yet, each approach often falls short or complicates your mapping unnecessarily.

Evaluating Feasible Solutions: Can MapStruct Handle This Scenario?

MapStruct excels at straightforward cases, but when dealing with complex JSON maps as inputs, limitations arise. It’s not entirely impossible—but rather tricky. Sometimes MapStruct struggles with complex, nested Map-to-object structures out-of-the-box, requiring you to implement custom mapping methods explicitly.

Given these hurdles, you might consider alternative object mapping solutions as a fallback:

But before giving up on MapStruct entirely, let’s explore a practical approach that allows you to utilize MapStruct without hitting the dreaded ClassCastException.

Implementing a Functional Mapping with ResponseDTO and Expressions

The solution involves clearly defining an intermediate ResponseDTO class matching your API response structure explicitly. Here’s how you define that class first:

public class ResponseDTO {
    private List<RequestDTO> successRequestList;

    // Getters and setters

    public static class RequestDTO {
        private int requestId;
        private Map<String, Map<String, Object>> cars;

        // getters and setters
    }
}

Next, enhance your RideMapper interface by providing explicit mapping rules using a specialized expression within the @Mapping annotation. This ensures your mapping logic clearly defines how to move data from the intermediate Map<String, Map> structure to your custom Drive objects:

@Mapper(uses = {ObjectMapper.class})
public interface RideMapper {
    RideMapper INSTANCE = Mappers.getMapper(RideMapper.class);

    @Mapping(target = "cars", expression = "java(mapCars(source.getCars()))")
    Assets map(RequestDTO source);

    default Map<String, Drive> mapCars(Map<String, Map<String, Object>> cars) {
        ObjectMapper mapper = new ObjectMapper();
        Map<String, Drive> result = new HashMap<>();
        cars.forEach((key, valueMap) -> {
            Drive drive = mapper.convertValue(valueMap, Drive.class);
            result.put(key, drive);
        });
        return result;
    }
}

With this design, the mapper explicitly defines how data should convert, guarding against ClassCastException. You utilize Jackson’s ObjectMapper convertValue internally, clarifying each mapping clearly and efficiently.

Reflecting on Choosing Correct Mapping Tools

Mapping JSON data structures into Java objects frequently seems deceptively simple—until you hit an unexpected error like ClassCastException. Even robust frameworks like MapStruct may require extra clarity when working with nested or complicated API response structures.

The key takeaway here is not that MapStruct is inadequate, but rather that certain scenarios call for explicit, intentional mappings with custom expressions or methods. Carefully exploring and implementing intermediate DTO objects alongside clearly defined @Mapping annotations offers one reliable approach to resolving such mapping roadblocks.

Considering alternative libraries, evaluating their capabilities and trade-offs, remains good practice. But often, utilizing MapStruct alongside strategically placed Jackson-related expressions—as shown above—fulfills complex mapping efficiently, clearly, and without errors.

Which mapping issues do you most commonly encounter in your Java projects? Have you stumbled across any handy workarounds or improvements for tricky MapStruct scenarios? Share your insights and stories in the comments!


Like it? Share with your friends!

Shivateja Keerthi
Hey there! I'm Shivateja Keerthi, a full-stack developer who loves diving deep into code, fixing tricky bugs, and figuring out why things break. I mainly work with JavaScript and Python, and I enjoy sharing everything I learn - especially about debugging, troubleshooting errors, and making development smoother. If you've ever struggled with weird bugs or just want to get better at coding, you're in the right place. Through my blog, I share tips, solutions, and insights to help you code smarter and debug faster. Let’s make coding less frustrating and more fun! My LinkedIn Follow Me on X

0 Comments

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