Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 

Repository files navigation

csv-lite

Full API reference, architecture and internals: docs/DOCUMENTATION.md A streaming, zero-dependency CSV parser and writer for Java implementing RFC 4180, built as a single-pass character state machine.

Compiles and runs with nothing but a JDK (11+). Companion project to ttl-cache — where that one is about concurrency and data structures, this one is about parsing and state-machine logic.

Why not String.split(",")?

Because real CSV breaks it immediately:

name,city,quote
Thabo,"Cape Town, WC","he said ""howzit"""
Lindiwe,"Jozi
(the big one)",fine

That file has a comma inside a field, a quote inside a field, and one record spanning two physical lines. split(",") mangles all three. csv-lite parses them correctly with a four-state character machine (FIELD_START, UNQUOTED_FIELD, QUOTED_FIELD, QUOTE_IN_QUOTED).

Features

  • RFC 4180 compliant — quoted fields, "" escaping, embedded commas and newlines, CRLF/LF, missing final newline
  • Streaming — reads from any Reader one record at a time, so a 2 GB file needs constant memory
  • Iterable<List<String>> — works directly in the enhanced for-loop
  • Helpful failures — malformed input raises CsvFormatException naming the offending record number
  • Custom delimiters — e.g. ; for locales where , is the decimal separator
  • Symmetric writer — minimal quoting, quote-doubling, fluent API; the test suite proves write-then-read is lossless

Usage

// Streaming a file (constant memory)
try (Reader r = Files.newBufferedReader(Path.of("transactions.csv"))) {
    for (List<String> record : new CsvReader(r)) {
        process(record);
    }
}

// Small inputs, eagerly
List<List<String>> rows = CsvReader.fromString("a,b\nc,d\n").readAll();

// Writing
StringBuilder sb = new StringBuilder();
new CsvWriter(sb)
        .writeRecord(List.of("name", "city"))
        .writeRecord(List.of("Thabo", "Cape Town, WC")); // auto-quoted

Build and test (JDK only)

javac -d out $(find src -name "*.java")
java -cp out dev.csvlite.CsvLiteTest

Expected output ends with:

21 passed, 0 failed

Design decisions

Why a character state machine instead of regex? CSV's grammar is context-sensitive in a way that line-oriented regex can't express: whether a comma separates fields depends on whether you're inside quotes, and a record can span multiple physical lines. A four-state machine handles every case in one pass, in O(n), with no backtracking.

Why stream instead of loading the file? Parsing record-by-record from a Reader means memory use is bounded by the largest single record, not the file size — the difference between handling a 2 GB export and an OutOfMemoryError.

Why refuse a second iterator()? The reader wraps a single-use stream. Silently returning an empty second iterator would be a lying API; failing loudly with IllegalStateException makes the contract explicit.

Why does the writer quote minimally? Quoting everything is valid but bloats files and diffs. Quoting only when necessary (delimiter, quote, or newline present) keeps output clean while the round-trip test guarantees it is always parseable.

Limitations (deliberate scope)

  • No header/column mapping or bean binding — records are List<String> by design
  • No charset detection; callers choose the Reader encoding
  • Not thread-safe (wraps a single-use stream)

License

MIT

About

A streaming, zero-dependency CSV parser and writer for Java implementing RFC 4180, built as a single-pass character state machine.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages