Java Exception Handling Tutorial with Runnable Examples
Learn how Java moves control through try, catch, and finally, then build checked custom exceptions and safer file handling with runnable code.
KnowledgeGate Team
Exam prep & CS education

A program may work for expected input, then stop when it receives "seven", divides by zero, or cannot find a file. Java exception handling redirects control through try, catch, finally, throw, throws, custom exceptions, and try-with-resources. For a focused treatment of checked versus unchecked rules and finally return traps, use Exception Handling in Java: Checked vs Unchecked, finally Traps and Output Questions. DivideDemo traces catch selection, WithdrawalDemo carries a checked domain exception, and ScoreReaderDemo closes its reader automatically.
What an exception is and how Java classifies it
An exception is an object representing a problem during execution. On the normal path, Java completes the current statement and moves to the next one. On the exceptional path, it creates or propagates an exception object until it finds a compatible handler. During debugging, inspect its type, message, and method-call stack trace.
Keep Error separate. A blanket catch (Throwable) can hide serious failures the application cannot handle.
Use two practical groups:
A checked exception such as
IOExceptionmust be caught or declared withthrows.An unchecked exception such as
NumberFormatExceptionorArithmeticExceptionneed not be caught or declared.
Catch matching follows inheritance. catch (RuntimeException e) matches its subclass NumberFormatException, but the specific catch usually supports a clearer response.
Follow try, catch, and finally in a runnable example
Save this as DivideDemo.java:
public class DivideDemo {
public static void main(String[] args) {
int numerator = 84;
String divisorText = "0";
try {
int divisor = Integer.parseInt(divisorText);
int quotient = numerator / divisor;
System.out.println("Quotient: " + quotient);
} catch (NumberFormatException e) {
System.out.println("Not a whole number: " + divisorText);
} catch (ArithmeticException e) {
System.out.println("Cannot divide " + numerator + " by zero");
} finally {
System.out.println("Attempt complete.");
}
}
}With divisorText = "0", parsing succeeds, division throws ArithmeticException, and the output is:
Cannot divide 84 by zero
Attempt complete.Change the text to "7". The calculation is 84 / 7 = 12, so Java prints Quotient: 12 and then Attempt complete. Change it to "seven", and parsing fails before division. Java prints Not a whole number: seven and then Attempt complete. Only the first compatible catch runs. The finally block runs after both the normal path and these handled-exception paths.
Catch where you can add context, choose a fallback, request input, or terminate cleanly. Do not catch and continue without a meaningful response.
Build a checked custom exception
This program separates invalid input from a valid request the account cannot satisfy. Save it as WithdrawalDemo.java:
class InsufficientBalanceException extends Exception {
private final int shortfall;
InsufficientBalanceException(int shortfall) {
super("insufficient balance");
this.shortfall = shortfall;
}
int getShortfall() {
return shortfall;
}
}
class BankAccount {
private int balance = 5000;
void withdraw(int amount) throws InsufficientBalanceException {
if (amount <= 0) {
throw new IllegalArgumentException("amount must be positive");
}
if (amount > balance) {
throw new InsufficientBalanceException(amount - balance);
}
balance -= amount;
}
int getBalance() {
return balance;
}
}
public class WithdrawalDemo {
public static void main(String[] args) {
BankAccount account = new BankAccount();
int[] attempts = {1250, 6200, -50};
for (int amount : attempts) {
try {
account.withdraw(amount);
System.out.println("Withdrew " + amount);
} catch (InsufficientBalanceException e) {
System.out.println("Declined: short by " + e.getShortfall());
} catch (IllegalArgumentException e) {
System.out.println("Invalid amount: " + amount);
} finally {
System.out.println("Balance now: " + account.getBalance());
}
}
}
}The first withdrawal leaves 5000 - 1250 = 3750. The second has shortfall 6200 - 3750 = 2450 and does not change the balance. The negative amount is rejected before any subtraction. The exact output is:
Withdrew 1250
Balance now: 3750
Declined: short by 2450
Balance now: 3750
Invalid amount: -50
Balance now: 3750throw creates and sends one exception at that point. throws declares that a method may pass a checked exception to its caller. The custom type carries shortfall = 2450 as structured data, so the caller does not have to parse a message.

