You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
* <p><b>Java 8+ users:</b> many use cases for this method are better addressed by {@link
* java.util.stream.Stream#map}. This method is not being deprecated, but we gently encourage you
* to migrate to streams.
I know it's a gentle suggestion to use Streams, but I still find Lists.transform and other lazy transformation methods superior for a lot of usage in the wild; for example, I often see a stream used to map to a target type, only to then be passed to an addAllFoo style method of a proto builder... which is just iterating and copying into another List. That sort of use case where you know it's a one shot iteration is generally more efficiently served via these Guava helpers from Lists, Collections2, Iterables, etc. No real action item here, just hoping that the Javadoc doesn't lead people to always reach for streams, as I find the Guava helpers still have their legitimate uses.
Thanks. It would be good to write more here. Some off-the-cuff thoughts:
We have seen occasionally horrifying bugs in which people do something like transform(list, this::executeRequestAsync), in which the called method returns a Future. The problem arises when the code then iterates over the result multiple times, resulting in multiple series of queries, each of which may complete at different times with different results. This is of course impossible in the simple case that you and others typically use the APIs for, but it's a very scary caveat.
This is perhaps especially surprising because it's different from the behavior of the similarly named Futures.transform, which executes eagerly and only once.
I have some in-progress work to make some Guava streams faster in common use cases. That could at least narrow the performance gap here, if obviously not the syntax gap.
Another alternative in the specific example you presented is if course to end the stream pipeline with .forEach(builder::addFoo). But that looks relatively unnatural, and it might well be less optimized on the proto side (on top of the stream overhead itself that I hope to address).
As usual, there is some value in presenting a single, well-lit path. Our static methods get ugly as soon as you need to both transform and filter (though there's also FluentIterable...). They also deviate from what is now Java's standard naming (like transform vs. map). But that's all a question of phrasing, so I don't think we disagree on any of it; we're nudging users in an overall reasonable direction, but we aren't giving them the reasons and caveats around it.
I should really write that (and maybe more?) somewhere and link to it from the Javadoc... someday. There is potentially also opportunity for static analysis for users who want to mix and match.
I’d like to help improve the Javadocs here. I plan to add a concise explanation of when the lazy transformation APIs are preferable, the repeated-iteration/eager-vs-lazy caveat, and how the guidance relates to Streams and Futures, while keeping the existing API recommendation intact. Please let me know if this scope fits the project.
I suspect that this will be tricky to write in a way that we're happy with. The overall framing that we'll want is still going to be that Stream is the norm nowadays and that our various transform* and filter* APIs have various subtle advantages and disadvantages, which may change over time. I wouldn't want to let the perfect be the enemy of the good, but I am coming from the perspective that long documentation is overall a bad thing, so each new addition should have a moderately strong justification in order to qualify as "good" :) You can still give it a try if you want, but you can see from the already pending PRs that I am quite behind on reviews as I juggle other things, so the two possibilities here are likely "uncontroversially good enough to merge almost untouched" and "stuck waiting until I have a bunch of free time."
Thanks @cpovirk . I am curious about the performance improvements to streams you alluded to. Part of my concern around streams has to do with optimizing the hot path, where the overhead can add up.
Mainly, I want to make immutableCollection.stream().<small sequence of map and filter calls>.<collect or forEach> as fast as it can be. (Of particular note, stream().map(...).collect(toImmutableList()) can be a toArray() call, an iteration over that to perform the mapping in place, and a creation of ImmutableList directly from that array without copying.) It's been straightforward to show benefits in microbenchmarks, and I believe that they'll hold up across our fleet, but I want to get the support for various Java versions just right, and I don't have a single app I can point to that will clearly benefit enough that I've been able to prioritize it yet.
Reacted by Jonathan Bluett-Duncan and Patrick Strawderman
Activity
cpovirk commentedon Sep 5, 2026
Thanks. It would be good to write more here. Some off-the-cuff thoughts:
transform(list, this::executeRequestAsync), in which the called method returns aFuture. The problem arises when the code then iterates over the result multiple times, resulting in multiple series of queries, each of which may complete at different times with different results. This is of course impossible in the simple case that you and others typically use the APIs for, but it's a very scary caveat.Futures.transform, which executes eagerly and only once..forEach(builder::addFoo). But that looks relatively unnatural, and it might well be less optimized on the proto side (on top of the stream overhead itself that I hope to address).FluentIterable...). They also deviate from what is now Java's standard naming (liketransformvs.map). But that's all a question of phrasing, so I don't think we disagree on any of it; we're nudging users in an overall reasonable direction, but we aren't giving them the reasons and caveats around it.I should really write that (and maybe more?) somewhere and link to it from the Javadoc... someday. There is potentially also opportunity for static analysis for users who want to mix and match.
seanxuu commentedon Sep 7, 2026
I’d like to help improve the Javadocs here. I plan to add a concise explanation of when the lazy transformation APIs are preferable, the repeated-iteration/eager-vs-lazy caveat, and how the guidance relates to Streams and Futures, while keeping the existing API recommendation intact. Please let me know if this scope fits the project.
cpovirk commentedon Sep 10, 2026
I suspect that this will be tricky to write in a way that we're happy with. The overall framing that we'll want is still going to be that
Streamis the norm nowadays and that our varioustransform*andfilter*APIs have various subtle advantages and disadvantages, which may change over time. I wouldn't want to let the perfect be the enemy of the good, but I am coming from the perspective that long documentation is overall a bad thing, so each new addition should have a moderately strong justification in order to qualify as "good" :) You can still give it a try if you want, but you can see from the already pending PRs that I am quite behind on reviews as I juggle other things, so the two possibilities here are likely "uncontroversially good enough to merge almost untouched" and "stuck waiting until I have a bunch of free time."kilink commentedon Sep 10, 2026
Thanks @cpovirk . I am curious about the performance improvements to streams you alluded to. Part of my concern around streams has to do with optimizing the hot path, where the overhead can add up.
cpovirk commentedon Sep 10, 2026
Mainly, I want to make
immutableCollection.stream().<small sequence of map and filter calls>.<collect or forEach>as fast as it can be. (Of particular note,stream().map(...).collect(toImmutableList())can be atoArray()call, an iteration over that to perform the mapping in place, and a creation ofImmutableListdirectly from that array without copying.) It's been straightforward to show benefits in microbenchmarks, and I believe that they'll hold up across our fleet, but I want to get the support for various Java versions just right, and I don't have a single app I can point to that will clearly benefit enough that I've been able to prioritize it yet.