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.
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.