Do I have to synchronize on a List that is read by a stream?

Yes, you have to synchronize access to the list when using streams in the same way you would do without streams. Synchronization should be taken care of by a user.

A stream by itself doesn't guarantee to create any copy of the original sequence. It can make a copy during some intermediate computations (e.g. sort), but you should not rely on that. And doing this for each usage of a stream would be a waste of resources since streams are not reusable.

If a user wants a stream to operate over a copy, they have to manually create a copy or use CopyOnWriteArrayList instead of ArrayList, for example.

Moreover, keep in mind that streams are lazy. The underlying sequence is not accessed until a terminal operation (e.g. collect, forEach) is executed.


Stream operations use spliterator() method internally.

Here is the spliterator() method from ArrayList:

    public Spliterator<E> spliterator() {
        checkForComodification();
        return new ArrayListSpliterator<E>(ArrayList.this, offset,
                                           offset + this.size, this.modCount);
    }

It checks for comodification, so it looks like stream() operations have to be inside synchronized blocks in your case.

Also, spliterator() of SynchronizedCollection (in Collections) has comment

    public Spliterator<E> spliterator() {
        return c.spliterator(); // Must be manually synched by user!
    }

which is analogous to the comment in iterator():

    public Iterator<E> iterator() {
        return c.iterator(); // Must be manually synched by user!
    }

which shows the same: synchronization is needed around stream() operations (at least, if iterator() requires such a synchronization).

And the most convincing: stream() method from SynchronizedCollection:

    public Stream<E> stream() {
        return c.stream(); // Must be manually synched by user!
    }