Java try-catch Block

In the last lesson you learned an introduction to exceptions. Until now, when something went wrong, the program just stopped. But real programs cannot give up at the first problem. Java gives you a clean way to handle errors instead of crashing, and the tool for that is try-catch.

🤔 What is an exception?

An exception is an event that disrupts the normal flow of your program:

  • Normal flow means Java runs your lines top to bottom, one after another.
  • An exception is what happens when a line hits a problem it cannot handle.
  • Java creates an exception object that describes the problem, then “throws” it.
  • Throwing means Java stops the current line and looks for someone to handle the error.
  • It is not a typo. Your code compiled fine. This happens while the program runs.

These kinds of things throw an exception:

  • Dividing an integer by zero.
  • Turning the word “hello” into a number.
  • Calling a method on a null variable.
  • Asking for item 5 in an array of only 3.

💥 What happens with no handling

Here is a program that divides by zero and nobody handles it.

public class Main {
public static void main(String[] args) {
int a = 10;
int b = 0;
System.out.println(a / b); // ❌ throws an exception here
System.out.println("This line never runs");
}
}

Dividing by zero throws an ArithmeticException. Nobody handles it, so it travels up and leaves main. Java has no one left to ask. So it stops the program and prints a stack trace, the report Java prints when an unhandled exception ends the program.

Output

Exception in thread "main" java.lang.ArithmeticException: / by zero
at Main.main(Main.java:5)

Read that report:

  • The first line names the type, ArithmeticException, and the message, ”/ by zero”.
  • The at line tells you exactly where: inside Main.main, line 5 of Main.java.
  • That at Main.main part is gold for debugging. It points at the broken line.
  • The last println never ran. Once thrown, everything after it was skipped.

🧩 The try-catch syntax

The fix is to wrap the risky code in a try block and handle any problem in a catch block. The try block holds the code that might throw. The catch block holds your plan for when it does.

try {
// code that might throw an exception
} catch (ExceptionType e) {
// what to do if it does
}

What each part means:

  • try wraps the risky lines. You tell Java to watch them for trouble.
  • catch names one exception type and gives that error a variable, here called e.
  • The body of catch is your recovery code. It runs only when a matching exception is thrown.

So the catch is not a backup that always runs. It is a safety net that catches you only if you fall.

🔀 How the flow actually works

The flow depends on whether an exception is thrown:

  • No problem in try? Java skips the catch entirely.
  • A line in try throws? Java stops right there. The rest of the try block is skipped.
  • Java jumps to the catch. If the type matches, that catch body runs.
  • After the catch finishes, the program continues past the whole try-catch.

That “rest of try is skipped” rule surprises people, so keep it in mind.

💡 Worked example: handling the divide by zero

Let’s take that crashing program and make it survive. Same code, now wrapped in try-catch.

public class Main {
public static void main(String[] args) {
int a = 10;
int b = 0;
try {
System.out.println(a / b); // risky line
System.out.println("after divide"); // skipped when divide throws
} catch (ArithmeticException e) {
System.out.println("Cannot divide by zero!"); // ✅ handled here
}
System.out.println("Program continues normally.");
}
}

Walk through it:

  • Java runs a / b, which throws an ArithmeticException.
  • The “after divide” line is inside try but never runs. That is the skip rule.
  • Java jumps to the catch, the type matches, and it prints the friendly message.
  • Then the program leaves the try-catch and runs the final line as normal.

Output

Cannot divide by zero!
Program continues normally.

No crash, no stack trace. A fatal crash became a handled situation. That is the whole point of try-catch.

📋 Reading the exception with getMessage

The caught exception is an object that carries information:

  • getMessage() returns a short text description of what went wrong.
  • It is great for logging and for showing helpful detail.

Here we reach past the end of an array, then read the message.

public class Main {
public static void main(String[] args) {
try {
int[] numbers = {1, 2, 3};
System.out.println(numbers[5]); // ❌ index 5 does not exist
} catch (ArrayIndexOutOfBoundsException e) {
System.out.println("Bad index: " + e.getMessage());
}
}
}

