x Get our new HTML parser and build any scraping project 80% faster.

Writing

As you can see in WriterExamples.java, writing is quite straightforward. All you need to write your data to some output resource is an instance of java.io.Writer, java.io.File or java.io.OutputStream and a settings object with the configuration of how the values should be written.

Basic writing example

You can write your data in any format using just 3 lines of code:

// All you need is to create an instance of CsvWriter with the default CsvWriterSettings.
// By default, only values that contain a field separator are enclosed within quotes.
// If quotes are part of the value, they are escaped automatically as well.
// Empty rows are discarded automatically.
CsvWriter writer = new CsvWriter(outputWriter, new CsvWriterSettings());

// Write the record headers of this file
writer.writeHeaders("Year", "Make", "Model", "Description", "Price");

// Here we just tell the writer to write everything and close the given output Writer instance.
writer.writeRowsAndClose(rows);

This will produce the following output:

Year,Make,Model,Description,Price
1997,Ford,E350,"ac, abs, moon",3000.00
1999,Chevy,Venture "Extended Edition",,4900.00
1996,Jeep,Grand Cherokee,"MUST SELL!
air, moon roof, loaded",4799.00
1999,Chevy,"Venture ""Extended Edition, Very Large""",,5000.00
,,Venture "Extended Edition",,4900.00

If you want to write the same content in TSV or fixed width format, all you need is to create an instance of TsvWriter or FixedWidthWriter instead. The remainder of the code remains essentially the same.

This will be the case for any other writers/parsers we might introduce in the future, and applies to all examples presented here. For example, the following code for writing TSV is exactly the same as the CSV example seen above. All you need is to instantiate a new writer:

// As with the CsvWriter, all you need is to create an instance of TsvWriter with the default TsvWriterSettings.
TsvWriter writer = new TsvWriter(outputWriter, new TsvWriterSettings());

// Write the record headers of this file
writer.writeHeaders("Year", "Make", "Model", "Description", "Price");

// Here we just tell the writer to write everything and close the given output Writer instance.
writer.writeRowsAndClose(rows);

Which will produce the following output:

Year    Make    Model    Description    Price
1997    Ford    E350    ac, abs, moon    3000.00
1999    Chevy    Venture "Extended Edition"        4900.00
1996    Jeep    Grand Cherokee    MUST SELL!\nair, moon roof, loaded    4799.00
1999    Chevy    Venture "Extended Edition, Very Large"        5000.00
        Venture "Extended Edition"        4900.00

Writing row by row, with comments

CsvWriterSettings settings = new CsvWriterSettings();
// Sets the character sequence to write for the values that are null.
settings.setNullValue("?");

//Changes the comment character to -
settings.getFormat().setComment('-');

// Sets the character sequence to write for the values that are empty.
settings.setEmptyValue("!");

// writes empty lines as well.
settings.setSkipEmptyLines(false);

// Creates a writer with the above settings;
CsvWriter writer = new CsvWriter(outputWriter, settings);

// writes the file headers
writer.writeHeaders("a", "b", "c", "d", "e");

// Let's write the rows one by one (the first row will be skipped)
for (int i = 1; i < rows.size(); i++) {
    // You can write comments above each row
    writer.commentRow("This is row " + i);
    // writes the row
    writer.writeRow(rows.get(i));
}

// we must close the writer. This also closes the java.io.Writer you used to create the CsvWriter instance
// note no checked exceptions are thrown here. If anything bad happens you'll get an IllegalStateException wrapping the original error.
writer.close();

The output of the above code should be:

a,b,c,d,e
-This is row 1
1999,Chevy,Venture "Extended Edition",!,4900.00
-This is row 2
1996,Jeep,Grand Cherokee,"MUST SELL!
    ...

Writing with column selection

You can write transparently to some fields of a CSV file, while keeping the output format consistent. Let’s say you have a CSV file with 5 columns but only have data for 3 of them, in a different order. All you have to do is configure the file headers and select what fields you have values for.

CsvWriterSettings settings = new CsvWriterSettings();

// when writing, nulls are printed using the empty value (defaults to "").
// Here we configure the writer to print ? to describe null values.
settings.setNullValue("?");

// if the value is not null, but is empty (e.g. ""), the writer will can be configured to
// print some default representation for a non-null/empty value
settings.setEmptyValue("!");

// Encloses all records within quotes even when they are not required.
settings.setQuoteAllFields(true);

// Sets the file headers (used for selection, these values won't be written automatically)
settings.setHeaders("Year", "Make", "Model", "Description", "Price");

