10

Is it possible to control what HTTP error code apexrest returns when an exception occurs in the code? By default it returns a 500 error and a developer who's working on the system that's consuming the API is complaining that this is misleading (and I tend to agree). SF defines 500 in this context as an Apex error, but generally 500 is "Internal Server Error" which would suggest more than failed validation.

It seems to throws the 500 on all unhandled exceptions, whether native or custom exceptions. (We're doing a bunch of validation on the input params to the webservices, and throwing custom exceptions when validation fails.)

According to this documentation SF determines the error code automatically.

Is there a better way to handle this? Can I control the error code returned? If so, what's the best pattern here?

Do I try to handle all exceptions and construct a response to return instead?

Thanks!

11

Below is a pattern I use to vary the status code returned. It only maps two categories of exceptions but if you need to you can add more. Note that only status codes from this list can be returned - you can't return other status codes.

By catching Exception it handles most unplanned errors (but can't catch governor limit exceptions), and the purpose of the custom EndUserMessageException is to return a simple message - such as a validation error - along with the 400 status code. The client can then handle the 400 and 500 status codes differently.

@RestResource(urlMapping='/mypath/*')
global without sharing class MyRest {
  
    @HttpGet 
    global static void get() {
        RestResponse res = RestContext.response;
        if (res == null) {
            res = new RestResponse();
            RestContext.response = res;
        }
        try {
            res.responseBody = Blob.valueOf(JSON.serialize(doGet()));
            res.statusCode = 200;
        } catch (EndUserMessageException e) {
            res.responseBody = Blob.valueOf(e.getMessage());
            res.statusCode = 400;
        } catch (Exception e) {
            res.responseBody = Blob.valueOf(
                    String.valueOf(e) + '\n\n' + e.getStackTraceString()
                    );
            res.statusCode = 500;
        }
    }

    private static Object doGet() {
        ...
    }
}

PS

If the request is doing DML, remember to do an explicit rollback as handling the exception stops the automatic rollback:

@HttpPost  
global static void post() {

    // Explicit rollback needed as exceptions are handled here
    Savepoint sp = Database.setSavepoint();        
    try {
        res.responseBody = Blob.valueOf(doPost());
        res.statusCode = 200;
    } catch (EndUserMessageException e) {
        res.responseBody = Blob.valueOf(e.getMessage());
        res.statusCode = 400;
        Database.rollback(sp);
    } catch (Exception e) {
        res.responseBody = Blob.valueOf(Strings.sanitizedMessage(e));
        res.statusCode = 500;
        Database.rollback(sp);
    }
}
2
  • Why do you check for null if (res == null)? Isn't it guaranteed to always be initialized? – Eduard Oct 6 '18 at 9:17
  • @Eduard It is not initialized when the code is called from Apex unit tests. But on reflection, perhaps better to do the initialization in the test code. – Keith C Oct 6 '18 at 9:32

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.