would the flatMap(..) approach have the same limitation?

The documentation for flatMap says that the stream it returns is the result of replacing each element of the old stream with the contents of the stream returned by the mapping function, when applied on that element. This wording suggests (to me) that there isn't any nesting of the streams, even though it isn't very clear about this. When the pipeline receives an element, it just needs to apply the function to it, and call forEach on the stream returned, doing whatever downstream thing it needs to do.

With concat however, there is clearly nesting. Each concat creates a Stream that has 2 parts. When you have a stream pipeline that is formed by repeated concating, downstream code will be executed for each element in the first part first, then for each element of in the second part. But since this is a heavily nested concat, one of the parts is also divided into two parts, and one of those parts also has two parts... To access the most deeply nested element, you need to go through a lot of calls.

Here's some code that illustrates this.

Suppose we have the streams a to g:

var a = Stream.of(1);
var b = Stream.of(2);
var c = Stream.of(3);
var d = Stream.of(4);
var e = Stream.of(5);
var f = Stream.of(6);
var g = Stream.of(7);

We can concat them using all three ways and print the stack trace length:

// 1
Stream.of(a, b, c, d, e, f, g).flatMap(Function.identity()).forEachOrdered(x -> {
  System.out.println(new Exception().getStackTrace().length);
});

// 2
Stream.concat(Stream.concat(Stream.concat(Stream.concat(Stream.concat(Stream.concat(a, b), c), d), e), f), g)
    .forEachOrdered(x -> {
      System.out.println(new Exception().getStackTrace().length);
    });

// 3
Stream.of(a, b, c, d, e, f, g).reduce(Stream::concat).get().forEachOrdered(x -> {
  System.out.println(new Exception().getStackTrace().length);
});

(Note that you should run only one of these at a time.)

On my machine, (1) prints 13 seven times no matter how many streams I pass into of. Both (2) and (3) prints:

10
10
9
8
7
6
5

The first elements 1 and 2 are the most deeply nested, which is why they have the longest stack trace.

Answer from Sweeper on Stack Overflow
🌐
GeeksforGeeks
geeksforgeeks.org › java › stream-concat-java
Stream.concat() in Java - GeeksforGeeks
January 23, 2026 - Stream.concat(stream1, stream2) combines both streams into one.
🌐
Medium
medium.com › @AlexanderObregon › javas-stream-concat-method-explained-4b5fab338fa2
Java’s Stream.concat() Method Explained | Medium
February 3, 2025 - The Stream.concat() method is used to merge two separate streams into a single stream. It does not modify the original streams but creates a new stream that processes elements from both input streams in sequence.
🌐
Baeldung
baeldung.com › home › java › java streams › merging streams in java
Merging Streams in Java | Baeldung
June 24, 2026 - The simplest way to combine 2 Streams is to use the static Stream.concat() method:
🌐
HowToDoInJava
howtodoinjava.com › home › java 8 › java 8 stream.concat(): how to combine streams?
Java 8 Stream.concat(): How to Combine Streams?
May 27, 2024 - The Java 8 Stream.concat() method merges two streams into one stream. The combined stream consists of all the elements of both streams.
Top answer
1 of 1
6

would the flatMap(..) approach have the same limitation?

The documentation for flatMap says that the stream it returns is the result of replacing each element of the old stream with the contents of the stream returned by the mapping function, when applied on that element. This wording suggests (to me) that there isn't any nesting of the streams, even though it isn't very clear about this. When the pipeline receives an element, it just needs to apply the function to it, and call forEach on the stream returned, doing whatever downstream thing it needs to do.

With concat however, there is clearly nesting. Each concat creates a Stream that has 2 parts. When you have a stream pipeline that is formed by repeated concating, downstream code will be executed for each element in the first part first, then for each element of in the second part. But since this is a heavily nested concat, one of the parts is also divided into two parts, and one of those parts also has two parts... To access the most deeply nested element, you need to go through a lot of calls.

