final vs finally vs finalize
Three Java words — final, finally, and finalize — look nearly identical but serve completely different purposes. Beginners mix them up constantly; even experienced developers occasionally pause to think through which one to reach for. This page clears up all three once and for all.
Quick Comparison
| Feature | final | finally | finalize |
|---|---|---|---|
| Type | Keyword | Keyword (block) | Method |
| Used with | Variables, methods, classes | try-catch blocks | Object class |
| Purpose | Prevent modification/override | Guarantee execution after try | Pre-GC cleanup hook (deprecated) |
| Belongs to | Language syntax | Exception handling | java.lang.Object |
| Always runs? | N/A | Yes (almost) | Not guaranteed |
final — Locking Things In Place
The final keyword is a modifier you place on variables, methods, or classes to prevent them from being changed or extended.
final Variables
A final variable can only be assigned once. After that, any attempt to reassign it causes a compile-time error.
public class CircleCalculator {
public static void main(String[] args) {
final double PI = 3.14159;
double radius = 5.0;
System.out.println("Area: " + (PI * radius * radius));
// PI = 3.14; // Compile error: cannot assign a value to final variable PI
}
}
Output:
Area: 78.53975
Tip: By convention,
finalconstants are written inUPPER_SNAKE_CASE.
final Methods
A final method cannot be overridden in a subclass. Use it when a method’s behavior must stay consistent across all subclasses.
class Vehicle {
public final void startEngine() {
System.out.println("Engine started.");
}
}
class Car extends Vehicle {
// public void startEngine() { } // Compile error: cannot override final method
}
final Classes
A final class cannot be subclassed at all. java.lang.String is the most famous example — this guarantees its immutability and thread safety.
final class ImmutablePoint {
private final int x, y;
ImmutablePoint(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
}
// class SpecialPoint extends ImmutablePoint { } // Compile error
Note: Making a class
finalalso allows the JIT compiler to inline method calls, which can improve performance since no virtual dispatch is needed.
finally — The Cleanup Block That Always Runs
The finally block belongs to Java’s exception handling mechanism. It is attached to a try-catch statement and always executes after the try block finishes — whether an exception was thrown or not.
This makes it perfect for releasing resources: closing files, database connections, or network sockets.
public class FileDemo {
public static void main(String[] args) {
try {
System.out.println("Opening file...");
int result = 10 / 0; // ArithmeticException thrown here
System.out.println("This won't print.");
} catch (ArithmeticException e) {
System.out.println("Caught: " + e.getMessage());
} finally {
System.out.println("Finally block runs — always!");
}
}
}
Output:
Opening file...
Caught: / by zero
Finally block runs — always!
finally Without catch
You can use finally without a catch block. This is useful when you want to guarantee cleanup but still let any exception propagate up to the caller.
public static void riskyOperation() {
try {
System.out.println("Doing risky work...");
// exception might bubble up
} finally {
System.out.println("Cleanup always happens.");
}
}
When Does finally NOT Run?
There is one rare case: if the JVM exits inside the try or catch block via System.exit(), the finally block is skipped entirely. An abrupt JVM crash (like running out of memory at the OS level) can also prevent it.
try {
System.out.println("Before exit");
System.exit(0); // JVM terminates — finally is NOT called
} finally {
System.out.println("This will NOT print.");
}
Warning: Do not use
System.exit()inside library code. It kills the entire JVM and bypasses all cleanup blocks and shutdown hooks.
try-with-resources: The Modern Alternative
Since Java 7, try-with-resources automatically closes any AutoCloseable resource at the end of the block. For resource cleanup, this is cleaner than a manual finally.
try (var reader = new java.io.BufferedReader(new java.io.FileReader("data.txt"))) {
System.out.println(reader.readLine());
} catch (java.io.IOException e) {
System.out.println("Error: " + e.getMessage());
}
// reader.close() is called automatically — no finally needed
Tip: Prefer
try-with-resources over manualfinallyforCloseable/AutoCloseableobjects. It handles suppressed exceptions correctly and is harder to get wrong.
finalize — Pre-Garbage-Collection Hook (Deprecated)
finalize() is an instance method inherited from java.lang.Object. The garbage collector was supposed to call it just before reclaiming an unreachable object, giving you a chance to free native resources.
In practice, finalize() is unreliable and was deprecated in Java 9 and marked for removal. You should not use it in new code.
public class ResourceHolder {
private String name;
ResourceHolder(String name) {
this.name = name;
}
@Override
@Deprecated
protected void finalize() throws Throwable {
try {
System.out.println("finalize() called for: " + name);
} finally {
super.finalize();
}
}
public static void main(String[] args) throws Exception {
ResourceHolder obj = new ResourceHolder("myResource");
obj = null; // eligible for GC
System.gc(); // suggest GC — no guarantee finalize() will run now
Thread.sleep(100); // give GC a chance (not reliable in real code!)
System.out.println("Main done.");
}
}
Output (not guaranteed):
finalize() called for: myResource
Main done.
Warning:
finalize()has serious problems: it may run on any thread, may never run at all, can cause objects to “resurrect” themselves (preventing collection), and adds significant GC overhead. The Java team officially deprecated it in Java 9 and marked it for removal.
What to Use Instead
| Old approach | Modern replacement |
|---|---|
finalize() for resource cleanup | Implement AutoCloseable + try-with-resources |
finalize() for native memory | Use java.lang.ref.Cleaner (Java 9+) |
finalize() for post-GC logic | PhantomReference + ReferenceQueue |
Here is the clean, modern approach using AutoCloseable:
public class NativeResource implements AutoCloseable {
public NativeResource() {
System.out.println("Resource acquired.");
}
@Override
public void close() {
System.out.println("Resource released cleanly.");
}
public static void main(String[] args) {
try (NativeResource r = new NativeResource()) {
System.out.println("Using resource...");
} // close() is called automatically here
}
}
Output:
Resource acquired.
Using resource...
Resource released cleanly.
Under the Hood
final variables and the JIT. When the compiler sees a final field that is initialized in the constructor and never reassigned, it can treat it as a constant and fold its value into other expressions at compile time. For static final primitives and String literals, javac inlines the value directly into the bytecode of every class that references it — the field lookup disappears entirely.
finally and bytecode. The Java compiler does not generate a special bytecode for finally. Instead, it duplicates the finally block into every exit path of the try: the normal return path, each catch path, and the re-throw path. You can confirm this with the javap tool — you will see the same cleanup instructions appear several times in the bytecode. This also explains why returning a value inside finally overrides a value returned inside try — the finally return executes last.
finalize and GC overhead. When an object overrides finalize(), the JVM must place it on a special finalizer queue instead of reclaiming it immediately. A dedicated finalizer thread processes that queue and calls finalize(). Only after finalize() returns can the GC reclaim the memory — on the next GC cycle. This two-pass overhead is expensive and unpredictable, which is why modern Java strongly discourages it.
Summary
final— a compile-time constraint. Use it on variables for constants, on methods to prevent overriding, and on classes to prevent subclassing.finally— a runtime guarantee. Use it to run cleanup code after atry-catchblock, or better yet, usetry-with-resources instead.finalize— a deprecated, unreliable pre-GC hook. Do not use it in new code; implementAutoCloseableinstead.
Related Topics
- final Keyword — deep dive into
finalon variables, methods, and classes - try-catch Block — the foundation of Java exception handling and where
finallylives - Exception Handling — full overview of Java’s exception model
- Garbage Collection Deep-Dive — how the JVM reclaims memory and why
finalize()is problematic - throw vs throws — two more look-alike exception keywords explained side by side
- Custom Exceptions — building your own exception hierarchy