# From C++ — two categories of exception, and no destructors

You already know what an exception is, and you know RAII better than most Java developers ever
will. Two things here have no counterpart in C++ at all. Java sorts exceptions into two
categories and enforces one of them at compile time. And Java has no destructors, so the cleanup
you get for free in C++ is something you and your caller both have to write down.

## The category C++ does not have

Every Java throwable is one of three things.

| Kind | Root | Compiler enforces a `throws` clause |
|---|---|---|
| Error | `Error` | no |
| Unchecked exception | `RuntimeException` | no |
| Checked exception | `Exception`, excluding the `RuntimeException` subtree | **yes** |

That third row is the new idea. A checked exception is part of the method's declared type. If a
method can throw one, the signature says so, and every caller has to either catch it or repeat
the clause.

**C++ — a signature says almost nothing about failure**

```cpp
std::string read(const std::string& path);                 // may throw anything
std::string read(const std::string& path) noexcept;        // throws nothing, or terminate()
```

`noexcept` is one bit, and it is checked at run time. Violate it and you get `std::terminate`,
after the program has already started failing.

**Java — the signature lists the failure modes, and `javac` checks the callers**

```java
String read(String path) throws NoSuchPathException;
```

Write a caller that ignores it and the build stops. This is the error you will meet inside an
hour of writing Java, and it is worth being able to read at a glance:

```
> javac -d out P1.java
P1.java:11: error: unreported exception ExportFailedException; must be caught or declared to be thrown
        flush("/notes/todo.txt");
             ^
1 error
```

The caret is on the *call*, not on the throw. `javac` is telling you which caller has not said
what it does about this failure. Two ways to satisfy it: `catch` it here, or add
`throws ExportFailedException` to the enclosing method and pass the decision upward.

The clause is a real part of the type, and narrowing rules apply the way you would expect from
overriding a virtual function. An implementation cannot add a checked exception the interface
did not declare:

```
P6.java:11: error: export(String) in SinkExporter cannot implement export(String) in Exporter
    public void export(String path) throws ExportFailedException {
                ^
  overridden method does not throw ExportFailedException
1 error
```

That message is the whole argument for why this feels different from C++. A `throws` clause is a
promise the interface made to its callers, so an implementation cannot break it. `noexcept` never
constrained you this way, because it was one bit and it was checked far too late.

## The rule, applied to `file-system`'s own choices

`corpus/file-system/contract/` has five ways to fail. Here is where each landed, and why.

| Condition | Type | Category | Why there |
|---|---|---|---|
| `"/notes//todo.txt"`, `"notes"`, `null` | `IllegalArgumentException` | unchecked | No tree contents could make it valid. The caller has a bug |
| `mv("/a", "/a/b/c")` | `IllegalArgumentException` | unchecked | Moving a directory into itself is structurally impossible |
| A segment names nothing | `NoSuchPathException` | unchecked | A domain outcome, and `exists()` lets a caller check first |
| A file where a directory was needed | `NotADirectoryException` | unchecked | Same, and a caller acts differently on it than on "missing" |
| The destination already exists | `PathAlreadyExistsException` | unchecked | Same again |

The four domain types share an abstract root, `FileSystemException extends RuntimeException`. The
malformed-path case sits outside that hierarchy on purpose. A caller catching "path problems" as
one group would otherwise have to sort its own bugs from the tree's answers by hand.

**The rule to carry into a round.** Unchecked for what the caller should have prevented. Checked
for a condition a reasonable caller must handle and could not have checked for first.

Both clauses matter, and the second is what sent every `file-system` failure to unchecked.
`exists(path)` and `kindOf(path)` are cheap, and one caller at a time is guaranteed by the
contract, so a caller who cannot cope with a throw asks before it reads. Nothing here is
unpredictable in the way a network or a disk is.

Contrast `ExportSession.close()` in `worked/`. It writes to a sink outside the process, the sink
can refuse at any moment, and there is no `canFlush()` to ask first. That is the shape checked
was built for, so `ExportFailedException extends Exception`.

### When a failure earns its own type

