Java final vs finally vs finalize
java final vs finally vs finalize: Understand the distinct roles of final, finally, and finalize in Java, including syntax, usage, and why finalize is deprecated.
When developers compare java final vs finally vs finalize, they are often dealing with three keywords that sound similar but serve completely different purposes. final is a modifier, finally is a block in exception handling, and finalize is a method on Object that has been deprecated. This article explains each one, shows how to use them correctly, and highlights why finalize should be avoided.
The Three Keywords Are Not Interchangeable
Java has three keywords that sound alike but are used in unrelated contexts. final is a modifier that restricts changes to variables, methods, and classes. finally is a block that runs after a try or catch block, regardless of whether an exception occurred. finalize is a method on the Object class that the garbage collector calls before reclaiming an object's memory. Mixing them up leads to compile-time errors or runtime surprises.
final as a Modifier
The final keyword can be applied to variables, methods, and classes, each with a different effect.
For a variable, final makes it a constant: the reference cannot be reassigned after initialization. For primitive types, the value cannot change. For reference types, the object's state can still change, but the reference cannot point to a different object.
final int MAX_RETRIES = 3; final List<String> names = new ArrayList<>(); names.add("Alice"); // allowed names = new ArrayList<>(); // compile error
For a method, final prevents subclasses from overriding it. This is useful when you want to guarantee a specific behavior that should not be altered.
public class Base { public final void log() { System.out.println("Logging"); } } public class Derived extends Base { // compile error: cannot override final method // public void log() { ... } }
For a class, final prevents inheritance entirely. This is common for utility classes or immutable data holders.
public final class MathUtils { // no subclassing allowed }
Using final communicates design intent and helps the compiler enforce constraints.
finally as a Block in Exception Handling
The finally block is part of the exception handling structure. It appears after a try block or after a catch block. Its code always executes, whether an exception is thrown or not, unless the JVM exits abruptly.
try { int result = riskyOperation(); } catch (Exception e) { System.out.println("Caught: " + e.getMessage()); } finally { System.out.println("This always runs"); }
The primary use case for finally is releasing resources such as file handles, network connections, or database connections. However, modern Java prefers try-with-resources for objects that implement AutoCloseable, because it is more concise and guarantees closing even if an exception occurs.
try (FileInputStream in = new FileInputStream("data.txt")) { // read from file } catch (IOException e) { // handle error }
The finally block is still useful for cleanup that is not tied to an AutoCloseable resource, such as releasing a lock or resetting a flag.
finalize as a Method on Object
finalize is a protected method defined in java.lang.Object. The garbage collector invokes it on an object before reclaiming its memory, giving the object a chance to release non-Java resources. However, relying on finalize is problematic for several reasons.
@Override protected void finalize() throws Throwable { try { // release native resource } finally { super.finalize(); } }
The method was deprecated in Java 9 and its use is strongly discouraged. The timing of its execution is unpredictable, it can cause performance issues, and it may never be called if the JVM exits before garbage collection. There is also a risk of resurrecting an object by making it reachable again inside finalize, which complicates memory management.
Comparing final, finally, and finalize
| Keyword | Purpose | Usage | Execution Guarantee | Best Practice |
|---|---|---|---|---|
final | Restrict modification | Variables, methods, classes | Compile-time enforcement | Use for constants and design constraints |
finally | Ensure cleanup after try/catch | Exception handling block | Runs unless JVM exits abruptly | Use for manual cleanup; prefer try-with-resources |
finalize | Hook before garbage collection | Override in Object subclass | No guarantee it will run | Avoid; use explicit cleanup methods |
This table highlights the fundamental differences. final is a compile-time constraint, finally is a runtime control flow construct, and finalize is a callback that the garbage collector may or may not invoke.
Why finalize Is Problematic and What to Use Instead
The finalize method has several severe drawbacks. First, its execution is nondeterministic; you cannot rely on it to release resources in a timely manner. Second, it adds overhead to the garbage collection process because objects with a non-trivial finalize method require extra processing. Third, if an exception is thrown inside finalize, it is ignored and the object is still collected, which can hide errors.
Instead of finalize, use explicit cleanup methods or implement AutoCloseable and use try-with-resources. For example, if you manage a native resource, provide a close() method and call it in a finally block or via try-with-resources.
public class NativeResource implements AutoCloseable { private long handle; @Override public void close() { // release native handle } } try (NativeResource resource = new NativeResource()) { // use resource }
This approach gives you deterministic control over when resources are released and avoids the unpredictability of finalize.
Common Mistakes with These Keywords
A frequent mistake is using final on a reference variable and assuming the object becomes immutable. For example, final List<String> list = new ArrayList<>(); still allows adding and removing elements. To make the list unmodifiable, you need Collections.unmodifiableList or a similar wrapper.
Another mistake is placing cleanup logic in a finally block that could be handled more cleanly with try-with-resources. While finally works, it requires more boilerplate and makes it easier to forget to close the resource if an exception occurs before the try block.
The most serious mistake is overriding finalize and expecting it to run promptly or at all. This can lead to resource leaks and unpredictable application behavior. Always prefer explicit cleanup.
Understanding these three keywords prevents subtle bugs and keeps your Java code maintainable. Each has a specific role, and using them correctly is part of writing robust Java applications.