Final, Finally, and Finalize Keyword Comparison in Java

Learning Objectives

By the end of this post, you will understand the purpose and usage of the final keyword for creating constants and preventing inheritance, comprehend how finally blocks guarantee code execution in exception handling, and recognize why the finalize method is deprecated and should be avoided in modern Java development.

Introduction

Java uses similar-sounding keywords for completely different purposes. The keywords final, finally, and finalize often confuse developers due to their naming similarity, yet each serves a distinct role in the language. The final keyword controls inheritance and mutability. The finally block ensures code execution during exception handling. The finalize method was designed for garbage collection cleanup but is now deprecated. Understanding these differences prevents bugs and improves code quality. This post examines each keyword's purpose, demonstrates proper usage, and explains when to apply each concept.

The Final Keyword

The final keyword restricts modification in three contexts: variables become constants, methods cannot be overridden, and classes cannot be extended. This keyword enforces design decisions at compile time, preventing unintended changes to critical code elements.

Variables marked as final cannot be reassigned after initialization:

package academy.javapro;

public class FinalVariableExample {
    public static void main(String[] args) {
        // Final variables cannot be reassigned
        final int MAX_SIZE = 100;
        // MAX_SIZE = 200; // Compilation error

        // Final reference - the reference cannot change
        final StringBuilder builder = new StringBuilder("Hello");
        // builder = new StringBuilder("New"); // Compilation error

        // But the object itself can be modified
        builder.append(" World");
        System.out.println(builder.toString()); // Prints: Hello World

        // Blank final - initialized in constructor
        Student student = new Student("Alice");
        System.out.println("Student: " + student.name);
    }

    static class Student {
        final String name;  // Must be initialized in constructor

        Student(String name) {
            this.name = name;  // Initialized once
        }
    }
}

The MAX_SIZE variable demonstrates a final constant. Once assigned the value 100, it cannot change. The builder variable shows an important distinction—while the reference cannot point to a different StringBuilder, the object itself remains mutable. We can modify its contents but cannot reassign the variable to a new StringBuilder instance.

Final also controls inheritance by preventing method overriding and class extension:

package academy.javapro;

public class FinalInheritanceExample {
    public static void main(String[] args) {
        Payment payment = new Payment();
        payment.process(50.00);
    }

    static class Payment {
        // Final method cannot be overridden
        public final void process(double amount) {
            validate(amount);
            charge(amount);
        }

        void validate(double amount) {
            System.out.println("Validating: $" + amount);
        }

        void charge(double amount) {
            System.out.println("Charging: $" + amount);
        }
    }

    // Final class cannot be extended
    static final class SecurityManager {
        public void checkPermission() {
            System.out.println("Permission checked");
        }
    }

    // This would cause error:
    // class CustomSecurity extends SecurityManager { }
}

The process method is final, ensuring subclasses cannot alter the payment processing sequence. Final classes like SecurityManager cannot have subclasses at all. Java's String class is final for this reason—preventing modifications that could compromise string handling throughout applications.

The Finally Block

The finally block appears in exception handling to guarantee code execution regardless of whether exceptions occur. Code in a finally block runs after the try block and any matching catch blocks, even if an exception is thrown or a return statement executes.

Resource cleanup represents the primary use case for finally blocks:

package academy.javapro;

import java.io.*;

public class FinallyBlockExample {
    public static void main(String[] args) {
        // Finally always executes
        testFinally(true);
        testFinally(false);

        // Resource cleanup example
        readFile("test.txt");
    }

    static void testFinally(boolean throwError) {
        try {
            System.out.println("In try block");
            if (throwError) {
                throw new Exception("Test error");
            }
        } catch (Exception e) {
            System.out.println("Caught: " + e.getMessage());
        } finally {
            System.out.println("Finally block always runs\n");
        }
    }

    static void readFile(String filename) {
        BufferedReader reader = null;
        try {
            reader = new BufferedReader(new FileReader(filename));
            String line = reader.readLine();
            System.out.println("Read: " + line);
        } catch (IOException e) {
            System.out.println("Error reading file");
        } finally {
            // Ensure reader closes even if exception occurs
            if (reader != null) {
                try {
                    reader.close();
                    System.out.println("File closed");
                } catch (IOException e) {
                    System.out.println("Error closing file");
                }
            }
        }
    }
}

The testFinally method demonstrates that the finally block executes whether an exception occurs or not. When throwError is true, the sequence is try → exception → catch → finally. When false, the sequence is try → finally. The finally block runs in both cases.

