一、问题复现

java.util.concurrent.CompletionException: 
java.lang.IllegalArgumentException: 
Java 8 date/time type `java.time.LocalDateTime` not supported by default: 
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310"

堆栈顶行指向我们「通用 Excel 导出」工具类:

java

private static Map<String, Object> beanToMap(Object bean) {
    return objectMapper.convertValue(bean, Map.class);   // ← 220 行炸雷
}

二、根因定位

  1. 导出逻辑为了通用,把任意 Bean 先转成 Map<String,Object>,再按字段顺序写 Excel。

  2. 业务对象 ColumnInfoResponse 里带有 LocalDateTime createTime

  3. Jackson 默认不带 Java 8 时间模块,遇到 java.time.* 直接抛 InvalidDefinitionException

  4. 异常被 CompletableFuture 捕获,包装成 CompletionException,线程池线程直接退出,前端收到「导出失败」。


三、最小复现

ObjectMapper mapper = new ObjectMapper();
mapper.convertValue(new ColumnInfoResponse(), Map.class);
// 复现一模一样异常

四、两种根治方案

方案步骤适用场景
A. 直接 new + 注册模块(本文采用)① 引入 jackson-datatype-jsr310
② new ObjectMapper().registerModule(new JavaTimeModule())
工具类独立,不依赖 Spring
B. 复用 Spring 全局 Bean① 配置类声明 @Bean ObjectMapper
② @PostConstruct 把 Spring 实例赋给 static 字段
已使用 Spring Boot,方便统一管理

下面给出完整可拷贝代码。


五、代码级解决方案

1. Maven 依赖

xml

复制

<!-- 与 jackson-databind 版本保持一致 -->
<dependency>
    <groupId>com.fasterxml.jackson.datatype</groupId>
    <artifactId>jackson-datatype-jsr310</artifactId>
    <version>2.15.2</version>
</dependency>
2. 工具类改造

@Slf4j
@Service
public class CommonExportService<T extends BasePageQry, R extends BaseDownloadResponse> {

    /* 静态工具 Mapper,一次性初始化 */
    private static final ObjectMapper objectMapper = createObjectMapper();

    private static ObjectMapper createObjectMapper() {
        return new ObjectMapper()
                .registerModule(new JavaTimeModule())              // 支持 LocalDateTime
                .disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS) // 返回 ISO-8601 字符串
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                .disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
    }

    /** 任意 Bean → Map,兼容 null */
    private static Map<String, Object> beanToMap(Object bean) {
        if (bean == null) {
            return new HashMap<>();
        }
        return objectMapper.convertValue(bean, Map.class);
    }

    /** 写 Excel 核心逻辑 */
    private static void writeSheetData(PageIndicator downloadData,
                                       List<String> fieldKeys,
                                       ExcelWriter writer,
                                       WriteSheet sheet) {
        List<List<String>> excelData = new ArrayList<>();
        for (Object row : downloadData.getRecords()) {
            Map<String, Object> rowMap = beanToMap(row);
            List<String> rowData = new ArrayList<>();
            for (String field : fieldKeys) {
                rowData.add(getCellValue(rowMap.get(field)));
            }
            excelData.add(rowData);
        }
        writer.write(excelData, sheet);
    }

    private static String getCellValue(Object val) {
        return val == null ? "" : val.toString();
    }
}
3. 验证
ColumnInfoResponse resp = new ColumnInfoResponse();
resp.setCreateTime(LocalDateTime.now());
Map<String, Object> map = CommonExportService.beanToMap(resp);
System.out.println(map.get("createTime")); // 2025-12-04T21:30:45

控制台正常输出 ISO 格式字符串,Excel 导出恢复绿灯。


六、容易踩的坑

坑点说明
版本不一致jackson-databind 与 jackson-datatype-jsr310 版本差一位就会出 NoSuchMethodError
时间变时间戳忘记关闭 WRITE_DATES_AS_TIMESTAMPS,Excel 出现 1700000000000 这种数字
循环依赖用 Spring 注入时,别把 ObjectMapper 再 @Autowired 到静态字段里,避免构造期循环

七、一句话总结

只要记住:
「Java 8 时间类型 → 必须注册 JavaTimeModule
从此 LocalDateTimeLocalDateInstant 都能被 Jackson 当成普通字段无缝转换,导出、缓存、RPC 全场景通用。


八、参考资源

  1. Jackson Java 8 Modules 官方文档

  2. Spring Boot 自动配置 Jackson2ObjectMapperBuilder 源码

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