File Paths & Working Directory - Java

  1. What is the value represented by result on a Unix-like default file system?
    Answer: /opt/data/in.txt When working with Java NIO paths, you need to understand two key operations: toAbsolutePath() and normalize(). The first prepends the working directory to any relative path, and the second resolves symbolic segments like .. (parent directory) and . (current directory). Start with Path.of("..", "data", ".", "in.txt"), which represents the relative path ../data/./in.txt. When you call toAbsolutePath() with a working directory of /opt/app, Java prepends that directory, giving you /opt/app/../data/./in.txt. Then normalize() cleans up the redundant segments: .. moves up one level from app to opt, and . is simply removed. The final result is /opt/data/in.txt, confirming B is correct. A (/opt/app/data/in.txt) is the most common trap — it ignores the leading .. entirely, as if Path.of() dropped it or normalize() didn't apply it. This would only be correct if the original path started with data, not ../data. C (/data/in.txt) over-applies the .., treating it as if it moves up two levels (past both app and opt) instead of just one. D (/opt/app/../data/./in.txt) shows what toAbsolutePath() produces before normalize() runs. If the question had stopped at toAbsolutePath(), D would be the answer — but normalize() is called, so the path gets cleaned up. A good study habit: always trace path operations step by step — apply toAbsolutePath() first, then normalize() — and count .. hops carefully from the working directory outward.
  2. Which statement correctly describes a and r?
    Answer: a is /work/current/app.properties, while r is /releases/v3/app.properties. When working with Java's Path API, you need to distinguish between lexical operations (pure string manipulation) and filesystem operations (which actually interact with the OS and resolve real locations). toAbsolutePath().normalize() is entirely lexical. It takes the string /work/current/config/../app.properties and resolves the .. by removing config/.., producing /work/current/app.properties. Crucially, it never touches the filesystem — it has no idea that /work/current is a symbolic link. The result a is /work/current/app.properties. toRealPath(), on the other hand, is a true filesystem call. It resolves symbolic links, normalizes the path, and returns the canonical, physical location. When it encounters /work/current, the OS follows the symlink to /releases/v3, and after resolving config/../app.properties, the real path becomes /releases/v3/app.properties. So r is /releases/v3/app.properties. This makes C the correct answer: a is /work/current/app.properties and r is /releases/v3/app.properties. A is wrong because toRealPath() does resolve symlinks — r won't stay under /work/current. B is wrong because normalize() performs no symlink resolution, so a cannot reach /releases/v3. D reverses the two methods — don't mix up which operation resolves symlinks and which stays lexical. As a study tip, remember the rule: **normalize() edits the string, toRealPath() reads the disk**. Any question mentioning symlinks and both methods is testing exactly this distinction.
  3. Which path does relative represent?
    Answer: ../archive/2025.txt When working with Path.relativize(), think of it as answering: *"Starting from the from path, what steps do I take to reach the to path?"* The key insight is that relativize computes a relative navigation path between two locations, using .. to move up directories when needed. Here, from is /srv/app/logs (a directory three levels deep) and to is /srv/app/archive/2025.txt. To navigate from logs to archive/2025.txt, you first move up one level out of logs (back to /srv/app), then descend into archive/2025.txt. That single .. plus archive/2025.txt gives you ../archive/2025.txt — confirming B is correct. A (archive/2025.txt) is wrong because it assumes you're already sitting at /srv/app. You're not — you're starting inside logs, so you must first escape it with ... Forgetting that one level of ascent is the most common trap here. C (../../archive/2025.txt) would be correct if from were /srv/app/logs/subdir — two levels below /srv/app. It overcounts the .. steps by one, suggesting a miscount of how deep logs sits relative to the common ancestor. D (/srv/app/archive/2025.txt) is the absolute path of to itself. relativize() never returns an absolute path — that's what toAbsolutePath() is for. If you see an absolute result from relativize(), something is wrong. Study tip: To solve any relativize() problem, count how many segments in from lie below the common ancestor — that's how many .. steps you need — then append the remaining segments of to.
  4. Assuming a Unix-like default file system, which path is printed?
    Answer: /opt/tools/reports/daily.txt When working with relative paths in Java's NIO.2 API, the key question to ask is: relative to what? The answer is always the working directory — the directory from which the JVM process was launched, exposed at runtime via System.getProperty("user.dir"). Path.of("reports", "daily.txt") creates a relative path: reports/daily.txt. When you call .toAbsolutePath(), Java resolves it against the current working directory — not the location of your .class files, not your home directory, and not the filesystem root. Since the application was launched with /opt/tools as its working directory, the resolved path becomes /opt/tools/reports/daily.txt. The .normalize() call here has no effect since there are no redundant . or .. segments to simplify. So A is correct. B is wrong because HOME=/home/lee is a shell environment variable. Java's toAbsolutePath() has no awareness of HOME — that's a common Unix/shell concept, not a JVM concept. C is a tempting trap: you might assume the path resolves relative to where the .class file lives (/opt/tools/classes), but the JVM doesn't care where the bytecode is stored when resolving relative paths. D would result if Java treated the relative path as starting from the filesystem root, but that's simply not how toAbsolutePath() works. Your study tip: always distinguish between the working directory (user.dir), the classpath, and environment variables like HOME. On exam questions involving Path.toAbsolutePath(), the working directory wins every time.
  5. Which statement best describes the two lookups?
    Answer: in can locate the classpath resource, while p denotes a working-directory-relative file path. When you see a question mixing getResourceAsStream with Path.of, you're being tested on a fundamental distinction: classpath lookups vs. filesystem lookups. These are two completely separate resolution mechanisms in Java, and confusing them is a classic mistake. Class.getResourceAsStream("/config/defaults.properties") — note the leading slash — searches from the root of the classpath. It has nothing to do with the working directory. Since config/defaults.properties exists at the classpath root (inside the packaged JAR or classpath directory), in successfully locates it. This is exactly what C describes for the first lookup. Path.of("config", "defaults.properties"), on the other hand, constructs a relative filesystem path. Relative paths resolve against the JVM's current working directory, not the classpath. Since no such file exists under the working directory, p points to a nonexistent file on disk — it can never reach a resource bundled inside a JAR. C correctly captures this distinction. A is wrong because it claims both lookups use the working directory. getResourceAsStream with a leading / never touches the working directory at all. B is wrong because Path.of has no awareness of the classpath whatsoever — it is strictly a filesystem API. D reverses the roles entirely, incorrectly suggesting Path accesses the classpath and getResourceAsStream searches the working directory — the opposite of how both APIs work. A helpful rule of thumb: anything from Class/ClassLoader → classpath; anything from Path/File → filesystem. Keep those two worlds mentally separate whenever you see resource-loading questions.
  6. Against which directory is the child's relative path resolved?
    Answer: /var/jobs/run1, because ProcessBuilder.directory sets the child's working directory. When you see a question about child processes in Java, focus on who controls what. The key concept here is that ProcessBuilder gives you explicit control over the environment in which a child process runs — including its working directory. Any relative path the child resolves will be anchored to that directory, not the parent's. ProcessBuilder.directory(File dir) sets the working directory for the spawned process. So when Worker calls Path.of("output", "result.txt"), the JVM resolves that relative path against the child's working directory, which was explicitly set to /var/jobs/run1. The full resolved path becomes /var/jobs/run1/output/result.txt. That makes B correct. A is a tempting trap. You might assume that child processes simply inherit everything from the parent, including working directory. And while processes do inherit some things (like environment variables by default), ProcessBuilder.directory() explicitly overrides the working directory for the child — so the parent's /opt/controller is irrelevant here. C reflects a misconception about how Java resolves paths. Java's Path and File APIs resolve relative paths against the process's working directory (user.dir system property), not against the location of .class files. The classpath location has nothing to do with file I/O resolution. D is simply incorrect. There is no Java convention that new processes start in the user's home directory. The working directory is inherited from the parent (or set explicitly), never defaulting to the home directory. As a study tip: whenever ProcessBuilder.directory() appears in a question, treat it as the authoritative working directory for everything the child process does with relative paths.
  7. Which statement is correct?
    Answer: p.equals(q) is false, but Files.isSameFile(p, q) is true. When working with Java's NIO Path API, you need to distinguish between syntactic path equality and filesystem-level file identity — these are two different things, and this question tests exactly that. Path.equals() performs a pure string/component comparison with no filesystem access. It compares paths literally as they are represented. Here, p resolves to /home/app/data/x.txt (a clean absolute path, since toAbsolutePath() prepends the working directory but does not normalize). Meanwhile, q is /home/app/data/./x.txt, which contains a redundant . component. Because these two paths have different string representations, p.equals(q) returns false. Files.isSameFile(p, q), however, actually accesses the filesystem. It resolves both paths to their real locations and checks whether they point to the same file (using inode-level identity on Unix). Since both paths physically resolve to the same file and no symbolic links are involved, Files.isSameFile(p, q) returns true. This confirms D is correct. A is wrong because it assumes equals() performs path normalization — it does not. B is wrong on both counts: it incorrectly claims isSameFile returns false, when the filesystem confirms they reference the same file. C has the logic exactly backwards — it's equals() that fails (due to literal mismatch), not isSameFile(). A good rule of thumb: treat Path.equals() as dumb string comparison, and Files.isSameFile() as the smart, filesystem-aware check. Always reach for isSameFile() when you actually care about whether two paths point to the same file.
  8. Which path does combined represent?
    Answer: /tmp/result.txt When working with Java's Path.resolve() method, the key rule to understand is how it handles absolute vs. relative paths in the argument. The resolve() method is designed to combine a base path with a child path — but with one critical behavior: if the child path is absolute, it completely replaces the base path. Think of it like navigating a filesystem: if someone gives you a full, unambiguous address (starting from root /), there's no need to "attach" it to anything — you just go directly there. In this example, child is Path.of("/tmp/result.txt"). Because it begins with /, it is an absolute path. So when base.resolve(child) is called, Java discards /srv/app/output entirely and returns /tmp/result.txt. That makes C the correct answer. A (/srv/app/output/tmp/result.txt) represents what would happen if resolve() simply concatenated the two paths as strings — it doesn't. It's smarter than that and respects path absoluteness. B (/srv/app/output/result.txt) would be the result if only the filename result.txt were passed as the child (a relative path), not the full absolute path /tmp/result.txt. D (/srv/app/tmp/result.txt) doesn't match any real behavior of resolve() — it might seem plausible if you mistakenly thought resolve() replaces the last segment of the base, but that's not how it works. A good study tip: whenever you see resolve() in an exam question, immediately check whether the argument is absolute or relative — that single fact determines the entire outcome.
  9. Which expression most reliably constructs the required path?
    Answer: base.resolve(Path.of("logs", "app.txt")) When working with file paths in Java across different operating systems, the key rule is: never hardcode separators, and always use the NIO.2 API to construct paths. The Path and Files APIs (introduced in Java 7) are designed to handle OS-specific separators transparently. Option A is the correct approach. Path.of("logs", "app.txt") accepts multiple path segments as separate arguments and joins them using the current file system's separator automatically. When passed to base.resolve(...), it appends this relative path beneath base, producing a fully correct, portable result on any OS. Option B introduces a critical flaw: the leading / in "/logs" makes that argument an absolute path. When resolve() receives an absolute path, it discards the base entirely and returns the argument as-is — so base is completely ignored. You'd end up at /logs/app.txt instead of base/logs/app.txt. Option C hardcodes Windows-style backslashes (\\), which immediately breaks on Unix/Linux systems where \ is a valid character in filenames, not a separator. It also concatenates base as a string, bypassing all path-safety guarantees. Option D uses File.pathSeparator, which is a common trap. File.pathSeparator is the classpath/list separator (; on Windows, : on Unix) — it separates entries in a list of paths, not segments within a single path. You'd want File.separator for that, but even then, string concatenation is fragile compared to the API. Your takeaway: always use multi-argument Path.of(...) or resolve() for cross-platform path construction — and never confuse File.separator with File.pathSeparator.
  10. Where is the file moved, assuming the operation succeeds?
    Answer: To /srv/app/done/a.txt, because both relative paths use the process's working directory. Whenever you see a question involving Path.of(...) and Files.move(...) in Java, the first thing to ask yourself is: are these paths absolute or relative? That single distinction determines everything about how the JVM resolves them. In Java's NIO file API, a relative Path is always resolved against the process's current working directory — not against other paths in the code, not against the file-system root, and not against any parent directory of another path. Here, both Path.of("archive", "a.txt") and Path.of("done", "a.txt") are relative. With a working directory of /srv/app, the JVM expands "archive/a.txt" to /srv/app/archive/a.txt (the source) and "done/a.txt" to /srv/app/done/a.txt (the target). The file is therefore moved to /srv/app/done/a.txt, making D correct. A is wrong because the target path is never resolved relative to the source path's parent directory. Each path is resolved independently from the working directory — Files.move doesn't establish any relationship between the two paths beyond "move this to that." B is wrong because relative paths are not resolved from the file-system root (/). Absolute paths start with /; relative paths do not, and they always anchor to the working directory. C is wrong because the working directory is used as a whole, not component by component. The first segment of the target (done) does not replace the last segment of the working directory (app). A useful rule of thumb: in Java NIO, relative = working directory prefix, always. If you're ever unsure, mentally prepend the working directory to any relative path and you'll resolve it correctly.

Start this quiz