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 •