Five Steps to Great Error Handling

Garrett James Cassar
Mar 1 · 6 min read

A crucial element to any serious business is clarity in communication.

Like most things in engineering, this is hard. It involves anywhere between a handful and hundreds of engineering teams, all trying to have a clear and informative conversation amongst themselves and to their customers about what is going on within their services and what they can do next. Hard.

While achieving effective communication is mostly a human and organisational exercise, your technology stack can and should enable this good behaviour. This blog post proposes a system that can address a huge slice of the communication pie. Error handling.

Exception Handlers
Achieving consistency around error handling is not easy and often ends in code duplication, fat microservices, inconsistent messaging and potential PII leaks. Eek!

But with a bit of clever design, tackling this problem presents a quick win and a great opportunity to standardize your messaging, keeping your microservices small and simplifying your developer’s lives.

Exception handlers translate exceptions into messages.

@ExceptionHandler(NotCuteEnoughException.class)
ErrorResponse handle(NotCuteEnoughException e) {
return ErrorResponse
.builder()
.reason(ErrorCode.PET_NOT_CUTE_ENOUGH.value())
.errorMessage("ya dog ugly")
.build();
}
@ExceptionHandler(TooFluffyException.class)
ErrorResponse handle(TooFluffyException e) {
return ErrorResponse
.builder()
.reason(ErrorCode.TOO_FLUFFY)
.errorMessage(”ya dog stinky”)
.build();
}

Solutions like this are common. They work and might fit all the acceptance criteria for your ticket. What happens if we get an error that is not related to your dog being not cute enough or not fluffy enough?

You would need a new exception handler, that produces a new arbitrary error response and error response format. And the next exception? Another.

It’s difficult to state in a short blog post how unscalable this approach really is. What happens if I decide to follow a CPRS pattern and split the microservice into two? That’s a potential duplication. What happens if we need to call an external “Pet Verification Service” that call is made by 8 Microservices? Another set of 8 duplications. I could go on for a while, but the point is it gets big.

Step 1: Decorate your Exceptions
The first and most obvious point here is actually that your exception can hold data. By decorating your exception and making it more generic, we can quickly collapse the three exception handlers into one.

class PetAintRightException extends RuntimeException { 
ErrorCode errorCode;
PetAintRightException(String message){
super(message);
this.errorCode= ErrorCode.PET_AINT_RIGHT;
}
}
@ExceptionHandler(PetAintRightException.class)
ErrorResponse handle(PetAintRightException e) {
return ErrorResponse
.builder()
.reason(e.getErrorCode())
.errorMessage(e.getMessage())
.build();
}

Now, we only have one method to handle all of my pet-related issues and do not need to create a new exception handler each time I encounter a new pet-related error. But it’s not that descriptive, is it? What actually went wrong here? Was the Pet not cute enough? Not fluffy enough? Too fluffy? Who knows?

By using a generic exception, it’s clear that we can’t really get a more specific error message or error code unless we define the message and code at the service level. But that’s yukky.

What happens if an irresponsible developer passes in a null error code or message? They’ll be a NullPointerException, and then what? An ExceptionHandler for our ExceptionHandler?

Don’t be silly. So how could we fix this problem?

Step 2: Use inheritance to organise your exceptions
Using inheritance for exception handlers has several major advantages.

We can use inheritance to target the same handler for different exceptions.

--- Exceptions ---
abstract class PetAintRightException extends RuntimeException {
ErrorCode errorCode;
PetAintRightException(String message, ErrorCode errorCode) {
super(message)
this .errorCode = errorCode
}
}
class PetNotCuteEnoughException extends PetAintRightException {PetNotCuteEnoughException (String pet) {
super(pet + “not cute enough!”), ErrorCode.PET_NOT_CUTE_ENOUGH)
}
}
class PetTooOldException extends PetAintRightException {PetTooOldException(String pet) {
super(pet + “is too old!”), ErrorCode.PET_TOO_OLD)
}
}
---- Handler ---@ExceptionHandler(PetAintRightException.class)
ErrorResponse handle(PetAintRightException e) {
return ErrorResponse
.builder()
.reason(e.getErrorCode())
.errorMessage(e.getMessage())
.build();
}

