Skip to content

Upgrading

This document describes the breaking changes when upgrading from earlier versions of FastCSV to 3.x. For a full list of changes, including new features, see the changelog.

Upgrading from 2.x

This section describes the breaking changes when migrating from FastCSV 2.x to 3.x.

Requirement changes

  • The minimum Java version is now 11 (compared to 8 in earlier versions)
  • This also raised the required Android API level from version 26 (Android 8) to 33 (Android 13)

New limitations

  • The maximum number of fields per record is now limited to 16,384 to avoid OutOfMemoryErrors caused by excessive field counts.
  • The maximum record size is now limited to (64 MiB) to prevent OutOfMemoryErrors.

Naming changes

Rows are now called Records (aligned to RFC 4180)

Reading CSV records:

try (CsvReader<CsvRecord> csv = CsvReader.builder().ofCsvRecord(file)) {
csv.forEach(System.out::println);
}

Write CSV records:

try (CsvWriter csv = CsvWriter.builder().build(file)) {
csv
.writeRecord("header1", "header2")
.writeRecord("value1", "value2");
}

Method names

  • In CsvReaderBuilder:
    • skipEmptyRows is now skipEmptyLines
    • errorOnDifferentFieldCount is now ignoreDifferentFieldCount (opposite meaning!)
    • build methods with callback handlers and ofCsvRecord / ofNamedCsvRecord as convenience methods
  • In CsvRecord (former CsvRow):
    • getOriginalLineNumber is now getStartingLineNumber

NamedCsvReader removed/replaced

A distinct NamedCsvReader is no longer needed as the CsvReader now supports callbacks for header and record processing.

CsvReader.builder().ofNamedCsvRecord("header 1,header 2\nfield 1,field 2")
.forEach(rec -> System.out.println(rec.getField("header2")));

or with a custom header:

NamedCsvRecordHandler callbackHandler = NamedCsvRecordHandler.builder()
.header("header1", "header2")
.build();
CsvReader.builder().build(callbackHandler, "field 1,field 2")
.forEach(rec -> System.out.println(rec.getField("header2")));

Extended/Refactored quote strategies

  • QuoteStrategy changed from an enum to an interface (see QuoteStrategies for the default implementations)
  • The REQUIRED quote strategy is removed as it is the default (no quote strategy is needed)

Example to enable always quoting:

CsvWriter.builder()
.quoteStrategy(QuoteStrategies.ALWAYS);

Exception changes

MalformedCsvException is now CsvParseException and is thrown instead of IOException for non-IO related errors.

Upgrading from 1.x

This section describes the breaking changes when migrating from FastCSV 1.x to 3.x.

Reader

Configuring the reader

Old way:

CsvReader csvReader = new CsvReader();
csvReader.setFieldSeparator(',');
csvReader.setTextDelimiter('"');
csvReader.setSkipEmptyRows(true);
csvReader.setErrorOnDifferentFieldCount(false);

New way:

CsvReader.builder()
.fieldSeparator(',')
.quoteCharacter('"')
.skipEmptyLines(true)
.ignoreDifferentFieldCount(true);

Reading data from file

Old way:

try (CsvParser csvParser = new CsvReader().parse(file, UTF_8)) {
CsvRow row;
while ((row = csvParser.nextRow()) != null) {
System.out.println("First field of row: " + row.getField(0));
}
}

New way:

try (CsvReader<CsvRecord> csv = CsvReader.builder().ofCsvRecord(file)) {
csv.forEach(rec ->
System.out.println("First field of record: " + rec.getField(0))
);
}

Reading data with a header from file

Old way:

CsvReader csvReader = new CsvReader();
csvReader.setContainsHeader(true);
try (CsvParser csvParser = csvReader.parse(file, UTF_8)) {
CsvRow row;
while ((row = csvParser.nextRow()) != null) {
System.out.println("Field named firstname: " + row.getField("firstname"));
}
}

New way:

try (CsvReader<NamedCsvRecord> csv = CsvReader.builder().ofNamedCsvRecord(file)) {
csv.forEach(rec ->
System.out.println("Field named firstname: " + rec.getField("firstname"))
);
}

Read an entire file at once

Old way:

CsvContainer csv = new CsvReader().read(file, UTF_8);

New way:

The container concept has been removed, but you can easily build something similar using the Java Stream API.

List<CsvRecord> data;
try (CsvReader<CsvRecord> csvReader = CsvReader.builder().ofCsvRecord(file)) {
data = csvReader.stream().toList();
}

Writer

Configuring the writer

Old way:

CsvWriter csvWriter = new CsvWriter();
csvWriter.setFieldSeparator(',');
csvWriter.setTextDelimiter('"');
csvWriter.setAlwaysDelimitText(true);
csvWriter.setLineDelimiter(new char[]{'\r','\n'});

New way:

CsvWriter.builder()
.fieldSeparator(',')
.quoteCharacter('"')
.quoteStrategy(QuoteStrategies.ALWAYS)
.lineDelimiter(LineDelimiter.CRLF);

Write to file

Old way:

try (CsvAppender csvAppender = new CsvWriter().append(file)) {
csvAppender.appendLine("header1", "header2");
csvAppender.appendLine("value1", "value2");
}

New way:

try (CsvWriter csvWriter = CsvWriter.builder().build(file)) {
csvWriter
.writeRecord("header1", "header2")
.writeRecord("value1", "value2");
}

Write to writer

Old way:

Writer writer = new StringWriter();
try (CsvAppender csvAppender = new CsvWriter().append(writer)) {
csvAppender.appendLine("header1", "header2");
csvAppender.appendLine("value1", "value2");
}

New way:

Writer writer = new StringWriter();
try (CsvWriter csvWriter = CsvWriter.builder().build(writer)) {
csvWriter
.writeRecord("header1", "header2")
.writeRecord("value1", "value2");
}