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.
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.
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.
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.
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.
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
- Close the write end: Call
pipe.write.close()so the reader sees end-of-file - Consume the read end: Listen to or await the read stream to avoid blocking the writer
- Handle errors: Attach an
onErrorhandler when decoding or transforming bytes - Use broadcast views carefully:
asBroadcastStreamlets multiple listeners share the data - Transfer only when needed: Passing a pipe end to another process consumes the underlying handle
Source
This tutorial covered Dart's Pipe class with practical examples
showing creation, stream transformations, error handling, broadcast listeners,
and cleanup.
Author
List all Dart tutorials.