Nice! Now the handler translates exceptions into errors without having to hard-code anything!

As you can see above, we get a consistent message format by sharing a handler but keep control of the message with the custom exceptions.

While this solution has fewer methods and looks a lot sexier, it’s still not perfect. What if the REST endpoint that we’re exposing is public-facing and we need to hide the fidos PII?

Step 3: Target your inheritance structures according to the audience

abstract class PetCustomerException extends PetException {PetCustomerException(String pii, String message,ErrorCode ec) {
super(RandomizationService.randomize(pii) + message);
this .errorCode = errorCode;
}
}
abstract class PetStoreOwnerException extends PetException {PetStoreOwnerException(String pii, String message, ErrorCode ec) {
super(pii + message)
this .errorCode = errorCode;
}
}
PetExpiredCustomerException extends PetCustomerException {

PetHasExpiredCustomerException(String name) {
super(name, “ has moved to a nice farm up state!"), ErrorCode.PET_MOVED_UPSTATE);
}
}
class PetExpiredStoreOwnerException extends PetStoreOwnerException {

PetHasExpiredStoreOwnerException(String name) {
super(name, “ has expired!”, ErrorCode.PET_DEAD);
}
}

This solution is slightly bigger, but it is unavoidable. Protecting details from a front-end client is not nice to have. If you think about it, the handler only has one job. To inform the recipient of the message of what went wrong and what to do about it. So having one handler for each type of audience represents excellent cohesion. While these errors divide nicely into “client-facing”, “non-client facing”, there’s once again no reason to stop there. You could easily break this down into “StoreToStoreExceptions, WebsiteExceptions, MobileExceptions, ContentManagerExceptions” and as many types of clients that you can dream up.

But there is still an elephant in the room (pun intended). What if we need to verify the pet id externally? Or translate external ids to internal ids? And what if we call this service from 15 different microservices around the company?

Step 4: Publish your abstract exceptions and exception handlers as a sharable library

Error handling is a great candidate for a shared library at many levels, which I can discuss at length in another article. It has great cohesion, minimal dependencies, loose coupling and unopinionated. It also represents something that every microservice needs to do and controls something that as a developer you don’t really want to care about, but easily regain control over by overriding if you really need to.

With a central error handling library, at minimum what you get is the option for every microservice to conform to the error handling message formats specified by the company. At maximum what you get is a super clean development experience that gives you maximum control with minimal effort, a shorter product discovery life-cycle, and a familiar format for all the teams in your organisation.

Step 5: Categorize your errors across your organisation
In a shared library, it becomes easy to find a meaningful set of error codes similar to HTML.

PS-4041 Pet not found because it ran away (PS-4041 runbook)
PS-4042 Pet not found it was taken already (PS-4042 runbook)
PS-4043 Pet not found because its living with a nice farmer up state (PS-4043 runbook)

This helps create semantics, link errors to your run-books, and create a consistent message across the entire organisation. While that process can be hard to manage across an organisation, once it’s in place it really helps to create clarity for your customers and within the company.

Conclusion

Implementing an error handling library on an organisational level gives you:

  1. Leaner, sexier microservices
  2. Less work for your backend programmers, product owners and team leads
  3. Better grouping and semantic meaning of issues within an organisation
  4. More consistent communication with your clients and within your company.

Please share this with all your Medium friends and hit that clap button below to spread it around even more. Also, add any other tricks that you use to keep microservices smaller!

Add me on LinkedIn here if you liked this article or want to give me lots of money.

From Confusion to Clarification

Garrett James Cassar

Written by

Grumpy developer and aspiring entrepreneur

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To stay up to date on other topics, follow us on LinkedIn. https://www.linkedin.com/company/nerdfortech

Garrett James Cassar

Written by

Grumpy developer and aspiring entrepreneur

Nerd For Tech

NFT is an Educational Media House. Our mission is to bring the invaluable knowledge and experiences of experts from all over the world to the novice. To stay up to date on other topics, follow us on LinkedIn. https://www.linkedin.com/company/nerdfortech

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store