Convert OFX Transactions to CSV
Updated: Oct 5, 2026
This example shows how to turn the transactions of an OFX bank statement into CSV, one row per transaction. OpenFinancialExchangeReader returns the statement as one nested record (see Read an OFX File), so the job first splits the STMTTRN array into one record per element with SplitArrayField, then copies the nested values into top-level fields with CopyField and keeps only those fields with SelectFields.
Input OFX file
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<?OFX OFXHEADER="200" VERSION="211" SECURITY="NONE" OLDFILEUID="NONE" NEWFILEUID="NONE"?>
<OFX>
<SIGNONMSGSRSV1>
<SONRS>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<DTSERVER>20260131120000.000[0:GMT]</DTSERVER>
<LANGUAGE>ENG</LANGUAGE>
<FI>
<ORG>Sample Bank</ORG>
<FID>12345</FID>
</FI>
</SONRS>
</SIGNONMSGSRSV1>
<BANKMSGSRSV1>
<STMTTRNRS>
<TRNUID>1</TRNUID>
<STATUS>
<CODE>0</CODE>
<SEVERITY>INFO</SEVERITY>
</STATUS>
<STMTRS>
<CURDEF>USD</CURDEF>
<BANKACCTFROM>
<BANKID>111000025</BANKID>
<ACCTID>000123456789</ACCTID>
<ACCTTYPE>CHECKING</ACCTTYPE>
</BANKACCTFROM>
<BANKTRANLIST>
<DTSTART>20260101000000.000[0:GMT]</DTSTART>
<DTEND>20260131000000.000[0:GMT]</DTEND>
<STMTTRN>
<TRNTYPE>CREDIT</TRNTYPE>
<DTPOSTED>20260105120000.000[0:GMT]</DTPOSTED>
<TRNAMT>2500.00</TRNAMT>
<FITID>2026010500001</FITID>
<NAME>PAYROLL DEPOSIT</NAME>
</STMTTRN>
<STMTTRN>
<TRNTYPE>DEBIT</TRNTYPE>
<DTPOSTED>20260112120000.000[0:GMT]</DTPOSTED>
<TRNAMT>-86.42</TRNAMT>
<FITID>2026011200002</FITID>
<NAME>GROCERY MART</NAME>
<MEMO>Card purchase</MEMO>
</STMTTRN>
<STMTTRN>
<TRNTYPE>CHECK</TRNTYPE>
<DTPOSTED>20260120120000.000[0:GMT]</DTPOSTED>
<TRNAMT>-1200.00</TRNAMT>
<FITID>2026012000003</FITID>
<CHECKNUM>1042</CHECKNUM>
<NAME>RENT</NAME>
</STMTTRN>
</BANKTRANLIST>
<LEDGERBAL>
<BALAMT>4213.58</BALAMT>
<DTASOF>20260131000000.000[0:GMT]</DTASOF>
</LEDGERBAL>
</STMTRS>
</STMTTRNRS>
</BANKMSGSRSV1>
</OFX>
Java Code Listing
package com.northconcepts.datapipeline.examples.openfinancialexchange;
import java.io.File;
import java.io.OutputStreamWriter;
import com.northconcepts.datapipeline.core.DataReader;
import com.northconcepts.datapipeline.core.DataWriter;
import com.northconcepts.datapipeline.csv.CSVWriter;
import com.northconcepts.datapipeline.job.Job;
import com.northconcepts.datapipeline.openfinancialexchange.OpenFinancialExchangeReader;
import com.northconcepts.datapipeline.transform.CopyField;
import com.northconcepts.datapipeline.transform.SelectFields;
import com.northconcepts.datapipeline.transform.SplitArrayField;
import com.northconcepts.datapipeline.transform.TransformingReader;
public class ConvertOfxTransactionsToCsv {
private static final String TRANSACTIONS = "BANKMSGSRSV1.STMTTRNRS[0].STMTRS.BANKTRANLIST.STMTTRN";
public static void main(String[] args) {
DataReader reader = new OpenFinancialExchangeReader(new File("example/data/input/bank-statement.ofx"));
reader = new TransformingReader(reader)
.add(new SplitArrayField(TRANSACTIONS));
reader = new TransformingReader(reader)
.add(new CopyField(TRANSACTIONS + ".TRNTYPE", "type"))
.add(new CopyField(TRANSACTIONS + ".DTPOSTED", "posted"))
.add(new CopyField(TRANSACTIONS + ".TRNAMT", "amount"))
.add(new CopyField(TRANSACTIONS + ".FITID", "id"))
.add(new CopyField(TRANSACTIONS + ".NAME", "name"))
.add(new SelectFields("type", "posted", "amount", "id", "name"));
DataWriter writer = new CSVWriter(new OutputStreamWriter(System.out))
.setFieldNamesInFirstRow(true);
Job.run(reader, writer);
}
}
Code Walkthrough
TRANSACTIONSis the field path of the transaction array inside the nested record:STMTTRNRS[0]selects the first statement response andSTMTTRNis its array of transactions.- An
OpenFinancialExchangeReaderis created forexample/data/input/bank-statement.ofx. - A first
TransformingReaderappliesSplitArrayFieldto the transaction array, producing one record per transaction that holds a single element at that path. The split records are pushed back into the reader rather than continuing down the same transformer chain, which is why the next step lives in a secondTransformingReader. - The second
TransformingReaderuses fiveCopyFieldtransformers to copyTRNTYPE,DTPOSTED,TRNAMT,FITIDandNAMEout of the nested transaction into the top-level fieldstype,posted,amount,idandname.SelectFieldsthen keeps only those five fields and drops the rest of the statement. - A
CSVWriteronSystem.outwrites the records with the field names in the first row. Job.run()transfers the records from the reader chain to the writer.
Console Output
type,posted,amount,id,name CREDIT,Mon Jan 05 07:00:00 EST 2026,2500,2026010500001,PAYROLL DEPOSIT DEBIT,Mon Jan 12 07:00:00 EST 2026,-86.42,2026011200002,GROCERY MART CHECK,Tue Jan 20 07:00:00 EST 2026,-1200,2026012000003,RENT
