final vs finally vs finalize()

This is a classic Java interview question that tests understanding of keywords, exception handling, and garbage collection. Though they sound similar, they serve completely different purposes.

The three terms final, finally, and finalize() look related because they share the same root word, but they belong to completely different parts of Java. final is a keyword used by the compiler to restrict changes. finally is a block used in exception handling to run cleanup code. finalize() is a method from the Object class that was historically connected to garbage collection cleanup, but modern Java discourages and deprecates it because it is unreliable.

Interviewers ask this question because it quickly separates memorized Java knowledge from practical understanding. A candidate who says "final is a keyword, finally is a block, finalize is a method" has the basic answer. A stronger candidate explains where each one is used, when it runs, what problem it solves, what its limitations are, and which one should be avoided in modern Java. That deeper answer shows awareness of OOP, exception handling, resource management, and garbage collection behavior.

The safest way to remember the difference is to connect each term to its domain. Use final when talking about variables, methods, classes, constants, inheritance, overriding, and immutable design. Use finally when talking about try, catch, cleanup, resource closing, and code that must run after exception handling. Use finalize() only when discussing legacy garbage collection cleanup and why it should not be relied on today.

final vs finally vs finalize()

High-Level Difference (One Line)

  • final → Prevents change (variables, methods, classes)
  • finally → Ensures execution of cleanup code
  • finalize() → Cleanup before garbage collection (deprecated)

This one-line difference is enough for a quick revision, but in interviews you should expand it with examples. final is checked mostly at compile time. If a final variable is reassigned, a final method is overridden, or a final class is extended, the compiler reports an error. finally is executed at runtime as part of exception flow. finalize() depends on garbage collection, which means the JVM may call it late, unpredictably, or not at all before the program ends.

The practical lesson is that only final and finally are recommended for normal modern Java use. final remains useful for clear design and safer code. finally remains useful for cleanup logic, though try-with-resources is often cleaner for closable resources. finalize() is a legacy mechanism and should be avoided in new code.

1. final Keyword

What Is final?

final is a keyword used to restrict modification.

It can be applied to:

  • Variables
  • Methods
  • Classes

The meaning of final depends on where it is used. With a variable, it prevents reassignment after initialization. With a method, it prevents overriding in subclasses. With a class, it prevents inheritance. These three uses are connected by one idea: Java is being told that something should not be changed in a particular way.

The final keyword is part of normal Java design. Developers use it for constants, method safety, API stability, and immutable-style classes. It can make code easier to reason about because it reduces the number of things that can change. However, it is important to understand exactly what it restricts. A final reference does not automatically make the object immutable. It prevents the reference from pointing somewhere else, but the object itself may still be mutable.

final Variable

final int x = 10;
x = 20; // ❌ compile-time error
          
  • ✔ Value cannot be changed
  • ✔ Used for constants

A final local variable must be assigned once before it is used. After assignment, it cannot be reassigned. In the example above, x is assigned the value 10. Trying to assign 20 later causes a compile-time error. This is useful when a value should remain fixed throughout a method or block.

For constants, Java commonly combines static and final. A constant such as public static final int MAX_RETRY_COUNT = 3; belongs to the class and cannot be reassigned. Naming conventions usually use uppercase letters with underscores for constants. This makes constant values easy to identify in code.

The interview trap is object mutability. If you write final StringBuilder sb = new StringBuilder(), you cannot reassign sb to another StringBuilder, but you can still call sb.append("Java"). The reference is final; the object is not necessarily immutable. This is why saying "final makes objects immutable" is incomplete and often wrong.

final Method

class Parent {
    final void show() {}
}
class Child extends Parent {
    // void show() {} ❌ cannot override
}
          
  • ✔ Prevents method overriding

A final method can be inherited, but it cannot be overridden by a child class. This is useful when a parent class wants to guarantee a specific behavior. For example, a framework base class may provide a final method that controls a fixed algorithm, while allowing subclasses to customize only selected steps through protected hook methods. This preserves the parent class contract.

A final method can still be overloaded because overloading creates a different method signature. The child cannot override show() with the same parameter list, but it could define show(String message) if that creates a new overload. This distinction is important because final prevents overriding, not every method with the same name.

final Class

final class Utility {}
class Test extends Utility {} // ❌ not allowed
          
  • ✔ Prevents inheritance
  • ✔ Used for immutability (String)

