I need to write a RestClient used by several application that return a specific object.
In case of bad request (400) I'd like to advice the caller application of the message error and status code.
I wonder if is it a good behavior to throw a managed Exception with code and message property in order to be catched properly from the caller code.
Something like this:
RestClient.java
ClientResponse response;
try {
response = client.resource(requestURI).queryParams(queryParams)
.type(MediaType.APPLICATION_JSON)
.accept(MediaType.APPLICATION_JSON)
.post(ClientResponse.class, richiesta);
boolean output = response.getStatus() == Response.Status.NO_CONTENT.getStatusCode();
if (!output && response.getStatus() == Response.Status.BAD_REQUEST)
throw new RestClientRuntimeException(response.getStatus, response.getEntity(String.class));
} catch (ClientHandlerException e) {
throw new RestClientRuntimeException(e);
} catch (UniformInterfaceException e) {
throw new RestClientRuntimeException(e);
}
Caller application
try
{
boolean output = restClient.method()
} catch (RestClientRuntimeException e) {
// read exception status and message entity
}
Is it a good practice ?
Your question has several aspects. Lets consider them separately:
Should I translate http error codes into exceptions?
Yes you should translate error codes into exceptions. Exceptions are nice (if used correctly of course). They separate the happy path from the exceptional cases. If you don't use exceptions but return codes, your callers need to check for that return code. And if you call several methods you get some nasty cascading ifs:
if (service1.method1() == NO_ERROR) {
if (service2.method2() = NO_ERROR) {
if (service3.method2() = NO_ERROR) {
...
} else {
...
}
} else {
...
}
} else {
...
}
Furthermore if you have several layers (and you almost certainly have), you need to do this at every level again. Exceptions are much better here. They let you code (and read!) the nice and clean happy path first and then
you can worry about the exceptions in the catch block. That's easier to write and easier to read.
Should I use checked exceptions?
This can get quite religious. Some people think checked exceptions are a good idea, some think, they are evil. I don't want to debate that here in detail. Just that: Do you want to force your callers to think about that particular exception? Is there always or at least in the vast majority of cases a way for the caller to handle that exception?
If you come to the conclusion that the caller cannot do anything but log the exception and fail itself, you should refrain from using checked exceptions. This makes the caller code much cleaner. There is no use in pointless try..catch blocks that are just there to make the compiler happy.
Speaking of REST: In case of a 500, there is most likely no chance to recover. If you get a 405 you most likely have a bug and there is no way to recover either. If you get a 409 Conflict with some special info on how to resolve the conflict there might be a good way to handle that (depending on your requirements). In such a case you may consider a checked exception.
Should I store the response code in the exception?
When you use generic RestClientRuntimeExceptions and have your callers query the response code, then your caller is obviously coupled to this being a REST call. You can do that if you are writing a generic REST client that can query arbitrary REST APIs. No problem in this case.
But you are already using a generic library (I guess Jersey). So what's the point in wrapping a generic API around a generic API? There may be some reasons (e.g. evaluating some internally used header params) but you should think about it whether this is justified.
Maybe you want to write an application-specific REST client, i.e. one that deserializes your application-specific representation classes (DTOs). In such a case you should not use generic exceptions but rather application-specific ones. If you use application-specific exceptions your callers are not coupled to this being a REST call. Maybe in the future a cool new technology shows up and you need to change your client. If you use the generic exceptions and have your callers evaluate the response codes, the callers need to change, too. But if you use application-specific exceptions, this interface will be more stable. The callers don't even need to know that such a thing as REST exists. This makes the caller code simpler and more maintainable as you separate concerns.
Should I put a rest client into a jar?
I know you haven't asked that question. But maybe you should do so. Providing a rest-client jar with a Jersey dependency to arbitrary clients (that's what it seems to me that you do) looks nice at first but can get you into real trouble with dependencies. If you are not convinced, I can explain that in detail but lets discuss this in a separate question.
Since your success response is JSON, I would model the error responses as JSON too.
For example consider the following JSON serialized from a POJO like ErrorMessage.java having corresponding fields.
{
"statusCode": 400,
"errorMessage": "Bad Request",
"errorDetails": "<details here>"
}
Since it is HTTP, it would be better to communicate error codes based on HTTP status codes. errorMessage and errorDetails can be blank in case of a successful status code.
Why don't you check the HTTP status code family?
Other errors besides 400 can happen. If you test the status code family, you catch them all:
switch (response.getStatusInfo().getFamily()) {
case CLIENT_ERROR:
// It's a client error
// You can throw an exception
break;
case SERVER_ERROR:
// It's a server error
// You can throw an exception
break;
default:
break;
}
IMHO it's a good practice at least for 400 status code to have custom exception because it implies malformed data was sent.
Instead of throwing unchecked exception and catching them, checked exceptions are more suitable.
Related
In my Java app, I have the following service method that calls another method and accumulate responses. Then returns these responses as a list. If there is not any exception, it works properly. However, it is possible to encounter exception for one of the call in the loop. In that case, it cannot return the previous responses retrieved until exception (if there are 10 process in the loop and there is an exception for the 6th process, then it cannot return the previous 5 responses added to the response list).
public List<CommandResponse> process(final UUID uuid) {
final Site site = siteRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException(SITE_ENTITY_NAME));
// code omitted for brevity
for (Type providerType : providerTypeList) {
// operations
responses.add(demoService.demoMethod());
}
return responses;
}
Under these conditions, I am wondering if I should use a try-catch mechanism or should I return response in the loop and finally return null. What would you suggest for this situations?
public CommandResponse operation(final UUID uuid) {
final Site site = siteRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException(SITE_ENTITY_NAME));
// code omitted for brevity
for (Type providerType : providerTypeList) {
// operations
return demoService.demoMethod();
}
return null;
}
Well, following the best practices the method demoMethod() should not throw exception, instead capture the exception and send it as response.
This implies either CommandResponse can hold exception response. Following this the code looks as follows:
class CommandResponse<T>{
public T errorResponse();
public T successResponse();
public boolean isSucces();
}
And then later while rendering response you can handle failures/exceptions as per use case.
OR
another way to handle this is having an interface Response with two implementations one for Success & another for failure. Thus making method process to return List<Response>.
It all depends on the requirements, the contract between your process() method and its callers.
I can imagine two different styles of contract:
All Or Nothing: the caller needs the complete responses list, and can't sensibly proceed if some partial response is missing. I'd recommend to throw an exception in case of an error. Typically, this is the straightforward approach, and applies to many real-world situations (and the reason why the concept of exceptions was introduced).
Partial Results: the caller wants to get as much of the complete results list as currently possible (plus the information which parts are missing?). Return a data structure consisting of partial results plus error descriptions. This places an additional burden on the caller (extracting reults from a structure instead of directly getting them, having to explicitly deal with error messages etc.), so I'd only go that way if there is a convincing use case.
Both contracts can be valid. Only you know which one matches your situation. So, choose the right one, and document the decision.
My question should be quite simple.
I have a Spring Boot REST API.
#GetMapping("/customer")
public Customer getCustomer() {
return service.getCustomer()
}
My controller returns List<Customer> and works well. But now I want to return another object with the errors that can happen when gathering the customers. Let's say it's called GenericErrorClass
So to return this I need to create a class that groups List and GenericErrorClass and return that class, right?
that will work but now I have Account, Product, etc... I don't think it makes sence create a class for each one of them.
How can I build a custom object without creating classes and return that as json from the rest controller?
Don't do that.
Throw your exception, or let it escape from your call stack. Use #ControllerAdvice (or #RestControllerAdvice) with #ExceptionHandler instead.
You can extend the abstract class ResponseEntityExceptionHandler, and provide additional handling methods. On the long run that will matter for a clean application design.
If you intend to return the error with a status code of 200, I'd like to understand why. I witness developers serving out responses for errored requests with 200 just because handling the HTTP error in another code branch at client side seems "difficult".
At first, you should know that there is already a similar class org.springframework.http.ResponseEntity, which object you could return as a response of your API. It can wrap your response body - List, Account, Product, etc. with the possibility to override default Http status.
So, based on this example you can write your own simple wrapper class like this:
public class Response<T>
{
private GenericErrorClass error;
private T body;
// constructors + getters + setters
}
and when your API method should return List<Customer> you will return Response<List<Customer>> , in the same way other objects.
However, I would recommend you to catch exceptions and send detailed error message + corresponding error code to API client. This is much better from a design point of view.
To implement this here is a good read.
If you have time to think about the design of your API, I would recommend this guide.
Generally you would return error information when the request is not succeeded, that coincides with a 4/5xx error code. Usually with Spring you manage this situation with exception handlers, as shown here where you can define a different response body. There is also another good practice: use envelopes to manage all responses, I will show you an example
{
status: 200,
message: 'user retrieved',
result:
[
{foo1:"bar1"},
.....
]
}
OR
{
status: 400,
message: 'bad request',
error:
{
reason : "wrong field xxx in request"
}
}
in this way clients can process the request and provide useful info to users. To do this you have to define a class that is used for all responses and should encapsulate the result or the error
I have some function works with database.
I have set a try/catch for error handling here, and display a message, It works fine.
Now the class calling this delete function need to know if there is a error or not. In my case : refresh the GUI if success, nothing to do if fail (as there already show up a message message dialog).
I come up a idea to return boolean in this function.
public static Boolean delete(int id){
String id2 = Integer.toString(id);
try {
String sql =
"DELETE FROM toDoItem " +
"WHERE id = ?;";
String[] values = {id2};
SQLiteConnection.start();
SQLiteConnection.updateWithPara(sql, values);
} catch (SQLException e) {
Main.getGui().alert("Fail when doing delete in DataBase.");
System.out.println("Exception : "+ e.getMessage());
return false;
}
return true;
}
Don't know if this is good or bad, please tell.
EDIT :
Here is more detail for How do I use :
Let's say the code above is inside Class A,
in Class B :
public boolean deleteItem(int id){
int i = index.get(id);
if(theList[i].delete()){ //<---- here is the function from Class A
theList[i] = null;
index.remove(id);
retutn true;
}
retutn false;
}
I need to pass the boolean in more than one class, I don't know if that can better through...
in Class C :
public void toDoList_deleteItem(){
MyButton btn = (MyButton)source;
int id = btn.getRefId();
List toDoList = Main.getToDoList();
if(toDoList.deleteItem(id)){ //<-------function in Class B
Main.getGui().refresh();
}
}
Edit 2 :
I have notice the question is somehow more likely asking "What should I handle a Exception at database Layer that affect to GUI Layer ?"... Something like that. Please correct me if the question title should be edit.
It looks like you are returning a boolean status to indicate that an exceptional condition had occurred. Generally, this is not a good practice, for two reasons:
It encourages an error-prone way of handling exceptions - it is very easy to miss a status check, leading to ignored errors
It limits your API's ability to report errors - a single pass/fail bit is not always sufficient, it may be desirable to pass more information about the error.
A better approach would be to define an application-specific exception, and use it in your API. This forces the users of your API to pay attention to exceptional situations that may happen, while letting you pass as much (or as little) additional information as you find necessary. At the same time, your code does not get polluted with if (!delete(id)) { /* handle error */ } code on each API call, shrinking your code base, and improving its readability.
Can you tell me more about "define an application-specific exception", or show some code example please?
Here is how I would do it:
public class DataAccessException extends Exception {
... // Define getters/setters for passing more info about the problem
}
...
public static void delete(int id) throws DataAccessException {
try {
... // Do something that may lead to SQLException
} catch (SQLException se) {
// Do additional logging etc., then
throw new DataAccessException("Error deleting "+id, se);
}
}
Note: It is common to give custom exceptions four constructors mirroring the constructors of the Exception class to allow exception chaining. The constructors are described here.
As long as you do not want the caller to know what happens, just that it fails (and that failing is part of its intended behavior) you should be fine.
That being said, I am noticing this: Main.getGui().alert("Fail when doing delete in DataBase.");.
It would seem that you are accessing the GUI layer from some other place. This might cause issues should you decide to multi-thread your application. Also, it is usually considered good practice to have your layers not intersect.
Don't return a Boolean, return a boolean. Since this is not an exception / error condition, it is fine.
Exceptions should be used when you don't expect a failure.
In your case, if it's fine for you that a SQLException is thrown and does not affect your program, it's ok to return a boolean.
If the SQLExcetion causing the delete to fail can cause problems in another part of your application it's better to throw an exception.
Edit:
Based on your edits, it seems that you are doing some maintenance and cleaning when an error happens. In such a case I would recommend to use Exceptions better than using booleans to control the execution.
This question is primarly opinion based. Personally I would prefer not to catch the exception at that point.
Depending on what the caller of delete() should do, you might need other resulutions. So you should better add a throw statement and let the calling method decide if the error is critical - or if it can proceed.
Just true and false is not necessary enough to let the caller decide correctly. He won't know if deletion fails due to database errors, due to foreignkey constraints, or something else.
letting the exception bubble up the call stack will provide the caller with the exact error going on, increasing the chance to handle the error in a proper way, or just displaying a custom error message helping the user to take proper actions.
I am writing piece of code in Java, which job is to parse configuration file. It's convenient for the end-users, because they can see and fix all parsing errors at once. But it's not very good for testing - instead of specific exceptions test function just expects very general ParsingError exception.
It's always a room for dark magic here, like testing private methods, but I don't want to go for it. Could you suggest better design solution for the case?
Why not throw just a single InvalidConfigurationException (I wouldn't use ParsingError - aside from anything else, I wouldn't expect this to be an Error subclass) which contains information about the specific problems? That information wouldn't be in terms of exceptions, but just "normal" data classes indicating (say) the line number and type of error.
Your tests would then catch the exception, and validate the contents was as expected.
The implementation would probably start off with an empty list of errors, and accumulate them - then if the list is non-empty at the end of the parsing code, throw an exception which is provided with that list of errors.
I have been here before. Exceptions are unsuitable. Instead you should provide a report inside your parser.
parser.parse();
if (parser.hasErrors()) {
for (ParserError error : parser.getErrors()) {
// Provide a report to the user somehow
}
}
Simple and easy to read. An exception should be thrown if there is an exception condition - e.g. there is no source data to parse, not because the parser found problems.
Why not use chained exceptions? You could build specific exceptions (say ParticularParsingError), then chain this with ParsingError and throw that back.
In your unit tests, use e.getCause() where e is a ParsingError.
First things first: ParsingError seems a strange name, ParsingException looks better (Error is a java.lang class that should not be caught)
You could add a list in your ParsingException and add a try-catch block in your test in which you test that your list contains what you expect.
For example you had:
#Test(expected=ParsingException.class)
public void test_myMethod_myTestCase(){
myMethod()
}
but then you would have:
public void test_myMethod_myTestCase(){
try {
myMethod()
}
catch(ParsingException pe) {
if (! pe.list.contains(anError)
|| ! pe.list.contains(anOtherError) ) {
fail();
}
}
}
We are building a Java SDK to simplify the access to one of our services that provide a REST API. This SDK is to be used by 3rd-party developers. I am struggling to find the best pattern to implement the error handling in the SDK that better fits the Java language.
Let's say we have the rest endpoint: GET /photos/{photoId}.
This may return the following HTTP status codes:
401 : The user is not authenticated
403 : The user does not have permission to access this photo
404 : There's no photo with that id
The service looks something like this:
interface RestService {
public Photo getPhoto(String photoID);
}
In the code above I am not addressing the error handling yet. I obviously want to provide a way for the client of the sdk to know which error happened, to potentially recover from it. Error handling in Java is done using Exceptions, so let's go with that. However, what is the best way to do this using exceptions?
1. Have a single exception with information about the error.
public Photo getPhoto(String photoID) throws RestServiceException;
public class RestServiceException extends Exception {
int statusCode;
...
}
The client of the sdk could then do something like this:
try {
Photo photo = getPhoto("photo1");
}
catch(RestServiceException e) {
swtich(e.getStatusCode()) {
case 401 : handleUnauthenticated(); break;
case 403 : handleUnauthorized(); break;
case 404 : handleNotFound(); break;
}
}
However I don't really like this solution mainly for 2 reasons:
By looking at the method's signature the developer has no idea what kind of error situations he may need to handle.
The developer needs to deal directly with the HTTP status codes and know what they mean in the context of this method (obviously if they are correctly used, a lot of the times the meaning is known, however that may not always be the case).
2. Have a class hierarchy of errors
The method signature remains:
public Photo getPhoto(String photoID) throws RestServiceException;
But now we create exceptions for each error type:
public class UnauthenticatedException extends RestServiceException;
public class UnauthorizedException extends RestServiceException;
public class NotFoundException extends RestServiceException;
Now the client of the SDK could then do something like this:
try {
Photo photo = getPhoto("photo1");
}
catch(UnauthenticatedException e) {
handleUnauthorized();
}
catch(UnauthorizedException e) {
handleUnauthenticated();
}
catch(NotFoundException e) {
handleNotFound();
}
With this approach the developer does not need to know about the HTTP status codes that generated the errors, he only has to handle Java Exceptions. Another advantage is that the developer may only catch the exceptions he wants to handle (unlike the previous situation where it would have to catch the single Exception (RestServiceException) and only then decide if he wants to deal with it or not).
However, there's still one problem. By looking at the method's signature the developer still has no idea about the kind of errors he may need to handle because we only have the super class in the method's signature.
3. Have a class hierarchy of errors + list them in the method's signature
Ok, so what comes to mind now is to change the method's signature to:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
However, it is possible that in the future new error situations could be added to this rest endpoint. That would mean adding a new Exception to the method's signature and that would be a breaking change to the java api. We would like to have a more robust solution that would not result in breaking changes to the api in the situation described.
4. Have a class hierarchy of errors (using Unchecked exceptions) + list them in the method's signature
So, what about Unchecked exceptions? If we change the RestServiceException to extend the RuntimeException:
public class RestServiceException extends RuntimeException
And we keep the method's signature:
public Photo getPhoto(String photoID) throws UnauthenticatedException, UnauthorizedException, NotFoundException;
This way I can add new exceptions to the method's signature without breaking existing code.
However, with this solution the developer is not forced to catch any exception and won't notice that there are error situations he needs to handle until he carefully reads the documentation (yeah, right!) or noticed the Exceptions that are in the method's signature.
What's the best practice for error handling in these kind of situations?
Are there other (better) alternatives to the ones I mentioned?
Exception handling alternatives: Callbacks
I don't know if it's a better alternative, but you could use callbacks. You can make some methods optional by providing a default implementation. Take a look to this:
/**
* Example 1.
* Some callbacks will be always executed even if they fail or
* not, all the request will finish.
* */
RestRequest request = RestRequest.get("http://myserver.com/photos/31",
Photo.class, new RestCallback(){
//I know that this error could be triggered, so I override the method.
#Override
public void onUnauthorized() {
//Handle this error, maybe pop up a login windows (?)
}
//I always must override this method.
#Override
public void onFinish () {
//Do some UI updates...
}
}).send();
This is how the callback class looks like:
public abstract class RestCallback {
public void onUnauthorized() {
//Override this method is optional.
}
public abstract void onFinish(); //Override this method is obligatory.
public void onError() {
//Override this method is optional.
}
public void onBadParamsError() {
//Override this method is optional.
}
}
Doing something like this you could define an request life-cycle, and manage every state of the request. You can make some methods optional to implement or not. You can get some general errors and give the chance at the user to implements the handling, like in the onError.
How can I define clearly what exceptions handle?
If you ask me, the best approach is draw the life-cycle of the request, something like this:
This is only a poor example, but the important it's keep in mind that all the methods implementation, could be or not, optionals. If onAuthenticationError is obligatory, not neccesarily the onBadUsername will be too, and viceversa. This is the point that makes this callbacks so flexible.
And how I implement the Http client?
Well I don't know much about http clients, I always use the apache HttpClient, but there's not a lot of differences between the http clients, the most have a little more or a little fewer features, but in the end, they are all just the same. Just pick up the http method, put the url, the params, and send. For this example I will use the apache HttpClient
public class RestRequest {
Gson gson = new Gson();
public <T> T post(String url, Class<T> clazz,
List<NameValuePair> parameters, RestCallback callback) {
// Create a new HttpClient and Post Header
HttpClient httpclient = new DefaultHttpClient();
HttpPost httppost = new HttpPost(url);
try {
// Add your data
httppost.setEntity(new UrlEncodedFormEntity(parameters));
// Execute HTTP Post Request
HttpResponse response = httpclient.execute(httppost);
StringBuilder json = inputStreamToString(response.getEntity()
.getContent());
T gsonObject = gson.fromJson(json.toString(), clazz);
callback.onSuccess(); // Everything has gone OK
return gsonObject;
} catch (HttpResponseException e) {
// Here are the http error codes!
callback.onError();
switch (e.getStatusCode()) {
case 401:
callback.onAuthorizationError();
break;
case 403:
callback.onPermissionRefuse();
break;
case 404:
callback.onNonExistingPhoto();
break;
}
e.printStackTrace();
} catch (ConnectTimeoutException e) {
callback.onTimeOutError();
e.printStackTrace();
} catch (MalformedJsonException e) {
callback.onMalformedJson();
}
return null;
}
// Fast Implementation
private StringBuilder inputStreamToString(InputStream is)
throws IOException {
String line = "";
StringBuilder total = new StringBuilder();
// Wrap a BufferedReader around the InputStream
BufferedReader rd = new BufferedReader(new InputStreamReader(is));
// Read response until the end
while ((line = rd.readLine()) != null) {
total.append(line);
}
// Return full string
return total;
}
}
This is an example implementation of the RestRequest. This is only one simple example, theres a lot of topics to discuss when you are making your own rest client. For example, "what kind of json library use to parse?", "are you working for android or for java?" (this is important because I don't know if android supports some features of java 7 like multi-catch exceptions, and there's some technologies that isn't availabe for java or android and viceversa).
But the best that I can say you is code the sdk api in terms of the user, note that the lines to make the rest request are few.
Hope this helps! Bye :]
It seems you are doing things by "hand".
I would recommend you0 give a try to Apache CXF.
It's a neat implementation the JAX-RS API that enables you to almost forget about REST. It plays well with (also recommended) Spring.
You simply write classes that implement your interfaces (API). What you need to do is to annotate the methods and parameters of your interfaces with JAX-RS annotations.
Then, CXF does the magic.
You throw normal Exceptions in your java code, and then use exception mapper on server/nd or client to translate between them and HTTP Status code.
This way, on server/Java client side, you only deal with regular 100% Java exception, and CXF handles the HTTP for you: You have both the benefits of a clear REST API and a Java Client ready to be used by your users.
The client can either be generated from your WDSL, or built at runtime from introspection of the interface annotations.
See :
http://cxf.apache.org/docs/jax-rs-basics.html#JAX-RSBasics-Exceptionhandling
http://cxf.apache.org/docs/how-do-i-develop-a-client.html
In our application, we have defined and mapped a set of error codes and their counterpart Exceptions :
4XX Expected / Functional excecption (like bad arguments, empty sets, etc)
5XX Unexpected / Unrecovable RunTimeException for internal errors that "should not happen"
It follows both REST and Java standards.
I've seen libraries that combine your suggestions 2 and 3, e.g.
public Photo getPhoto(String photoID) throws RestServiceException, UnauthenticatedException, UnauthorizedException, NotFoundException;
This way, when you add a new checked exception that extends RestServiceException, you're not changing the method's contract and any code using it still compiles.
Compared to a callback or unchecked exception solution, an advantage is that this ensures your new error will be handled by the client code, even if it's only as a general error. In a callback, nothing would happen, and with an unchecked exception, your client application might crash.
The solution may vary depending on your needs.
If it is supposed that there could appear unpredictable new exception types in the future, your second solution with checked exception hierarchy and method that throw their superclass RestServiceException is the best one. All known subclasses should be listed in the javadoc like Subclasses: {#link UnauthenticatedException}, ..., to let developers know what kind of of exceptions there could hide. It should be noticed that if some method could throw only few exceptions from this list, they should be described in the javadoc of this method using #throws.
This solution is also applicable in the case when all appearances of RestServiceException means that any of it's subclasses could hide behind it. But in this case, if RestServiceException subclasses hasn't their specific fields and methods, your first solution is preferrable, but with some modifications:
public class RestServiceException extends Exception {
private final Type type;
public Type getType();
...
public static enum Type {
UNAUTHENTICATED,
UNAUTHORISED,
NOT_FOUND;
}
}
Also there is a good practice to create alternative method that will throw unchecked exception that wraps RestServiceException exeption itself for usage within ‘all-or-nothing’ business logic.
public Photo getPhotoUnchecked(String photoID) {
try {
return getPhoto(photoID);
catch (RestServiceException ex) {
throw new RestServiceUncheckedException(ex);
}
}
It all comes down to how informative your API error responses are. The more informative the error handling of the API is, the more informative the exception handling can be. I would believe the exceptions would only be as informative as the errors returned from the API.
Example:
{ "status":404,"code":2001,"message":"Photo could not be found."}
Following your first suggestion, if the Exception contained both the status and the API error code, the developer has a better understanding of what he needs to do and more option when it comes to exception handling. If the exception also contained the error message that was returned, as well, the developer shouldn't even need to reference the documentation.