Repository navigation
[Bug] Header cells are converted with user-registered String converters, breaking @ExcelProperty(name) matching #1098
Description
Search before asking
- I searched in the issues and found nothing similar.
Fesod version
main (2.1.0-incubating). Not tied to a recent change — the explicit-key case below reproduces on released versions too.
JDK version
17 (Temurin); not JDK-specific.
Operating system
Any
Steps To Reproduce
public static class Bean {
@ExcelProperty("flag")
private String flag;
// getters / setters
}
public static class UpperCaseConverter implements Converter<String> {
@Override public Class<?> supportJavaTypeKey() { return String.class; }
@Override public CellDataTypeEnum supportExcelTypeKey() { return CellDataTypeEnum.STRING; }
@Override public String convertToJavaData(
ReadCellData<?> cellData, ExcelContentProperty p, GlobalConfiguration g) {
return cellData.getStringValue().toUpperCase();
}
}
// file under test: header "flag", one data row with the value "abc"
FesodSheet.write(file, Bean.class).sheet().doWrite(Collections.singletonList(new Bean("abc")));
List<Bean> rows = FesodSheet.read(file, Bean.class, listener)
.registerConverter(new UpperCaseConverter())
.sheet()
.doReadSync();
// rows.get(0).getFlag() is nullCurrent Behavior
The converter is invoked with the header cell ("flag") instead of the data cell ("abc"): convertToJavaData returns "FLAG", the framework then compares "FLAG" with the @ExcelProperty("flag") name, finds no matching column, and leaves the field null. No exception is raised, so the failure is silent.
The same path also fills AnalysisEventListener.invokeHeadMap(...), so the header map handed to listeners goes through user converters as well.
Using @ExcelProperty(index = 0) works, because matching then does not depend on the header text.
Expected Behavior
When the framework converts the head row to strings for name matching, header cells should be resolved against the built-in converters — user-registered converters are meant for data cells. invokeHeadMap should likewise receive the raw header text.
Note that in the failing case the converter is never invoked for the data cell either, so even a user who wants header conversion loses data conversion entirely.
Anything else?
Root cause: ConverterUtils.convertToStringMap resolves ConverterKeyBuild.buildKey(String.class, cellData.getType()) in the holder's converter map, which also contains user registrations. It is called from DefaultAnalysisEventProcessor.buildHead (header to field matching) and from AnalysisEventListener.invokeHead.
Found while reviewing #1086 (read-side wildcard converter fix). That PR does not introduce this — an explicit (String, STRING) registration already reproduces it on main. It does widen the set of affected registrations, since a wildcard Converter<String> gets expanded to (String, STRING) and would therefore be picked up by the header path too.
Proposed fix: resolve the header path against the built-in read converters (e.g. DefaultConverterLoader.copyDefaultReadConverter()) rather than the user-extended map. One decision point for the maintainers: whether invokeHeadMap should keep seeing user-converted text (a niche behaviour someone might rely on) or always see the raw header.
Happy to prepare the PR if the direction looks right — unless it is preferred to handle this within #1086.
Are you willing to submit a PR?
- I'm willing to submit a PR!
Activity
bengbengbalabalabeng commentedon Sep 12, 2026
Based on my understanding, Converters should only apply to data cells and should not be involved in header parsing. Keeping the header as raw text ensures stable field matching and avoids the side effects of header conversion.
This likely requires a new PR to handle it.
Mayank-Sr commentedon Sep 13, 2026
Is this issue available for contributions or is the already assigned?
Mikkey-f commentedon Sep 13, 2026
Thanks for the interest! This one is already taken — I reported it and I'm preparing the PR myself (as noted under "Are you willing to submit a PR?"), and the author of #1086 has handed the header-path fix over to it as well.
Sorry to turn you away — hope to see you on another issue soon!
docs(read): document converter resolution priority across read and write