Here’s the scenario: You’re writing a REST API endpoint. Like a good programmer, you want to handle error conditions. “What happens if the thing isn’t found?” you ask yourself. You consult your favorite HTTP status code reference. “Ah-ha! Status code 404 means ‘Not found’. I’ll return that.” You code it up, write the tests (or the other way around, for you TDD enthusiasts). Off to production it goes.
Job well done!
And then your system monitors start going off. The on-call person calls you and wants to know why there is suddenly a spike in errors.
Overuse of 404
It turns out, callers of your API don’t always know if the thing exists. So the endpoint returns 404 errors. A lot of them.
This is a common mistake. Most people see the heading that says the 404 status code equals “Not Found” and assume that’s what they should return. But the 404 status code should be returned only in exceptional cases. Otherwise, stick to something in the 200 status code range: 200 or 204, for example.
According to the W3C, “The 4xx class of status code is intended for cases in which the client seems to have erred.” In other words, if you get back an 4xx status code, you, the caller, messed something up.
Most HTTP client libraries I’ve looked at throw an exception or otherwise indicate error for response codes 400 and up. In addition many monitoring systems report these status codes as an error, by default.
If you read more carefully, the 404 error code means that “The server has not found anything matching the Request-URI”. This is much more specific than a general purpose “Not Found”. Said another way, the 404 status code means “I have no mapping for that URL”, not “I understood the request, but I couldn’t find your data.”
Treat Errors Like Exceptions
4xx and 5xx status codes should be treated much the same way we treat Exceptions in languages like Java. You wouldn’t normally throw an exception when you find no records in the database. Instead, you’d return an empty list, an empty Optional or null. HTTP error codes should be treated the same way.
Consider whether the caller has a reasonable expectation of knowing the data to pass. If it’s perfectly reasonable for the client to expect an “I didn’t find your data” response, then 404 is not the correct response code. Instead consider returning a 204 (No Content) or 200 response.
For example, suppose you have an API to return a user’s address (GET /user/{userId}/address
). In this case, it seems reasonable to assume that the caller knows the userId. If the user doesn’t exist, returning 404 would be reasonable. Not knowing the user ID is an exceptional case. However, a user may not always have an address. This is a standard condition, so the API should return either a 204 or a 200 with an empty value where the address would normally be.
Conclusion
The moral of the story is this: Sometimes you need to consider your design decisions in the context of the larger ecosystem of tools and systems your code will interact with. Narrowly following a spec can sometimes have unintended consequences. In this case, overusing the 4xx series status codes could cause monitoring systems to wake up people in the middle of the night for no particularly good reason.
Question: How do you handle missing data in your REST endpoints?