Kafka CommonErrorHandler fatal exceptions
Error handling with Kafka is nothing but straightforward. In this blog post I will cover the CommonErrorHandler and its difference of behaviour between regular exceptions and the ones interpreted as fatal.
2024-12-17 at 8:40PM
# Kafka
Apache Kafka is more than a decade old, and anyone who has worked with it can attest to its high complexity. Not only are its inner workings complex, but the entire setup and countless properties do not make it any easier.
The Spring Kafka
documentation
, though helpful, accurately reflects that complexity. If you are like me, you might miss some parts because it is super condensed and too much for my brain.
# Error Management
There are numerous options when it comes down to error management. Should we discard failures? set up retries? fixed? with back off? or a dead letter queue?
Rule of thumb is that the error handling should be configured accordingly to your topic needs. If an event can be omitted or replied then you don't need much. However if the process is critical (e.g state-machine) and not a single event can be missed then you probably want to retry the processing indefinitely until whatever goes wrong gets fixed.
# CommonErrorHandler
Spring offers out of the box a
CommonErrorHandler
(DefaultErrorHandler) pre-configured with a
FixedBackOff
policy which will retry 10 times and then skip the record and log an error.
# How to configure an Error Handler?
Let's take the example of a critical event that cannot be missed, and you need a handler that retries indefinitely. By registering a @Bean spring-boot will automatically discover and wire it. A sensible solution to avoid aggressive retries would be to define a handler that could rely on an
ExponentialBackOff.
@Bean
public CommonErrorHandler defaultErrorHandler() {
var exponentialBackOff = new ExponentialBackOff();
exponentialBackOff.setMaxInterval(Duration.ofHours(1).toMillis());
return new DefaultErrorHandler(exponentialBackOff);
}
# Exceptional cases
Now, whatever exceptions thrown during the consumption of the event will be caught by the error handler, right?
Well... not quite.
By default, unless specified, a DefaultErrorHandler
classifies
a set of
exceptions
as being
fatal
. Any fatal exceptions ignore the defined policy and will ack and skip the record. Consequently errors such as a DeserializationException, ClassCastException or a likely runtime dependency issue as NoSuchMethodException may lead to bypass the defined handler.
# Infinite retries
Let's complete the setup which would consider all events being critical and requires infinite retries
whatever happens.
By overriding the default set of classifications we make sure all exceptions are handled uniformly.
@Bean
public CommonErrorHandler defaultErrorHandler() {
var exponentialBackOff = new ExponentialBackOff();
exponentialBackOff.setMaxInterval(Duration.ofMinutes(2).toMillis());
exponentialBackOff.setInitialInterval(Duration.ofSeconds(2).toMillis());
var handler = new DefaultErrorHandler(exponentialBackOff);
// do not specify any fatal exceptions
handler.setClassifications(Map.of(), true);
return handler;
}