Skip to content

Header validation

When reading CSV files with a header (using NamedCsvRecordHandler), you may want to assert that the file actually comes with the header you expect – and fail fast (before processing any records) if it doesn’t.

FastCSV allows you to do this by configuring a HeaderValidator. The validator is called once the header is determined – either from the first record or from a predefined header. If the validation fails, a CsvParseException is thrown. Input without any header (e.g., an empty file) yields no records and is not validated.

Two standard validators are provided:

  • HeaderValidator.containsExactly(...): the header must consist of exactly the given fields, in the given order.
  • HeaderValidator.containsAtLeast(...): the header must contain at least the given fields, in any order.

For anything beyond that (e.g., case-insensitive comparison or pattern matching), you can implement custom validation logic using a lambda expression.

In the following example, the header is validated while reading a CSV file.

ExampleNamedCsvReaderWithHeaderValidation.java
import de.siegmar.fastcsv.reader.CsvParseException;
import de.siegmar.fastcsv.reader.CsvReader;
import de.siegmar.fastcsv.reader.HeaderValidator;
import de.siegmar.fastcsv.reader.NamedCsvRecordHandler;
/// Example for reading CSV data while validating the header.
///
/// FastCSV supports Java 17 and later, but this code uses Java 25
/// for brevity, leveraging newer language features.
void main() {
final String data = """
header1,header2
foo,bar
""";
IO.println("Require an exact header (order-sensitive):");
final NamedCsvRecordHandler exactHandler = NamedCsvRecordHandler.of(builder ->
builder.headerValidator(HeaderValidator.containsExactly("header1", "header2"))
);
CsvReader.builder().build(exactHandler, data)
.forEach(rec -> IO.println(rec.getField("header2")));
IO.println("Require certain fields (in any order; additional fields are allowed):");
final NamedCsvRecordHandler atLeastHandler = NamedCsvRecordHandler.of(builder ->
builder.headerValidator(HeaderValidator.containsAtLeast("header2"))
);
CsvReader.builder().build(atLeastHandler, data)
.forEach(rec -> IO.println(rec.getField("header2")));
IO.println("Custom validation logic:");
final NamedCsvRecordHandler customHandler = NamedCsvRecordHandler.of(builder ->
builder.headerValidator(header -> {
if (header.size() != 2) {
throw new CsvParseException("Expected 2 header fields, but found " + header.size());
}
})
);
CsvReader.builder().build(customHandler, data)
.forEach(rec -> IO.println(rec.getField("header2")));
IO.println("A failed validation aborts reading with a CsvParseException:");
final NamedCsvRecordHandler failingHandler = NamedCsvRecordHandler.of(builder ->
builder.headerValidator(HeaderValidator.containsExactly("headerA", "headerB"))
);
try {
CsvReader.builder().build(failingHandler, data)
.forEach(rec -> IO.println(rec.getField("header2")));
} catch (final CsvParseException e) {
IO.println(e.getMessage());
}
}

You also find this source code example in theFastCSV GitHub repository.