Skip to main content

SF-0321 · Coding · Medium

Can you write sample code for a custom exception?

✓ Verified by Vikas Singhal · Last reviewed 5/17/2026 · Updated for Spring '26

A custom exception in Apex is a class that extends the built-in Exception type. You throw it when your code hits a domain-specific failure that the platform’s built-in exceptions don’t describe well — a business rule violation, an integration mapping failure, a state transition that shouldn’t happen. The convention is one custom exception per failure mode, with a name that ends in Exception.

Minimal custom exception

public class OrderValidationException extends Exception {}

That’s the whole class. Apex’s Exception base class already gives you the constructors and methods you need — getMessage(), getStackTraceString(), getCause(), and the constructor that takes a String message.

Throwing and catching

public class OrderProcessor {

    public static void process(Order__c order) {
        if (order.Status__c == 'Closed') {
            throw new OrderValidationException(
                'Cannot process order ' + order.Name + ' — already closed'
            );
        }
        if (order.Total__c == null || order.Total__c <= 0) {
            throw new OrderValidationException(
                'Order ' + order.Name + ' has invalid total: ' + order.Total__c
            );
        }
        // ... happy path ...
    }
}

// Caller
try {
    OrderProcessor.process(myOrder);
} catch (OrderValidationException e) {
    // Caller specifically wanted to handle bad orders
    System.debug('Validation failed: ' + e.getMessage());
    Logger.warn(e);
} catch (DmlException e) {
    // Caller also wants to handle DML failures
    Logger.error(e);
} catch (Exception e) {
    // Catch-all for anything else
    Logger.error(e);
    throw e;   // re-throw so the transaction rolls back
}

A richer custom exception with extra context

You can add fields and constructors to carry structured context that callers might want to inspect:

public class IntegrationException extends Exception {
    public Integer httpStatus;
    public String endpoint;
    public String responseBody;

    // Convenience constructor that takes structured data
    public IntegrationException(
        String message,
        Integer httpStatus,
        String endpoint,
        String responseBody
    ) {
        this(message);                     // call the inherited Exception(String) ctor
        this.httpStatus = httpStatus;
        this.endpoint = endpoint;
        this.responseBody = responseBody;
    }
}

// Usage
HttpResponse res = http.send(req);
if (res.getStatusCode() >= 400) {
    throw new IntegrationException(
        'Stripe API returned an error',
        res.getStatusCode(),
        req.getEndpoint(),
        res.getBody()
    );
}

// Caller can branch on the structured fields
try {
    StripeClient.charge(order);
} catch (IntegrationException e) {
    if (e.httpStatus == 429) {
        // rate limited — retry later
        System.enqueueJob(new RetryStripe(order.Id));
    } else if (e.httpStatus >= 500) {
        // upstream error — log and alert
        Logger.error(e);
    } else {
        // client error — fail loudly
        throw e;
    }
}

Exception hierarchy

You can build a small hierarchy of custom exceptions so callers can catch broad or specific types:

public virtual class AppException extends Exception {}

public class OrderValidationException extends AppException {}
public class PaymentException extends AppException {}
public class IntegrationException extends AppException {}

Now any caller that wants to handle all app-level failures uniformly can catch (AppException e), while callers that care about one specific type can catch the narrower class.

What the platform requires

Apex enforces three rules on custom exception classes:

  1. Must extend Exception (directly or through another custom exception).
  2. Class name must end with Exception. If it doesn’t, the compiler rejects the declaration.
  3. No explicit constructor needed — the inherited constructors from Exception are sufficient unless you’re adding fields.

The inherited constructors are:

new Exception()
new Exception(String message)
new Exception(Exception cause)                       // wraps a cause
new Exception(String message, Exception cause)

getCause() returns the wrapped exception when you chain them, which is invaluable for debugging when one failure triggers another.

Wrapping a cause

try {
    insert someAccounts;
} catch (DmlException e) {
    throw new OrderProcessingException(
        'Failed to provision accounts for order ' + orderId, e
    );
}

The new exception carries the original DmlException as its cause. Whatever logs the outer exception can walk the chain and print the original DML error too.

Common pitfalls

  • Catching Exception and swallowing it. Always log it, and re-throw unless you have a specific reason to suppress. Swallowed exceptions are the most painful bugs to diagnose later.
  • Using built-in NullPointerException for business errors. Use a custom exception named for the domain failure, not a generic platform type.
  • Forgetting the Exception suffix. The compiler rejects class OrderInvalid extends Exception {} — must be OrderInvalidException.
  • Putting business rules inside catch blocks. If you find yourself doing real work in a catch, the throw site is probably wrong.

What interviewers are really looking for

This question is a language-knowledge check plus a design check. The code part is short — one class, two constructors. The design signal is whether you can articulate when to define one (specific failure modes the caller might want to handle differently) vs when to let a platform exception fly. Mention the Exception suffix requirement, the inherited constructors, and chaining causes with getCause(), and you’ve covered everything an interviewer is checking for.

Verified against: Apex Developer Guide — Creating Custom Exception Classes, Exception Class and Built-In Exceptions. Last reviewed 2026-05-17 for Spring ‘26 release.