Why this matters
The bug. The JDBC spec leaves it implementation-defined whether closing a Statement closes its ResultSet. Postgres' driver does, Oracle's used to leak in older versions, embedded drivers vary. The only safe assumption is: every JDBC resource you open, you close.
The fix. Close rs before ps, or — better — switch to try-with-resources so the compiler enforces it:
try (PreparedStatement ps = ...; ResultSet rs = ps.executeQuery()) { ... }
The puzzle accepts the minimal inline fix; in real code, prefer try-with-resources.
Defense in depth. Static analyzers (Error Prone's MustBeClosedChecker, SpotBugs' OS_OPEN_STREAM) catch this.
Review heuristic
Read every function with an open, connect, acquire, or lock and ask: on every path out of this function — successful return, error return, exception, panic — does the matching close fire? If you can't quickly answer yes for all paths, the cleanup needs to move into a structured construct.
↳ CWE-772; JDBC spec ambiguity.