A final class cannot be extended. This is useful when a class should not have child classes because inheritance could break its guarantees. The String class is final, which helps protect its immutability and security behavior. If String could be subclassed, a child class might override methods in a way that violates expectations around equality, hashing, pooling, or safe sharing.

Utility classes are often made final when they contain only static helper methods and are not designed for inheritance. However, final class design should be used thoughtfully. Making every class final can reduce extensibility and make testing harder in some designs. The decision should communicate intent: this class is complete and should not be specialized through inheritance.

2. finally Block

What Is finally?

finally is a block used with try–catch to execute important cleanup code.

✔ Executes always (almost)

The finally block belongs to exception handling. It is written after a try block and optional catch blocks. Its purpose is to run code that should execute whether the risky operation succeeds, fails with a handled exception, or fails with an exception that is thrown further up the call stack. This makes it suitable for cleanup work.

Common cleanup examples include closing files, releasing database connections, unlocking resources, stopping timers, clearing temporary state, and writing final logs. Before try-with-resources became the preferred style for many resources, finally was the standard place to close resources manually. It is still useful for cleanup actions that do not fit try-with-resources.

Syntax

try {
    // risky code
} catch (Exception e) {
    // handling
} finally {
    // cleanup code
}
          

In this structure, Java first enters the try block. If no exception occurs, the catch block is skipped and finally runs. If a matching exception occurs, the catch block handles it and then finally runs. If an exception occurs and is not handled by the available catch blocks, finally still runs before the exception continues to the caller.

Example

try {
    int a = 10 / 0;
} catch (ArithmeticException e) {
    System.out.println("Exception");
} finally {
    System.out.println("Always executes");
}
          

✔ Executes whether exception occurs or not

In the example, division by zero causes an ArithmeticException. The catch block prints Exception, and the finally block prints Always executes. If the risky line did not throw an exception, the finally block would still execute. This is the main promise of finally: it runs after the try-catch flow in normal JVM execution.

A useful interview phrase is "finally executes almost always." The word "almost" matters because there are rare termination cases where Java cannot continue normal execution. If the JVM is forcibly stopped, the machine loses power, the process is killed, or System.exit() is called before the finally block can run, the finally block may not execute.

When finally Does NOT Execute (Interview Trap)

  • System.exit()
  • JVM crash
  • Power failure

System.exit() is the most common interview trap. If the program calls System.exit(0) inside the try or catch block, the JVM begins shutdown and the finally block does not run in the normal way. Similarly, a JVM crash or power failure prevents Java from executing further instructions. These cases do not make finally unreliable in normal application flow; they simply show that finally depends on the JVM continuing execution.

Another subtle trap involves return statements. If a try block returns a value and a finally block also runs, the finally block executes before the method actually returns. If the finally block itself contains a return statement, it can override the earlier return. This is legal but strongly discouraged because it makes code confusing and can hide exceptions. A professional guideline is to avoid returning from finally blocks.

3. finalize() Method

What Is finalize()?

finalize() is a method of the Object class that is called by the Garbage Collector before object destruction.

protected void finalize() throws Throwable {
    // cleanup
}
          

Historically, finalize() was intended as a last-chance cleanup method before an object was reclaimed by the garbage collector. Since every class extends Object directly or indirectly, a class could override finalize() and place cleanup code there. The idea looked convenient, but in practice it created serious problems because garbage collection timing is not deterministic.

The JVM decides when garbage collection should happen. It may happen soon, later, or not before the program ends. Even when garbage collection runs, there is no simple guarantee that finalize() will execute at a time useful for releasing important resources. This makes finalize unsuitable for resources such as files, sockets, locks, and database connections. Such resources must be released predictably, not whenever the garbage collector happens to run.

Example

class Test {
    protected void finalize() {
        System.out.println("Finalize called");
    }
}
          
  • ✔ Called before garbage collection
  • ❌ Not guaranteed when or if it runs

This example may print Finalize called in some situations, but a program should never depend on that output. If an object does not become eligible for garbage collection, finalize will not be called. If the program terminates before garbage collection occurs, finalize may not be called. If garbage collection is delayed, resource cleanup is delayed too. These uncertainties are the main reason finalize is not recommended.

Another problem is performance. Objects with finalizers require extra work from the JVM because they cannot be reclaimed as simply as ordinary objects. Finalization can also create security and lifecycle issues, including object resurrection, where a finalizer makes an object reachable again. These problems make finalization complex and unsafe compared with explicit cleanup patterns.

