Beginner's Guide To Try And Catch In Java

Maaike van Putten
Maaike van Putten
hero image

Java is great until something goes wrong, because one unexpected error, and your whole program crashes. But what if you could stop that from happening?

That’s where try and catch comes in. They let you handle errors before they derail your program, keeping everything running smoothly.

In this guide, you’ll learn how to catch errors the right way, so debugging feels less like a guessing game and more like problem-solving.

Let’s dive in.

Sidenote: If you find that you’re struggling with the topics in this guide, or perhaps feel that you could use some more training, or simply want to build some more impressive projects for your portfolio, then check out my Java programming bootcamp:

Learn Java coding

Updated for 2025, you'll learn Java programming fundamentals all the way from complete beginner to advanced skills.

Better still, you’ll be able to reinforce your learning with over 80 exercises and 18 quizzes. This is the only course you need to go from complete programming beginner to being able to get hired as a Backend Developer!

With that out of the way, let’s get into this 5-minute tutorial…

What is try and catch in Java?

No matter how well Java developers write their code, errors will happen. Some come from coding mistakes, like dividing by zero or forgetting to initialize a variable. Others are out of the developer’s control, such as missing files, failed internet connections, or bad user input.

Java categorizes these errors into two types:

  • Unchecked exceptions - These are mistakes in code that should be fixed (e.g., NullPointerException)
  • Checked exceptions - These are external failures that can happen even in well-written code

Since checked exceptions are unavoidable, Java requires developers to handle them themselves, because if they aren’t caught, the program will crash.

That’s exactly what try and catch are for. We can assume errors will happen and engineer solutions in advance. So if X problem happens, do Y instead of crashing.

Or in Java terms:

Let’s try to run this, and if an exception is about to happen, catch it and do this instead.

For example

Imagine you’re building a calculator that divides two numbers. Everything works fine until someone enters zero as the denominator and it crashes the whole thing:

public class NoTryCatch {
	public static void main(String[] args) {
    	    int result = 10 / 0;  // Error: Division by zero!
    	    System.out.println("This will never run.");
	}
}

Here we can see that Java detects the division by zero, knows this wont work, and so throws an ArithmeticException, and shuts the program down immediately.

The problem of course is that the user has no idea what went wrong because our warning of "This will never run." never even gets printed! The program crashing stops them from knowing about their error.

Now imagine this happening in an online banking app. If a transaction fails, you don’t want the entire system to crash or the user to think their money is lost.

Instead, you want the program to catch the error and handle it properly, like so:

public class TryCatchExample {
	public static void main(String[] args) {
    	    try {
        	        int result = 10 / 0;
                    System.out.println("This won't run.");
    	    } catch (ArithmeticException e) {
                    System.out.println("Oops! You can't divide by zero.");
    	    }
    	    System.out.println("Try again.");
	}
}

This time, instead of letting the program crash, Java jumps to the catch block, where we handle the error by printing "Oops! You can't divide by zero." After handling the exception, execution moves past the catch block, and "Try again" is printed. That’s a huge difference.

Now, the user at least knows what went wrong instead of being left in the dark. More importantly, the program keeps running so they can try again.

Bear in mind that this is actually not a situation that requires try and catch. This is merely to illustrate the mechanism. The ArithmeticException is actually an unchecked exception. If it were a checked exception we would have to handle it.

TL;DR

This is the power of try and catch. Errors will happen and you can’t stop that. But what you can do is decide how your program should respond when they do.

Now that you understand the goal of try and catch, let’s go deeper and look at some other situations, such as what would you do if multiple things can go wrong at once?...

How to use multiple catch blocks to handle different errors

Errors rarely happen in isolation. A single block of code can fail in multiple ways, depending on the input, the environment, or just bad luck.

For example

Let’s say a program divides numbers and accesses an array.

Both can go wrong. Java throws the first error it encounters and immediately stops execution. The second error never even occurs. How would we handle that, we could do something like this:

public class NoTryCatch {
	public static void main(String[] args) {
                try {
    	    int[] numbers = {1, 2, 3};
    	    int result = 10 / 0;  // Error: Division by zero!
    	    System.out.println(numbers[5]);  // Error: Out of bounds!
                } catch (Exception e) {
                    System.out.println("Problem: " + e.getMessage());
                }
	}
}

