Advanced 25 min read

Handle Errors and Timeouts

By the end of this lesson, you'll be able to:

  • Handle a non-200 response with a clear, specific exception
  • Handle a CalloutException from a genuine network failure or timeout
  • Explain why these are two genuinely different failure modes

Prerequisites: "Parse the Response (JSON)"

Handling a non-200 response

public class CurrencyConversionException extends Exception {}

public Decimal convert(Decimal amountInZar, String targetCurrency) {
    HttpRequest request = new HttpRequest();
    request.setEndpoint('callout:Currency_API/convert?from=ZAR&to=' + targetCurrency + '&amount=' + amountInZar);
    request.setMethod('GET');

    HttpResponse response = new Http().send(request);

    if (response.getStatusCode() != 200) {
        throw new CurrencyConversionException(
            'Currency API returned status ' + response.getStatusCode() + ': ' + response.getBody()
        );
    }

    ConversionResponse parsed = (ConversionResponse) JSON.deserialize(response.getBody(), ConversionResponse.class);
    return parsed.convertedAmount;
}
  1. public class CurrencyConversionException extends Exception {} — Module 22's custom exception pattern, once more.
  2. (blank line)
  3. public Decimal convert(Decimal amountInZar, String targetCurrency) { — the method signature.
  4. HttpRequest request = new HttpRequest(); — an empty request.
  5. request.setEndpoint('callout:Currency_API/convert?from=ZAR&to=' + targetCurrency + '&amount=' + amountInZar); — unchanged.
  6. request.setMethod('GET'); — unchanged.
  7. (blank line)
  8. HttpResponse response = new Http().send(request); — sends it.
  9. (blank line)
  10. if (response.getStatusCode() != 200) { — checked before trusting the body at all.
  11. throw new CurrencyConversionException( — opens the exception.
  12. 'Currency API returned status ' + response.getStatusCode() + ': ' + response.getBody() — carries the actual status and body, so whoever catches it knows exactly what went wrong.
  13. ); — closes the constructor call.
  14. } — closes the if.
  15. (blank line)
  16. ConversionResponse parsed = (ConversionResponse) JSON.deserialize(response.getBody(), ConversionResponse.class); — only reached once a 200 is confirmed.
  17. return parsed.convertedAmount; — the final result.
  18. } — closes the method.

Module 22's custom exception pattern, once more — CurrencyConversionException carries the actual status code and response body, so whoever catches it (or reads the debug log, Module 33) knows exactly what the external API said went wrong, e.g. an unrecognized currency code (Lesson 1's edge case).

Handling a network failure or timeout

public Decimal convert(Decimal amountInZar, String targetCurrency) {
    HttpRequest request = new HttpRequest();
    request.setEndpoint('callout:Currency_API/convert?from=ZAR&to=' + targetCurrency + '&amount=' + amountInZar);
    request.setMethod('GET');
    request.setTimeout(5000); // 5 seconds, well under the 10-second callout limit

    HttpResponse response;
    try {
        response = new Http().send(request);
    } catch (CalloutException e) {
        throw new CurrencyConversionException('Could not reach the currency service: ' + e.getMessage());
    }

    if (response.getStatusCode() != 200) {
        throw new CurrencyConversionException('Currency API returned status ' + response.getStatusCode());
    }

    ConversionResponse parsed = (ConversionResponse) JSON.deserialize(response.getBody(), ConversionResponse.class);
    return parsed.convertedAmount;
}
  1. public Decimal convert(Decimal amountInZar, String targetCurrency) { — the method signature.
  2. HttpRequest request = new HttpRequest(); — an empty request.
  3. request.setEndpoint('callout:Currency_API/convert?from=ZAR&to=' + targetCurrency + '&amount=' + amountInZar); — unchanged.
  4. request.setMethod('GET'); — unchanged.
  5. request.setTimeout(5000); // 5 seconds, well under the 10-second callout limit — a new, explicit timeout.
  6. (blank line)
  7. HttpResponse response; — declared outside the try so it's usable afterward.
  8. try { — Module 20's try/catch, applied to a callout.
  9. response = new Http().send(request); — the callout itself, which might fail to complete at all.
  10. } catch (CalloutException e) { — catches a genuine network failure or timeout — no response was ever received.
  11. throw new CurrencyConversionException('Could not reach the currency service: ' + e.getMessage()); — re-wraps it in the service's own exception type.
  12. } — closes the try/catch.
  13. (blank line)
  14. if (response.getStatusCode() != 200) { — a different failure mode: a response was received, just an error one.
  15. throw new CurrencyConversionException('Currency API returned status ' + response.getStatusCode()); — Lesson 5's earlier error handling, unchanged.
  16. } — closes the if.
  17. (blank line)
  18. ConversionResponse parsed = (ConversionResponse) JSON.deserialize(response.getBody(), ConversionResponse.class); — only reached once a real 200 response exists.
  19. return parsed.convertedAmount; — the final result.
  20. } — closes the method.

A CalloutException (Module 20's try/catch, applied here) means the request never even got a response at all — a genuine network failure, a DNS problem, or exceeding setTimeout's limit — a fundamentally different failure from "the API responded, but with an error status." Both get wrapped in the same CurrencyConversionException for a consistent interface to callers, but they're detected and handled at different points in the method.

Exercise

As a comment, explain the difference between a non-200 HttpResponse and a caught CalloutException.

Show hint

Think about whether a response was actually received in each case.

APEX

Handle Errors and Timeouts Quiz

1. What causes a CalloutException, as opposed to a non-200 HttpResponse?

Log in to submit the quiz and save your score.

My Notes

Log in to keep private notes on this lesson.

Questions about this lesson

No questions yet — be the first to ask.

Log in to ask a question about this lesson.

Summary

A real integration can fail in two distinct ways — the external API responds with an error, or the callout itself fails to complete at all — and this lesson handles both explicitly.