DEV Community

Cover image for Java final, finally, finalize: Three Bugs They Prevent
Mark Yu
Mark Yu

Posted on • Edited on

Java final, finally, finalize: Three Bugs They Prevent

final, finally, and finalize look like siblings.

They are not.

I have seen beginners mix them up because tutorials explain them as definitions. I think they make more sense if you connect each one to the bug it prevents.

Quick Map

Keyword / method What it protects against
final accidental reassignment, override, or inheritance
finally cleanup code being skipped
finalize mostly historical cleanup confusion

The uncomfortable truth: you should understand finalize, but you probably should not use it.

final: Stop Accidental Change

Use final when a value or behavior should not be changed after it is set.

public class RateLimiter {
    private final int maxRequests;

    public RateLimiter(int maxRequests) {
        this.maxRequests = maxRequests;
    }

    public boolean allow(int currentRequests) {
        return currentRequests < maxRequests;
    }
}
Enter fullscreen mode Exit fullscreen mode

Here maxRequests cannot be reassigned after construction. That is boring, and boring is good.

You can also use final on methods:

class BaseService {
    public final void audit() {
        System.out.println("audit event written");
    }
}
Enter fullscreen mode Exit fullscreen mode

A subclass cannot override audit(). I would use this sparingly. If you need final everywhere, your inheritance design may already be too clever.

And on classes:

public final class StringUtils {
    private StringUtils() {}
}
Enter fullscreen mode Exit fullscreen mode

This says: do not extend this class.

finally: Cleanup Even When Things Break

finally runs whether the try block succeeds or throws.

Old-style file handling:

FileReader reader = null;

try {
    reader = new FileReader("config.txt");
    // read file
} catch (IOException e) {
    System.out.println("read failed: " + e.getMessage());
} finally {
    if (reader != null) {
        try {
            reader.close();
        } catch (IOException ignored) {
            // logging would be better in production
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

This works, but modern Java gives you a cleaner option:

try (FileReader reader = new FileReader("config.txt")) {
    // read file
} catch (IOException e) {
    System.out.println("read failed: " + e.getMessage());
}
Enter fullscreen mode Exit fullscreen mode

That is try-with-resources. In most production code, I would prefer it over a manual finally.

Still, finally matters when cleanup is not an AutoCloseable resource:

lock.lock();
try {
    updateSharedState();
} finally {
    lock.unlock();
}
Enter fullscreen mode Exit fullscreen mode

This is the real bug finally prevents: a lock stays locked because an exception jumped over your cleanup line.

finalize: Know It, Then Avoid It

finalize() was intended to run before garbage collection reclaims an object.

@Override
protected void finalize() throws Throwable {
    System.out.println("cleanup before GC");
}
Enter fullscreen mode Exit fullscreen mode

Do not build production cleanup around this.

Why?

  • You do not control when GC runs.
  • It may run much later than expected.
  • It adds performance and reliability problems.
  • It has been deprecated for removal in modern Java.

If you need cleanup, use AutoCloseable:

class ConnectionHandle implements AutoCloseable {
    @Override
    public void close() {
        System.out.println("connection closed");
    }
}

try (ConnectionHandle handle = new ConnectionHandle()) {
    System.out.println("using connection");
}
Enter fullscreen mode Exit fullscreen mode

This is explicit. You can reason about it. Production code likes that.

The Rule I Use

  • Use final to make intent harder to accidentally break.
  • Use finally when cleanup must happen even after exceptions.
  • Avoid finalize; use explicit cleanup instead.

Final Thought

These three names are confusing, but the responsibilities are not.

final is about preventing change. finally is about guaranteed cleanup. finalize is a legacy GC hook you should not lean on.

Which Java keyword confused you the most when you first learned backend development?

Top comments (3)

Collapse
 
prsaya profile image
Prasad Saya

Consider the following Java program:

public class Main1 {
    final int i;
    public static void main(String[] args) {
        new Main1();
    }
    Main1() {
        i = 10;
        System.out.println("Value of i is " + i);
    }
}
Enter fullscreen mode Exit fullscreen mode

Will this compile? Runs without errors? Does this print "Value of i is 10"?

More about final variables at final Variables (Java Language Specification)

Collapse
 
aminmansuri profile image
hidden_dude

finalize() and try-with-resources are not really related.

Finalize() could be used for things such as making sure that a temp file was deleted when no longer used and other such cleanup applications.

In modern Java the same can be accomplished with Phantom references as shown here:

stackoverflow.com/questions/433118...

Collapse
 
tmsanghoclaptrinh profile image
Sáng Minh Trần