fyeeme opened a new issue, #889: URL: https://github.com/apache/fesod/issues/889
### Search before asking - [x] I searched in the [issues](https://github.com/apache/fesod/issues) and found nothing similar. ### Fesod version 2.0.1-incubating ### JDK version 21 ### Operating system macos 26.4 ### Steps To Reproduce ## Summary Using the native POJO write path in `org.apache.fesod:fesod-sheet:2.0.1-incubating` can trigger a `StackOverflowError` when: - the DTO contains a `ZonedDateTime` field - a custom converter is registered for `ZonedDateTime` - the DTO also uses normal field metadata such as: - `@DateTimeFormat` - `@NumberFormat` - `@ExcelProperty(converter = ...)` In our project this blocks switching back to the pure native Fesod POJO export path for `ZonedDateTime`-based exports. ## Environment - Java: project currently runs on Java 21 - Library: `org.apache.fesod:fesod-sheet:2.0.1-incubating` - Output format: XLSX ## Minimal Reproduction Repository repro artifact: - Test file: ```java package com.linzi.pitpat.excel; import lombok.Data; import org.apache.fesod.sheet.FesodSheet; import org.apache.fesod.sheet.annotation.ExcelProperty; import org.apache.fesod.sheet.annotation.format.DateTimeFormat; import org.apache.fesod.sheet.annotation.format.NumberFormat; import org.apache.fesod.sheet.converters.Converter; import org.apache.fesod.sheet.enums.CellDataTypeEnum; import org.apache.fesod.sheet.metadata.GlobalConfiguration; import org.apache.fesod.sheet.metadata.data.WriteCellData; import org.apache.fesod.sheet.metadata.property.ExcelContentProperty; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import java.io.ByteArrayOutputStream; import java.math.BigDecimal; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.List; import static org.junit.jupiter.api.Assertions.assertThrows; /** * Documents an upstream Fesod bug we can reproduce reliably. * * <p>When using the native POJO write path with a DTO containing ZonedDateTime, * Fesod 2.0.1-incubating triggers a StackOverflowError inside its write holder hashCode chain. * We keep this test disabled because enabling it would break CI, but it provides a stable * reproduction if we need to validate an upstream fix or a local dependency patch.</p> */ class FesodNativeMetadataRegressionTest { @Test @Disabled("Stable repro for upstream Fesod StackOverflowError on native POJO write path") @DisplayName("native Fesod POJO export should reproduce StackOverflowError with ZonedDateTime metadata") void nativePojoExport_shouldReproduceStackOverflowError() { ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); assertThrows(StackOverflowError.class, () -> FesodSheet.write(outputStream, NativeMixedMetadataRow.class) .registerConverter(new NativeZonedDateStringConverter()) .sheet("NativeMixedMetadata") .doWrite(List.of(createRow()))); } private NativeMixedMetadataRow createRow() { NativeMixedMetadataRow row = new NativeMixedMetadataRow(); row.setStatus("ACTIVE"); row.setAmount(new BigDecimal("1234.5")); row.setCreatedAt(ZonedDateTime.of(2026, 3, 24, 15, 30, 45, 0, ZoneId.of("Asia/Shanghai"))); return row; } @Data public static class NativeMixedMetadataRow { @ExcelProperty(value = "Status", converter = NativeStatusLabelConverter.class) private String status; @ExcelProperty("Amount") @NumberFormat("¥#,##0.00") private BigDecimal amount; @ExcelProperty("Created At") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") private ZonedDateTime createdAt; } public static class NativeStatusLabelConverter implements Converter<String> { @Override public Class<?> supportJavaTypeKey() { return String.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public WriteCellData<?> convertToExcelData( String value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { return new WriteCellData<>("STATUS:" + value); } } public static class NativeZonedDateStringConverter implements Converter<ZonedDateTime> { @Override public Class<?> supportJavaTypeKey() { return ZonedDateTime.class; } @Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; } @Override public WriteCellData<?> convertToExcelData( ZonedDateTime value, ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) { String format = contentProperty != null && contentProperty.getDateTimeFormatProperty() != null ? contentProperty.getDateTimeFormatProperty().getFormat() : "yyyy-MM-dd HH:mm:ss"; return new WriteCellData<>(value.toLocalDateTime().format(java.time.format.DateTimeFormatter.ofPattern(format))); } } } ``` The test is intentionally marked `@Disabled` so CI stays green, but it is a stable reproducer. ### Repro DTO ```java @Data public static class NativeMixedMetadataRow { @ExcelProperty(value = "Status", converter = NativeStatusLabelConverter.class) private String status; @ExcelProperty("Amount") @NumberFormat("¥#,##0.00") private BigDecimal amount; @ExcelProperty("Created At") @DateTimeFormat("yyyy-MM-dd HH:mm:ss") private ZonedDateTime createdAt; } ``` ### Repro Export Code ```java ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); FesodSheet.write(outputStream, NativeMixedMetadataRow.class) .registerConverter(new NativeZonedDateStringConverter()) .sheet("NativeMixedMetadata") .doWrite(List.of(row)); ``` ### Expected Result Workbook is exported successfully, with native Fesod metadata handling preserved: - `@ExcelProperty(converter = ...)` is applied - `@NumberFormat` is applied - `@DateTimeFormat` is applied ### Actual Result `StackOverflowError` ## Observed Stack Pattern The stack repeatedly cycles through `hashCode()` calls on Fesod write holder objects: ```text java.lang.StackOverflowError at java.util.ArrayList.hashCode(...) at org.apache.fesod.sheet.metadata.Head.hashCode(...) at org.apache.fesod.sheet.metadata.property.ExcelHeadProperty.hashCode(...) at org.apache.fesod.sheet.write.property.ExcelWriteHeadProperty.hashCode(...) at org.apache.fesod.sheet.write.metadata.holder.AbstractWriteHolder.hashCode(...) at org.apache.fesod.sheet.write.metadata.holder.WriteWorkbookHolder.hashCode(...) at org.apache.fesod.sheet.write.metadata.holder.WriteSheetHolder.hashCode(...) ... ``` ## Suspected Root Cause This is an inference based on source inspection plus stable reproduction. `WriteContextImpl.initSheet()` stores `WriteSheetHolder` into maps owned by `WriteWorkbookHolder`: ```java writeWorkbookHolder.getHasBeenInitializedSheetIndexMap().put(writeSheetHolder.getSheetNo(), writeSheetHolder); writeWorkbookHolder.getHasBeenInitializedSheetNameMap().put(writeSheetHolder.getSheetName(), writeSheetHolder); ``` At the same time: - `WriteSheetHolder` holds `parentWriteWorkbookHolder` - `WriteWorkbookHolder` holds maps of `WriteSheetHolder` - `AbstractWriteHolder`, `WriteWorkbookHolder`, and `WriteSheetHolder` use Lombok `@EqualsAndHashCode` That appears to create a recursive object graph for structural `hashCode()` evaluation: ```text WriteWorkbookHolder -> hasBeenInitializedSheetIndexMap / hasBeenInitializedSheetNameMap -> WriteSheetHolder -> parentWriteWorkbookHolder -> WriteWorkbookHolder -> ... ``` If Fesod evaluates structural `hashCode()` on these holder objects, recursion does not terminate. ## Project Impact In our project: - the plain native POJO path would be the preferred implementation - but this Fesod issue prevents using it safely for `ZonedDateTime` - we currently use a compatibility path that still reuses Fesod field metadata instead of fully hardcoding formatting logic Compatibility implementation reference: - [ExcelExportUtils.java](/Users/Young/Projects/profressional/linzikg/pitpat-framework/pitpat-common/pitpat-excel/src/main/java/com/linzi/pitpat/excel/util/ExcelExportUtils.java#L228) - fallback branch starts at [ExcelExportUtils.java](/Users/Young/Projects/profressional/linzikg/pitpat-framework/pitpat-common/pitpat-excel/src/main/java/com/linzi/pitpat/excel/util/ExcelExportUtils.java#L388) ## Suggested Fix Direction Likely fix directions in Fesod: - exclude recursive holder references from `equals/hashCode` - or avoid structural `equals/hashCode` for runtime write holders entirely - or replace the relevant holder equality semantics with identity-based handling ## Local Verification Project-level verification after documenting the repro: ```bash mvn -q -pl pitpat-common/pitpat-excel test mvn -q -pl demo-service -am -DskipTests compile ``` ### Current Behavior StackOverflowError ### Expected Behavior work normally ### Anything else? _No response_ ### Are you willing to submit a PR? - [ ] I'm willing to submit a PR! -- This is an automated message from the Apache Git Service. To respond to the message, please log on to GitHub and use the URL above to go to the specific comment. To unsubscribe, e-mail: [email protected] For queries about this service, please contact Infrastructure at: [email protected] --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