// Selects which fields from the input should be written. In this case, fields "make" and "model" will be empty
// The field selection is not case sensitive
settings.selectFields("description", "price", "year");

// Creates a writer with the above settings;
CsvWriter writer = new CsvWriter(outputWriter, settings);

// Writes the headers specified in the settings
writer.writeHeaders();

// writes each row providing values for the selected fields (note the values and field selection order must match)
writer.writeRow("ac, abs, moon", 3000.00, 1997);
writer.writeRow("", 4900.00, 1999); // NOTE: empty string will be replaced by "!" as per configured emptyQuotedValue.
writer.writeRow("MUST SELL!\nair, moon roof, loaded", 4799.00, 1996);

writer.close();

The output of such setting will be:

"Year","Make","Model","Description","Price"
"1997","?","?","ac, abs, moon","3000.0"
"1999","?","?","!","4900.0"
"1996","?","?","MUST SELL!
    ...

Writing with value conversions (using ObjectRowWriterProcessor)

All writers have a settings object that accepts an instance of RowWriterProcessor. Use the writer methods prefixed with “processRecord” to execute the RowWriterProcessor against your input.

In the following example, we use ObjectRowWriterProcessor to execute custom value conversions on each element of a row of objects. This object executes a sequence of Conversion actions on the row elements before they are written.

FixedWidthFields lengths = new FixedWidthFields(15, 10, 35);
FixedWidthWriterSettings settings = new FixedWidthWriterSettings(lengths);

// Any null values will be written as 'nil'
settings.setNullValue("nil");
settings.getFormat().setPadding('_');
settings.setIgnoreLeadingWhitespaces(false);
settings.setIgnoreTrailingWhitespaces(false);

// Creates an ObjectRowWriterProcessor that handles annotated fields in the TestBean class.
ObjectRowWriterProcessor processor = new ObjectRowWriterProcessor();
settings.setRowWriterProcessor(processor);

// Converts objects in the "date" field using the yyyy-MMM-dd format.
processor.convertFields(Conversions.toDate(Locale.ENGLISH," yyyy MMM dd "), Conversions.trim()).add("date");

// Trims Strings at position 2 of the input row.
processor.convertIndexes(Conversions.trim(), Conversions.toUpperCase()).add(2);

// Sets the file headers so the writer knows the correct order when writing values taken from a TestBean instance
settings.setHeaders("date", "quantity", "comments");

// Creates a writer with the above settings;
FixedWidthWriter writer = new FixedWidthWriter(outputWriter, settings);

// Writes the headers specified in the settings
writer.writeHeaders();

// writes a Fixed Width row with the values set in "bean". Notice that there's no annotated
// attribute for the "date" column, so it will just be null (an then converted to ? a )
writer.processRecord(new Date(0), null, "  a comment  ");
writer.processRecord(null, 1000, "");

writer.close();

The output will be:

date___________quantity__comments___________________________
1970 Jan 01____nil_______A COMMENT__________________________
nil____________1000_________________________________________

Writing value by value

If you don’t have entire rows available when writing your data, you can simply define the values of each row one by one.

TsvWriter writer = new TsvWriter(outputWriter, new TsvWriterSettings());

writer.writeHeaders("A", "B", "C", "D", "E");

//writes a value to the first column
writer.addValue(10);

//writes a value to the second column
writer.addValue(20);

//writes a value to the fourth column (index 3 represents the 4th column - the one with header "D")
writer.addValue(3, 40);

//overrides the value in the first column. "A" indicates the header name.
writer.addValue("A", 100.0);

//flushes all values to the output, creating a row.
writer.writeValuesToRow();

Which will produce:

A    B    C    D    E
100.0    20        40

Writing maps

In many cases you will have to write data that is stored in a map. The API allows you to do this quite easily:

ObjectRowWriterProcessor processor = new ObjectRowWriterProcessor();

//Strings are trimmed and lower cased by default
processor.convertType(String.class, Conversions.trim(), Conversions.toLowerCase());

// If a null is written to our boolean column, we want to print "N/A", otherwise Y and N for true and false.
// Note that column-specific conversions also prevent the type-conversions to be applied.
// The lower case conversion applied over Strings won't execute on this column.
processor.convertFields(Conversions.toBoolean(null, "N/A", "Y", "N")).add("Boolean column");

settings.setRowWriterProcessor(processor);
settings.setHeaderWritingEnabled(true);

//Let's create a CSV writer
CsvWriter writer = new CsvWriter(settings);

