Menu

PHP try catch: Catch Exceptions, finally and Throwable

In PHP, code that might fail goes in a try { } block, and catch (Exception $e) { } runs if it throws, with the reason in $e->getMessage(). Learn catching several exception types, finally, why catch (Exception) misses TypeError and DivisionByZeroError (catch Throwable), rethrowing, and a global handler.

This page includes runnable editors - edit, run, and see output instantly.

In PHP you put code that might fail inside try { ... } and handle the failure in catch (Exception $e) { ... }. If anything in the try block throws, PHP jumps straight to the matching catch, and $e->getMessage() tells you what went wrong. If nothing throws, the catch block is skipped.

Change divide(10, 0) to divide(10, 5) and run it again: no exception, so not printed is printed and the catch block is skipped. Without try, the exception would end the script with a fatal "Uncaught" error. How to throw your own exceptions and write exception classes is on the exceptions page.

The try catch syntax

The full form has three parts. Only try is required, plus at least one catch or a finally:

try {
    // code that may throw
} catch (SomeException $e) {
    // runs if SomeException (or a subclass) was thrown
} catch (OtherException | ThirdException $e) {
    // runs for either of these types
} finally {
    // always runs, thrown or not
}

The type in catch decides what it catches: the class itself and every class that extends it. Since PHP 8.0 the variable is optional when you do not need it: catch (JsonException) { ... }.

Catch Exception vs Error vs Throwable

Since PHP 7, failures that PHP itself detects (calling a missing method, a wrong argument type, division by zero) throw an Error, not an Exception, and PHP 8 turned many former warnings into TypeError and ValueError. Both implement the Throwable interface, but neither extends the other, so catch (Exception $e) does not catch an Error:

Throwable
├── Exception                 (problems a program expects and handles)
│   ├── InvalidArgumentException, RuntimeException, JsonException, ...
└── Error                     (engine errors, usually a bug in the code)
    ├── TypeError
    ├── ValueError
    ├── ArithmeticError
    │   └── DivisionByZeroError
    └── UnhandledMatchError, ...

Run this to see which catch handles each problem:

So which one should you catch?

  • catch (SomeSpecificException $e) when you know what can go wrong and what to do about it. This is most code.
  • catch (Exception $e) for "anything a library or my code threw on purpose".
  • catch (Throwable $e) at the outermost level (a request handler, a job runner) to log every failure, PHP's own errors included, and show a friendly message.

Catch multiple exception types

Write several catch blocks to handle different types in different ways. PHP tries them from top to bottom and uses the first match, so a specific type must come before its parent, or the parent will catch everything first. When several types get the same treatment, combine them in one catch with |:

All three classes extend Exception, so a single catch (Exception $e) would also catch them, but then the code could no longer answer 404 for one and 400 for the others.

finally: code that always runs

A finally block runs after try and catch no matter how they ended: normally, with a caught exception, with an exception nobody caught, or with a return. It is the place to release whatever the try block acquired: close a file, release a lock, stop a timer.

For good.txt the file is closed before saved is printed, because finally runs before the function actually returns. For bad.txt there is no catch inside process(): finally closes the file, then the exception continues up to the caller's catch.

Avoid return inside finally: it replaces the value the try block returned and silently discards an exception that was on its way out:

function f(): string
{
    try {
        return 'from try';
    } finally {
        return 'from finally';
    }
}

function g(): string
{
    try {
        throw new RuntimeException('lost');
    } finally {
        return 'no exception reaches the caller';
    }
}

echo f(); // from finally
echo g(); // no exception reaches the caller

Rethrow or wrap an exception

Sometimes a catch block can only partly handle a problem: log it, clean up, then let it continue. throw $e; rethrows the same exception. More often you want to add context while keeping the original cause, which is what the third constructor argument, $previous, is for:

The caller deals with one ConfigException and does not need to know the config is JSON, while getPrevious() keeps the low-level detail for the log.

Uncaught exceptions and a global handler

An exception that no catch handles ends the script with a fatal error that includes the class, the message, the file and line, and a stack trace:

<?php
// No try/catch anywhere in this file
function charge(int $cents): void
{
    if ($cents <= 0) {
        throw new DomainException("Amount must be positive, got $cents");
    }
}

charge(500);
echo "first charge ok\n";
charge(-1);
echo "never printed\n";

PHP prints first charge ok and then:

PHP Fatal error:  Uncaught DomainException: Amount must be positive, got -1 in /home/index.php:6
Stack trace:
#0 /home/index.php(12): charge()
#1 {main}
  thrown in /home/index.php on line 6

On a live site that message should never reach visitors. set_exception_handler() registers a function that receives any exception nobody caught, a single place to log it and print a polite page:

The handler runs and then the script stops; there is no way to resume after the throw. Whether PHP's own error text is shown on the page at all is controlled by display_errors, covered in error reporting.

Warnings are not exceptions

try/catch only sees things that are thrown. Many older PHP functions do not throw: they return false or null and, at most, print a warning. json_decode() is a typical case, and wrapping it in try changes nothing until you ask it to throw:

For functions without such a flag, check the return value (if ($handle === false)), or check the precondition first (file_exists(), isset()). A set_error_handler() that throws ErrorException turns every warning into a real exception if you want one rule for everything.

Recipe: validate form input with exceptions

A practical pattern: validation functions throw, the page catches and shows the message next to the form. Run it, then type an age into the form and press Send: the same script runs again with $_POST filled in.

Try an empty field, twelve, 9 and 30. Each failing rule throws with its own message, and one catch turns all of them into the same red line. The forms page builds on this with several fields and errors next to each one.

Frequently Asked Questions

How does try catch work in PHP?

PHP runs the code inside try { }. If something in it throws, PHP skips the rest of the try block and runs the first catch block whose type matches, with the thrown object in the variable: catch (Exception $e) { echo $e->getMessage(); }. If nothing throws, the catch blocks are skipped.

How do I catch all exceptions and errors in PHP?

Catch Throwable: catch (Throwable $e). catch (Exception $e) only catches exceptions, not engine errors such as TypeError, ValueError or DivisionByZeroError, which extend Error. Both Exception and Error implement Throwable.

How do I catch multiple exceptions in one catch block in PHP?

Separate the types with a pipe: catch (InvalidArgumentException | RangeException $e). You can also write several catch blocks one after another; PHP uses the first one that matches, so put the most specific types first.

Does finally run if there is a return in try?

Yes. finally runs after try and catch whether the code returned, threw, or finished normally, including when try contains return. If finally itself returns a value, that value replaces the one from try, so avoid return inside finally.

Does try catch catch warnings in PHP?

No. Warnings and notices (such as reading a missing array key) are not exceptions, so catch never sees them. Check the condition before the call, use the function's exception mode where it has one (json_decode(..., flags: JSON_THROW_ON_ERROR)), or convert warnings with set_error_handler() and ErrorException.

Coddy programming languages illustration

Learn to code with Coddy

GET STARTED