Menu

#264 OpenCsvReader.iterator() parses content line

v1.0 (example)
closed-rejected
None
5
2025-10-22
2025-10-06
No

I want to read a csv file line by line using a CsvToBean reader. All members of the bean-to-read are marked as mandatory.

public class TestBean
{
  @CsvBindByName(column = "COLUMN_A", required = true)
  String columnA;
  @CsvBindByName(column = "COLUMN_B", required = true)
  String columnB;
}

The input file consists of the header line and a content line where one column is empty.
When the CsvToBean.iterator() is created it throws a runtime exception with cause CsvRequiredFieldEmptyException.

  @Test
  public void testOpenCsvIterator()
  {
    Reader reader = new StringReader("COLUMN_A,COLUMN_B\n,b");
    CsvToBean<TestBean> csvReader = new CsvToBeanBuilder<TestBean>(reader) //
        .withType(TestBean.class) //
        .withSeparator(',') //
        .build();

    final Iterator<TestBean>[] iterator = new Iterator[] { null };
    assertDoesNotThrow(() -> {
      iterator[0] = csvReader.iterator();
    });
  }

The iterator() call should not read a content line. Only the first iterator.next() call should parse the content line and throw an exeption.

1 Attachments

Discussion

  • Scott Conway

    Scott Conway - 2025-10-12
    • status: open --> closed-rejected
    • assigned_to: Scott Conway
     
  • Scott Conway

    Scott Conway - 2025-10-12

    Sorry for the delay in the response but work happens :)

    My rejection is based around your last statement.

    The iterator() call should not read a content line. Only the first iterator.next() call should parse the content line and throw an exeption.

    Iterator is an interface - how it is implemented is wholly up to whomever implements the class. Which allows for the implementor to add desired features like caching/buffering.

    Besides what's the best way to implement the hasNext method? Go ahead and read the next record.

     
  • Michael Heitkamp

    Of course you are right: the implementation of the iterator() interface is up to the implementor.
    But the current implementation makes it very hard to differentiate between an error in the header line and an error in the first content line.

    file1.csv
    COLUMN_A, COLUMN_B
    ,b
    throws
    java.lang.RuntimeException: com.opencsv.exceptions.CsvRequiredFieldEmptyException: Field 'columnA' is mandatory but no value was provided.

    file2.csv
    COLUMN_A, COLUMN_C
    a, b
    throws
    java.lang.RuntimeException: Error capturing CSV header!

    Two RuntimeException which have to be parsed using the exception message. If the iterator() call would only check the header line the error handling would be much easier.

     
  • Scott Conway

    Scott Conway - 2025-10-22

    Sorry Michael for the delay again - work, family, and all the life stuff again as always :)

    I see your frustration but I do not believe it is hard to differentiate - just confusing because it happened before what you think a read has happened. But the error stated that a mandatory field is missing information.

    That said what surporised me reading the code is that internally it very specifically reads the first record after reading the header. Unfortunately it was done in a private inner class with little documentation but it was done on purpose - you can see that it was the intent be reading the code. However I cannot tell if the intent was to prime the hasNext which I alluded to in my first response or to check if the record matched the class it was being mapped to or if it was a simple sanity check or all three. But it was done on purpose so changing it would potentially break that purpose.

    Now if you are not strapped for memory I would recommend using streams.

    Modifying your code to use streams (note bean name changed because there are multiple test beans in the code <bg></bg>

       @Test
        @DisplayName("Try reading values as stream")
        public void testOpenCsvStream()
        {
            Reader reader = new StringReader("COLUMN_A,COLUMN_B\n,b");
    
            assertDoesNotThrow(() -> {
                List<IssueBug264TestBean> resultStream = new CsvToBeanBuilder<IssueBug264TestBean>(reader)
                        .withType(IssueBug264TestBean.class)
                        .build()
                        .stream()
                        .collect(Collectors.toList());
                assertTrue(resultStream.isEmpty());
            });
        }
    

    Yields a slightly more detailed error with the actual line that caused the error

        org.opentest4j.AssertionFailedError: Unexpected exception thrown: java.lang.RuntimeException: Error parsing CSV line: 2. [,b]
    
        at org.junit.jupiter.api.AssertionFailureBuilder.build(AssertionFailureBuilder.java:152)
        at org.junit.jupiter.api.AssertDoesNotThrow.createAssertionFailedError(AssertDoesNotThrow.java:84)
        at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:53)
        at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:36)
        at org.junit.jupiter.api.Assertions.assertDoesNotThrow(Assertions.java:3199)
        at integrationTest.IssueBug264.IssueBug264OriginalTest.testOpenCsvStream(IssueBug264OriginalTest.java:78)
        at java.lang.reflect.Method.invoke(Method.java:498)
        at java.util.ArrayList.forEach(ArrayList.java:1259)
        at java.util.ArrayList.forEach(ArrayList.java:1259)
    Caused by: java.lang.RuntimeException: Error parsing CSV line: 2. [,b]
        at com.opencsv.bean.concurrent.IntolerantThreadPoolExecutor.checkExceptions(IntolerantThreadPoolExecutor.java:250)
        at com.opencsv.bean.concurrent.LineExecutor.checkExceptions(LineExecutor.java:67)
        at com.opencsv.bean.concurrent.IntolerantThreadPoolExecutor.areMoreResultsAvailable(IntolerantThreadPoolExecutor.java:303)
        at com.opencsv.bean.concurrent.IntolerantThreadPoolExecutor.tryAdvance(IntolerantThreadPoolExecutor.java:313)
        at com.opencsv.bean.concurrent.LineExecutor.tryAdvance(LineExecutor.java:24)
        at java.util.Spliterator.forEachRemaining(Spliterator.java:326)
        at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:482)
        at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
        at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
        at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
        at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:566)
        at integrationTest.IssueBug264.IssueBug264OriginalTest.lambda$testOpenCsvStream$3(IssueBug264OriginalTest.java:83)
        at org.junit.jupiter.api.AssertDoesNotThrow.assertDoesNotThrow(AssertDoesNotThrow.java:49)
        ... 6 more
    Caused by: com.opencsv.exceptions.CsvRequiredFieldEmptyException: Field 'columnA' is mandatory but no value was provided.
        at com.opencsv.bean.AbstractBeanField.setFieldValue(AbstractBeanField.java:164)
        at com.opencsv.bean.AbstractMappingStrategy.setFieldValue(AbstractMappingStrategy.java:631)
        at com.opencsv.bean.AbstractMappingStrategy.populateNewBean(AbstractMappingStrategy.java:334)
        at com.opencsv.bean.concurrent.ProcessCsvLine.processLine(ProcessCsvLine.java:131)
        at com.opencsv.bean.concurrent.ProcessCsvLine.run(ProcessCsvLine.java:87)
    
     

    Last edit: Scott Conway 2025-10-22

Log in to post a comment.