Important Note (Modern Java)

  • ⚠ finalize() is deprecated (Java 9+)
  • ✔ Unreliable
  • ✔ Performance issues
  • ✔ Use try-with-resources / AutoCloseable instead

Modern Java treats finalize() as a legacy feature. It was deprecated because it is unpredictable, slow, and difficult to use safely. New code should not override finalize for resource management. If you see it in older code, treat it as a maintenance concern and look for ways to replace it with explicit cleanup, try-with-resources, AutoCloseable, or cleaner resource ownership.

finalize() vs Cleanup (Modern Practice)

❌ Avoid:

finalize()
          

✔ Prefer:

try-with-resources

try (FileInputStream fis = new FileInputStream("a.txt")) {
    // use resource
}
          

Try-with-resources is the preferred cleanup pattern for resources that implement AutoCloseable or Closeable. The resource is opened in the parentheses after try, and Java automatically calls close() when the block finishes. This happens whether the block completes normally or throws an exception. It is deterministic, readable, and safer than waiting for garbage collection.

The difference between finally and try-with-resources is practical. A finally block can still be used for general cleanup, but try-with-resources is more concise and less error-prone for closable resources. It also handles suppressed exceptions correctly when both the main operation and the close operation throw exceptions. For modern Java interviews, mentioning try-with-resources shows current knowledge.

Side-by-Side Comparison (Interview Favorite)

Aspect final finally finalize()
Type Keyword Block Method
Used for Restriction Cleanup GC cleanup
Related to OOP Exception handling Garbage Collection
Execution Compile-time Runtime GC dependent
Overridable ❌ No ❌ N/A ✔ Yes
Recommended today ✔ Yes ✔ Yes ❌ No (deprecated)

The table summarizes the differences, but the most important row is the "Type" row. final is a keyword. It is part of the Java language syntax. finally is a block. It is part of exception handling structure. finalize() is a method. It is inherited from Object and was historically tied to garbage collection. Once you separate keyword, block, and method, the confusion becomes much smaller.

The second important row is "Recommended today." final and finally are both valid and useful. finalize() is not recommended for new code. In interviews, it is acceptable to explain what finalize was intended to do, but you should immediately add that it is deprecated and should be avoided.

final and Immutability

One of the most common misunderstandings is the relationship between final and immutability. A final variable cannot be reassigned, but if it points to a mutable object, the object's internal state can still change. For example, a final ArrayList reference cannot point to another ArrayList, but elements can still be added to the same list. The reference is fixed; the object may not be.

To make a class immutable, Java needs more than final references. The class often should be final or carefully designed for inheritance, fields should be private and final where appropriate, mutable internal objects should not be exposed directly, and methods should not modify state after construction. String is a well-known immutable class because its design combines several protections, not because of one keyword alone.

In interviews, a precise answer is: final helps support immutability, but it does not guarantee immutability by itself. This answer avoids the common trap and shows that you understand the difference between reference immutability and object immutability.

finally and Exception Flow

A finally block runs after the try block and any matching catch block, but before control leaves the method. This is true even when the try block contains a return statement. Java evaluates the return path, executes the finally block, and then completes the return unless the finally block changes the flow by throwing another exception or returning another value.

This behavior is useful for cleanup but dangerous if misused. A finally block should normally release resources or perform simple cleanup. It should not contain complicated business logic, hidden return statements, or code that can easily throw another exception. If a finally block throws an exception, it may hide the original exception from the try block, making debugging harder.

The professional practice is to keep finally blocks small and predictable. Close resources, release locks, clear temporary state, or write essential cleanup logs. For files, streams, sockets, and database resources, prefer try-with-resources when possible.

finalize() and Garbage Collection Reality

Garbage collection manages memory, not business resources. This distinction is important. The garbage collector reclaims heap memory used by unreachable objects. It does not provide a reliable schedule for closing files, flushing logs, releasing network sockets, or returning database connections to a pool. Those resources often exist outside the Java heap and must be managed explicitly.

Relying on finalize() mixes memory reclamation with resource cleanup, which is one reason the design failed in practice. A program may run out of file handles or database connections long before the JVM decides to collect the abandoned objects. Cleanup must happen when the resource is no longer needed, not someday when the garbage collector notices.

