Key Takeaways
- BufferedReader and Scanner can read CSV files as text, but neither automatically handles CSV quoting or record boundaries.
- Use Apache Commons CSV when fields can contain commas, escaped quotes, or line breaks, or when you need access by header name.
- Specify the input charset and use try-with-resources to close readers and parsers.
- Process records as you iterate for large imports; collecting every record into an ArrayList retains the whole dataset in memory.
To read a CSV file in Java, open a reader with an explicit charset and pass it to a CSV parser such as Apache Commons CSV. BufferedReader is enough to read physical lines, but parsing CSV requires recognizing quoted fields and records that can span multiple lines.
The examples below use JDK 25. Start with the standard-library examples to understand file reading, then use the complete Commons CSV example for actual CSV parsing and header-based access.
Sample CSV Files: Lines Are Not Always Records
Save this small example as data.csv in your working directory, using UTF-8 encoding. Its first record contains the column names:
First,Last,Age
John,Doe,23
Sam,Smith,40
Jan,Miller,18This file happens to have one record per physical line. A more realistic file, saved as complex.csv, might contain:
First,Last,Age,Note
Jane,"Doe, Jr.",31,"Said ""hello"""
Sam,Smith,40,"Two
lines"
Jan,Miller,18,Under the quoting rules described in RFC 4180, a quoted field can contain commas and line breaks, and a double quote inside a quoted field is represented by two double quotes. Here, Sam's record spans two physical lines, while Jan's final field is empty.
UTF-8 bytes --> decoded characters
|--> line reader: physical lines
+--> CSV parser: fields and logical records
"Two [line break] lines" = one field, not two recordsWhy String.split Is Not a General CSV Parser
line.split(",") cannot distinguish a separator from a comma inside quotes. Splitting individual lines also loses the context needed to recognize multiline fields.
There is another trap: String.split(String) discards trailing empty strings. A negative limit preserves them, but still does not implement CSV quoting:
// Fragment: inside a method; only for input guaranteed to have no quoting.
String[] values = "a,b,".split(",", -1); // ["a", "b", ""]Use this shortcut only when you control the producer and its format explicitly forbids quoted fields and embedded line breaks. Don't use it for arbitrary files labeled CSV.
Read a CSV File in Java Using BufferedReader
This example reads text, not CSV fields. Save it as LineReader.java beside data.csv:
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
public class LineReader {
public static void main(String[] args) throws IOException {
Path path = Path.of("data.csv");
try (BufferedReader reader =
Files.newBufferedReader(path, StandardCharsets.UTF_8)) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
}
}Run it with java LineReader.java. The output is:
First,Last,Age
John,Doe,23
Sam,Smith,40
Jan,Miller,18Files.newBufferedReader(Path, Charset) opens a buffered text reader using the specified encoding. BufferedReader.readLine() reads physical lines; it does not recognize CSV quotes.
Try-with-resources closes the reader when the block exits, including when an exception occurs. This small demo propagates IOException rather than swallowing a read failure.
Read the Header Separately for Simple Input
For the trivial sample, replace the loop inside the preceding try block with this fragment to print the header separately:
// Fragment: inside LineReader's try block.
String header = reader.readLine();
if (header == null) {
throw new IOException("The file is empty");
}
System.out.println("Headers: " + header);
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}This prints Headers: First,Last,Age, followed by the data lines. It does not associate fields with names; the Commons CSV example below does that.
Read CSV Text Using Scanner
Scanner is another way to read the same physical lines. Save this as ScannerLines.java and run java ScannerLines.java:
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.util.Scanner;
public class ScannerLines {
public static void main(String[] args) throws IOException {
try (Scanner scanner = new Scanner(
Path.of("data.csv"), StandardCharsets.UTF_8)) {
while (scanner.hasNextLine()) {
System.out.println(scanner.nextLine());
}
IOException failure = scanner.ioException();
if (failure != null) {
throw failure;
}
}
}
}Scanner provides token-reading methods such as nextInt(), which are useful for controlled token-based input. Setting a comma delimiter does not turn it into a CSV parser.
The ioException() check matters: Scanner treats an underlying read IOException as the end of input and exposes that failure through this method. For more on the underlying readers, see FileReader vs BufferedReader vs Scanner.
Which Approach Should You Choose?
| Approach | What it gives you | Use it for |
|---|---|---|
| BufferedReader | Buffered character and line reading | Inspecting raw text or reading a tightly controlled, trivial format |
| Scanner | Line reading and token conversion | Controlled token-based input, not general CSV parsing |
| Apache Commons CSV | CSV records, quoted fields, and header mappings | CSV imports and exports with documented format rules |
Read CSV with Apache Commons CSV
The example pins Apache Commons CSV 1.14.1 from Maven Central, also listed on Apache's release download page. Create this complete pom.xml beside your CSV files:
<project xmlns="http://maven.apache.org/POM/4.0.0">
<modelVersion>4.0.0</modelVersion>
<groupId>example</groupId>
<artifactId>csv-demo</artifactId>
<version>1.0.0</version>
<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-csv</artifactId>
<version>1.14.1</version>
</dependency>
</dependencies>
</project>Read a CSV File by Header Name
Save this as CsvByHeader.java. It discovers the header names, validates the required columns and record widths, and prints each person's first name:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.List;
import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVParser;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.csv.DuplicateHeaderMode;
public class CsvByHeader {
private static final CSVFormat FORMAT = CSVFormat.DEFAULT.builder()
.setDelimiter(',')
.setQuote('"')
.setHeader()
.setSkipHeaderRecord(true)
.setDuplicateHeaderMode(DuplicateHeaderMode.DISALLOW)
.get();
public static void main(String[] args) {
String file = args.length == 0 ? "data.csv" : args[0];
try {
read(Path.of(file));
} catch (IOException | UncheckedIOException | IllegalArgumentException e) {
System.err.println("CSV import failed for " + file + ": " + e.getMessage());
System.exit(1);
}
}
private static void read(Path path) throws IOException {
try (BufferedReader reader =
Files.newBufferedReader(path, StandardCharsets.UTF_8);
CSVParser parser = FORMAT.parse(reader)) {
if (!parser.getHeaderNames().containsAll(
List.of("First", "Last", "Age"))) {
throw new IllegalArgumentException(
"Required headers: First, Last, Age");
}
for (CSVRecord record : parser) {
if (!record.isConsistent()) {
throw new IllegalArgumentException(
"Wrong field count at record " + record.getRecordNumber());
}
System.out.println(record.get("First"));
}
}
}
}Download the dependencies, then launch the source file:
mvn dependency:copy-dependencies
java --class-path "target/dependency/*" CsvByHeader.java data.csvMaven's copy-dependencies goal includes transitive dependencies and defaults to the project's target/dependency directory. The Java launcher supports source-file execution and the classpath wildcard used here.
The output is:
John
Sam
JanRun the same command with complex.csv instead of data.csv to print Jane, Sam, and Jan. With that input, record.get("Last") for Jane returns Doe, Jr., and her Note field contains Said "hello"; Jan's Note field is the empty string.
Understand the Header and Format Configuration
The CSVFormat builder makes the parsing contract explicit:
setHeader()discovers column names from the first CSV record.setSkipHeaderRecord(true)keeps the header out of the data-record iteration.setDuplicateHeaderMode(DuplicateHeaderMode.DISALLOW)rejects duplicate header names rather than allowing ambiguous name-based access.get()builds the configured format.
The example uses comma delimiters, double-quoted fields, and DEFAULT's empty-line skipping. The older withFirstRecordAsHeader() convenience method is deprecated; use the builder configuration shown above.
CSVRecord.get(String) accesses a value by header name, and isConsistent() checks whether the record's field count matches the header count. Parsing fields does not validate your business rules: you still need to check whether Age is a valid number and whether required values are present.
Configure Other Delimiters or Headerless Input
A CSV dialect is the producer's choice of delimiters, quoting, and related rules. For semicolon-separated input, change the builder to setDelimiter(';'); don't guess the delimiter from a filename.
For a headerless three-column file, replace setHeader() with setHeader("First", "Last", "Age") and use setSkipHeaderRecord(false). When you supply names yourself, skipping the first record would discard actual data.
Read a CSV File into an ArrayList
When you need repeated access to a small dataset, collect the parsed fields instead of splitting raw lines. Add import java.util.ArrayList; to CsvByHeader.java, then replace its record-processing loop with this fragment:
// Fragment: inside read(), after the required-header check.
List<List<String>> rows = new ArrayList<>();
rows.add(new ArrayList<>(parser.getHeaderNames()));
for (CSVRecord record : parser) {
if (!record.isConsistent()) {
throw new IllegalArgumentException(
"Wrong field count at record " + record.getRecordNumber());
}
rows.add(record.toList());
}
rows.forEach(System.out::println);For data.csv, the output preserves the original header-plus-rows structure:
[First, Last, Age]
[John, Doe, 23]
[Sam, Smith, 40]
[Jan, Miller, 18]List is the interface; ArrayList is the implementation used for the outer collection. record.toList() creates a new list of that record's fields. The header is added explicitly here; remove that addition if you want data rows only.
For more on these collection types, see the Java Collections Framework guide. Retaining every row makes memory usage grow with the dataset, including the strings and collection objects—not just the file's byte size.
Stream Large Files and Handle Import Failures
The original CsvByHeader loop already streams records: it processes each record without retaining previous records. CSVParser.getRecords(), by contrast, collects all remaining records into a list.
Keep downstream work bounded too. Write to your destination incrementally or in limited batches rather than accumulating rows elsewhere. Streaming avoids retaining the whole file, but a single unusually large record can still require substantial memory.
The pinned release's iterator implementation can wrap read and parsing failures in UncheckedIOException. CSVException extends IOException, so the example handles both checked I/O failures and their unchecked iteration wrappers, plus explicit validation failures.
Common Mistakes
- Assuming the default charset describes the file. The no-charset FileReader constructor uses Java's default charset. Specify the producer's actual encoding; choosing UTF-8 cannot repair a file encoded differently.
- Ignoring byte order marks. Some spreadsheet exports include a byte order mark, or BOM. Follow the Commons CSV BOM-handling recipe, which uses Commons IO's BOMInputStream before parsing.
- Confusing record numbers with physical line numbers. Multiline fields mean those numbers can differ. Label diagnostics accordingly.
- Skipping malformed input silently. Decide which validation failures are recoverable, and stop when malformed quoting prevents reliable record boundaries.
Before shipping an importer, test quoted commas, doubled quotes, multiline fields, trailing empty values, non-ASCII text, missing headers, duplicate headers, and inconsistent field counts.
FAQ
How do I read a CSV file in Java with a header row?
Use Apache Commons CSV with setHeader() and setSkipHeaderRecord(true) to discover the first record's column names and exclude that header from data iteration. Access values with record.get("First") or another header name.
Can I parse CSV in Java with String.split?
Only for a restricted format that forbids quoting and embedded line breaks. split(",", -1) preserves trailing empty fields, but it still does not handle quoted commas, escaped quotes, or multiline records.
How do I read a large CSV file in Java without loading it all into memory?
Iterate over CSVParser and process each CSVRecord immediately or in bounded batches. Avoid getRecords() and collections that retain every row; memory still depends on individual record sizes and downstream processing.
How do I set the character encoding when reading a CSV file in Java?
For UTF-8 input, use Files.newBufferedReader(path, StandardCharsets.UTF_8) and pass the returned reader to your CSV parser. Use a different charset when the producer documents a different encoding.
Which Java library should I use to parse CSV?
Apache Commons CSV is a practical starting point for record iteration, quoted fields, configurable delimiters, and header-based access. BufferedReader and Scanner read text but do not provide those CSV parsing rules themselves.
Further Reading
- RFC 4180: Common CSV format — field quoting, escaped quotes, and embedded line breaks.
- Apache Commons CSV guide — format examples, header access, and BOM handling.
- CSVFormat.Builder API — delimiter, quote, and header configuration.
- CSVParser API — record iteration, resource ownership, and collecting records.
- CSVRecord API — name-based access, field counts, and list conversion.
- Java Files API — opening buffered readers with an explicit charset.
- Java Scanner API — tokenization and underlying I/O error behavior.
- Commons CSV 1.14.1 on Maven Central — the pinned dependency and its published metadata.