Since 10 / 0 is an invalid operation, Java throws an ArithmeticException and crashes. That means the next line — System.out.println(numbers[5]); — never runs. However, if we didn’t have the first problem, we would have had the ArrayIndexOutOfBoundsException. Both of these are exceptions and would be handled by our catch block.

Why does this matter?

If we can have multiple errors, we might want to handle them differently. That is why multiple catch blocks are great. They tell us specifically what went wrong and can take precise action.

So how do we do this?

We can handle both exceptions separately by using multiple catch blocks.

public class MultipleCatchExample {
	public static void main(String[] args) {
    	    try {
          	        int[] numbers = {1, 2, 3};
        	        int result = 10 / 0;  // ArithmeticException
        	        System.out.println(numbers[5]);  // ArrayIndexOutOfBoundsException
    	    } catch (ArithmeticException e) {
        	        System.out.println("Math error: " + e.getMessage());
    	    } catch (ArrayIndexOutOfBoundsException e) {
        	        System.out.println("Invalid array index!");
    	    }
	}
}

Now, Java detects the first error (10 / 0) and jumps to the corresponding catch block, printing:

Math error: / by zero

Since an exception stops execution inside the try block, the second line (numbers[5]) never runs - just like before.

The difference? The program now has precise error handling when it goes wrong. And we know which error was thrown. Also, anything that we can’t really imagine at this point, such as a NullPointerException in that block, won’t be caught anymore either.

For example

Let’s say we change 10 / 0 to 10 / 2, so the division no longer fails.

public class FixedFirstError {
	public static void main(String[] args) {
    	try {
        	int[] numbers = {1, 2, 3};
        	int result = 10 / 2;  // Fixed: No more division by zero
        	System.out.println(numbers[5]);  // Still causes an error!
    	} catch (ArithmeticException e) {
        	System.out.println("Math error: " + e.getMessage());
    	} catch (ArrayIndexOutOfBoundsException e) {
        	System.out.println("Invalid array index!");
    	}
	}
}

Now that division works, Java continues execution and reaches numbers[5], which is out of bounds. This time, it throws an ArrayIndexOutOfBoundsException, which is caught by the second catch block, printing:

Invalid array index!

That’s exactly why multiple catch blocks exist — so we can handle each potential failure individually rather than dealing with all of them in the same way.

How to use the finally block

So far, we’ve seen how try and catch handle exceptions and prevent your program from crashing. But what if you always need to run some code no matter what happens?

Let’s say your program opens a file, reads its contents, and then closes it. If the file is missing, an error occurs. But whether or not there’s an error, you still need to close the file to prevent resource leaks.

Well, that’s where the finally block comes in.

The finally block runs after the try block - no matter what happens inside it. Whether an exception is thrown or everything works perfectly, the code inside finally always executes. (Well… Almost always, except if you call something like System.exit(0); in the catch block, which would not be a very common requirement.)

For example

Here we can use finally to close a scanner object that’s reading a file.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class FinallyExample {
	public static void main(String[] args) {
    	Scanner scanner = null; // This has to go outside of the try for scope in the finally block

    	try {
        	File file = new File("data.txt");
        	scanner = new Scanner(file);
        	System.out.println(scanner.nextLine());
    	} catch (FileNotFoundException e) {
        	System.out.println("File not found.");
    	} finally {
        	if (scanner != null) {
            	scanner.close();
            	System.out.println("Scanner closed.");
        	}
    	}
	}
}

What’s happening here?

  • The try block attempts to open the file and read its first line
  • If the file doesn’t exist, Java throws a FileNotFoundException, and the catch block prints "File not found"
  • The finally block runs no matter what happens and closes the scanner to free up system resources

Even if an error occurs, the scanner will always be closed.

Why is this useful?

Simply because many situations require cleaning things up after running some code, including:

  • Closing a file after reading or writing (even if an error occurs)
  • Disconnecting from a database to free up resources
  • Releasing memory or network connections so your program doesn’t slow down over time

Without finally, you’d have to repeat cleanup code in both the try and catch blocks — which is inefficient and easy to forget. But finally ensures that cleanup always happens, whether an error occurs or not, making your program more reliable and efficient.