Modern Java encourages explicit resource ownership. If a class opens a resource, it should close it through a clear method, try-with-resources, or a lifecycle framework. This makes cleanup deterministic and testable. finalize() should be treated as historical knowledge for interviews and legacy maintenance, not as a tool for new development.

Real-World Usage Examples

Use final for constants, method parameters that should not be reassigned, local variables that should remain stable, classes that should not be extended, and methods whose behavior must not be changed by subclasses. For example, a tax rate constant, a maximum retry count, or a utility class may use final to communicate fixed intent.

Use finally when a cleanup action must run after risky code. For example, if a manual lock is acquired before a risky operation, the unlock operation belongs in finally so the lock is released even when an exception occurs. Similarly, older code may close resources in finally, though try-with-resources is preferred for resources that implement AutoCloseable.

Avoid finalize() in real-world new code. If cleanup is needed, design the class so the owner closes the resource explicitly. For simple resources, use try-with-resources. For complex lifecycle objects, use framework lifecycle hooks or explicit shutdown methods. This approach is easier to test and does not depend on garbage collection timing.

final Variables in Method Arguments and Lambdas

The final keyword also appears in discussions about method arguments, anonymous classes, and lambda expressions. A method parameter can be declared final to prevent reassignment inside the method. This does not change the value passed by the caller, because Java is pass-by-value, but it can make the method body clearer by preventing accidental reassignment of the parameter name.

Java also uses the idea of effectively final variables. A local variable is effectively final when it is assigned once and not reassigned, even if the final keyword is not written. Lambda expressions and anonymous inner classes can capture local variables only when they are final or effectively final. This rule prevents confusing behavior where a captured local variable changes after the lambda is created.

In interviews, this is a useful extension point. If asked whether final is only for constants, the answer is no. It is also used to prevent reassignment, protect methods from overriding, prevent inheritance, support clear API design, and work with effectively final variables in lambda-related contexts.

finally with return Statements

Return behavior with finally is a common code-output question. If a method returns from the try block, Java still executes the finally block before completing the return. This is why finally is suitable for cleanup even when a method exits early. The cleanup still gets a chance to run before control returns to the caller.

Problems appear when the finally block also returns a value. A return inside finally can override a return from try or catch. It can also hide an exception thrown from the try block. Although Java allows this, it is poor practice because it makes control flow difficult to understand. Most teams avoid return statements inside finally blocks for this reason.

The interview-safe answer is: finally executes before the method returns, but returning from finally is discouraged because it can suppress earlier returns or exceptions. Use finally for cleanup, not for deciding business return values.

finally vs try-with-resources

Before Java 7, developers commonly used finally blocks to close files, streams, readers, writers, sockets, and database resources. The pattern worked, but it required careful null checks and nested exception handling. If closing a resource also threw an exception, the code could become verbose and difficult to maintain.

Try-with-resources simplified this pattern. Any resource that implements AutoCloseable can be declared in the try header, and Java closes it automatically. This makes the code shorter and more reliable. It also preserves suppressed exceptions, which means debugging information is not lost when both the main code and the close operation fail.

That does not mean finally is obsolete. Finally is still useful for cleanup that is not simply closing an AutoCloseable resource. For example, resetting a flag, releasing a custom lock, recording completion, or clearing temporary thread-local state may still fit naturally in finally. The modern rule is: use try-with-resources for closable resources, use finally for other must-run cleanup logic.

finalize() vs close()

Another common interview comparison is finalize() versus close(). The close() method is explicit. The developer or try-with-resources block calls it when the resource is no longer needed. This makes cleanup predictable. finalize() is implicit and garbage-collector dependent. The developer cannot rely on when it will be called.

For resources, predictability matters more than convenience. A file stream should be closed as soon as file work is complete. A database connection should be returned to the pool as soon as the query work is complete. Waiting for garbage collection can exhaust limited system resources even if heap memory is still available.

A strong interview line is: close() is deterministic cleanup controlled by program flow, while finalize() is non-deterministic cleanup controlled by garbage collection and should not be used in modern Java.

Code-Output Reasoning Strategy

When an interview question asks for output, identify which of the three terms is involved before solving the code. If the question uses final, ask whether the code attempts reassignment, overriding, or inheritance. Most errors involving final are compile-time errors, so the answer may be "the code does not compile" rather than a runtime output.