Four classes for four conditions is not free. Each is a file to write and a name to learn.

Give a failure its own type when a caller will **branch** on it: a distinct `catch`, a distinct
recovery, a distinct message to a user. `file-system` has four because `ls` on a file leads
somewhere different from `ls` on a missing path. One offers to create it, the other tells you to
stop walking. `worked/Main` prints both branches.

Use `IllegalArgumentException` with the value in the message when every caller's response is
identical, or when the only consumer is a log line. Malformed paths got no type for this reason.
There is nothing to do about `"/notes//todo.txt"` except fix the call site, so a `catch` clause
for it would be dead code. `PathSyntax` throws
`IllegalArgumentException("malformed path (empty segment): /notes//todo.txt")`, and that message
is the entire recovery plan.

The failing middle option is one type with a `Reason` enum inside it. Callers then parse a field
to work out what happened, which is the string-matching the hierarchy exists to remove.

## `try`-with-resources is not RAII, and the differences bite

**C++ — the destructor runs, and you cannot forget it**

```cpp
{
    ExportSession session{sink};              // header written
    session.entry("/notes/todo.txt", body);   // may throw
}                                             // ~ExportSession() runs on every exit path
```

**Java — the resource is named in the header, and the block is the scope**

```java
try (ExportSession session = ExportSession.open("nightly", sink, false)) {
    session.entry("/notes/todo.txt", fs.read("/notes/todo.txt"));
}   // session.close() runs here, on every exit path out of the block
```

Four differences, and each one costs something.

| | C++ RAII | Java `try`-with-resources |
|---|---|---|
| Trigger | end of the enclosing scope | end of the `try` block |
| Opting in | automatic, every object with a destructor | explicit, per `try` statement |
| Applies to | any type | only `AutoCloseable` implementations |
| Forgetting it | impossible | compiles silently and closes nothing |

The type restriction is checked, at least:

```
P2.java:7: error: incompatible types: try-with-resources not applicable to variable type
        try (ExportSession session = new ExportSession()) {
                           ^
    (ExportSession cannot be converted to AutoCloseable)
1 error
```

### The resource has to be declared in the parentheses

This is the one that loses a handle in real code. Move the declaration one line up and everything
still compiles:

```java
ExportSession session = ExportSession.open("leaked", sink, false);
try {
    session.entry("/notes/todo.txt", fs.read("/notes/todo.txt"));
} catch (NoSuchPathException missing) {
    System.out.println("  " + missing.getMessage());
}
```

Real output from `worked/Main`, section 4:

```
== 4. the resource declared outside the parentheses ==
  session.isClosed() -> false
  | BEGIN EXPORT leaked
  |   /notes/todo.txt -> renew passport
  no END EXPORT line, so nothing may read this export
```

`close()` never ran, so the footer was never written, so the export is unreadable. Plain `javac`
exits 0. `javac -Xlint:all` on the same file reports the `finally` problem in section 6 and says
nothing whatever about this. There is no flag that finds it.

Java 9 onward, the header can also name a local that is already effectively final, which is the
fix when the resource is built by a long expression:

```java
ExportSession session = ExportSession.open("nightly", sink, false);
try (session) {
    session.entry("/notes/todo.txt", fs.read("/notes/todo.txt"));
}
```

Two resources in one header close in reverse declaration order, matching destructor order. From
`worked/Main`, section 3, where `all` was declared first and `notes` second:

```
  | BEGIN EXPORT all
  | BEGIN EXPORT notes
  |   /notes/todo.txt -> renew passport
  |   /notes/2026/plan.txt -> ship the trainer
  | END EXPORT notes (1 entries)
  | END EXPORT all (1 entries)
```

## Suppressed exceptions, where C++ has nothing to compare

In C++ a destructor that throws while the stack is unwinding calls `std::terminate`. Since C++11
destructors are implicitly `noexcept`, so the language's answer is that this situation must not
arise.

Java lets it arise and keeps both exceptions. If the body throws and `close()` also throws, the
body's exception is the one that propagates, and `close()`'s is attached to it as *suppressed*.
`worked/Main` section 5 does exactly that: the body reads a missing path, and the session's sink
refuses the flush.

