syndicated · dev.to / @markyu
A practical Java explanation of final, finally, and finalize using real failure modes instead of memorized definitions.
- Published
- May 24 '24
- Reading time
- 2 min read
- Reactions
- 13
- Comments
- 3
javabackendprogrammingbeginners
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;
}
}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");
}
}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() {}
}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
}
}
}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());
}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();
}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");
}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");
}This is explicit. You can reason about it. Production code likes that.
The Rule I Use
- Use
finalto make intent harder to accidentally break. - Use
finallywhen 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?
Related reading
Java throw vs throws: The Exception Bug They Reveal
A practical Java exception handling guide explaining throw, throws, checked exceptions, runtime exceptions, and API boundary decisions.
java
Java BeanUtils Copying: Convenient, but Not Free
A practical Java guide to BeanUtils, shallow copy pitfalls, reflection overhead, and when MapStruct or manual mapping is a better choice.
java
Frontend Linear Data Structures Deep Dive: Arrays, Stacks, Queues, and Linked Lists
The Big Picture Before diving into stacks, queues, and linked lists, it helps to know...
computerscience
originally published
This post first ran on dev.to. Comments and reactions live there.
Continue on dev.to