Sidenote: We won’t deal with the concept of try-with-resources until the end of this beginner guide, but often resources are autocloseable. This means that they can be automatically closed with a special try syntax, eliminating the need for a finally block. You’ll see this soon! Just keep reading.

What if there’s a return statement?

One useful thing to know is that finally always runs, even if a return statement is used inside try or catch.

For example

public class FinallyWithReturn {
	public static void main(String[] args) {
    	System.out.println(testMethod());
	}

	public static String testMethod() {
    	try {
        	return "Try block executed.";
    	} finally {
        	System.out.println("Finally block executed.");
    	}
	}
}

Even though the try block returns a value, Java doesn’t exit the method until the finally block finishes running. That means this prints:

Finally block executed.
Try block executed.

This guarantees that cleanup code inside finally always executes, no matter what.

Now that we’ve covered try, catch, and finally, let’s look at some common mistakes developers make when handling exceptions, and how to avoid them.

Common mistakes in exception handling (and how to avoid them)

Using try and catch is essential for handling errors in Java, but they’re easy to misuse. If you don’t handle exceptions properly, you could hide real bugs, make debugging harder, or even introduce new issues.

Let’s go through the most common mistakes developers make — and how to do things the right way.

Mistake #1. Catching every exception without handling it properly

One of the biggest mistakes Java developers make is catching exceptions without actually handling them. Just because your program doesn’t crash doesn’t mean it’s running correctly.

For example

Some developers wrap their code in a try-catch block but don’t take action when an error occurs. Instead, they leave the catch block empty or print a vague message that doesn’t help with debugging.

public class BadExample {
	public static void main(String[] args) {
    	try {
        	int result = 10 / 0;
    	} catch (Exception e) {
        	// Doing nothing here!
    	}
    	System.out.println("Program continues...");
	}
}

At first glance, this might seem fine because the program doesn’t crash. But here’s why this is a bad idea:

  • The error is completely ignored in that there’s no message, no logging, no indication that anything went wrong
  • The program keeps running in an unknown state, potentially leading to even more failures later
  • You are catching the type Exception here, meaning all sorts of exceptions are ignored
  • Debugging becomes impossible because there’s no information about what went wrong or where it happened

Imagine this happening in a financial application. If an error occurs while processing a payment but gets silently ignored, users might never know their transaction failed and you can later not find out why.

How to fix it

If you catch an exception, make sure you’re actually handling it. That could mean logging the error, notifying the user, retrying an operation, or gracefully shutting down — but never ignoring it completely.

At the very least, log the error so you know what happened:

public class BetterExample {
	public static void main(String[] args) {
    	try {
        	int result = 10 / 0;
    	} catch (ArithmeticException e) {
        	System.out.println("Error: Division by zero occurred.");
        	e.printStackTrace(); // Prints full error details for debugging
    	}
	}
}

In this example:

  • The program logs useful information instead of silently failing
  • The e.printStackTrace(); method prints the full error details, making it easier to trace the problem. In real applications, this is probably replaced with actual logging
  • We still catch the exception, but we also understand what went wrong

Mistake #2. Catching overly generic exceptions

At first, catching every possible exception with Exception might seem like a good safety net. After all, it ensures the program doesn’t crash unexpectedly, right?

But in reality, catching all exceptions this way hides specific errors and makes debugging much harder.

For example

public class BadGenericCatch {
	public static void main(String[] args) {
    	try {
        	int result = 10 / 0;
    	} catch (Exception e) {  // Too broad!
        	System.out.println("An error occurred.");
    	}
	}
}

Why is this bad?

  • It catches all exceptions, even ones you weren’t expecting
  • The error message doesn’t tell us what went wrong—it could be anything!
  • If another serious exception happens (e.g., NullPointerException), you might never realize it

So instead of catching everything, you should catch only the exceptions you expect.

public class BetterSpecificCatch {
	public static void main(String[] args) {
    	try {
        	int result = 10 / 0;
    	} catch (ArithmeticException e) {
        	System.out.println("Math error: " + e.getMessage());
    	}
	}
}
  • Keeps errors meaningful – "Math error: / by zero" is far more helpful than "An error occurred"
  • Avoids hiding critical issues – Other unexpected exceptions remain visible for debugging
  • Ensures better exception handling – Each issue is handled appropriately, not generically

