Why is finalize() deprecated, and what should you use instead?

8 minintermediatefinalizecleanerresource-management

Quick Answer

Object.finalize() is deprecated (Java 9+, planned for removal) because it's unpredictable — there's no guarantee when, or even whether, it runs before JVM shutdown — it can significantly delay garbage collection of an object (since it must survive an extra GC cycle to be finalized), can resurrect 'dead' objects, and exceptions thrown inside it are silently swallowed. Modern code uses try-with-resources (AutoCloseable) for deterministic cleanup, or java.lang.ref.Cleaner for last-resort, GC-triggered cleanup with clearer semantics.

Detailed Answer

Object.finalize() was Java's original mechanism for letting an object run cleanup code before the GC reclaims it — but it has enough serious problems that it's been deprecated since Java 9 and is planned for eventual removal:

  • No timing guarantee: there's no promise about when finalize() runs relative to an object becoming unreachable — it could be immediately, much later, or (if the JVM exits first) never at all.
  • Delays reclamation: an object with a finalize() method requires an extra GC cycle to actually be collected (it must first be queued for finalization, then finalized, then becomes eligible for real collection on a subsequent pass) — this can meaningfully increase memory pressure for objects that override it.
  • Can "resurrect" objects: a poorly written finalize() could store this somewhere else, making the "dying" object reachable again — a confusing, hard-to-reason-about possibility.
  • Silently swallowed exceptions: an exception thrown from finalize() is caught internally and ignored, so cleanup failures are invisible.
  • No ordering guarantee between multiple objects' finalizers, even ones that reference each other.

Modern replacements:

  1. try-with-resources (AutoCloseable) — for deterministic, immediate cleanup tied to a clear scope; this should be the default choice whenever cleanup timing matters (closing files, sockets, database connections).
try (var conn = openConnection()) {
    // use conn
} // conn.close() guaranteed here, deterministically
  1. java.lang.ref.Cleaner (Java 9+) — for the rarer case where you genuinely need GC-triggered cleanup as a last-resort safety net (e.g., releasing off-heap/native memory if a caller forgot to explicitly close a resource). It registers a cleanup action tied to an object's reachability via a PhantomReference-based mechanism, without any of finalize()'s reliability and exception-swallowing problems.

Rule of thumb: design APIs so callers explicitly close resources (AutoCloseable); reserve Cleaner purely as a defensive backstop, never as the primary cleanup mechanism.