ZetCode

Dart Pipe

last modified October 18, 2026

The Pipe class in Dart represents an anonymous operating system pipe for sending bytes in one direction. It is part of the dart:io library and is useful for passing data between isolates, processes, or different parts of an application.

A pipe has a read end and a write end. On macOS and Linux (excluding Android), either end can also be transferred to another process as a ResourceHandle.

Basic Definition

Pipe is an abstract interface class with two properties: read, a ReadPipe, and write, a WritePipe. Create a pipe with Pipe.create or Pipe.createSync.

The read end is a Stream<List<int>>, while the write end is an IOSink. Closing the write end signals end-of-file to the reader.

Basic Pipe Usage

This example creates an anonymous pipe, writes bytes to it, and reads them back.

main.dart
import 'dart:convert';
import 'dart:io';

void main() async {
  var pipe = await Pipe.create();
  var result = pipe.read.transform(utf8.decoder).join();

  pipe.write.write('Hello from a pipe!');
  await pipe.write.close();

  print(await result);
}

Pipe.create returns a Future<Pipe>. We start reading from the read end, write a string to the write end, and close it so the reader receives the end-of-file event. The future from join then completes with the decoded text.

$ dart main.dart
Hello from a pipe!

Transforming Data with Pipe

This example demonstrates applying stream transformers to the read end of a pipe.

main.dart
import 'dart:convert';
import 'dart:io';

void main() async {
  var pipe = await Pipe.create();

  var result = pipe.read
      .transform(utf8.decoder)
      .transform(const LineSplitter())
      .map((line) => 'Number $line')
      .join('\n');

  pipe.write.write('1\n2\n3\n');
  await pipe.write.close();

  print(await result);
}

The read end is a normal byte stream, so it supports transform, map, and other stream operations. Here the bytes are decoded, split into lines, and formatted before being joined for printing.

$ dart main.dart
Number 1
Number 2
Number 3

Error Handling in Pipe

This example shows how errors from a stream transformer are reported while reading from a pipe.

main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';

void main() async {
  var pipe = await Pipe.create();
  var done = Completer<void>();

  pipe.read.transform(utf8.decoder).listen(
    (data) => print('Data: $data'),
    onError: (e) => print('Error: $e'),
    onDone: () {
      print('Done');
      done.complete();
    },
  );

  pipe.write.add('Hello'.codeUnits);
  pipe.write.add([0xff]);
  await pipe.write.close();

  await done.future;
}

The read end delivers raw bytes. Passing malformed UTF-8 through utf8.decoder produces a FormatException, which is forwarded to the stream's onError handler. The stream still closes after the write end is closed.

$ dart main.dart
Data: Hello
Error: FormatException: Invalid UTF-8 byte (at offset 0)
Done

Pipe with Broadcast Streams

This example demonstrates making the pipe's read side available to multiple listeners.

main.dart
import 'dart:async';
import 'dart:convert';
import 'dart:io';

void main() async {
  var pipe = await Pipe.create();
  var stream = pipe.read.transform(utf8.decoder).asBroadcastStream();
  var done = Completer<void>();
  var remaining = 2;

  void finished() {
    if (--remaining == 0) done.complete();
  }

  stream.listen((data) => print('Listener 1: $data'), onDone: finished);
  stream.listen((data) => print('Listener 2: $data'), onDone: finished);

  pipe.write.write('hello');
  await pipe.write.close();

  await done.future;
  print('All listeners done');
}

asBroadcastStream creates a broadcast view of the read end. Each listener receives the same data, and the program waits until both listeners have seen the end of the stream.

$ dart main.dart
Listener 1: hello
Listener 2: hello
All listeners done

Closing Pipe Connections

This example shows proper cleanup of both ends of a pipe.

main.dart
import 'dart:convert';
import 'dart:io';

void main() async {
  var pipe = await Pipe.create();
  var result = pipe.read.transform(utf8.decoder).join();

  pipe.write.writeln('first');
  pipe.write.writeln('second');

  await pipe.write.close();
  print(await result);
  print('Pipe closed');
}

Closing the write end flushes buffered data and sends end-of-file to the reader. There is no separate close method on the read end; the read stream finishes when the write end is closed.

$ dart main.dart
first
second
Pipe closed

Best Practices

Source

Dart Pipe Documentation

This tutorial covered Dart's Pipe class with practical examples showing creation, stream transformations, error handling, broadcast listeners, and cleanup.

Author

My name is Jan Bodnar, and I am a passionate programmer with extensive programming experience. I have been writing programming articles since 2007. To date, I have authored over 1,400 articles and 8 e-books. I possess more than ten years of experience in teaching programming.

List all Dart tutorials.