Here's some code that illustrates this.

Suppose we have the streams a to g:

var a = Stream.of(1);
var b = Stream.of(2);
var c = Stream.of(3);
var d = Stream.of(4);
var e = Stream.of(5);
var f = Stream.of(6);
var g = Stream.of(7);

We can concat them using all three ways and print the stack trace length:

// 1
Stream.of(a, b, c, d, e, f, g).flatMap(Function.identity()).forEachOrdered(x -> {
  System.out.println(new Exception().getStackTrace().length);
});

// 2
Stream.concat(Stream.concat(Stream.concat(Stream.concat(Stream.concat(Stream.concat(a, b), c), d), e), f), g)
    .forEachOrdered(x -> {
      System.out.println(new Exception().getStackTrace().length);
    });

// 3
Stream.of(a, b, c, d, e, f, g).reduce(Stream::concat).get().forEachOrdered(x -> {
  System.out.println(new Exception().getStackTrace().length);
});

(Note that you should run only one of these at a time.)

On my machine, (1) prints 13 seven times no matter how many streams I pass into of. Both (2) and (3) prints:

10
10
9
8
7
6
5

The first elements 1 and 2 are the most deeply nested, which is why they have the longest stack trace.

🌐
TechEmpower
techempower.com › home › efficient multiple-stream concatenation in java
Efficient multiple-stream concatenation in Java – TechEmpower
December 11, 2023 - It is a bit much to inline in a blog article, so take a look at StreamConcatenation.java for the source code. This implementation is similar to Stream.concat(a, b) in that it uses a custom Spliterator, except this implementation handles any number of input streams.
🌐
Medium
medium.com › @playwithjava › concatenation-in-java-streams-9b87114411ef
concatenation in Java Streams - Playwithjava - Medium
August 21, 2023 - static <T> Stream<T> concat(Stream<? extends T> a, Stream<? extends T> b) Creates a lazily concatenated stream whose elements are all the elements of the first stream followed by all the elements of the second stream
🌐
Netjstech
netjstech.com › 2022 › 01 › java-stream-concat-with-examples.html
Java Stream - concat() With Examples | Tech Tutorials
March 17, 2026 - The concat() method in the Java Stream API allows you to merge two streams into a single stream. It takes two streams as arguments and returns a new stream containing all elements of the first stream followed by all elements of the second.
Find elsewhere
🌐
Coderwall
coderwall.com › p › im4lja › joining-objects-into-a-string-with-java-8-stream-api
Joining Objects into a String with Java 8 Stream API (Example)
July 17, 2023 - #stream · You can leverage Java 8 Collectors to concatenate some objects into a string separated by a delimiter: For example: List<Integer> numbers = Arrays.asList( 4, 8, 15, 16, 23, 42 ); return numbers.stream() .map( n -> n.toString() ) .collect( ...
Top answer
1 of 8
179

Unfortunately this answer is probably of little or no help whatsoever, but I did a forensics analysis of the Java Lambda Mailing list to see if I could find the cause of this design. This is what I found out.

In the beginning there was an instance method for Stream.concat(Stream)

In the mailing list I can clearly see the method was originally implemented as an instance method, as you can read in this thread by Paul Sandoz, about the concat operation.

In it they discuss the issues that could arise from those cases in which the stream could be infinite and what concatenation would mean in those cases, but I do not think that was the reason for the modification.

You see in this other thread that some early users of the JDK 8 questioned about the behavior of the concat instance method when used with null arguments.

This other thread reveals, though, that the design of the concat method was under discussion.

Refactored to Streams.concat(Stream,Stream)

But without any explanation, suddenly, the methods were changed to static methods, as you can see in this thread about combining streams. This is perhaps the only mail thread that sheds a bit of light about this change, but it was not clear enough for me to determine the reason for the refactoring. But we can see they did a commit in which they suggested to move the concat method out of Stream and into the helper class Streams.

Refactored to Stream.concat(Stream,Stream)

