Streams provide the most direct access to the binary content, so any [InputStream](<https://docs.oracle.com/javase/7/docs/api/java/io/InputStream.html>) / [OutputStream](<https://docs.oracle.com/javase/7/docs/api/java/io/OutputStream.html>) implementations always operate on ints and bytes.

// Read a single byte from the stream
int b = inputStream.read();
if (b >= 0) { // A negative value represents the end of the stream, normal values are in the range 0 - 255
    // Write the byte to another stream
    outputStream.write(b);
}

// Read a chunk
byte[] data = new byte[1024];
int nBytesRead = inputStream.read(data);
if (nBytesRead >= 0) { // A negative value represents end of stream
    // Write the chunk to another stream
    outputStream.write(data, 0, nBytesRead);
}

There are some exceptions, probably most notably the [PrintStream](<https://docs.oracle.com/javase/7/docs/api/java/io/PrintStream.html>) which adds the “ability to print representations of various data values conveniently”. This allows to use [System.out](<https://docs.oracle.com/javase/7/docs/api/java/lang/System.html#out>) both as a binary InputStream and as a textual output using methods such as System.out.println().

Also, some stream implementations work as an interface to higher-level contents such as Java objects (see Serialization) or native types, e.g. [DataOutputStream](<https://docs.oracle.com/javase/7/docs/api/java/io/DataOutputStream.html>) / [DataInputStream](<https://docs.oracle.com/javase/7/docs/api/java/io/DataInputStream.html>).

With the [Writer](<https://docs.oracle.com/javase/7/docs/api/java/io/Writer.html>) and [Reader](<https://docs.oracle.com/javase/7/docs/api/java/io/Reader.html>) classes, Java also provides an API for explicit character streams. Although most applications will base these implementations on streams, the character stream API does not expose any methods for binary content.

// This example uses the platform's default charset, see below
// for a better implementation.

Writer writer = new OutputStreamWriter(System.out);
writer.write("Hello world!");

Reader reader = new InputStreamReader(System.in);
char singleCharacter = reader.read();

Whenever it is necessary to encode characters into binary data (e.g. when using the InputStreamWriter / OutputStreamWriter classes), you should specify a charset if you do not want to depend on the platform’s default charset. When in doubt, use a Unicode-compatible encoding, e.g. UTF-8 which is supported on all Java platforms. Therefore, you should probably stay away from classes like FileWriter and FileReader as those always use the default platform charset. A better way to access files using character streams is this:

Charset myCharset = StandardCharsets.UTF_8;

Writer writer = new OutputStreamWriter( new FileOutputStream("test.txt"), myCharset );
writer.write('Ä');
writer.flush();
writer.close();

Reader reader = new InputStreamReader( new FileInputStream("test.txt"), myCharset );
char someUnicodeCharacter = reader.read();
reader.close();

One of the most commonly used Readers is BufferedReader which provides a method to read whole lines of text from another reader and is presumably the simplest way to read a character stream line by line:

// Read from baseReader, one line at a time
BufferedReader reader = new BufferedReader( baseReader );
String line;
while((line = reader.readLine()) != null) {
  // Remember: System.out is a stream, not a writer!
  System.out.println(line);
}