[Bug]POJO Export with custom converter StackOverflow Repro
- Dominant language
- Java
- Stars
- 6.2k
- Forks
- 532
- Avg merge
- 1d 3h
- Merged PRs (30d)
- 42
Description
### 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.
*
*
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.
*/
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 {
@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 {
@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
## 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!
Contributor guide
Assessment
This issue has not been assessed yet.