1. Introduction to the finally block
When you work with resources — files, network connections, databases — it is important to be sure they will be closed or released always, even if an error occurs during processing. Java provides a special block for this — finally.
How does finally work?
The finally block is part of the try-catch-finally construct. The code inside finally always runs (if it’s present) — regardless of whether an exception occurred or not. Even if there is a return in try or an exception was thrown — finally will still execute (unless you power off the computer or forcibly terminate the program via System.exit(0)).
Syntax:
try {
// Code where an exception may occur
} catch (ExceptionType e) {
// Error handling
} finally {
// This code will always run!
}
Example
try {
System.out.println("Start");
int result = 10 / 0; // an error will occur here
System.out.println("Result: " + result);
} catch (ArithmeticException e) {
System.out.println("Error: division by zero");
} finally {
System.out.println("This code will run no matter what");
}
Execution result:
Start
Error: division by zero
This code will run no matter what
What happens?
- In try we attempt to divide two numbers and get an error.
- If an error occurs during division — catch handles it.
- But! In any case finally runs and writes a message to the console.
2. finally without catch
There are 3 possible variants of the construct:
- Full: try-catch-finally
- Without finally: try-catch
- Without catch: try-finally
The third option is used when a method one level up will catch and handle the error. But the finally block is needed to guarantee that certain code will run:
- Closing files, network connections, databases.
- Releasing any resources (for example, locks).
- Logging: recording information about the completion of an operation.
Example:
try {
System.out.println("Dividing numbers");
int result = 10 / 0; // error!
System.out.println("Result: " + result);
} finally {
System.out.println("finally block executed");
}
Result:
Dividing numbers
finally block executed
Exception in thread "main" java.lang.ArithmeticException: / by zero
When does finally NOT run?
It runs almost always. Exceptions — if:
- The program is forcibly terminated using: System.exit(0).
- The thread executing finally is forcibly “killed”.
- The computer is powered off.
3. The throw statement: how to generate an exception manually
Sometimes Java throws exceptions on its own (for example, division by zero, out-of-bounds array access). But there are situations when you want to explicitly say: “This is an error! I cannot continue execution!” For this, Java has the throw statement.
Analogy: If you are in a store and see an expired product — you file a complaint. Likewise in code: if something is wrong — you throw an exception.
throw syntax
throw new ExceptionType("Error message");
ExceptionType is any class that extends Throwable (typically Exception or RuntimeException). In parentheses is a message that helps you understand what went wrong.
Example: validating method arguments
public static int safeDivide(int a, int b) {
if (b == 0) {
throw new IllegalArgumentException("Divisor cannot be zero");
}
return a / b;
}
Usage:
public static void main(String[] args) {
try {
int result = safeDivide(10, 0);
System.out.println("Result: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
}
Result:
Error: Divisor cannot be zero
When to use throw?
- Validating method arguments (for example, when null or invalid data is passed).
- Validating an object's state (for example, attempting to withdraw money from an account that has €0).
- Inside catch — if you want to rethrow the exception further (for example, to add additional information).
4. Combining try-catch-finally and throw
Sometimes these constructs work together. For example, you catch one error, and then decide to throw your own, more informative one.
public static int parseAndDivide(String text, int divisor) {
try {
int number = Integer.parseInt(text);
if (divisor == 0) {
throw new IllegalArgumentException("Divisor cannot be zero");
}
return number / divisor;
} catch (NumberFormatException e) {
throw new IllegalArgumentException("The string '" + text + "' is not a number");
} finally {
System.out.println("Attempt to process string: " + text);
}
}
Usage:
try {
int result = parseAndDivide("42a", 2);
System.out.println("Result: " + result);
} catch (IllegalArgumentException e) {
System.out.println("Error: " + e.getMessage());
}
Result:
Attempt to process string: 42a
Error: The string '42a' is not a number
Important nuance: return and finally
Even if there is a return in the try block, finally will still run!
public static int getValue() {
try {
return 10;
} finally {
System.out.println("finally will still run!");
}
}
Calling getValue() will print:
finally will still run!
5. Common mistakes when using finally and throw
Mistake #1: forgot to close a resource without finally.
A very common issue: you opened a file and didn’t close it — you got a resource leak. Always use finally (or try-with-resources, which we will discuss later).
Mistake #2: threw an exception but didn’t handle it.
If you throw an exception using throw but don’t catch it anywhere (no try-catch), the program will crash. Always think about who will catch your exception.
Mistake #3: return in finally.
If you mistakenly write return inside finally, it will override all previous return or throw. This can lead to very hard-to-find bugs. Doing this is strongly discouraged!
public int tricky() {
try {
return 1;
} finally {
return 2; // DANGEROUS: 2 will be returned, not 1!
}
}
Result: 2 will be returned even though try had 1.
Mistake #4: losing exception information.
If you catch one exception and then throw a new one without preserving information about the original (e), you lose the stack trace, which makes debugging harder. It’s better to write:
catch (NumberFormatException e) {
throw new IllegalArgumentException("Conversion error", e);
}
GO TO FULL VERSION