絶対にはまる SuperCSV(ver2.2.1で解消済み) の罠

2015年1月リリースのSuperCSV 2.2.1 より前のバージョンには、大抵の人がはまる罠があります(長い間放置されてきました)。

新しいバージョンでは修正されていますが、いくつものプロジェクトで誤った使われ方となっているのを見てきましたので、古い SuperCSV を使っている場合には再度確認することをおすすめします。


放置され続けていたスレッドセーフの問題

CSVの読み書きの設定には、CsvPreference.STANDARD_PREFERENCE といった組み込みの設定値が用意されています。

以下のように static final で定義されており、大抵の用途に事足りる とのコメント付きです。

/**
 * Ready to use configuration that should cover 99% of all usages.
 */
public static final CsvPreference STANDARD_PREFERENCE = new CsvPreference.Builder('"', ',', "\r\n").build();

build() は以下のようになっており、新しいインスタンスが生成されます。

    public CsvPreference build() {
            
        if( encoder == null ) {
            encoder = new DefaultCsvEncoder();
        }
            
        if( quoteMode == null ) {
            quoteMode = new NormalQuoteMode();
        }
          
        return new CsvPreference(this);
    }

問題は DefaultCsvEncoder で、カラムの情報をフィールドに入れてエンコード操作を行っています。

public class DefaultCsvEncoder implements CsvEncoder {
    
    private final StringBuilder currentColumn = new StringBuilder();
    
    /**
     * Constructs a new <tt>DefaultCsvEncoder</tt>.
     */
    public DefaultCsvEncoder() {
    }

DefaultCsvEncoderSTANDARD_PREFERENCE 経由で複数スレッドで共有された場合、タイミングによって currentColumn の中身が壊れてしまいます。

そして一度壊れると何をやっても StringIndexOutOfBoundsException を投げ続けます。

CsvPreference.STANDARD_PREFERENCE の利用をやめる

初期化時には自力で build() してあげましょう。

new CsvResultSetWriter(new FileWriter("target/writeWithCsvResultSetWriter.csv"),
                        new CsvPreference.Builder('"', ',', "\r\n").build());

ver2.2.1 からは CsvPreference.STANDARD_PREFERENCE でOK

2.2.1 で修正されている。

diff はこちら。should now be thread-safe!


気付かず潜在的な問題として抱えているプロジェクトも多いかと思い、古い情報ですが書いときました。