0

I'm writing an application based on a RESTful API located in a server (written in Python). An Android app will access the API endpoints to get, post, put or delete data. The usual.

The server has a getUser endpoint:

<app base URL>/users/<user email>

If no user has that e-mail, then a response with http error code 404 is returned.

The client API offers the rest of the application a method with the following signature:

User getUser(String email)

The problem Java gives me here is, I can only return 2 distinct values: a User object and null. However, null isn't expressive enough to distinguish between 'no internet connection' or 'there's no user with that e-mail'.

How should I communicate to the calling method that no user exists with that e-mail?

  • Should I throw an exception, f. ex. "NoAssetFound"?
  • Should I keep a "last error occurred" mailbox such as in C, which the user can check after calling getUser() in order to know what happened?
  • Should I wrap the User object in another object which contains error information?

What are some common patterns to solve this problem?

Thanks!

3 Answers 3

2

It's a matter of style and preferences. So, before giving my recommendation, let's analyze some more. User getUser(String email) is meant to return a User description, when given an e-mail address. I see three different outcome categories:

  • The e-mail was found, and a valid User could be returned.
  • The e-mail doesn't exist in the users database.
  • Contacting the service failed somehow, so that no valid anser can be given.

Now my recommendation:

  • If the e-mail was found, return the User instance.
  • If the e-mail doesn't exist in th database, return null. A fashionable alternative could be to change the method to Optional<User> getUser(String email) and return an empty Optional here.
  • If something goes wrong when contacting the service, make sure an exception is thrown. In this case, the getUser() method cannot give a meaningful answer to the question, and that's what exceptions are meant for, to express that the method was not able to fulfill its contract. Typically, you let the exception from the underlying REST layer bubble up.

This way, you get a very useful behavior:

  • If everything works, you get useful answers without any wrapping/unwrapping.
  • You can easily distinguish between known and unknown e-mail addresses
  • If something goes wrong, it's covered in the exception handling that every production-quality application needs anyway. If you can't get an answer whether the e-mail already exists, you most probably can't continue with your procedure anyway, so the default exception bahavior, to abort the method, is what you want.

Of course, it can be done differently as well, but I see reasons why the following approaches are inferior:

  • Throw an exception for unknown e-mails. But then you mis-use exceptions for something that I'd see as a normal, expected situation (simply a new e-mail address). But maybe I mis-interpret your context.
  • Wrap the result in an outcome object that can cover all three cases. But then you effectively fall back to the 1970s error handling based on return codes, and it's much too easy to forget checking for all three cases.
2
  • "it's much too easy to forget checking for all three cases." <- it's possible to put structure in place to stop this. Make the actual result private and the only way of accessing it via T resultObject.getResult(errorHandlingCallback). Commented Feb 8, 2022 at 12:10
  • Ralf Kleberhoff, thanks a lot for your detailed explanation. Your approach makes indeed a lot of sense and solves the problem with no architectural changes. I have applied this strategy to my code.
    – ismarlowe
    Commented Feb 8, 2022 at 21:30
0

You mentioned Java as the implementation language, and for that it looks like exception handling in combination with a possible null result (or Optional<User>) is the right approach.

Note that some languages (I'm thinking of Rust now) don't have exceptions but use a Result<OkType, ErrorType> type instead. In your case where it is possible that the operation worked successfully but there is no result, you'd use Result<Option<User>, Error>. The Rust compiler checks that you cover all possible cases, so it's not easy to forget a case in a modern language and environment.

0

You will have to be able to distinguish between “get request failed”, “get request succeeded, no data available”, and “get request succeeded”.

As far as error handling is concerned, I would write a function that does a best effort attempt to get the data and handles errors. For example, if there’s no network, because Wi-Fi is turned off, you could ask the user to activate Wi-Fi and try again when it becomes available. Same if some authentication is missing. Plus I tend to have a parameter whether this is a background call that should not show any UI, so errors won’t be displayed, the user won’t be asked for actions.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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