Reusable single instance wrapper/object in Java stream map

You can have some handy functions and also can have thread-safe version to work with parallel.

Function<T,U> threadSafeReusableWrapper(Supplier<U> newWrapperInstanceFn, BiConsumer<U,T> wrapFn) {
   final ThreadLocal<T> wrapperStorage = ThreadLocal.withInitial(newWrapperInstanceFn);
   return item -> {
      T wrapper = wrapperStorage.get();
      wrapFn.consume(wrapper, item);
      return wrapper;
   }
}

Function<T,U> reusableWrapper(U wrapper, BiConsumer<U,T> wrapFn) {
   return item -> {
      wrapFn.consume(wrapper, item);
      return wrapper;
   };
}

list.stream()
    .map(reusableWrapper(new Wrapper(), Wrapper::setSource))
    .forEach( w -> processWrapper(w) );
list.stream()
    .map(threadSafeReusableWrapper(Wrapper::new, Wrapper::setSource))
     .parallel()
    .forEach( w -> processWrapper(w) );

However, I do not think it is worth. These wrappers are short-living so unlikely leave young generation so will be garbage collected very quickly. Though, I think this idea worth checking with micro-benchmark library JMH


Although it is possible, referring to an object outside the stream makes the code less functional in style. A very close equivalent that is better encapsulated can be achieved simply with a helper function:

public class Context {

    private static final Wrapper WRAPPER = new Wrapper();

    private static void helper(Source source) {
        WRAPPER.setSource(source);
        processWrapper(WRAPPER);
    }

    public static void main(String[] args) {
        List<Source> list = Arrays.asList(new Source("Foo"), new Source("Baz"), new Source("Bar"));
        list.stream().forEach(Context::helper);
}

Your approach happens to work because the stream pipeline only consists of stateless operation. In such constellations, the sequential stream evaluation may process one element at a time, so accesses to wrapper instances do not overlap, like illustrated here. But note that this is not a guaranteed behavior.

It definitely doesn’t work with stateful operations like sorted and distinct. It also can’t work with reduction operations, as they always have to hold at least two elements for processing, which includes reduce, min, and max. In the case of collect, it depends on the particular Collector. forEachOrdered wouldn’t work with parallel streams, due to the required buffering.

Note that parallel processing would be problematic even when you use TheadLocal to create thread confined wrappers, as there is no guaranty that objects created in one worker thread stay local to that thread. A worker thread may hand over a partial result to another thread before picking up another, unrelated workload.

So this shared mutable wrapper works with a particular set of stateless operations, like map, filter, forEach, findFirst/Any, all/any/noneMatch, in a sequential execution of a particular implementation. You don’t get the flexibility of the API, as you have to limit yourself, can’t pass the stream to arbitrary code expecting a Stream nor use arbitrary Collector implementations. You also don’t have the encapsulation of the interface, as you are assuming particular implementation behavior.

In other words, if you want to use such a mutable wrapper, you are better off with a loop implementing the particular operation. You do already have the disadvantages of such a manual implementation, so why not implementing it to have the advantages.


The other aspect to consider is, what you gain from reusing such a mutable wrapper. It only works in loop-like usages where a temporary object might get optimized away after applying Escape Analysis anyway. In such scenarios, reusing objects, extending their lifetime, may actually degrade performance.

Of course, Object Scalarization is not a guaranteed behavior. There might be scenarios, like a long stream pipeline exceeding the JVM’s inlining limit, where the objects don’t get elided. But still, temporary objects are not necessarily expensive.

This has been explained in this answer. Temporary objects are cheaply allocated. The main costs of a garbage collection are caused by objects which are still alive. These need to be traversed and these need to be moved when making room for new allocations. The negative impact of temporary objects is that they may shorten the time between garbage collection rounds. But this is a function of allocation rate and available allocation space, so this is truly a problem that can be solved by throwing more RAM at it. More RAM means more time between GC cycles and more dead objects when GC happens, which makes the net costs of the GC smaller.

Still, avoiding excessive allocations of temporary objects is a valid concern. The existence of IntStream, LongStream, and DoubleStream shows that. But these are special, as using primitive types is a viable alternative to using the wrapper objects without the disadvantages of reusing a mutable wrapper. It’s also different because it’s applied to problems where the primitive type and the wrapper type are semantically equivalent. In contrast, you want to solve a problem where the operation requires the wrapper type. For the primitive stream also applies, when you need the objects for your problem, there is no way around boxing, which will create distinct objects for distinct values, not sharing a mutable object.

So if you similarly have a problem where a semantically equivalent wrapper-object-avoiding alternative without substantial problems exists, like just using Comparator.comparingInt instead of Comparator.comparing where feasible, you may still prefer it. But only then.


In short, most of the time, the savings of such object reuse, if any, will not justify the disadvantages. In special cases, where it’s beneficial and matters, you may be better off with a loop or any other construct under your full control, instead of using a Stream.