Later, it was moved again from Streams to Stream, but yet again, no explanation for that.

So, bottom line, the reason for the design is not entirely clear for me and I could not find a good explanation. I guess you could still ask the question in the mailing list.

Some Alternatives for Stream Concatenation

This other thread by Michael Hixson discusses/asks about other ways to combine/concat streams

  1. To combine two streams, I should do this:

    Stream.concat(s1, s2)
    

    not this:

    Stream.of(s1, s2).flatMap(x -> x)
    

    ... right?

  2. To combine more than two streams, I should do this:

    Stream.of(s1, s2, s3, ...).flatMap(x -> x)
    

    not this:

    Stream.of(s1, s2, s3, ...).reduce(Stream.empty(), Stream::concat)
    

    ... right?

2 of 8
129

If you add static imports for Stream.concat and Stream.of, the first example could be written as follows:

Stream<Foo> stream = concat(stream1, concat(stream2, of(element)));

Importing static methods with generic names can result in code that becomes difficult to read and maintain (namespace pollution). So, it might be better to create your own static methods with more meaningful names. However, for demonstration I will stick with this name.

public static <T> Stream<T> concat(Stream<? extends T> lhs, Stream<? extends T> rhs) {
    return Stream.concat(lhs, rhs);
}
public static <T> Stream<T> concat(Stream<? extends T> lhs, T rhs) {
    return Stream.concat(lhs, Stream.of(rhs));
}

With these two static methods (optionally in combination with static imports), the two examples could be written as follows:

Stream<Foo> stream = concat(stream1, concat(stream2, element));

Stream<Foo> stream = concat(
                         concat(stream1.filter(x -> x!=0), stream2).filter(x -> x!=1),
                         element)
                     .filter(x -> x!=2);

The code is now significantly shorter. However, I agree that the readability hasn't improved. So I have another solution.


In a lot of situations, Collectors can be used to extend the functionality of streams. With the two Collectors at the bottom, the two examples could be written as follows:

Stream<Foo> stream = stream1.collect(concat(stream2)).collect(concat(element));

Stream<Foo> stream = stream1
                     .filter(x -> x!=0)
                     .collect(concat(stream2))
                     .filter(x -> x!=1)
                     .collect(concat(element))
                     .filter(x -> x!=2);

The only difference between your desired syntax and the syntax above is, that you have to replace concat(...) with collect(concat(...)). The two static methods can be implemented as follows (optionally used in combination with static imports):

private static <T,A,R,S> Collector<T,?,S> combine(Collector<T,A,R> collector, Function<? super R, ? extends S> function) {
    return Collector.of(
        collector.supplier(),
        collector.accumulator(),
        collector.combiner(),
        collector.finisher().andThen(function));
}
public static <T> Collector<T,?,Stream<T>> concat(Stream<? extends T> other) {
    return combine(Collectors.toList(),
        list -> Stream.concat(list.stream(), other));
}
public static <T> Collector<T,?,Stream<T>> concat(T element) {
    return concat(Stream.of(element));
}

Of course there is a drawback with this solution that should be mentioned. collect is a final operation that consumes all elements of the stream. On top of that, the collector concat creates an intermediate ArrayList each time it is used in the chain. Both operations can have a significant impact on the behaviour of your program. However, if readability is more important than performance, it might still be a very helpful approach.