If the question uses finally, trace the try-catch-finally flow. Check whether an exception occurs, whether a catch block matches, whether finally prints something, and whether there are return statements. Remember that finally usually runs before control leaves the method. Then check for abnormal termination such as System.exit().

If the question uses finalize(), be cautious about predicting output. Unless the code explicitly triggers garbage collection and even then with no firm guarantee, finalize output should not be treated as reliable. The better answer is often that finalize is not guaranteed to run, so the output cannot be relied on.

Common Interview Traps

  • Confusing finally with finalize()
  • Thinking finalize() always runs
  • Assuming finally executes after System.exit()
  • Believing final makes objects immutable (only references)

The first trap is confusing finally and finalize(). The names are similar, but the concepts are unrelated. finally is part of exception handling and runs after a try-catch flow. finalize() is a method associated with garbage collection and should not be used for reliable cleanup.

The second trap is saying finally always executes without qualification. In normal exception flow, it executes. But if the JVM exits through System.exit(), crashes, or loses power, the finally block may not execute. A careful answer says "finally executes almost always in normal JVM flow."

The third trap is believing finalize() always runs before an object disappears. Garbage collection is not guaranteed to run at a specific time, and finalize is deprecated. A correct answer says finalize was intended for cleanup before garbage collection, but it is unreliable and should be avoided.

The fourth trap is overgeneralizing final. A final primitive value cannot change, and a final reference cannot be reassigned, but a final reference can still point to a mutable object. This distinction is especially important in questions about immutable classes.

How to Answer in an Interview

A strong answer should begin with a clean comparison: final is a keyword for restriction, finally is a block for cleanup after try-catch, and finalize() is a deprecated Object method related to garbage collection. Then expand each term with one example. Mention final variables, final methods, and final classes. Mention finally running after try-catch. Mention finalize being unreliable and deprecated.

After the basic comparison, add the modern Java recommendation. Say that final and finally are valid language features, but finalize() should not be used for new code. For resource cleanup, prefer try-with-resources and AutoCloseable. This statement shows that you know both the historical concept and the current best practice.

If the interviewer asks for edge cases, discuss System.exit() for finally, final references versus immutable objects for final, and garbage collection uncertainty for finalize. These follow-up points are often what turn an average answer into a strong one.

Best Practices

Use final when it improves clarity. Constants should generally be static final. Method parameters and local variables may be final when reassignment would make code harder to understand. Methods and classes should be final when the design intentionally forbids overriding or inheritance. Avoid using final everywhere mechanically if it reduces readability without adding value.

Use finally for cleanup that must run after a try-catch operation, but keep it simple. Avoid returning from finally blocks. Avoid throwing new exceptions from finally unless you intentionally want to replace the original exception. Prefer try-with-resources for closeable resources because it is cleaner and handles resource closing more safely.

Do not use finalize() for cleanup in modern Java. If you maintain legacy code that uses finalize, treat it as technical debt. Replace it with explicit lifecycle management, try-with-resources, AutoCloseable, or cleaner alternatives depending on the use case. Cleanup should be predictable, not garbage-collector dependent.

Interview-Ready Answers

Short Answer

final restricts modification, finally ensures execution of cleanup code, and finalize() is called by the garbage collector before object destruction.

Detailed Answer

In Java, final is a keyword used to restrict inheritance, overriding, or reassignment. finally is a block associated with exception handling that executes regardless of whether an exception occurs. finalize() is a method of the Object class that was intended for cleanup before garbage collection, but it is deprecated due to unpredictability.

You can make the detailed answer stronger by adding examples: a final variable cannot be reassigned, a final method cannot be overridden, and a final class cannot be extended. A finally block runs after try-catch to release resources or perform cleanup, except in abnormal termination cases such as System.exit() or JVM crash. The finalize() method is garbage-collection dependent, not guaranteed, deprecated, and replaced in practice by try-with-resources and explicit cleanup.

Key Takeaway

final → restriction
finally → guaranteed execution
finalize() → unreliable, deprecated
Mastering this distinction is mandatory for Core Java interviews and demonstrates strong fundamentals.

Remember the domains: final belongs to restriction and object-oriented design, finally belongs to exception handling and cleanup, and finalize() belongs to legacy garbage-collection cleanup. The terms sound similar, but they are not interchangeable. If you explain the type, purpose, execution timing, and modern recommendation for each one, you can handle almost any interview question on this topic confidently.