```
  caught: NoSuchPathException
  suppressed: ExportFailedException: sink refused the flush of export nightly after 1 entries
  its cause: java.io.IOException: no space left on device
  --- printStackTrace() ---
NoSuchPathException: no such path: /notes/absent.txt
	at TinyFileSystem.read(TinyFileSystem.java:71)
	at Main.suppressedException(Main.java:100)
	at Main.main(Main.java:20)
	Suppressed: ExportFailedException: sink refused the flush of export nightly after 1 entries
		at ExportSession.close(ExportSession.java:62)
		at Main.suppressedException(Main.java:98)
		... 1 more
	Caused by: java.io.IOException: no space left on device
		... 3 more
```

Read the indentation. `Suppressed:` is nested under the exception that won, with its own frames
and its own `Caused by:` chain. Nothing was lost, and nothing called `terminate`.

Two consequences worth holding on to. `e.getSuppressed()` returns an array, so code that has to
report every failure can walk it; the loop that printed the two `suppressed:` lines above is four
lines long. And if you hand-roll the cleanup with `finally` instead, `close()`'s exception
replaces the body's and the original is gone. That is the mistake `try`-with-resources exists to
stop you making.

## `finally`, and the two traps

`finally` runs on every exit path out of the `try`: normal completion, a `return`, a `break`, or
an exception on its way out. C++ has no equivalent because RAII covers the cases you would use it
for, so both of these are new.

**Trap one: a `return` inside `finally` discards the exception in flight.**

```java
private static String swallow() {
    try {
        throw new NoSuchPathException("/notes/absent.txt");
    } finally {
        return "looked fine to me";
    }
}
```

```
== 6. a return inside finally discards the exception ==
  swallow() returned: looked fine to me
  the NoSuchPathException it threw is gone; nothing recorded it
```

The exception did not propagate and nothing logged it. `return` in a `finally` block completes
the method normally, and a normal completion has no exception. A `break`, a `continue`, or a
fresh `throw` in there does the same thing. One flag catches it:

```
> javac -Xlint:finally -d out P3.java
P3.java:11: warning: [finally] finally clause cannot complete normally
        }
        ^
1 warning
```

A warning, not an error, and off by default. It is in `-Xlint:all`, which is the argument for
running with `-Xlint:all` on any code you are being graded on.

**Trap two: `finally` runs after the `return` expression is evaluated, and cannot change it.**

```java
private static int entriesWritten() {
    int written = 0;
    try {
        return written;
    } finally {
        written = 99;
    }
}
```

```
== 7. finally runs even after return, and cannot alter it ==
  body: the return expression is evaluated here
  finally: runs now, after that, before the caller is resumed
  returned: the body's value
  entriesWritten() returned: 0   (finally set the local to 99 after the value was taken)
```

The order is: evaluate the return expression, stash the value, run `finally`, then hand the
stashed value to the caller. So `finally` does run after `return`, and assigning to the returned
local afterwards changes nothing. Mutating a returned *object* through the same reference does
change what the caller sees, which is the version of this bug that is hard to find.

Given both traps, the rule is narrow. Use `finally` for a resource that is not `AutoCloseable`
and for restoring state you changed on the way in. Never put a control-flow statement in one.

## No destructors, so cleanup is in the type system

There is no deterministic destruction in Java, and the pieces that look like it are not it.

- **Garbage collection is not cleanup.** It reclaims memory at an unspecified time. It never runs
  your `close()`, and an unreachable object holding a file handle keeps holding it.
- **`finalize()` is dead.** Deprecated in Java 9, deprecated for removal in Java 18, and a JVM
  can be started with `--finalization=disabled`. It never had a guaranteed time or a guaranteed
  run. Do not mention it in an interview except to say it is gone.
- **`java.lang.ref.Cleaner` exists and is not the answer here.** It registers an action to run
  after an object becomes unreachable, which makes it a safety net under a class that wraps a
  native handle. It is still non-deterministic, it needs its own thread, and the action must not
  capture the object it guards. For an LLD round it is the wrong tool: it hides the lifetime
  question the interviewer is asking about.