The array has 3 items, so valid indexes are 0, 1, and 2. Asking for index 5 throws an ArrayIndexOutOfBoundsException. We catch it and call e.getMessage() for the exact detail.

Output

Bad index: Index 5 out of bounds for length 3

The catch type matches the actual error. That match is how Java picks this catch. Catch the wrong type and the catch is skipped, like a key that does not fit the lock.

🔢 Parsing input that is not a number

You will turn text into numbers constantly. Integer.parseInt takes a String and returns an int. But if the text is not a valid number, it throws a NumberFormatException.

This program reads a price from text that has a stray letter.

public class Main {
public static void main(String[] args) {
String input = "12x";
try {
int price = Integer.parseInt(input); // ❌ "12x" is not a number
System.out.println("Price is " + price);
} catch (NumberFormatException e) {
System.out.println("That is not a valid number: " + e.getMessage());
}
}
}

The text "12x" cannot become an int, so parseInt throws. We catch the NumberFormatException and tell the user clearly. In a real app this is where you ask them to type the value again.

Output

That is not a valid number: For input string: "12x"

🎯 Catching specific types vs a broad Exception

Every catch names a type. It can be specific like ArithmeticException, or the broad parent Exception, which matches almost everything. Catching Exception is tempting but usually a poor choice:

  • A broad catch hides surprises. A bug you did not expect gets swallowed.
  • A broad catch cannot react smartly. A divide-by-zero and a bad file need different responses.
  • A specific catch documents your intent. catch (NumberFormatException e) says exactly what you planned for.
  • So catch the specific types you can handle. Use broad Exception only as a last-resort net, and log what you caught.
// ❌ Too broad: swallows every problem, even bugs you did not plan for
try {
int price = Integer.parseInt(input);
} catch (Exception e) {
System.out.println("Something went wrong");
}
// ✅ Specific: you know exactly what you are handling
try {
int price = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.out.println("Please enter a valid number");
}

🧱 Multiple catch blocks and their order

One try can have several catch blocks after it:

  • Different lines can throw different exceptions.
  • Java checks the catches top to bottom.
  • It uses the first one whose type matches.

Here we call a method on a null String.

public class Main {
public static void main(String[] args) {
try {
String text = null;
System.out.println(text.length()); // ❌ calling a method on null
} catch (ArithmeticException e) {
System.out.println("Math error");
} catch (NullPointerException e) {
System.out.println("Tried to use a null value"); // ✅ this one runs
}
}
}

Calling length() on null throws a NullPointerException. Java checks the first catch, ArithmeticException, sees no match, and skips it. The second catch matches and runs. So each kind of error gets its own response.

Output

Tried to use a null value

Order matters here. Specific exceptions must come before general ones. Exception is the parent of almost everything. So a catch for Exception placed first grabs the error before any specific catch gets a turn. Java refuses to compile that, because the specific catch would be unreachable.

// ❌ Won't compile: the broad Exception catch makes the specific one unreachable
try {
int x = Integer.parseInt("abc");
} catch (Exception e) { // grabs everything first
System.out.println("general");
} catch (NumberFormatException e) { // can never be reached
System.out.println("number problem");
}
// ✅ Specific first, general last
try {
int x = Integer.parseInt("abc");
} catch (NumberFormatException e) { // specific, checked first
System.out.println("number problem");
} catch (Exception e) { // general safety net, last
System.out.println("general");
}

➕ Multi-catch with the pipe symbol

Sometimes two exceptions need the same response. Writing two near-identical catch blocks is repetitive. So Java lets you combine them in one catch using the | symbol. This is called multi-catch.

This single catch handles a bad number and a bad array index the same way.

public class Main {
public static void main(String[] args) {
String[] data = {"10", "20"};
try {
int value = Integer.parseInt(data[5]); // could fail two ways
System.out.println(value);
} catch (NumberFormatException | ArrayIndexOutOfBoundsException e) {
System.out.println("Bad data: " + e.getMessage()); // ✅ one handler, two types
}
}
}

Read the catch line as “catch a NumberFormatException OR an ArrayIndexOutOfBoundsException”. Whichever is thrown, this one block handles it. Here data[5] is out of bounds, so the array exception is caught.

Output

