Java I/O and NIO.2 practice questions

From OCP Java SE 25 (1Z0-831) · 15 questions on this topic

Java I/O and NIO.2 practice questions from OCP Java SE 25 (1Z0-831). This pack has 15 questions tagged Java I/O and NIO.2, drawn from its timed mock exams. 8 of them are worked through in full below — the question, every option, why each is right or wrong, and the explanation.

Worked examples for Java I/O and NIO.2

  1. Question 1

    Which two statements about Java serialization and stream buffering are correct? (Choose two.)

    1. A. Declaring a private static final long serialVersionUID lets a class control serialization version compatibility across changesCorrect answer

      serialVersionUID is an optional private static final long that pins a class's serialization version; matching IDs let a changed class deserialize old data, while mismatched IDs cause InvalidClassException, making it the developer's version-compatibility control.

    2. B. Bytes written to a BufferedOutputStream may stay in the buffer and not reach the destination until flush() or close() is calledCorrect answer

      A BufferedOutputStream accumulates bytes in memory and only forwards them to the wrapped stream when the buffer fills, or when flush() or close() is called; without one of those, buffered bytes can be lost.

    3. C. Marking a field static causes it to be written as part of each object's serialized state

      static fields belong to the class, not to any instance, so they are never part of an object's serialized state; only instance, non-transient fields are written.

    4. D. A class must implement Externalizable before ObjectOutputStream can serialize it

      Implementing Serializable is sufficient; Externalizable is an optional alternative that gives full manual control, not a prerequisite for serialization.

    Explanation

    Default serialization writes only an object's instance, non-transient fields; static and transient fields are excluded. serialVersionUID is an optional marker that pins the serialization version so a changed class can still read old data, while a mismatch raises InvalidClassException. Separately, buffered output streams hold bytes until the buffer fills or an explicit flush/close occurs, which is exactly why the try-with-resources idiom (whose close flushes) is the safe pattern.

  2. Question 2

    The string "café" is encoded to UTF-8 bytes, then those bytes are read back through an InputStreamReader using UTF-8 and the characters are counted. What does this print? ```java import java.io.*; import java.nio.charset.*; public class Main { public static void main(String[] args) throws IOException { byte[] bytes = "café".getBytes(StandardCharsets.UTF_8); int chars = 0; try (Reader r = new InputStreamReader(new ByteArrayInputStream(bytes), StandardCharsets.UTF_8)) { while (r.read() != -1) { chars++; } } System.out.println(bytes.length + " " + chars); } } ```

    1. A. 4 4

      This assumes one byte per character; that holds only for pure ASCII, not for the accented character é, which needs two UTF-8 bytes.

    2. B. 4 5

      Both numbers are backwards; there are more bytes than characters here, not fewer.

    3. C. 5 5

      This correctly counts 5 bytes but forgets that decoding recombines the two é bytes into a single character, so the character count is 4, not 5.

    4. D. 5 4Correct answer

      In UTF-8 the ASCII letters c, a, f take one byte each while é (U+00E9) needs two, so "café" encodes to 5 bytes; reading them back through an InputStreamReader set to UTF-8 decodes the two-byte sequence into the single character é, yielding 4 characters. Output: 5 4.

    Explanation

    In UTF-8, ASCII characters occupy one byte each while é (U+00E9) requires two, so the four-character string encodes to five bytes. An InputStreamReader applies the charset and decodes the two-byte sequence back into a single character, so the character count is four while the byte count is five. Outside pure ASCII, byte count and character count diverge, and an InputStreamReader/OutputStreamWriter is the bridge that applies the charset.

  3. Question 3

    What does Path.of("a/b").relativize(Path.of("a/b/c/d")) print? ```java import java.nio.file.*; public class Main { public static void main(String[] args) { System.out.println(Path.of("a/b").relativize(Path.of("a/b/c/d")).toString().replace('\\', '/')); } } ```

    1. A. /c/d

      /c/d is an absolute path; relativize produces a relative result, never one with a root.

    2. B. ../../c/d

      The two upward steps would only appear if the receiver had elements NOT shared with the target; here the receiver is a pure prefix of the target, so no upward steps are needed.

    3. C. c/dCorrect answer

      relativize builds the relative route from the receiver to the argument. The target a/b/c/d sits directly beneath a/b, sharing the whole a/b prefix, so only the descent into the extra elements c and d remains, giving c/d.

    4. D. a/b/c/d

      a/b/c/d is the target itself, which is what you would append with resolve, not the route computed by relativize.

    Explanation

    relativize computes the relative route that leads from the receiver to the argument, the inverse of resolve. When the receiver is a pure prefix of the target, no upward steps are required and the result is simply the leftover tail. Upward .. steps appear only for the receiver elements that the target does not share, and the result is always relative rather than rooted.

  4. Question 4

    What does this print? ```java public class Main { static class Res implements AutoCloseable { final String name; Res(String n) { this.name = n; System.out.print("open " + n + " "); } public void close() { System.out.print("close " + name + " "); } } public static void main(String[] args) { try (Res x = new Res("A"); Res y = new Res("B")) { System.out.print("body "); } System.out.println(); } } ```

    1. A. open A open B body close A close B

      This closes in declaration order (close A close B); try-with-resources always closes in reverse of declaration.

    2. B. open A open B body

      This omits the close calls entirely; close() runs automatically at the end of the block even on the normal (non-exception) path.

    3. C. open B open A body close A close B

      This reverses the construction order (open B open A); resources are constructed in the order written, and only closing is reversed.

    4. D. open A open B body close B close ACorrect answer

      Resources are created left to right (open A open B), the body runs, then resources close in the reverse of declaration order, so B closes before A: close B close A.

    Explanation

    Resources in a try-with-resources statement are constructed in the order they are declared, left to right. At the end of the block they are closed in the reverse of that order, last opened first closed, mirroring nested try-finally blocks. Closing happens automatically whether the block completes normally or exits via an exception.

  5. Question 5

    A two-line document is held in memory as UTF-8 bytes and read back line by line through a BufferedReader. Note the extra readLine() call after the loop. What is the result? ```java import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; public class Main { public static void main(String[] args) throws IOException { byte[] data = "alpha\nbeta\n".getBytes(StandardCharsets.UTF_8); try (BufferedReader br = new BufferedReader( new InputStreamReader(new ByteArrayInputStream(data), StandardCharsets.UTF_8))) { int lines = 0; while (br.readLine() != null) { lines++; } System.out.println(lines + " " + br.readLine()); } } } ```

    1. A. 2 nullCorrect answer

      readLine treats the line terminator as a separator, so the two-line text is exactly two lines, and a further readLine at end of stream returns null.

    2. B. 3 null

      Assumes the trailing newline starts a third empty line; the terminator ends the last line rather than beginning a new one, so there are two lines.

    3. C. Throws IOException

      Assumes reading past the end throws; readLine returns null at end of stream instead of throwing.

    4. D. 2 beta

      Assumes the extra readLine replays the last line; once the stream is exhausted readLine keeps returning null.

    Explanation

    BufferedReader.readLine strips the line terminator and treats it as a separator, so a trailing newline ends the final line rather than creating an additional empty one. At end of stream readLine returns null and continues to return null on every subsequent call rather than throwing or replaying content.

  6. Question 6

    The reader is marked after the first character has already been consumed. What does this print? ```java import java.io.BufferedReader; import java.io.IOException; import java.io.StringReader; public class Main { public static void main(String[] args) throws IOException { try (BufferedReader br = new BufferedReader(new StringReader("abcdef"))) { System.out.print((char) br.read()); br.mark(5); System.out.print((char) br.read()); System.out.print((char) br.read()); br.reset(); System.out.print((char) br.read()); br.skip(2); System.out.println((char) br.read()); } } } ```

    1. A. abcbeCorrect answer

      read() consumes `a`; mark(5) records the current position (on `b`), not the stream start; the next two reads print `b` and `c`; reset() rewinds to the mark so read prints `b` again; skip(2) discards `c` and `d`; the final read prints `e` — abcbe.

    2. B. abcde

      Treats mark()/reset() as no-ops that never move the cursor, so it reads straight through the string; reset() really does rewind to the marked position.

    3. C. abcad

      Believes reset() rewinds to the beginning of the stream rather than to the mark, so it re-reads `a`; the mark is a bookmark at the current cursor, and reset() returns to it, not to position zero.

    4. D. abcbd

      Gets the rewind right but reads skip(2) as advancing one position; skip(n) discards n characters outright, so after re-reading `b` the skip eats `c` and `d` and the next character is `e`.

    Explanation

    Trace, position by position. The first read() consumes `a` and leaves the cursor on `b`. mark(5) records THAT position — the current one, not the start of the stream — and promises to keep at most 5 more characters available for rewinding. The next two reads print `b` and `c`, leaving the cursor on `d`. reset() rewinds to the marked position, i.e. back onto `b`, so the fourth read prints `b` again and the cursor lands on `c`. skip(2) then discards `c` and `d`, and the final read prints `e`. Concatenated: `abcbe`. Why the others are wrong: `abcad` believes reset() rewinds to the beginning of the stream rather than to the mark, so it re-reads `a`, then skips `b` and `c` and reads `d`. mark() is a bookmark at the current cursor, and reset() goes to the bookmark — not to position zero. `abcde` treats mark()/reset() as no-ops that only manage bookkeeping and never move the cursor, so it just reads straight through the string. `abcbd` gets the rewind right but reads skip(2) as "advance to the second character from here", i.e. as skipping one. skip(n) discards n characters outright: after re-reading `b` the cursor is on `c`, so skip(2) eats `c` and `d` and the next character is `e`. Exam tip: mark(readAheadLimit) bookmarks the position you are at right now, and reset() returns to it — the argument is not a position, it is only the number of characters the stream guarantees to buffer before the mark is allowed to become invalid. The reverse trap: mark/reset is optional, so on a stream whose markSupported() is false (a plain InputStreamReader over a network stream, for example) reset() throws IOException — BufferedReader and StringReader both support it, which is why this code runs.

  7. Question 7

    No file system is touched here — this is pure path arithmetic. (The replace call only normalises the platform separator so the answer reads the same everywhere.) What is printed? ```java import java.nio.file.Path; public class Main { public static void main(String[] args) { Path p = Path.of("/a/b").resolve("/x/y").resolveSibling("z"); System.out.println(p.toString().replace('\\', '/')); } } ```

    1. A. /a/b/x/y/z

      Assumes resolve always appends; resolving against an absolute path returns that absolute path, discarding the base.

    2. B. /a/b/x/z

      Assumes resolve appends the absolute path and resolveSibling appends the final name; resolve returns the absolute path, and resolveSibling replaces the last name.

    3. C. /x/y/z

      Assumes resolveSibling appends its argument; it replaces the final name element instead.

    4. D. /x/zCorrect answer

      resolve returns the absolute path unchanged, then resolveSibling replaces the last name element, giving the sibling path.

    Explanation

    Path.resolve short-circuits when its argument is absolute, returning that argument and discarding the base path. Path.resolveSibling replaces the final name element rather than appending, producing a sibling path. Both operations are purely lexical and never consult the file system.

  8. Question 8

    No file system is touched here — Path.of() is pure path arithmetic, and a single name element never contains a separator, so the output reads the same on every platform. What does this print? ```java import java.nio.file.Path; public class Main { public static void main(String[] args) { Path p = Path.of("/data//logs/", "app.log"); System.out.println(p.getNameCount() + " " + p.getName(0) + " " + p.getName(p.getNameCount() - 1)); } } ```

    1. A. 4 data app.log

      Assumes the doubled separator in // produces an empty name element between data and logs; the path parser collapses repeated separators during construction, so no empty element exists.

    2. B. 4 / app.log

      Encodes both halves of the classic error: that the root counts toward getNameCount() and sits at index 0; on an absolute path the root is reachable only through getRoot(), and name elements start after it.

    3. C. 3 /data app.log

      Gets the count right but assumes the first name element carries the leading separator; a name element is a bare single component (data), never /data.

    4. D. 3 data app.logCorrect answer

      Correct: the separators collapse and the trailing one is dropped, leaving three name elements data, logs, app.log; the root is invisible to getNameCount()/getName(), so getName(0) is data and the last is app.log.

    Explanation

    Trace: Path.of joins its arguments with the default separator and then parses the result, collapsing redundant separators and dropping a trailing one. So "/data//logs/" plus "app.log" becomes the absolute path whose name elements are exactly `data`, `logs`, `app.log` — three of them. The root is NOT a name element: getNameCount() counts only the names, and getName(0) is therefore `data`, not the root. getName(getNameCount() - 1) is the last element, `app.log`. Hence `3 data app.log`. Why the others are wrong: `4 data app.log` assumes the doubled separator in `//` produces an empty name element between `data` and `logs`. It does not — the path parser collapses repeated separators (and a trailing separator) during construction, before any element exists. `4 / app.log` encodes both halves of the classic error: that the root counts toward getNameCount() and that it sits at index 0. On an absolute path the root is reachable only through getRoot(); the name elements start after it. `3 /data app.log` gets the count right but still believes the first name element carries the leading separator. A name element is a bare single component: `data`, never `/data`. Exam tip: getNameCount() and getName(i) see only the names — the root is invisible to them. That is why Path.of("/").getNameCount() is 0 (a root and nothing else) while the reverse trap, Path.of(""), has a count of 1: the empty path is one empty name element, not zero.

Practise all 15 Java I/O and NIO.2 questions

OCP Java SE 25 has the full set, inside timed mock exams that mirror real exam conditions — every question with a worked explanation.

Open OCP Java SE 25

Other topics in this pack