//Creating a map of rows to write our data. Keys will be used as the headers
//Each entry contains the values of a column
Map<String, Object[]> rows = new LinkedHashMap<String, Object[]>();
rows.put("String column", new Object[]{" Paid ", "   PAID", "\npaid"});
rows.put("Boolean column", new Object[]{null, true, false});
rows.put("Last column", new Object[]{199, 288, 11});

//Let's write everything into a list of Strings. Each element of the list will be a new row
List<String> writtenRows = writer.processObjectRecordsToString(rows);
for (String row : writtenRows) {
    println(row);
}

Which will produce:

String column,Boolean column,Last column
paid,N/A,199
paid,Y,288
paid,N,11

In other cases your map will come with keys that won’t work as column headers and their values can’t be directly assigned to a column. In this situation you can provide an extra parameter with a header mapping, where each key is mapped to a header name:

settings.setHeaderWritingEnabled(true);

TsvWriter writer = new TsvWriter(output, settings);

//Creating a map of rows to write our data.
//Each entry contains the values of a column
Map<Long, Object> values = new HashMap<Long, Object>();
values.put(5L, "value @ 5");
values.put(7L, "value @ 7");
values.put(10L, "value @ 10");

//we want to write the data stored in the map above, but the keys don't make any sense as column headers.
//This can be easily solved with a map of headers:
Map<Long, String> headerMapping = new HashMap<Long, String>();
headerMapping.put(5L, "Header 5");
headerMapping.put(7L, "Header 7");
headerMapping.put(10L, "Header 10");

writer.writeRow(headerMapping, values);

values.put(5L, "other @ 5");
values.put(10L, "other @ 10");
values.put(11L, "something else entirely"); //will be ignored
writer.writeRow(headerMapping, values);

writer.close();

println(output.toString());

By mapping the long values to header names, we can easily write the data from our map, and generate the following output:

Header 5    Header 7    Header 10
value @ 5    value @ 7    value @ 10
other @ 5    value @ 7    other @ 10

Writing annotated java beans

If you have a java class with fields annotated with the annotations defined in package com.univocity.parsers.annotations, you can use a BeanWriterProcessor to map its attributes directly to the output.

A RowWriterProcessor is just an interface that “knows” how to map a given object to a sequence of values. By default, univocity-parsers provides the BeanWriterProcessor to map annotated beans to rows.

The following example writes instances of TestBean:

FixedWidthFields lengths = new FixedWidthFields(10, 10, 35, 10, 40);
FixedWidthWriterSettings settings = new FixedWidthWriterSettings(lengths);

// Any null values will be written as ?
settings.setNullValue("?");

// Creates a BeanWriterProcessor that handles annotated fields in the TestBean class.
settings.setRowWriterProcessor(new BeanWriterProcessor<TestBean>(TestBean.class));

// Sets the file headers so the writer knows the correct order when writing values taken from a TestBean instance
settings.setHeaders("amount", "pending", "date", "quantity", "comments");

// Creates a writer with the above settings;
FixedWidthWriter writer = new FixedWidthWriter(outputWriter, settings);

// Writes the headers specified in the settings
writer.writeHeaders();

// writes a fixed width row with empty values (as nothing was set in the TestBean instance).
writer.processRecord(new TestBean());

TestBean bean = new TestBean();
bean.setAmount(new BigDecimal("500.33"));
bean.setComments("Blah,blah");
bean.setPending(false);
bean.setQuantity(100);

// writes a Fixed Width row with the values set in "bean". Notice that there's no annotated
// attribute for the "date" column, so it will just be null (an then converted to ?, as we have settings.setNullValue("?");)
writer.processRecord(bean);

// you can still write rows passing in its values directly.
writer.writeRow(BigDecimal.ONE, true, "1990-01-10", 3, null);

writer.close();

The resulting output of the above code should be:

amount    pending   date                               quantity  comments                                
?         ?         ?                                  ?         ?                                       
500.33    no        ?                                  100       blah,blah                               
1         true      1990-01-10                         3         ?

Further Reading

Feel free to proceed to the following sections (in any order).

Bugs, contributions & support

If you find a bug, please report it on github or send us an email on parsers@univocity.com.

We try out best to eliminate all bugs as soon as possible and you’ll rarely see a bug open for more than 24 hours after it’s reported. We do our best to answer all questions. Enhancements/suggestions are implemented on a best effort basis.

Fell free to submit your contribution via pull requests. Any little bit is appreciated, from improvements on documentation to a full blown rewrite from scratch.

For commercial support, customizations or anything in between, please contact support@univocity.com.

Thank you for using our parsers!

The univocity team.