Bad data: Index 5 out of bounds for length 2

📦 Checked vs unchecked exceptions

Two families of exceptions decide when you must write try-catch:

  • Unchecked exceptions like ArithmeticException and NullPointerException are the ones we have used. Java does not force you to handle them. They usually come from bugs in your logic.
  • Checked exceptions like IOException come from reading a file or opening a connection. Java forces you to deal with them, by try-catch or by declaring the method passes them on.
  • Simple rule: unchecked means Java trusts you to avoid it. Checked means Java insists you have a plan.

You will work with checked exceptions a lot once you start reading files.

⚠️ Common Mistakes

A few try-catch slip-ups trip up almost everyone.

Catching Exception too broadly. A blanket catch grabs problems you never meant to handle, including real bugs.

// ❌ Hides any and every problem behind one vague line
try {
process(order);
} catch (Exception e) {
System.out.println("error");
}
// ✅ Catch what you can actually handle
try {
process(order);
} catch (NumberFormatException e) {
System.out.println("Order amount was not a number");
}

An empty catch that swallows the error. A catch with nothing inside makes the exception vanish without a trace. The program limps on in a broken state and you have no idea why.

// ❌ Silence: the error disappears and you can never debug it
try {
int x = Integer.parseInt(input);
} catch (NumberFormatException e) {
}
// ✅ At the very least, report what happened
try {
int x = Integer.parseInt(input);
} catch (NumberFormatException e) {
System.out.println("Could not read number: " + e.getMessage());
}

Wrong catch order. Putting the general Exception before a specific type makes the specific catch unreachable, and Java will not even compile it.

// ❌ Specific catch can never run, compile error
try {
risky();
} catch (Exception e) {
handleGeneral();
} catch (NullPointerException e) {
handleNull();
}
// ✅ Specific first, then the general net
try {
risky();
} catch (NullPointerException e) {
handleNull();
} catch (Exception e) {
handleGeneral();
}

✅ Best Practices

  • Wrap only the risky code. Keep the try block small and around the line that can actually fail. A giant try makes it unclear which line broke.
  • Catch specific exceptions. Handle ArithmeticException or NumberFormatException by name, not just a blanket Exception, so each problem gets the right response.
  • Order specific before general. If you do use a broad Exception catch, put it last as a final net.
  • Never leave a catch empty. Log it, show a message, or recover. A swallowed error is a bug you will hunt for hours.
  • Use the exception’s message. e.getMessage() tells you and your logs what actually went wrong.
  • Use multi-catch for shared handling. When two exceptions need the same response, combine them with | instead of repeating yourself.

🧩 What You’ve Learned

Nicely done. Let’s recap.

  • ✅ An exception is an event that disrupts normal flow; unhandled, it stops the program and prints a stack trace.
  • ✅ Put risky code in a try block and handle errors in a catch block of the matching type.
  • ✅ When an exception is thrown, the rest of try is skipped and Java jumps to the matching catch, then continues after it.
  • ✅ Prefer specific exception types over a broad Exception, and use getMessage() for detail.
  • ✅ Use multiple catch blocks ordered specific to general, or multi-catch with | when several errors share one response.
  • ✅ Exceptions come in checked and unchecked families; never leave a catch empty.

Check Your Knowledge

Test what you learned. Pick an answer for each question, then click Check.

  1. 1

    What is an exception?

    Why: An exception is an event that breaks the normal top-to-bottom flow when a line hits a problem it cannot handle.

  2. 2

    When an exception is thrown inside a try block, what happens to the rest of the try block?

    Why: Java stops at the throwing line, skips the rest of try, and jumps to the matching catch block.

  3. 3

    How should you order multiple catch blocks?

    Why: Specific types must come first; a general Exception placed first would make later specific catches unreachable and fail to compile.

  4. 4

    What does the | symbol do in a catch like catch (A | B e)?

    Why: Multi-catch with | lets a single catch block handle two or more exception types that share the same response.

🚀 What’s Next?

Sometimes there is cleanup you must do whether or not an error happens, like closing a file. For that, try-catch has a companion block that always runs: finally. Let’s learn it next.

Java finally Block

Share & Connect