How to Implement Unified Exception Handling with Zuul Error Filters
This article explains why exceptions thrown in a Zuul pre‑filter are silent, shows how to set error parameters in the request context, and provides two solutions—adding try‑catch logic to the filter or creating a dedicated error‑filter—to ensure that error details are correctly propagated and returned to the client.
Background
The previous article introduced several core Zuul filters but omitted the error filter, leaving no unified way to handle exceptions. When a pre‑filter throws an exception, the gateway logs the filter name but returns no response.
Default Exception Behavior
Example of a pre‑filter that throws a RuntimeException:
public class ThrowExceptionFilter extends ZuulFilter {
private static Logger log = LoggerFactory.getLogger(ThrowExceptionFilter.class);
@Override
public String filterType() { return "pre"; }
@Override
public int filterOrder() { return 0; }
@Override
public boolean shouldFilter() { return true; }
@Override
public Object run() {
log.info("This is a pre filter, it will throw a RuntimeException");
doSomething();
return null;
}
private void doSomething() {
throw new RuntimeException("Exist some errors...");
}
}Running the gateway shows the log message but no error details in the response because the SendErrorFilter is never invoked.
Why SendErrorFilter Is Not Triggered
The SendErrorFilter checks the request context for the key error.status_code:
public boolean shouldFilter() {
RequestContext ctx = RequestContext.getCurrentContext();
return ctx.containsKey("error.status_code") &&
!ctx.getBoolean(SEND_ERROR_FILTER_RAN, false);
}Since the original ThrowExceptionFilter does not set this key, the error filter skips processing.
How Existing Filters Set Error Information
Filters such as RibbonRoutingFilter catch exceptions and populate three context parameters: error.status_code – HTTP status code error.exception – the exception object error.message – error message
public Object run() {
RequestContext context = RequestContext.getCurrentContext();
try {
// normal routing logic
} catch (ZuulException ex) {
context.set(ERROR_STATUS_CODE, ex.nStatusCode);
context.set("error.message", ex.errorCause);
context.set("error.exception", ex);
} catch (Exception ex) {
context.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
context.set("error.exception", ex);
}
return null;
}Solution 1: Add Try‑Catch Logic to the Pre‑Filter
Modify the pre‑filter to catch exceptions and set the required error parameters:
public Object run() {
log.info("This is a pre filter, it will throw a RuntimeException");
RequestContext ctx = RequestContext.getCurrentContext();
try {
doSomething();
} catch (Exception e) {
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception", e);
}
return null;
}After redeploying, a request that triggers the exception returns a JSON payload such as:
{
"timestamp": 1481674980376,
"status": 500,
"error": "Internal Server Error",
"exception": "java.lang.RuntimeException",
"message": "Exist some errors..."
}Solution 2: Create a Dedicated error Filter
Implement an error filter that runs when any exception bubbles up, extracts the throwable from the context, and populates the same error keys:
public class ErrorFilter extends ZuulFilter {
Logger log = LoggerFactory.getLogger(ErrorFilter.class);
@Override
public String filterType() { return "error"; }
@Override
public int filterOrder() { return 10; }
@Override
public boolean shouldFilter() { return true; }
@Override
public Object run() {
RequestContext ctx = RequestContext.getCurrentContext();
Throwable throwable = ctx.getThrowable();
log.error("this is an ErrorFilter : {}", throwable.getCause().getMessage());
ctx.set("error.status_code", HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
ctx.set("error.exception", throwable.getCause());
// optional custom message
// ctx.set("error.message", "Custom error message");
return null;
}
}When this filter is added to the gateway, the same exception now produces the expected error response, and the gateway console logs the exception details.
Key Takeaways
Zuul only forwards error information to SendErrorFilter if error.status_code is present in the request context.
Either enrich your own filters with try‑catch blocks that set the error keys, or implement a dedicated error filter to handle uncaught exceptions uniformly.
You can customize the message returned to the client by setting error.message in the context.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Programmer DD
A tinkering programmer and author of "Spring Cloud Microservices in Action"
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