🌐
Javaprogramto
javaprogramto.com › 2020 › 08 › java-stream-concat.html
Java 8 Stream concat() - How To Merge Two Streams or More JavaProgramTo.com
August 18, 2020 - Next, learn how to merge more than 2 streams using concat() chaining concept. The objective is to club and collect all stream values into a single stream. The below program does merge 5 streams into a single stream. Observe that used static imports to make the code readable. import static java.util.stream.Stream.concat; import java.util.stream.Stream; public class MultipleStreamMergeExample { public static void main(String[] args) { // Creating stream 1 Stream<Integer> stream1 = Stream.of(1, 2, 3); // Creating stream 2 Stream<Integer> stream2 = Stream.of(4, 5, 6); // Creating stream 3 Stream<I
🌐
GeeksforGeeks
geeksforgeeks.org › java › intstream-concat-java
IntStream concat() in Java - GeeksforGeeks
July 11, 2025 - IntStream.concat(IntStream.concat(stream1, stream2), stream3); IntStream.concat(IntStream.concat(stream2, stream1), stream3); Example 1 : ... // Implementation of IntStream.concat() // method in Java 8 with 2 IntStreams import java.util.*; import java.util.stream.IntStream; import java.util.stream.Stream; class GFG { // Driver code public static void main(String[] args) { // Creating two IntStreams IntStream stream1 = IntStream.of(2, 4, 6); IntStream stream2 = IntStream.of(1, 3, 5); // concatenating both the Streams // with IntStream.concat() function // and displaying the result IntStream.concat(stream1, stream2) .forEach(element -> System.out.println(element)); } }
🌐
TutorialsPoint
tutorialspoint.com › stream-concat-in-java
Stream.concat() in Java
Following is an example to implement the concat() method of the Stream class − · import java.util.*; import java.util.stream.IntStream; import java.util.stream.Stream; public class Demo { public static void main(String[] args) { Stream<String> streamOne = Stream.of("John"); Stream<String> streamTwo = Stream.of("Tom"); Stream.concat(streamOne, streamTwo).forEach(val -> System.out.println(val)); } }
🌐
KapreSoft
kapresoft.com › java › 2023 › 12 › 24 › java-guide-to-stream-concatenation.html
Java • Guide to Stream Concatenation | KapreSoft
December 24, 2023 - While Stream.concat is a good general-purpose tool, flatMap and custom collectors offer more flexibility for complex scenarios. Similarly, alternatives like using the Collections API or Iterable<Stream<T» can be more suitable for certain situations, especially when working with a large number of streams or when stream processing needs to be integrated with non-stream APIs. To finalize, Java’s stream API, with its various advanced techniques and alternatives, provides a powerful toolkit for developers to efficiently handle data concatenation and manipulation, enabling more readable, maintainable, and efficient code.
🌐
LabEx
labex.io › tutorials › java-how-to-concatenate-streams-in-java-462122
How to concatenate streams in Java | LabEx
By understanding these stream basics, developers can leverage Java's functional programming capabilities effectively in their LabEx projects and real-world applications. Stream concatenation allows combining multiple streams into a single stream, ...
🌐
Educative
educative.io › answers › what-is-the-concat-method-of-stream-interface
What is the concat() method of Stream Interface?
The concat() method is a static method of the Stream Interface that can be used to merge two streams into a single stream.
🌐
Tabnine
tabnine.com › home › code library
Code Library - Tabnine
July 25, 2024 - Get the answers and suggestions you need from our AI code assistant. Get started in minutes with a free 90 day trial of Tabnine Pro.
🌐
GeeksforGeeks
geeksforgeeks.org › java › concat-method-of-stream-interface-in-java-api
concat() Method of Stream Interface in Java API - GeeksforGeeks
July 23, 2025 - Since at a time only two streams ... the idea that first, we concatenate two of the streams and then concatenate that result with another stream so that lastly all the streams are concatenated....
🌐
Baeldung
baeldung.com › home › java › java collections › java – combine multiple collections
Java - Combine Multiple Collections | Baeldung
May 11, 2024 - The static method concat() combines two Streams logically by creating a lazily concatenated Stream whose elements are all the elements of the first Stream followed by all the elements of the second Stream.
🌐
ConcretePage
concretepage.com › java › jdk-8 › java-8-concat-streams-lists-sets-arrays-example
Java 8 Concat Streams, Lists, Sets, Arrays Example
Stream provides concat() method to concatenate two streams and will return a stream. To concatenate Lists, Sets and Arrays we will convert them into stream first and using concat() we will combine them.