DataInputStream
When you need to read typed data — integers, doubles, booleans, strings — from a binary file or network socket, casting raw bytes manually is tedious and error-prone. DataInputStream gives you named readInt(), readDouble(), readUTF(), and other methods that handle the byte-to-type conversion for you, always in a portable big-endian format.
What DataInputStream Does
DataInputStream is part of java.io and extends FilterInputStream, so it wraps any existing InputStream and layers a typed reading API on top. It implements the DataInput interface, which defines the contract shared with RandomAccessFile.
Its primary design goal is to be the read counterpart to DataOutputStream. Data written with DataOutputStream is guaranteed to be readable by DataInputStream, regardless of the operating system or CPU architecture, because both use big-endian byte order (most significant byte first).
Note:
DataInputStreamis strictly for binary data written in Java’s portable format. It is not suitable for reading arbitrary binary files produced by C programs or other languages, which may use little-endian order or different struct layouts.
Constructors
There is only one constructor:
DataInputStream(InputStream in)
You pass any InputStream — a FileInputStream, a socket’s input stream, a ByteArrayInputStream, or anything else. For file-based I/O, wrapping with a BufferedInputStream first is a good idea to reduce system calls:
DataInputStream dis = new DataInputStream(
new BufferedInputStream(new FileInputStream("data.bin")));
Key Read Methods
| Method | Returns | Bytes Read |
|---|---|---|
readBoolean() | boolean | 1 |
readByte() | byte | 1 |
readShort() | short | 2 |
readChar() | char | 2 |
readInt() | int | 4 |
readLong() | long | 8 |
readFloat() | float | 4 |
readDouble() | double | 8 |
readUTF() | String | variable |
readFully(byte[]) | void | byte[].length |
All methods throw EOFException (a subclass of IOException) if the stream ends before enough bytes are available — so you always know the difference between “end of data” and a genuine read error.
Basic Example: Write and Read Primitives
The most common pattern is to pair DataOutputStream with DataInputStream. Here is a self-contained example that writes several primitive values to a byte array in memory and then reads them back:
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
public class DataStreamDemo {
public static void main(String[] args) throws IOException {
// --- Write ---
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try (DataOutputStream dos = new DataOutputStream(baos)) {
dos.writeInt(42);
dos.writeDouble(3.14);
dos.writeBoolean(true);
dos.writeUTF("Hello, DataInputStream!");
}
// --- Read ---
byte[] bytes = baos.toByteArray();
try (DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(bytes))) {
int i = dis.readInt();
double d = dis.readDouble();
boolean b = dis.readBoolean();
String s = dis.readUTF();
System.out.println("int: " + i);
System.out.println("double: " + d);
System.out.println("boolean: " + b);
System.out.println("String: " + s);
}
}
}
Output:
int: 42
double: 3.14
boolean: true
String: Hello, DataInputStream!
Warning: You must read values back in exactly the same order they were written. If you call
readDouble()where anintwas written, you will get garbage data with no error — the stream has no type metadata, only raw bytes.
Reading from a File
In practice, you often write a binary file once and read it many times. Here is a pattern for reading an existing binary file that was created by DataOutputStream:
import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadBinaryFile {
public static void main(String[] args) throws IOException {
try (DataInputStream dis = new DataInputStream(
new BufferedInputStream(
new FileInputStream("scores.dat")))) {
// Assume the file contains: player name (UTF), score (int), time (double)
while (dis.available() > 0) {
String name = dis.readUTF();
int score = dis.readInt();
double time = dis.readDouble();
System.out.printf("%-15s %5d %.2fs%n", name, score, time);
}
}
}
}
Tip:
available()tells you how many bytes can be read without blocking, but it is not a reliable end-of-stream check for all stream types (especially network sockets). For files, it works well. For robust code, catchEOFExceptioninstead and treat it as normal termination.
readFully() — Guaranteed Reads
Ordinary read(byte[]) is not guaranteed to fill the entire array in one call (the OS may return fewer bytes). readFully(byte[] b) keeps reading until the array is completely filled or throws EOFException:
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
public class ReadFullyDemo {
public static void main(String[] args) throws IOException {
try (DataInputStream dis = new DataInputStream(
new FileInputStream("header.bin"))) {
byte[] header = new byte[16];
dis.readFully(header); // guaranteed to fill all 16 bytes
System.out.println("Header read: " + header.length + " bytes");
}
}
}
Output:
Header read: 16 bytes
This is especially useful when you need to parse fixed-size binary headers — for example, image file headers or custom protocol frames.
readUTF() — Modified UTF-8
readUTF() reads a string encoded in Java’s modified UTF-8 format. The encoding stores a 2-byte length prefix (number of bytes, not characters) followed by the encoded characters. This is not standard UTF-8 — it encodes null characters (\u0000) as two bytes instead of one, so the resulting byte sequence is never zero-padded.
// Writing
dos.writeUTF("café"); // writes 2-byte length + encoded bytes
// Reading back
String s = dis.readUTF(); // "café"
You should only use readUTF() to read strings written by writeUTF(). For general UTF-8 decoding from external sources, use InputStreamReader with StandardCharsets.UTF_8.
readUnsignedByte() and readUnsignedShort()
Java does not have unsigned primitive types, but binary protocols often use unsigned values. DataInputStream provides two helpers:
import java.io.DataInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
public class UnsignedDemo {
public static void main(String[] args) throws IOException {
byte[] data = {(byte) 200, (byte) 0xFF, (byte) 0x80};
try (DataInputStream dis = new DataInputStream(
new ByteArrayInputStream(data))) {
int ub1 = dis.readUnsignedByte(); // 200 (not -56)
int ub2 = dis.readUnsignedByte(); // 255 (not -1)
int ub3 = dis.readUnsignedByte(); // 128 (not -128)
System.out.println(ub1 + ", " + ub2 + ", " + ub3);
}
}
}
Output:
200, 255, 128
readUnsignedByte() returns an int in the range 0–255. readUnsignedShort() returns an int in the range 0–65535. These are pure convenience — they just mask the sign bits that Java’s signed types would otherwise extend.
Under the Hood
Big-Endian Byte Order
When DataInputStream.readInt() executes, it reads exactly 4 bytes from the underlying stream and reassembles them as:
value = (b0 << 24) | (b1 << 16) | (b2 << 8) | b3
This is network byte order (big-endian), consistent with java.net and many binary protocols. If you need little-endian reading (e.g., for Windows BMP or WAV files), use java.nio.ByteBuffer with ByteOrder.LITTLE_ENDIAN instead.
No Buffering of Its Own
DataInputStream performs no buffering. Every call to readInt() triggers four read() calls on the underlying stream. On an unbuffered FileInputStream, those become four separate system calls. That is why wrapping in a BufferedInputStream before passing to DataInputStream is almost always worthwhile:
FileInputStream → BufferedInputStream → DataInputStream
(OS layer) (memory buffer) (typed API)
FilterInputStream Inheritance
DataInputStream extends FilterInputStream, which stores the wrapped stream in a protected field called in. All read() calls delegate to in.read(...). This is the classic Decorator pattern from the Gang of Four — each wrapper layer adds behaviour without changing the interface.
Thread Safety
DataInputStream is not thread-safe. If multiple threads share one instance, interleaved reads will corrupt the data. Each thread should use its own stream instance, or access must be externally synchronized.
Handling EOFException
When reading a stream of unknown length, the idiomatic Java pattern is to catch EOFException:
import java.io.DataInputStream;
import java.io.EOFException;
import java.io.FileInputStream;
import java.io.IOException;
public class EOFHandling {
public static void main(String[] args) {
try (DataInputStream dis = new DataInputStream(
new FileInputStream("numbers.dat"))) {
while (true) {
int value = dis.readInt(); // throws EOFException at end
System.out.println(value);
}
} catch (EOFException e) {
System.out.println("Finished reading.");
} catch (IOException e) {
e.printStackTrace();
}
}
}
EOFException is a subclass of IOException, so you can also let the outer catch handle it — but catching it separately makes the intent clear.
DataInputStream vs Other Stream Classes
| Feature | DataInputStream | BufferedInputStream | ObjectInputStream |
|---|---|---|---|
| Reads primitives | Yes | No | Yes |
| Reads objects | No | No | Yes |
| Buffering | No (delegate) | Yes | Internal |
| Use case | Typed binary data | Raw byte performance | Full object graphs |
If you need to read entire serialized Java objects, see Object Streams. For reading text line by line, BufferedReader is the right tool.
Related Topics
- DataOutputStream — the matching writer; data written here is read by DataInputStream
- BufferedInputStream — add buffering before DataInputStream for better file performance
- Object Streams — read and write full Java objects, not just primitives
- Byte vs Character Streams — understand when to use byte streams versus character streams
- FileInputStream — the raw file byte stream that DataInputStream typically wraps
- RandomAccessFile — also implements DataInput, but lets you seek to any position in the file