If you’re working with Spring, check out "REST With Spring":

>> CHECK OUT THE COURSE

1. Overview

In this quick article, we explain different ways of merging Java Streams – which is not a very intuitive operation.

2. Using Plain Java

The JDK 8 Stream class has some useful static utility methods. Let’s take a closer look at the concat() method.

2.1. Merging Two Streams

The simplest way to combine 2 Streams is to use the static Stream.concat() method:

@Test
public void whenMergingStreams_thenResultStreamContainsElementsFromBoth() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);

    Stream<Integer> resultingStream = Stream.concat(stream1, stream2);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6),
      resultingStream.collect(Collectors.toList()));
}

2.2. Merging Multiple Streams

When we need to merge more than 2 Streams, things become a bit more complex. One possibility is to concatenate the first two streams, then concatenate the result with the next one and so on.

The next code snippet shows this in action:

@Test
public void given3Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);

    Stream<Integer> resultingStream = Stream.concat(
      Stream.concat(stream1, stream2), stream3);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36),
      resultingStream.collect(Collectors.toList()));
}

As we can see, this approach becomes unfeasible for more streams. Of course, we can create intermediate variables or helper methods to make it more readable, but here is a better option:

@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);
    Stream<Integer> stream4 = Stream.of(99);

    Stream<Integer> resultingStream = Stream.of(
      stream1, stream2, stream3, stream4)
      .flatMap(i -> i);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
      resultingStream.collect(Collectors.toList()));
}

What happens here is:

  • We first create a new Stream containing the 4 Streams, which results in a Stream<Stream<Integer>>
  • Then we flatMap() this into a Stream<Integer> using the identity function

3. Using StreamEx

StreamEx is an open-source Java library that extends possibilities of Java 8 Streams. It uses the StreamEx class as an enhancement to the JDK’s Stream interface.

3.1. Merging Streams

The StreamEx library allows us to merge streams using the append() instance method:

@Test
public void given4Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> stream1 = Stream.of(1, 3, 5);
    Stream<Integer> stream2 = Stream.of(2, 4, 6);
    Stream<Integer> stream3 = Stream.of(18, 15, 36);
    Stream<Integer> stream4 = Stream.of(99);

    Stream<Integer> resultingStream = StreamEx.of(stream1)
      .append(stream2)
      .append(stream3)
      .append(stream4);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6, 18, 15, 36, 99),
      resultingStream.collect(Collectors.toList()));
}

Since it is an instance method, we can easily chain it and append multiple streams.

Note that we could also create a List out of the stream by using toList() if we type the resultingStream variable to the StreamEx type.

3.2. Merging Streams Using prepend()

StreamEx also contains a method that adds elements before one another called prepend():

@Test
public void given3Streams_whenPrepended_thenResultStreamContainsAllElements() {
    Stream<String> stream1 = Stream.of("foo", "bar");
    Stream<String> openingBracketStream = Stream.of("[");
    Stream<String> closingBracketStream = Stream.of("]");

    Stream<String> resultingStream = StreamEx.of(stream1)
      .append(closingBracketStream)
      .prepend(openingBracketStream);

    assertEquals(
      Arrays.asList("[", "foo", "bar", "]"),
      resultingStream.collect(Collectors.toList()));
}

4. Using jOOλ

jOOλ is a JDK 8 compatible library that provides useful extensions to the JDK. The most important stream abstraction here is called Seq. Note that this is a sequential and ordered stream, so calling parallel() will have no effect.

4.1. Merging Streams

Just like the StreamEx library, jOOλ has an append() method:

@Test
public void given2Streams_whenMerged_thenResultStreamContainsAllElements() {
    Stream<Integer> seq1 = Stream.of(1, 3, 5);
    Stream<Integer> seq2 = Stream.of(2, 4, 6);

    Stream<Integer> resultingSeq = Seq.ofType(seq1, Integer.class)
      .append(seq2);

    assertEquals(
      Arrays.asList(1, 3, 5, 2, 4, 6),
      resultingSeq.collect(Collectors.toList()));
}

Also, there is a convenience toList() method if we type the resultingSeq variable to the jOOλ Seq type.

4.2. Merging Streams with prepend()

As expected, since an append() method exists, there is also a prepend() method in jOOλ:

@Test
public void given3Streams_whenPrepending_thenResultStreamContainsAllElements() {
    Stream<String> seq = Stream.of("foo", "bar");
    Stream<String> openingBracketSeq = Stream.of("[");
    Stream<String> closingBracketSeq = Stream.of("]");

    Stream<String> resultingStream = Seq.ofType(seq, String.class)
      .append(closingBracketSeq)
      .prepend(openingBracketSeq);

    Assert.assertEquals(
      Arrays.asList("[", "foo", "bar", "]"),
      resultingStream.collect(Collectors.toList()));
}

5. Conclusion

We saw that merging streams is relatively straightforward using JDK 8. When we need to do a lot of merging, it might be beneficial to use the StreamEx or jOOλ library for the sake of readability.

You can find the source code on GitHub.

The new Certification Class of "REST With Spring" is finally out:

>> CHECK OUT THE COURSE