File handling shows practical resource management. Whether the file reads successfully or throws an IOException, the finally block ensures the reader closes. This prevents resource leaks that could exhaust system resources.

Modern Java provides try-with-resources as a cleaner alternative:

package academy.javapro;

public class TryWithResourcesExample {
    public static void main(String[] args) {
        // Try-with-resources automatically handles cleanup
        try (BufferedReader reader = new BufferedReader(new FileReader("test.txt"))) {
            String line = reader.readLine();
            System.out.println("Read: " + line);
        } catch (IOException e) {
            System.out.println("Error: " + e.getMessage());
        }
        // No finally needed - reader closes automatically
    }
}

Try-with-resources automatically closes any resource that implements AutoCloseable, eliminating the need for explicit finally blocks in many scenarios.

The Finalize Method

The finalize method was intended for cleanup operations before garbage collection removes an object from memory. However, this method has serious flaws and was deprecated in Java 9.

Understanding why finalize should be avoided:

package academy.javapro;

public class FinalizeExample {
    public static void main(String[] args) {
        // DON'T DO THIS - Showing why finalize is bad
        createObjects();

        // CORRECT APPROACH - Use AutoCloseable
        useAutoCloseable();
    }

    static void createObjects() {
        for (int i = 0; i < 3; i++) {
            OldResource resource = new OldResource(i);
        }
        System.gc(); // Request garbage collection
        // Finalize may or may not run - unpredictable!
    }

    static void useAutoCloseable() {
        try (ModernResource resource = new ModernResource(1)) {
            resource.use();
        } // Automatically closed here - predictable!
    }

    // DEPRECATED - Don't use this approach
    static class OldResource {
        private int id;

        OldResource(int id) {
            this.id = id;
            System.out.println("OldResource " + id + " created");
        }

        @Override
        @Deprecated
        protected void finalize() {
            // Unreliable - may never be called
            System.out.println("Finalize called for " + id);
        }
    }

    // CORRECT - Use AutoCloseable instead
    static class ModernResource implements AutoCloseable {
        private int id;

        ModernResource(int id) {
            this.id = id;
            System.out.println("ModernResource " + id + " created");
        }

        void use() {
            System.out.println("Using resource " + id);
        }

        @Override
        public void close() {
            System.out.println("ModernResource " + id + " closed properly");
        }
    }
}

The OldResource class shows the deprecated finalize approach. Problems include unpredictable execution timing—the garbage collector might never call finalize, especially if the program exits quickly. Performance also suffers because objects with finalize methods require special handling, delaying garbage collection.

The ModernResource class demonstrates the correct approach using AutoCloseable. The close method executes deterministically when the try-with-resources block exits, guaranteeing timely cleanup without relying on garbage collection.

Key problems with finalize:

  • No guarantee it will ever run
  • Unpredictable timing if it does run
  • Significant performance overhead
  • Deprecated since Java 9
  • Will be removed in future Java versions

Always use try-with-resources or explicit close methods instead of finalize for resource management.

Summary

The keywords final, finally, and finalize serve completely different purposes despite their similar names. The final keyword creates unchangeable variables, prevents method overriding, and blocks class inheritance, providing compile-time guarantees about code behavior. The finally block ensures code executes during exception handling, making it ideal for cleanup operations that must run regardless of success or failure. The finalize method, originally designed for pre-garbage collection cleanup, suffers from unpredictability and performance problems that led to its deprecation—modern code should use AutoCloseable and try-with-resources instead. Understanding these distinctions helps write more reliable Java applications, using final for immutability, finally for guaranteed execution, and avoiding finalize entirely.


Accelerate your programming journey with our comprehensive Free Java training programs! Choose your path to success with our Core Java Course or intensive Java Bootcamp. Our Core Java Course provides a solid foundation in programming fundamentals, perfect for beginners mastering object-oriented concepts and essential development skills. Ready for the next level? Our Java Bootcamp transforms your basic knowledge into professional expertise through hands-on enterprise projects and advanced frameworks. Both programs feature experienced instructors, practical assignments, and personalized attention to ensure your success. Whether you’re starting from scratch or advancing your skills, our structured curriculum combines theory with real-world applications, preparing you for a rewarding career in software development. Start your transformation today with our Core Java Course or take the fast track with our Java Bootcamp!

Complete Core Java Programming Course

Master Java from scratch to advanced! This comprehensive bootcamp covers everything from your first line of code to building complex applications, with expert guidance on collections, multithreading, exception handling, and more.

Leave a Comment