Which leaves one real mechanism, and one design consequence. **Cleanup is the caller's job, and
you state that in the type system by implementing `AutoCloseable`.** That is what
`ExportSession implements AutoCloseable` buys in `worked/`: the compiler will not let a caller
write `try (ExportSession s = ...)` unless the interface is there, and a reviewer reading the
class declaration knows a `close()` is owed. A class that needs closing and does not implement
`AutoCloseable` is a class whose caller has to read the javadoc to find out.

The threshold, since not everything wants this. Implement `AutoCloseable` when the object holds
something the process must give back, and the giving back can fail or has to happen at a known
point. A file, a socket, a lock, a transaction, a batch awaiting a commit. Do not implement it so
a plain value object can sit in a `try` header. An `ExportSession` earns it, and a `Tariff` does
not.

## What an interviewer reads as carelessness

Exception design is API design, and it is visible in a way most decisions are not. Three habits
cost credit, and each has a cheap fix.

**Swallowing.** `catch (NoSuchPathException e) {}` compiles and hides a real failure. If a caller
genuinely has nothing to do, say so where it is written down, the way `TreeResolver.find` does:

```java
try {
    return get(path);
} catch (FileSystemException notThere) {
    return null;
}
```

The named variable and the comment above it turn a swallow into a decision. The rule: an empty
`catch` needs a comment saying why nothing is the correct response, or it is a bug.

**Catching `Exception`.** It catches your own `NullPointerException` too, so a bug in the
`try` block gets reported as a domain failure. `TinyFileSystem.exists` catches
`FileSystemException` and lets `IllegalArgumentException` through, because a malformed path is
still a caller bug even inside `exists`. When two unrelated types need the same handling, use
multi-catch, `catch (NoSuchPathException | NotADirectoryException e)`, rather than widening.
Ordering is checked, so a broad clause first is a compile error:

```
P7.java:11: error: exception NoSuchPathException has already been caught
        } catch (NoSuchPathException e) {
          ^
1 error
```

**`throw new RuntimeException()` with no message.** The stack trace names your line and nothing
about the data. Compare the corpus, where every constructor builds its own message:
`new NoSuchPathException("/notes/absent.txt")` prints `no such path: /notes/absent.txt`. The rule
is one you can apply without thinking: the message names the value that was wrong. `got 0` beats
`invalid argument` at 1am, and it is the difference between one second of diagnosis and reading
the call site.

## The delta table

| C++ | Java |
|---|---|
| one category of exception | two: checked (`Exception`) and unchecked (`RuntimeException`) |
| a signature carries `noexcept` and nothing else | `throws A, B` names each checked failure mode |
| violating `noexcept` calls `std::terminate` at run time | omitting a `throws` clause is a compile error |
| `catch (const std::exception& e)`, `e.what()` | `catch (FileSystemException e)`, `e.getMessage()` |
| `catch (...)` | `catch (Exception e)`, and it reads as carelessness |
| an override may throw anything | an override may not add a checked exception |
| RAII: destructor at scope exit, automatic | `try`-with-resources: `close()` at block exit, opt-in per statement |
| any type can manage a resource | only an `AutoCloseable` may sit in a `try` header |
| destructors run in reverse construction order | resources close in reverse declaration order |
| a destructor throwing while unwinding calls `std::terminate` | `close()`'s exception is attached as `Suppressed:` and the body's wins |
| `catch (...) { cleanup(); throw; }` | `finally`, and it can discard the exception if you `return` in it |
| `std::unique_ptr` makes ownership deterministic | GC makes memory automatic and cleanup nobody's job until you declare it |
| destructor is invisible in the interface | `implements AutoCloseable` is the interface saying it |

## The habit that will cost you time today

Writing `throws Exception` on a method to make the compiler quiet. It compiles, and it deletes the
information the clause exists to carry. Every caller now sees one opaque failure, and no caller
can catch one condition without catching all of them. `main` is the one place it is defensible,
because there is nobody above it to inform.
