Skip to content
Java io 5 min read

SequenceInputStream

SequenceInputStream lets you read from several input streams one after another as if they were a single continuous stream. Think of it like combining multiple audio tracks into a playlist — each stream plays in order, and the stitching is invisible to the reader.

What is SequenceInputStream?

SequenceInputStream is a class in the java.io package that extends InputStream. It takes two or more input streams and concatenates them logically. When the first stream is exhausted, reading continues seamlessly from the second, then the third, and so on.

This is useful when you need to:

  • Merge the contents of multiple files without loading them all into memory first
  • Assemble a response from several byte-stream fragments
  • Process a sequence of resources as one logical unit

Note: SequenceInputStream is a byte-stream class. For text-based concatenation, consider using BufferedReader with multiple readers instead.

Constructors

SequenceInputStream provides two constructors:

ConstructorDescription
SequenceInputStream(InputStream s1, InputStream s2)Chains exactly two streams: reads s1 first, then s2
SequenceInputStream(Enumeration<? extends InputStream> e)Chains any number of streams supplied via an Enumeration

Combining Two Streams

The simplest use case chains two streams directly.

import java.io.*;

public class TwoStreamDemo {
    public static void main(String[] args) throws IOException {
        // Create two in-memory byte streams for demonstration
        InputStream first  = new ByteArrayInputStream("Hello, ".getBytes());
        InputStream second = new ByteArrayInputStream("World!".getBytes());

        try (SequenceInputStream sis = new SequenceInputStream(first, second)) {
            int b;
            while ((b = sis.read()) != -1) {
                System.out.print((char) b);
            }
            System.out.println();
        }
    }
}

Output:

Hello, World!

The try-with-resources block automatically closes the SequenceInputStream, which in turn closes both underlying streams.

Chaining More Than Two Streams

To combine three or more streams, use the Enumeration-based constructor. Collections.enumeration() converts any List to an Enumeration.

import java.io.*;
import java.util.*;

public class MultiStreamDemo {
    public static void main(String[] args) throws IOException {
        List<InputStream> streams = List.of(
            new ByteArrayInputStream("Line 1\n".getBytes()),
            new ByteArrayInputStream("Line 2\n".getBytes()),
            new ByteArrayInputStream("Line 3\n".getBytes())
        );

        Enumeration<InputStream> enumeration = Collections.enumeration(streams);

        try (SequenceInputStream sis = new SequenceInputStream(enumeration)) {
            byte[] buffer = new byte[64];
            int bytesRead;
            while ((bytesRead = sis.read(buffer)) != -1) {
                System.out.print(new String(buffer, 0, bytesRead));
            }
        }
    }
}

Output:

Line 1
Line 2
Line 3

Using a buffer (here byte[64]) is more efficient than reading one byte at a time with read().

Merging Files on Disk

A practical real-world example: concatenating two log files and writing the merged result to a third file.

import java.io.*;
import java.util.*;

public class MergeFiles {
    public static void main(String[] args) throws IOException {
        // Suppose log1.txt and log2.txt already exist on disk
        InputStream log1 = new FileInputStream("log1.txt");
        InputStream log2 = new FileInputStream("log2.txt");

        try (SequenceInputStream sis   = new SequenceInputStream(log1, log2);
             FileOutputStream     fos  = new FileOutputStream("merged.txt");
             BufferedOutputStream bos  = new BufferedOutputStream(fos)) {

            byte[] buffer = new byte[4096];
            int bytesRead;
            while ((bytesRead = sis.read(buffer)) != -1) {
                bos.write(buffer, 0, bytesRead);
            }
            System.out.println("Files merged successfully.");
        }
    }
}

Tip: Wrap your FileOutputStream in a BufferedOutputStream to reduce the number of native write calls and boost performance on large files.

Available and Skip Methods

Like other InputStream subclasses, SequenceInputStream supports available() and skip():

import java.io.*;

public class AvailableDemo {
    public static void main(String[] args) throws IOException {
        InputStream s1 = new ByteArrayInputStream(new byte[]{10, 20, 30});
        InputStream s2 = new ByteArrayInputStream(new byte[]{40, 50});

        try (SequenceInputStream sis = new SequenceInputStream(s1, s2)) {
            System.out.println("Available: " + sis.available()); // reports from current stream only
            sis.skip(2);
            System.out.println("After skip(2), next byte: " + sis.read()); // 30
        }
    }
}

Output:

Available: 3
After skip(2), next byte: 30

Warning: available() on a SequenceInputStream only queries the current underlying stream, not the total remaining bytes across all chained streams. Do not rely on it for the combined total.

Under the Hood

Understanding how SequenceInputStream works internally helps you use it correctly.

State Machine

Internally, SequenceInputStream holds:

  • A reference to the current InputStream (in)
  • An Enumeration<? extends InputStream> of remaining streams

When you call read(), it delegates to the current stream. As soon as that stream returns -1 (end-of-stream), SequenceInputStream calls nextElement() on the enumeration to advance to the next stream, closes the exhausted stream, and retries the read. This cycle repeats until all streams are exhausted, at which point -1 is returned to the caller.

The Two-Argument Constructor

When you use SequenceInputStream(s1, s2), the JDK implementation wraps both streams in a two-element Vector and calls elements() on it to get the required Enumeration. No magic — it’s sugar over the Enumeration constructor.

Closing Behavior

Calling close() on a SequenceInputStream closes the current active stream and discards the remaining enumeration. Any streams not yet started are never explicitly closed. If you need deterministic cleanup of all streams, close them individually in a finally block or use try-with-resources on each FileInputStream you created.

// Safer pattern: keep references and close them yourself
InputStream a = new FileInputStream("a.txt");
InputStream b = new FileInputStream("b.txt");
try (SequenceInputStream sis = new SequenceInputStream(a, b)) {
    // read ...
} finally {
    a.close(); // safe to call even if already closed
    b.close();
}

No mark/reset Support

SequenceInputStream does not support mark() and reset(). Calling markSupported() always returns false. If you need mark/reset, wrap the result in a BufferedInputStream after it has been fully buffered, or collect bytes into a ByteArrayInputStream first.

Thread Safety

SequenceInputStream is not thread-safe. If multiple threads share a single instance, external synchronization is required.

Quick Reference

MethodBehavior
int read()Reads one byte; advances to next stream on EOF
int read(byte[] b, int off, int len)Bulk read; crosses stream boundaries
int available()Available bytes in the current stream only
long skip(long n)Skips bytes within the current stream
void close()Closes current stream, discards remaining enumeration
boolean markSupported()Always false
Last updated June 13, 2026
Was this helpful?