Summary
Add a new Maven module fixedformat4j-delimited that brings the same annotation-driven, bidirectional, type-safe POJO mapping that fixedformat4j provides for fixed-width files to delimiter-separated formats: CSV, TSV, pipe-delimited, and any single-character delimiter variant.
Prerequisite: #121 — fixedformat4j-core extraction must be complete before this module can be implemented.
Motivation
No Java library currently combines:
- Annotation-driven POJO binding (just annotate your getters/fields)
- Bidirectional support (parse → POJO and POJO → line)
- Reuse of typed formatters (
DateFormatter, BigDecimalFormatter, etc.)
- Strict startup validation of the mapping configuration
OpenCSV and Apache Commons CSV handle delimiter parsing mechanics but offer no annotation-driven type-mapped POJO binding. Jackson CSV requires a schema object rather than annotations on the domain class. This module fills that gap using the same model consumers already know from fixedformat4j.
Proposed annotation API
@DelimitedRecord
Marks a class as a delimiter-separated record. Analogous to @Record.
@DelimitedRecord(delimiter = '|', quote = '"')
public class TradeRecord { ... }
| Attribute |
Type |
Default |
Description |
delimiter |
char |
',' |
Field separator character |
quote |
char |
'"' |
Quote character for fields containing the delimiter |
hasHeader |
boolean |
false |
Whether the first line is a header row (skipped on load) |
@Column — index-based mapping
Placed on getter methods or fields. Analogous to @Field.
@DelimitedRecord(delimiter = ',')
public class TradeRecord {
@Column(index = 1)
String getTradeId();
@Column(index = 2)
@FixedFormatPattern("yyyyMMdd")
LocalDate getSettlementDate();
@Column(index = 3)
@FixedFormatDecimal(decimals = 2, useDecimalDelimiter = true)
BigDecimal getAmount();
@Column(index = 4)
TradeStatus getStatus(); // enum — LITERAL by default
}
| Attribute |
Type |
Default |
Description |
index |
int |
— |
1-based column position (required) |
formatter |
Class<? extends FixedFormatter<?>> |
ByTypeFormatter.class |
Override the type formatter |
nullValue |
String |
"" |
The raw string that represents null on load/export |
@ColumnName — header-based mapping (alternative to @Column)
For files with a header row, bind by column name rather than position:
@DelimitedRecord(delimiter = ',', hasHeader = true)
public class TradeRecord {
@ColumnName("trade_id")
String getTradeId();
@ColumnName("settlement_date")
@FixedFormatPattern("yyyy-MM-dd")
LocalDate getSettlementDate();
}
Proposed manager API
DelimitedFormatManager manager = DelimitedFormatManagerImpl.create();
// load a single line
TradeRecord record = manager.load(TradeRecord.class, "T001|20260506|12500.00|SETTLED");
// export a single record
String line = manager.export(tradeRecord);
// → "T001|20260506|12500.00|SETTLED"
DelimitedFormatManager is a separate interface from FixedFormatManager — it is not a subtype, and FixedFormatManager is not widened. This respects ISP and keeps both interfaces narrow.
Reused from fixedformat4j-core
All typed formatters carry over unchanged:
StringFormatter, IntegerFormatter, LongFormatter, BigDecimalFormatter, BooleanFormatter, DateFormatter, LocalDateFormatter, LocalDateTimeFormatter, EnumFormatter, etc.
FixedFormatter<T> interface
- Supplementary annotations:
@FixedFormatPattern, @FixedFormatDecimal, @FixedFormatBoolean, @FixedFormatNumber, @FixedFormatEnum
The only new formatting concern is quote handling — fields that contain the delimiter character must be quoted on export and unquoted on load. This is handled by the delimited layer before field values reach the typed formatters.
IO integration — DelimitedReader
A DelimitedReader mirroring FixedFormatReader:
DelimitedReader reader = DelimitedReader.builder()
.addMapping(TradeRecord.class) // single type — no discriminator needed
.parseErrorStrategy(ParseErrorStrategy.skipAndLog())
.build();
List<TradeRecord> trades = reader.read(Path.of("trades.csv")).get(TradeRecord.class);
For heterogeneous delimited files (multiple record types in one file), a column-value discriminator replaces LinePattern:
DelimitedReader reader = DelimitedReader.builder()
.addMapping(HeaderRecord.class, ColumnDiscriminator.column(1, "HDR"))
.addMapping(DetailRecord.class, ColumnDiscriminator.column(1, "DTL"))
.build();
Validation at startup
Following the same model as FixedFormatManagerImpl, DelimitedFormatManagerImpl validates on first use per class:
- Duplicate column indices within a record.
@ColumnName used without hasHeader = true on @DelimitedRecord.
@ColumnName and @Column mixed on the same record class.
- Formatter incompatible with the field's declared Java type.
nullValue configured on a primitive-typed field.
Design notes
- Quote handling uses a minimal RFC 4180-compliant parser. Embedded newlines within quoted fields are supported on load but rejected on export with a clear exception (fixed-width assumption: one record per line).
ClassMetadataCache pattern is replicated for DelimitedFormatManagerImpl — ClassValue-based, GC-safe, thread-safe.
@Column indices are 1-based to align with the existing fixed-width convention.
- Multi-char delimiters (e.g.
||) are explicitly out of scope — delimiter is a single char.
SOLID check
- SRP:
DelimitedFormatManagerImpl owns delimiter-separated parsing. Quote handling is extracted to a DelimitedLineParser with a single responsibility.
- OCP: adding a new delimiter format does not modify
FixedFormatManager or any existing fixed-width class.
- LSP:
DelimitedFormatManager is a new interface; no existing subtype contracts are affected.
- ISP:
DelimitedFormatManager is narrow (load, export); introspection and IO live in separate types.
- DIP:
DelimitedFormatManagerImpl depends on FixedFormatter<T> from fixedformat4j-core, not on concrete fixed-width classes.
Version classification
Minor (2.x.0) relative to the 2.0.0 major introduced by #121 — a new optional Maven artifact with no changes to existing public surfaces. Existing fixedformat4j consumers are completely unaffected.
Summary
Add a new Maven module
fixedformat4j-delimitedthat brings the same annotation-driven, bidirectional, type-safe POJO mapping thatfixedformat4jprovides for fixed-width files to delimiter-separated formats: CSV, TSV, pipe-delimited, and any single-character delimiter variant.Prerequisite: #121 —
fixedformat4j-coreextraction must be complete before this module can be implemented.Motivation
No Java library currently combines:
DateFormatter,BigDecimalFormatter, etc.)OpenCSV and Apache Commons CSV handle delimiter parsing mechanics but offer no annotation-driven type-mapped POJO binding. Jackson CSV requires a schema object rather than annotations on the domain class. This module fills that gap using the same model consumers already know from
fixedformat4j.Proposed annotation API
@DelimitedRecordMarks a class as a delimiter-separated record. Analogous to
@Record.delimiterchar','quotechar'"'hasHeaderbooleanfalse@Column— index-based mappingPlaced on getter methods or fields. Analogous to
@Field.indexintformatterClass<? extends FixedFormatter<?>>ByTypeFormatter.classnullValueString""nullon load/export@ColumnName— header-based mapping (alternative to@Column)For files with a header row, bind by column name rather than position:
Proposed manager API
DelimitedFormatManageris a separate interface fromFixedFormatManager— it is not a subtype, andFixedFormatManageris not widened. This respects ISP and keeps both interfaces narrow.Reused from fixedformat4j-core
All typed formatters carry over unchanged:
StringFormatter,IntegerFormatter,LongFormatter,BigDecimalFormatter,BooleanFormatter,DateFormatter,LocalDateFormatter,LocalDateTimeFormatter,EnumFormatter, etc.FixedFormatter<T>interface@FixedFormatPattern,@FixedFormatDecimal,@FixedFormatBoolean,@FixedFormatNumber,@FixedFormatEnumThe only new formatting concern is quote handling — fields that contain the delimiter character must be quoted on export and unquoted on load. This is handled by the delimited layer before field values reach the typed formatters.
IO integration — DelimitedReader
A
DelimitedReadermirroringFixedFormatReader:For heterogeneous delimited files (multiple record types in one file), a column-value discriminator replaces
LinePattern:Validation at startup
Following the same model as
FixedFormatManagerImpl,DelimitedFormatManagerImplvalidates on first use per class:@ColumnNameused withouthasHeader = trueon@DelimitedRecord.@ColumnNameand@Columnmixed on the same record class.nullValueconfigured on a primitive-typed field.Design notes
ClassMetadataCachepattern is replicated forDelimitedFormatManagerImpl—ClassValue-based, GC-safe, thread-safe.@Columnindices are 1-based to align with the existing fixed-width convention.||) are explicitly out of scope —delimiteris a singlechar.SOLID check
DelimitedFormatManagerImplowns delimiter-separated parsing. Quote handling is extracted to aDelimitedLineParserwith a single responsibility.FixedFormatManageror any existing fixed-width class.DelimitedFormatManageris a new interface; no existing subtype contracts are affected.DelimitedFormatManageris narrow (load,export); introspection and IO live in separate types.DelimitedFormatManagerImpldepends onFixedFormatter<T>fromfixedformat4j-core, not on concrete fixed-width classes.Version classification
Minor (2.x.0) relative to the 2.0.0 major introduced by #121 — a new optional Maven artifact with no changes to existing public surfaces. Existing
fixedformat4jconsumers are completely unaffected.