The words final, finally and finalize look alike but have nothing in common, because final is a modifier that prevents change, finally is a block that always runs after try, and finalize() is a deprecated method of Object. Interviewers like the question because the three similar names belong to unrelated features.
We use final every day for constants, immutable fields and classes that must not be extended. The finally block releases resources and restores state, although try-with-resources covers most of its uses. The finalize() method is a cleanup hook that is deprecated for removal, so we only need to recognize it and replace it.
The following example uses all three words in a few lines.
final int maxRetries = 3; // final: cannot be reassigned
String status;
try {
status = "attempts allowed: " + maxRetries;
} finally {
status = "checked"; // finally: runs after try
}
String result = status; // "checked"
// finalize(): never called here, and deprecated for removal since Java 18
Notice that only final and finally are keywords, whereas finalize is a plain method name. We go through each of the three, compare them in one table, and show what replaces finalize() in modern Java.
1. The final Keyword
The final modifier means that something cannot change after it is set. Its exact meaning depends on where we put it.

| Applied to | Effect |
|---|---|
| Local variable or parameter | Can be assigned only once |
| Field | Must be assigned once, in its declaration, an initializer block or every constructor |
| Method | Cannot be overridden in a subclass |
| Class | Cannot be extended, such as String and Integer |
final class Money {
private final long cents; // blank final, set in the constructor
Money(long cents) {
this.cents = cents;
}
final long cents() {
return cents;
}
}
Money price = new Money(1999);
long cents = price.cents(); // 1999
A final reference cannot point to another object, but the object itself can still change. A final List can still receive new elements, so a truly immutable field also needs an immutable object, such as one from List.of().
final List<String> tags = new ArrayList<>();
tags.add("sale"); // allowed, the list changes
int tagCount = tags.size(); // 1
final List<String> fixed = List.of("new");
fixed.add("sale"); // UnsupportedOperationException
Lambdas and anonymous classes can use local variables only if they are final or effectively final, which means they are never reassigned after initialization.
2. The finally Block
A finally block follows a try block, with or without catch blocks, and runs when the try block completes normally, throws an exception, or returns. It is the place for cleanup that must happen on every path.
static String readConfig(boolean fail) {
StringBuilder log = new StringBuilder("open;");
try {
if (fail) {
throw new IllegalStateException("bad file");
}
log.append("read;");
} catch (IllegalStateException e) {
log.append("error;");
} finally {
log.append("close");
}
return log.toString();
}
String ok = readConfig(false); // "open;read;close"
String failed = readConfig(true); // "open;error;close"
For files, streams, connections and locks, try-with-resources is shorter and safer than a hand-written finally block, because it closes every resource and keeps both exceptions when close() also fails.
2.1. When finally Does Not Run
The block runs in almost every case, but not when the JVM stops before reaching it. That happens with System.exit() or Runtime.halt() inside the try block, a JVM crash, a killed process, or a daemon thread that is still running when the JVM exits.
2.2. Never Return from finally
A return inside finally replaces the value returned by try and discards any exception thrown there. The compiler accepts it and warns only with -Xlint:finally, although IDEs flag it, so we keep finally blocks free of return statements.
@SuppressWarnings("finally")
static int lostException() {
try {
throw new IllegalStateException("lost");
} finally {
return -1; // the exception disappears
}
}
int code = lostException(); // -1, no exception reaches the caller
3. The finalize() Method
The Object.finalize() method was meant to run cleanup code before the garbage collector reclaims an object. It never worked well, because the JVM gives no guarantee when, or whether, it runs, and a slow finalizer can delay the collection of many objects. The guide on why not to use finalize() explains these problems in detail.
The method was deprecated in Java 9, and JEP 421 deprecated it for removal in Java 18. Since Java 18, we can test a program without finalization by starting it with –finalization=disabled, which shows whether anything still depends on it.
Two tools replace it. The AutoCloseable interface with try-with-resources releases resources at a known point, and the Cleaner class runs a cleanup action as a fallback when an object becomes unreachable.
class TempFile implements AutoCloseable {
private static final Cleaner CLEANER = Cleaner.create();
private final Cleaner.Cleanable cleanable;
static final List<String> LOG = new ArrayList<>();
TempFile(String name) {
cleanable = CLEANER.register(this, () -> LOG.add("deleted " + name));
}
@Override
public void close() {
cleanable.clean(); // runs the action once
}
}
try (TempFile file = new TempFile("report.tmp")) {
TempFile.LOG.add("writing");
}
String log = TempFile.LOG.toString(); // "[writing, deleted report.tmp]"
The cleanup lambda must not refer to the TempFile object itself, otherwise the object stays reachable and the action never runs.
4. final vs finally vs finalize
The table compares the three, which is the short answer to the interview question.
| final | finally | finalize() | |
|---|---|---|---|
| What it is | Keyword, a modifier | Keyword, a block after try | Method of Object |
| Purpose | Prevents reassignment, overriding or extension | Runs cleanup code on every exit from try | Cleanup before garbage collection |
| Runs | At compile time, as a rule the compiler checks | Every time the try block ends | Maybe, at an unknown time |
| Status in Java 25 | Used everywhere | Used, often replaced by try-with-resources | Deprecated for removal |
5. final, finally and finalize FAQs
Interviewers and developers ask these follow-up questions.
5.1. Is finalize() removed in Java 25?
Not yet. It is deprecated for removal and still exists, but new code should not use it. The –finalization=disabled option lets us check that a program works without it.
5.2. Does finally run if catch throws an exception?
Yes. The finally block runs first, and the exception from catch continues to the caller, unless finally throws its own exception or returns.
5.3. Can a final variable be assigned later?
Yes, once. A blank final local variable can be assigned in a later statement, and a blank final field must be assigned in every constructor or in an initializer block.
5.4. Can we make a constructor final?
No. Constructors are not inherited, so they cannot be overridden, and final has no meaning for them.
6. Conclusion
The final modifier prevents reassignment, overriding and extension, but a final reference can still point to a mutable object. The finally block runs on every exit from try, except when the JVM stops, and it should never contain return.
The finalize() method is deprecated for removal. We release resources with AutoCloseable and try-with-resources, and use Cleaner only as a fallback for objects that a caller forgets to close.
7. References
- JLS 25, final Variables
- JLS 25, Execution of try-finally
- JEP 421, Deprecate Finalization for Removal
- Cleaner (Java SE 25 API)
Happy Learning !!