You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

MapStruct映射Map时运行时抛出ClassCastException异常排查

MapStruct映射Map时抛出ClassCastException问题

问题场景

我正在使用MapStruct实现POJO之间的模型转换,对应映射方法项目构建可正常通过,但运行时会抛出类型转换异常。
以下是MapStruct自动生成的映射方法代码:

protected Map<String, GenericAttributeData> headerAttributeGenericDataTypeMapToStringGenericAttributeDataMap(Map<HeaderAttribute, GenericDataType> map) {
    if ( map == null ) {
        return null;
    }

    Map<String, GenericAttributeData> map1 = new HashMap<String, GenericAttributeData>( Math.max( (int) ( map.size() / .75f ) + 1, 16 ) );

    for ( java.util.Map.Entry<HeaderAttribute, GenericDataType> entry : map.entrySet() ) {
        String key = entry.getKey().name(); // 异常抛出所在行
        GenericAttributeData value = genericDataTypeToGenericAttributeData( entry.getValue() );
        map1.put( key, value );
    }

    return map1;
}

protected GenericAttributeData genericDataTypeToGenericAttributeData(GenericDataType genericDataType) {
    if ( genericDataType == null ) {
        return null;
    }

    GenericAttributeData genericAttributeData = new GenericAttributeData();

    if ( genericDataType.getType() != null ) {
        genericAttributeData.setType( genericDataType.getType().name() );
    }
    genericAttributeData.setValue( genericDataType.getValue() );

    return genericAttributeData;
}

该方法预期将键为HeaderAttribute枚举、值为GenericDataType的源Map,转换为键为String、值为GenericAttributeData的目标Map。

异常信息

运行时异常固定抛出在上述代码标注的遍历Map键的行,异常栈核心信息如下:

23 Jun 2022 09:34:46,218 [ERROR] dd6af157-4089-4b37-aa9b-225ed4b4394b (Bobcat-0) com.service.myActivity: class java.lang.String cannot be cast to class com.service.dao.dynamodb.entity.header.HeaderAttribute (java.lang.String is in module java.base of loader 'bootstrap'; com.service.dao.dynamodb.entity.header.HeaderAttribute is in unnamed module of loader com.company.cloud9.launcher.BootstrapClassLoader @75bd9247)
java.lang.ClassCastException: class java.lang.String cannot be cast to class com.service.dao.dynamodb.entity.header.HeaderAttribute (java.lang.String is in module java.base of loader 'bootstrap'; com.service.dao.dynamodb.entity.header.HeaderAttribute is in unnamed module of loader com.company.cloud9.launcher.BootstrapClassLoader @75bd9247)
        at com.service.adapters.mapper.InvoiceHeaderDoMapperImpl.headerAttributeGenericDataTypeMapToStringGenericAttributeDataMap(InvoiceHeaderDoMapperImpl.java:187) ~[myService-1.0.jar:?]
        at com.service.adapters.mapper.InvoiceHeaderDoMapperImpl.mapDOToInvoiceHeader(InvoiceHeaderDoMapperImpl.java:46) ~[myService-1.0.jar:?]
        at com.service.adapters.impl.InvoiceHeaderAdapterImpl.adaptDBInvoiceHeaderToCoralModel(InvoiceHeaderAdapterImpl.java:29) ~[myService-1.0.jar:?]
        at com.service.handler.SubmitAdditionalLinesHandler.buildInventoryInvoiceFromStagingDb(SubmitAdditionalLinesHandler.java:129) ~[myService-1.0.jar:?]
        at com.service.handler.SubmitAdditionalLinesHandler.handleAdditionalLineItemsSubmissionRequest(SubmitAdditionalLinesHandler.java:83) ~[myService-1.0.jar:?]
        at com.service.component.SubmitAdditionalLinesComponent.submitAdditionalLines(SubmitAdditionalLinesComponent.java:30) ~[myService-1.0.jar:?]
        at com.service.service.SubmitInvoiceActivity.submitAdditionalLines_aroundBody2(SubmitInvoiceActivity.java:84) ~[myService-1.0.jar:?]
        at com.service.service.SubmitInvoiceActivity$AjcClosure3.run(SubmitInvoiceActivity.java:1) ~[myService-1.0.jar:?]
        at org.aspectj.runtime.reflect.JoinPointImpl.proceed(JoinPointImpl.java:167) ~[aspectjweaver-1.9.6.jar:?]
        at com.company.metrics.declarative.aspectj.JoinpointInvocationHandle.proceed(JoinpointInvocationHandle.java:60) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?]
        at com.company.metrics.declarative.AbstractMethodMetricInterceptor.handleInvocation(AbstractMethodMetricInterceptor.java:283) ~[DeclarativeCoralMetrics-2.2.jar:?]
        at com.company.metrics.declarative.aspectj.MetricMethodAspect$ConfiguredMethodAspect.invoke(MetricMethodAspect.java:108) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?]
        at com.company.metrics.declarative.aspectj.MetricMethodAspect.captureMethodMetrics(MetricMethodAspect.java:59) ~[DeclarativeCoralMetricsAspectJ-2.2.jar:?]
        at com.service.service.SubmitInvoiceActivity.submitAdditionalLines(SubmitInvoiceActivity.java:82) ~[myService-1.0.jar:?]
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[?:?]
        at jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[?:?]
        at jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[?:?]
        at java.lang.reflect.Method.invoke(Method.java:566) ~[?:?]
        at com.company.coral.reflect.invoke.ReflectiveInvokerFactory$1.invoke(ReflectiveInvokerFactory.java:77) ~[CoralReflect-1.1.jar:?]
        at com.company.coralx.exception.ExceptionTranslationInterceptor.intercept(ExceptionTranslationInterceptor.java:213) ~[companyCoralExceptionTranslation-1.1.jar:?]
        at com.company.coral.reflect.invoke.InterceptedInvoker.invoke(InterceptedInvoker.java:74) ~[CoralReflect-1.1.jar:?]
        at com.company.coral.validate.ValidationInterceptor.intercept(ValidationInterceptor.java:106) ~[CoralValidate-1.1.jar:?]
        at com.company.coral.reflect.invoke.InterceptedInvoker.invoke(InterceptedInvoker.java:74) ~[CoralReflect-1.1.jar:?]
        at com.company.coral.service.DefaultActivityInvoker.invoke(DefaultActivityInvoker.java:29) ~[CoralActivity-1.1.jar:?]
        at com.company.coral.service.ContinuationActivityInvokerFactory$1.invoke(ContinuationActivityInvokerFactory.java:89) ~[CoralActivity-1.1.jar:?]
        at com.company.coral.service.ActivityInterceptors.invoke(ActivityInterceptors.java:74) ~[CoralActivity-1.1.jar:?]
        at com.company.coral.service.ActivityHandler.invoke(ActivityHandler.java:186) ~[CoralActivity-1.1.jar:?]
        at com.company.coral.service.ActivityHandler.before(ActivityHandler.java:145) ~[CoralActivity-1.1.jar:?]
        at com.company.coral.service.TransmutingContinuationHandler.before(TransmutingContinuationHandler.java:50) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.Chain.componentBefore(Chain.java:94) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.Chain.before(Chain.java:72) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.helper.ChainHelper.before(ChainHelper.java:94) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.AbstractOrchestrator.doWork(AbstractOrchestrator.java:84) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.PassiveOrchestrator.enqueue(PassiveOrchestrator.java:93) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.GracefulShutdownOrchestrator.enqueue(GracefulShutdownOrchestrator.java:301) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.service.helper.OrchestratorHelper.enqueue(OrchestratorHelper.java:72) ~[CoralOrchestrator-1.1.jar:?]
        at com.company.coral.bobcat.CoralServletRequestHandler.processRequestOrThrow(CoralServletRequestHandler.java:172) ~[Bobcat-3.0.jar:?]
        at com.company.coral.bobcat.CoralServletRequestHandler.processRequest(CoralServletRequestHandler.java:150) ~[Bobcat-3.0.jar:?]
        at com.company.coral.bobcat.CoralServletRequestHandler.service(CoralServletRequestHandler.java:115) ~[Bobcat-3.0.jar:?]