Mistake #3. Using try and catch for unchecked exceptions instead of fixing the root cause

Unchecked exceptions happen when something is wrong in your code—like trying to access a null value. Unlike checked exceptions (which result from external problems like missing files), unchecked exceptions signal a bug that should be fixed, not caught.

But some Java developers misuse try and catch as a shortcut. Instead of fixing the actual problem, they wrap the code in a try-catch block to stop the program from crashing. This hides the real issue, making debugging harder and leading to unpredictable behavior.

For example

Bad practice: Catching NullPointerException instead of preventing it

public class WrongUsage {
	public static void main(String[] args) {
    	try {
        	String text = null;
        	System.out.println(text.length()); // This throws NullPointerException
    	} catch (NullPointerException e) {
        	System.out.println("Oops, something went wrong.");
    	}
	}
}

At first glance, this might seem like a quick fix—the program doesn’t crash. But in reality, this is just covering up a deeper issue instead of solving it.

The real issue isn’t the exception—it’s that text is null in the first place.

Catching a NullPointerException doesn’t fix anything — it just prevents the program from visibly failing. So if you rely on try-catch here, you’re hiding a logic error instead of addressing it properly.

How to fix it

Instead of catching the error, check if the object is null before using it:

public class CorrectUsage {
	public static void main(String[] args) {
    	String text = null;

    	if (text != null) {
        	System.out.println(text.length());
    	} else {
        	System.out.println("Text is null, can't get length.");
    	}
	}
}

Now, instead of catching an exception, we prevent it from happening in the first place.

Mistake #4. Using try and catch when an if statement is better

Some Java developers overuse try-catch for handling predictable situations, like checking user input. While exception handling is great for unexpected failures, it’s not the best tool for normal program logic.

If you can check for a condition before an error happens, that’s almost always better than catching an exception after the fact.

For example

Bad practice: Using try-catch for input validation

public class BadValidation {
	public static void main(String[] args) {
    	String input = "abc";
    	try {
        	int number = Integer.parseInt(input);
        	System.out.println("You entered: " + number);
    	} catch (NumberFormatException e) {
        	System.out.println("Invalid input!");
    	}
	}
}

At first glance, this might seem like a reasonable way to catch bad input—if the user enters something that’s not a number, the program won’t crash.

But this approach is inefficient. Why? Because the program already knows input might be invalid — it should check before attempting to convert it.

Why is this bad?

  • Exception handling should be for unexpected failures, not predictable issues like input validation
  • Parsing errors are preventable — why wait for an exception when you can validate first?
  • Performance impacttry-catch blocks are slower than simple condition checks, especially if used repeatedly in loops

How to fix it

try-catch should be your last resort, not your first choice. If you can prevent an error before it happens (like validating user input), it’s always a better approach. Use try-catch for true unexpected failures, not normal program flow.

For example

Instead of waiting for an exception, check if the input is valid before converting it:

public class CorrectValidation {
	public static void main(String[] args) {
    	String input = "abc";

    	if (input.matches("\\d+")) { // Checks if input contains only digits
        	int number = Integer.parseInt(input);
        	System.out.println("You entered: " + number);
    	} else {
        	System.out.println("Invalid input! You should have entered a number.");
    	}
	}
}

Now, we only try to parse the number if we know it’s valid.

Mistake #5. Not closing resources properly (and relying on finally when you don’t need to)

Earlier, we covered how the finally block ensures cleanup happens no matter what. But a common mistake many Java developers make is forgetting to clean up resources altogether — especially when working with files, databases, or network connections.

If you don’t close a resource after using it, your program can leak memory, slow down, or even crash. It might not be obvious right away, but over time, unclosed resources build up, consuming system memory and causing unpredictable behavior.

For example

A typical mistake is opening a resource inside a try block but not closing it in the finally block.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class BadFileHandling {
	public static void main(String[] args) {
    	Scanner scanner = null;
    	try {
        	scanner = new Scanner(new File("data.txt"));
        	System.out.println(scanner.nextLine());
	scanner.close();
    	} catch (FileNotFoundException e) {
        	System.out.println("File not found.");
    	}
    	// Scanner never gets closed!
	}
}

