DataOutputStream
DataOutputStream wraps any OutputStream and adds methods for writing Java primitive types in a machine-independent binary format using big-endian byte order. It writes boolean, byte, short, int, long, float, double, and char values using fixed byte widths, and strings using a modified UTF-8 encoding with a 2-byte length prefix. Every value written by DataOutputStream can be read back by DataInputStream using the corresponding read method, making them the standard pair for binary serialization of primitive data across files, sockets, pipes, and inter-process communication. DataOutputStream is unbuffered — each write call propagates to the underlying OutputStream — so it should always be wrapped over a BufferedOutputStream for file or network I/O. DataOutputStream tracks a written byte counter via the size() method, which returns the total bytes written since construction (wraps at Integer.MAX_VALUE). This entry covers all write methods and their byte-level encoding, size() semantics, the writeUTF contract and its limitations, flush() behavior, composition with BufferedOutputStream, and patterns for building binary protocol messages.
Construction, Write Methods, and Byte Encoding
// ── Construction: always wrap with BufferedOutputStream ───────────────
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("data.bin")))) {
// writes accumulate in 8192-byte buffer
}
// ── Writing all primitive types ───────────────────────────────────────
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("primitives.bin")))) {
dos.writeBoolean(true); // 1 byte: value 1
dos.writeBoolean(false); // 1 byte: value 0
dos.writeByte(127); // 1 byte: 0x7F (low-order 8 bits of int)
dos.writeByte(-1); // 1 byte: 0xFF
dos.writeShort(1000); // 2 bytes big-endian: 0x03 0xE8
dos.writeShort(-1); // 2 bytes: 0xFF 0xFF
dos.writeChar('A'); // 2 bytes big-endian: 0x00 0x41
dos.writeChar('€'); // 2 bytes big-endian: 0x20 0xAC (U+20AC)
dos.writeInt(0x12345678); // 4 bytes: 0x12 0x34 0x56 0x78
dos.writeInt(-1); // 4 bytes: 0xFF 0xFF 0xFF 0xFF
dos.writeLong(Long.MAX_VALUE);// 8 bytes: 0x7F 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF 0xFF
dos.writeFloat(3.14f); // 4 bytes IEEE 754: Float.floatToIntBits(3.14f)
dos.writeDouble(Math.PI); // 8 bytes IEEE 754: Double.doubleToLongBits(π)
System.out.println("Written: " + dos.size() + " bytes"); // total written so far
}
// ── Byte layout of writeInt(0x12345678) ──────────────────────────────
// Byte 0 (MSB): 0x12
// Byte 1: 0x34
// Byte 2: 0x56
// Byte 3 (LSB): 0x78
// Big-endian: most significant byte written first (network byte order)
// ── Little-endian via ByteBuffer ─────────────────────────────────────
// For BMP, WAV, AVI, Windows PE, ELF, and other little-endian formats:
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("le_data.bin")))) {
// Encode int 0x12345678 in little-endian:
ByteBuffer bb = ByteBuffer.allocate(4).order(ByteOrder.LITTLE_ENDIAN);
bb.putInt(0x12345678);
dos.write(bb.array()); // writes: 0x78 0x56 0x34 0x12 (little-endian)
// Batch encoding: encode multiple fields at once
ByteBuffer multi = ByteBuffer.allocate(16).order(ByteOrder.LITTLE_ENDIAN);
multi.putInt(42);
multi.putLong(System.currentTimeMillis());
multi.putInt(100);
dos.write(multi.array());
}
// ── writeBytes: ASCII-only legacy method — avoid for Unicode ──────────
dos.writeBytes("ASCII only"); // LOW BYTE ONLY — truncates Unicode silently
// 'é' (U+00E9) → writes 0xE9 only (one byte) — data loss for high bytes > 0x7F
// NEVER use writeBytes for Unicode content
// For UTF-8 string writing with explicit length:
byte[] utf8Bytes = "Hello, 世界!".getBytes(StandardCharsets.UTF_8);
dos.writeInt(utf8Bytes.length); // 4-byte length prefix
dos.write(utf8Bytes); // followed by UTF-8 byteswriteUTF(), size(), flush(), and the DataOutputStream/DataInputStream Contract
// ── writeUTF: DataOutputStream's modified UTF-8 format ────────────────
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream("strings.bin")));
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("strings.bin")))) {
// Write:
dos.writeUTF("Hello"); // 2-byte length (5) + 5 bytes
dos.writeUTF("世界"); // 2-byte length (6) + 6 bytes (3 bytes per CJK char)
dos.writeUTF("Hello, 世界!"); // 2-byte length + encoded bytes
dos.flush(); // ensure all bytes reach the file
// Read back — exactly matches what was written:
System.out.println(dis.readUTF()); // "Hello"
System.out.println(dis.readUTF()); // "世界"
System.out.println(dis.readUTF()); // "Hello, 世界!"
}
// ── writeUTF limitations ─────────────────────────────────────────────
// Max 65535 bytes in modified UTF-8 (NOT max 65535 characters):
String tooLong = "A".repeat(65536); // 65536 ASCII chars = 65536 bytes
try {
dos.writeUTF(tooLong); // throws UTFDataFormatException: string too long
} catch (UTFDataFormatException e) {
System.out.println("String too long for writeUTF: " + e.getMessage());
}
// For long strings: write 4-byte length + raw UTF-8 bytes
byte[] utf8 = tooLong.getBytes(StandardCharsets.UTF_8);
dos.writeInt(utf8.length); // 4-byte length — handles strings up to 2GB
dos.write(utf8); // raw UTF-8 bytes (standard format)
// ── size(): total bytes written since construction ────────────────────
ByteArrayOutputStream baos = new ByteArrayOutputStream();
DataOutputStream counter = new DataOutputStream(baos);
counter.writeInt(42); // 4 bytes
counter.writeDouble(3.14); // 8 bytes
counter.writeUTF("Hi"); // 2 (length) + 2 (bytes) = 4 bytes
System.out.println("Written: " + counter.size() + " bytes"); // 16 bytes
System.out.println("Buffer size: " + baos.size() + " bytes"); // also 16 bytes
// size() counts ALL bytes, including multi-byte writes:
ByteArrayOutputStream baos2 = new ByteArrayOutputStream();
DataOutputStream dos2 = new DataOutputStream(baos2);
dos2.writeLong(0L); // 8 bytes
dos2.writeLong(0L); // 8 bytes more
dos2.write(new byte[100]); // 100 bytes more
System.out.println(dos2.size()); // 116
// ── flush() propagation chain ─────────────────────────────────────────
try (DataOutputStream dos3 = new DataOutputStream(
new BufferedOutputStream(
new FileOutputStream("flushed.bin")))) {
dos3.writeInt(42);
dos3.writeLong(123456789L);
// At this point: bytes in BufferedOutputStream's internal buffer (not on disk)
dos3.flush();
// After flush: BufferedOutputStream.flush() writes buffer to FileOutputStream
// FileOutputStream makes OS system call — bytes in OS kernel buffer
// NOT guaranteed on disk yet (OS may still buffer)
// For disk durability:
// ((FileOutputStream)((BufferedOutputStream)dos3.out).out).getFD().sync();
// (requires accessing the FileDescriptor through the chain)
}Binary Protocol Serialization and Message Building Patterns
// ── Two-phase message building: serialize body first, then frame ───────
public static void sendMessage(DataOutputStream networkDos, int msgType, Object payload)
throws IOException {
// Phase 1: serialize payload to byte array to get its length
ByteArrayOutputStream bodyBaos = new ByteArrayOutputStream(256);
DataOutputStream bodyDos = new DataOutputStream(bodyBaos);
if (payload instanceof LoginRequest lr) {
bodyDos.writeInt(lr.userId());
bodyDos.writeShort(lr.flags());
byte[] nameBytes = lr.username().getBytes(StandardCharsets.UTF_8);
bodyDos.writeShort(nameBytes.length); // 2-byte length prefix
bodyDos.write(nameBytes); // UTF-8 bytes
} else if (payload instanceof TradeRequest tr) {
bodyDos.writeLong(tr.timestamp());
bodyDos.writeInt(tr.symbolId());
bodyDos.writeLong(Math.round(tr.price() * 10000)); // fixed-point
bodyDos.writeInt(tr.quantity());
}
bodyDos.flush();
byte[] body = bodyBaos.toByteArray();
// Phase 2: write frame header + body to network
networkDos.writeInt(msgType); // 4-byte type
networkDos.writeInt(body.length); // 4-byte length
networkDos.write(body); // body bytes
networkDos.flush(); // ensure TCP sends the complete message
}
// ── ByteBuffer alternative for big-endian binary serialization ────────
public static byte[] serializeTrade(long timestamp, int symbolId,
double price, int quantity) {
ByteBuffer buf = ByteBuffer.allocate(24); // 8+4+8+4 bytes
buf.order(ByteOrder.BIG_ENDIAN); // match DataOutputStream
buf.putLong(timestamp);
buf.putInt(symbolId);
buf.putLong(Math.round(price * 10000)); // fixed-point
buf.putInt(quantity);
return buf.array(); // ready to write to any OutputStream
}
// ── Writing a complete binary file format ─────────────────────────────
// Simple custom binary format: [8-byte magic][4-byte version][4-byte record_count][records...]
// Each record: [8-byte id][4-byte value][2-byte tag_length][N-byte tag]
public static void writeDataFile(Path path, List<DataRecord> records) throws IOException {
try (DataOutputStream dos = new DataOutputStream(
new BufferedOutputStream(new FileOutputStream(path.toFile()), 65536))) {
// File header:
dos.writeLong(0x4A415641_44415441L); // "JAVADATA" magic bytes
dos.writeInt(1); // format version 1
dos.writeInt(records.size()); // record count
// Records:
for (DataRecord rec : records) {
dos.writeLong(rec.id());
dos.writeInt(rec.value());
byte[] tagBytes = rec.tag().getBytes(StandardCharsets.UTF_8);
dos.writeShort(tagBytes.length); // 2-byte tag length (max 65535 bytes)
dos.write(tagBytes);
}
System.out.printf("Wrote %d records, %d total bytes%n",
records.size(), dos.size());
}
}
// ── Reading back the same format with DataInputStream ─────────────────
public static List<DataRecord> readDataFile(Path path) throws IOException {
List<DataRecord> records = new ArrayList<>();
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream(path.toFile()), 65536))) {
// Verify magic:
long magic = dis.readLong();
if (magic != 0x4A415641_44415441L) throw new IOException("Not a JAVADATA file");
int version = dis.readInt();
if (version != 1) throw new IOException("Unsupported version: " + version);
int count = dis.readInt();
for (int i = 0; i < count; i++) {
long id = dis.readLong();
int value = dis.readInt();
int tagLen = dis.readUnsignedShort();
byte[] tagBytes = new byte[tagLen];
dis.readFully(tagBytes);
String tag = new String(tagBytes, StandardCharsets.UTF_8);
records.add(new DataRecord(id, value, tag));
}
}
return records;
}