此前尝试打印参数取值、加断点观测时,未发现数据值有明显异常。

根因分析

  1. 核心原因是Java泛型类型擦除+数据源反序列化不符合预期:
    从异常栈可以看到项目使用DynamoDB作为存储,DynamoDB默认反序列化Map类型字段时,如果没有配置专门的枚举转换器,会将枚举类型的键直接反序列化为String类型。由于Java泛型只在编译期做类型检查,编译后Map的泛型参数会被擦除,运行时实际传入MapStruct方法的是一个键为String的原始类型Map,不会提前触发编译错误。
  2. 断点观测未发现异常的原因:
    枚举HeaderAttribute的name()返回值和反序列化得到的String键文本完全一致,仅观测值的文本内容无法发现类型差异,实际断点查看entry.getKey().getClass()会发现其类型为java.lang.String,并非声明的HeaderAttribute枚举。
  3. MapStruct默认生成的代码逻辑无错误:它按照编译期的泛型声明,直接将entry的key作为HeaderAttribute枚举调用name()方法,遇到实际类型为String的key时就会触发强转异常。

解决方案

  • 修复数据源反序列化逻辑:给DynamoDB实体类中对应Map字段配置枚举类型转换器,指定Map的键反序列化时转换为HeaderAttribute枚举,从源头保证传入MapStruct的参数类型和声明一致。
  • 自定义MapStruct映射逻辑:如果暂时无法修改数据源反序列化规则,可以编写自定义Map映射方法替代MapStruct默认生成的实现,手动兼容键为String的场景:
    default Map<String, GenericAttributeData> customMapConvert(Map<?, GenericDataType> source) {
        if (source == null) return null;
        Map<String, GenericAttributeData> result = new HashMap<>();
        for (Map.Entry<?, GenericDataType> entry : source.entrySet()) {
            String key;
            Object rawKey = entry.getKey();
            if (rawKey instanceof HeaderAttribute) {
                key = ((HeaderAttribute) rawKey).name();
            } else if (rawKey instanceof String) {
                // 校验String是否对应合法枚举,避免非法值
                key = HeaderAttribute.valueOf((String) rawKey).name();
            } else {
                throw new IllegalArgumentException("不支持的Map键类型:" + rawKey.getClass());
            }
            result.put(key, genericDataTypeToGenericAttributeData(entry.getValue()));
        }
        return result;
    }
    
    之后在Mapper接口的对应映射方法上通过@Mapping注解指定使用该自定义方法做Map转换即可。
  • 调试时注意检查对象实际运行时类型,不要仅通过文本值判断类型是否匹配。

内容的提问来源于stack exchange,提问作者shubham tambere

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.29 00:31:16