At first glance, this might not seem like a big deal. But if an error occurs before scanner.close() is called, the scanner stays open. When this happens repeatedly — say, in a loop or a long-running process — resources pile up, memory usage spikes, and performance suffers.

The best fix: Use try-with-resources instead

Since Java 7, try-with-resources is the best way to manage resources automatically. Instead of worrying about closing resources manually, you let Java handle it for you.

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class TryWithResourcesExample {
	public static void main(String[] args) {
    	try (Scanner scanner = new Scanner(new File("data.txt"))) {  
        	System.out.println(scanner.nextLine());
    	} catch (FileNotFoundException e) {
        	System.out.println("File not found.");
    	}
    	// No need for a finally block—Java automatically closes the scanner.
	}
}

This approach ensures the scanner is always closed as soon as the try block exits, whether an exception occurs or not. You don’t need a finally block, and there’s no risk of forgetting to clean up resources.

When should you still use finally?

try-with-resources is the best way to handle autoclosable resources like files and database connections. But there are cases where finally is still useful.

(Some objects don’t implement AutoCloseable, meaning they can’t be used inside try-with-resources. If that’s the case, finally is your fallback option).

It’s also useful when you need to clean up non-resource-related tasks, like resetting global variables or writing log messages.

For example

Here’s how finally can be used as a backup solution:

import java.io.File;
import java.io.FileNotFoundException;
import java.util.Scanner;

public class FinallyExample {
	public static void main(String[] args) {
    	Scanner scanner = null;
    	try {
        	scanner = new Scanner(new File("data.txt"));
        	System.out.println(scanner.nextLine());
    	} catch (FileNotFoundException e) {
        	System.out.println("File not found.");
    	} finally {
        	if (scanner != null) {
            	scanner.close();
            	System.out.println("Scanner closed.");
        	}
    	}
	}
}

Here, even if an exception is thrown, the finally block ensures the scanner gets closed properly. It’s not as clean as try-with-resources, but it’s better than leaving resources open.

TL;DR

Forgetting to clean up resources is a common mistake that can lead to slowdowns, crashes, and memory leaks.

So:

  • If you're working with files, databases, or network connections, use try-with-resources whenever possible. It's cleaner, safer, and removes the risk of forgetting to close things manually
  • If try-with-resources isn’t an option, then a finally block is your next best choice. It guarantees that cleanup happens, even if an error occurs

Time to start breaking (and fixing) your code!

Now that you understand try, catch, and finally, the best way to learn is to experiment.

Write a program, force errors to happen, and see how different exceptions behave. Try catching specific errors, using multiple catch blocks, and adding a finally block to clean up resources.

Fix logic errors instead of just catching them, and always handle exceptions in a way that makes debugging easier.

The more you practice, the better you'll get at writing stable, error-proof Java code.

Now, go break some code — so you can fix it!

P.S.

Remember, if you want to fast-track your Java knowledge and get as much hands-on practice as possible, then check out my Java programming bootcamp:

Learn Java coding

Updated for 2025, you'll learn Java programming fundamentals all the way from complete beginner to advanced skills.

Better still, you’ll be able to reinforce your learning with over 80 exercises and 18 quizzes. This is the only course you need to go from complete programming beginner to being able to get hired as a Backend Developer!

Plus, once you join, you'll have the opportunity to ask questions in our private Discord community from me, other students and working DevOps professionals.


More from Zero To Mastery

How To Ace The Coding Interview preview
How To Ace The Coding Interview

Are you ready to apply for & land a coding job but not sure how? This coding interview guide will show you how it works, how to study, what to learn & more!

How To Get A Job In Tech & Succeed When You’re There! preview
Popular
How To Get A Job In Tech & Succeed When You’re There!

Are you looking to get a job in tech? These are the steps (+ tips, tricks, and resources) from a Senior Developer to get hired in tech with zero experience!

How to Get More Interviews, More Job Offers, and Even a Raise preview
How to Get More Interviews, More Job Offers, and Even a Raise

This is Part 3 of a 3 part series on how I taught myself programming, and ended up with multiple job offers in less than 6 months!