Java Stream | Produces Duplicate Records After .map() or .forEach()

Viewed 205

I'm currently working on an order submission feature for a medical product. Its very simple, what I have to do is just:

  1. query multiple tables (*source of the issue, tables joined without using unique key and this is out of our control, causing the query result with duplicate of same record)
  2. combine data of all 3 tables into a single payload to submit to manufacturer/supplier
  3. Extra step to take, loop the list of all orders retrieved from step 2 and insert orderNo based on today date + counter starting from 1

The Problem: After running stream().map() of the 10 records I got (i.e. containing 2 sets of duplicates due to query issue as stated above), I would be expecting the order number to be [yyyyMMdd0001,yyyyMMdd0002,yyyyMMdd0003,yyyyMMdd0004,yyyyMMdd0005,yyyyMMdd0006,yyyyMMdd0007,yyyyMMdd0008,yyyyMMdd0009,yyyyMMdd0010], However what I really got was [yyyyMMdd0009,yyyyMMdd0010,yyyyMMdd0003,yyyyMMdd0004,yyyyMMdd0005,yyyyMMdd0006,yyyyMMdd0007,yyyyMMdd0008,yyyyMMdd0009,yyyyMMdd0010]

After checking, I found that the purchaseNo which was missing, i.e. yyyyMMdd0001, yyyyMMdd0002 was the duplicate copy of yyyyMMdd0009 and yyyyMMdd0010 but I did not expect that the record would be replaced like this.

Example Code When stream map through the list to insert purchaseNo:

AtomicInteger counter = new AtomicInteger(1);
return orderRepository.retrieveOrderByDate(date)
           .stream()
           .map(order -> {
             var currentCount = String.format("%06d", counter.getAndIncrement());
             var purchaseNo = LocalDate.now().format(DATE_FORMAT) + currentCount;
             order.setPurchaseNo(purchaseNo);
             return order;
           }).collect(toList());

Here is roughly what the Order Entity looks like:

public class OrderEntity implments Serializable {
    @Transient
    private String purchaseNo;

    @Id
    @Column(name="product_code")
    private String productCode;

    @Id
    @Column(name="recipient_id")
    private String recipientId;

    //many others recipient and product and shipment related fields
}

I have tried and found the same even when using .forEach() and at this stage assumed that this is the behaviour of stream() in general... but still dont quite understand it.

Would like to get help to understand this behavior from using Java Stream. Thanks!

1 Answers

It is usually a bad idea to utilize a map function with a stateful access to your objects.

I usually recommend using functional methods in a functional way ( i.e: [Static List] -> Stream Lambdas [*] -> New [Static List] result ) which is the common functional practice.

For the scenario above, if we really want to do it in that method we should need to introduce a SynchronizedList (or) Set to handle it.

From Java Docs: link

Stream pipeline results may be nondeterministic or incorrect if the behavioral parameters to the stream operations are stateful. A stateful lambda (or other object implementing the appropriate functional interface) is one whose result depends on any state which might change during the execution of the stream pipeline. An example of a stateful lambda is the parameter to map() in:

 Set<Integer> seen = Collections.synchronizedSet(new HashSet<>());
 stream.parallel().map(e -> { if (seen.add(e)) return 0; else return e; })...   Here, if the mapping operation is performed in

parallel, the results for the same input could vary from run to run, due to thread scheduling differences, whereas, with a stateless lambda expression the results would always be the same. Note also that attempting to access mutable state from behavioral parameters presents you with a bad choice with respect to safety and performance; if you do not synchronize access to that state, you have a data race and therefore your code is broken, but if you do synchronize access to that state, you risk having contention undermine the parallelism you are seeking to benefit from. The best approach is to avoid stateful behavioral parameters to stream operations entirely; there is usually a way to restructure the stream pipeline to avoid statefulness.

Related