ByteScrollGet the app
☰ Topics
final-finally-finalize19 / 200‹›
JAVA / BASICS3 minute read

final vs finally vs finalize — three different things

Easy

final is a modifier: a variable assigned once, a method that can't be overridden, a class that can't be extended. finally is the block that runs after try/catch however it exits. finalize() was a GC callback, deprecated for removal since Java 18.

How it works

final (keyword)

  • On a variable or field: it can be assigned exactly once. For an object reference, the reference is fixed; the object can still change.
  • On a method: subclasses can't override it.
  • On a class: nobody can extend it (String, Integer, every record).
  • final fields also get a memory-model guarantee: once the constructor finishes, other threads see their values without extra synchronization (as long as this didn't leak out during construction).

finally (block)

  • Attached to try. It runs whether the try completes normally, returns, or throws, after any matching catch.
  • Used for cleanup, though try-with-resources is now the preferred way to close things.

finalize() (method)

  • A method on Object that the garbage collector might call before reclaiming an object.
  • No guarantee when, or whether, it runs. It slows down GC and can even resurrect objects.
  • Marked @Deprecated back in Java 9; JEP 421 in Java 18 went further and flagged finalization as slated to be removed entirely. Use try-with-resources, or java.lang.ref.Cleaner as a safety net.
noyesyesnoexceptionreturn / normalEnter tryExceptionthrown?finally blockMatchingcatch?catch blockPendingexception orreturn?Propagate tocallerContinue orreturn
noyesyesnoexceptionreturn / normalEnter tryExceptionthrown?finally blockMatchingcatch?catch blockPendingexception orreturn?Propagate tocallerContinue orreturn

Example

Example.javaJava
final class Invoice {                       // can't be subclassed
    private final List<String> lines = new ArrayList<>();
    void add(String l) { lines.add(l); }    // fine: the list is mutable, the reference isn't
}

static int attempts() {
    int n = 1;
    try {
        return n;          // value 1 is captured here
    } finally {
        n = 99;            // runs, but doesn't change the returned value
        System.out.println("cleanup");
    }
}                          // returns 1

// Preferred over finally for resources
try (var in = Files.newBufferedReader(Path.of("orders.csv"))) {
    System.out.println(in.readLine());
}                          // in.close() runs automatically

Edge cases

  • finally doesn't run if the JVM stops first: System.exit(), a crash, or the process being killed. It also never runs if the try loops forever.
  • A return inside finally overrides the try's return value and silently discards any exception in flight.
  • A local variable used inside a lambda must be final or effectively final (never reassigned), even without the keyword.
  • A final field must be assigned exactly once in every constructor path, or the class doesn't compile.

Common mistakes

  • Believing final List<X> makes the list immutable. Use List.of(...) or Collections.unmodifiableList for that.
  • Returning or throwing from finally and hiding the real error.
  • Overriding finalize() to close files or sockets. It may never run.

Likely follow-up

"What replaces finalize for native resources?" Implement AutoCloseable and use try-with-resources. If you need a backstop for users who forget, register a cleanup action with Cleaner, making sure it doesn't hold a reference back to the object itself.

Get every deep dive in the app

Coming soon to the App StoreComing soon to Google Play