Use try-with-resources for closeable resources
Save this as ScoreReaderDemo.java. Place scores.txt beside it with 42 on the first line:
import java.io.BufferedReader;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class ScoreReaderDemo {
public static void main(String[] args) {
try (BufferedReader reader =
Files.newBufferedReader(Path.of("scores.txt"))) {
int score = Integer.parseInt(reader.readLine());
System.out.println("Score: " + score);
} catch (IOException e) {
System.out.println("Could not read scores.txt");
} catch (NumberFormatException e) {
System.out.println("scores.txt must start with an integer");
}
}
}The valid file prints Score: 42. A missing file reaches the IOException catch; forty-two reaches the NumberFormatException catch. An opened reader closes automatically after success or failure. Keep finally for small actions such as the balance log; use try-with-resources for reliable closing.

Avoid exception-handling mistakes that hide bugs
An empty catch hides both the failure and its location. If Integer.parseInt("12A") fails, reject 12A with a clear message or propagate the exception with useful context. Do not use exceptions for an expected boolean decision, and do not return from finally, because that return can replace an earlier result or exception.
Order catches from specific to broad. Save this example as CatchOrderDemo.java. The ordering keeps the IOException response reachable and preserves its cause:
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
public class CatchOrderDemo {
public static void main(String[] args) {
try {
Files.readString(Path.of("order-1042.txt"));
} catch (IOException e) {
throw new IllegalStateException("Cannot load order 1042", e);
} catch (Exception e) {
System.out.println("Unexpected problem");
}
}
}Reversing the catches makes the later IOException catch unreachable because Exception already matches it. Passing e into the replacement exception preserves the original trace. For a related Java value-object idea, revise the String handling article.
Practise output tracing and compiler diagnosis
Place each of the next two snippets inside a main method. First predict this output:
int zero = 0;
try {
System.out.print("A");
int x = 8 / zero;
} catch (ArithmeticException e) {
System.out.print("B");
} finally {
System.out.print("C");
}
System.out.print("D");It prints ABCD. Java prints A, division throws before the assignment completes, the catch prints B, finally prints C, and execution resumes after the handled exception to print D.
Next, Integer.parseInt("24x") throws NumberFormatException. A multi-catch can show the matched runtime type:
try {
Integer.parseInt("24x");
} catch (NumberFormatException | ArithmeticException e) {
System.out.print(e.getClass().getSimpleName());
} finally {
System.out.print(":done");
}The output is NumberFormatException:done. For a compiler question, suppose a method calls Files.readString(Path.of("marks.txt")) but has neither catch (IOException e) nor throws IOException. The diagnosis is an unhandled checked exception, not a runtime output. If an assessment names a language version or syntax subset, confirm its rules in the assessment owner's current official instructions.
Exercises, the short version, and your next step
Try these without copying the earlier programs:
Parse
{"18", "x", "24"}. Produce valid-value sum18 + 24 = 42and rejected-count1.Validate age
15against minimum18with a checkedAgeOutOfRangeException. PrintAge 15 is below minimum 18.Read
marks.txtcontaining72,88,bad, and95. Report invalid line3; the valid sum is72 + 88 + 95 = 255, and the average is255 / 3 = 85.0. Use try-with-resources.
Use these rules:
Catch only an exception you can handle meaningfully.
Order catches from the most specific type to the broadest.
Use
throwfor the event andthrowsfor the method contract.Create a custom exception for a meaningful domain failure.
Use try-with-resources for closeable resources.
Continue the language sequence with the Java course. Move to DSA using Java after your language basics are stable, or browse the broader Coding & Skills category. Run all three DivideDemo input variants, then complete Exercise 1 before moving on.
Keep learning
TreeMap and SortedMap in Java: Ordering, Range Views and Worked Examples
Learn how Java's TreeMap orders keys, answers floor and ceiling queries, exposes live range views, and handles comparator collisions through worked examples.
HashSet and TreeSet in Java: Runnable Examples and Key Differences
Learn how Java sets handle duplicates, ordering, navigation and custom objects through deterministic examples you can run and modify.
Java Garbage Collection Tutorial: Reachability, Leaks and Runnable Examples
Trace three connected objects, find exactly when each becomes eligible for collection, and repair a cache that keeps byte arrays reachable.
Java Memory Model Tutorial: Happens-Before, volatile and Safe Publication
Learn the Java Memory Model through exact start/join, volatile, synchronized, final-field and AtomicInteger examples, with clear happens-before guarantees.





