For the latest stable version, please use Spring for Apache Kafka 3.3.0!

Combining Blocking and Non-Blocking Retries

Starting in 2.8.4 you can configure the framework to use both blocking and non-blocking retries in conjunction. For example, you can have a set of exceptions that would likely trigger errors on the next records as well, such as DatabaseAccessException, so you can retry the same record a few times before sending it to the retry topic, or straight to the DLT.

To configure blocking retries, override the configureBlockingRetries method in a @Configuration class that extends RetryTopicConfigurationSupport and add the exceptions you want to retry, along with the BackOff to be used. The default BackOff is a FixedBackOff with no delay and 9 attempts. See Configuring Global Settings and Features for more information.

@Override
protected void configureBlockingRetries(BlockingRetriesConfigurer blockingRetries) {
    blockingRetries
            .retryOn(MyBlockingRetryException.class, MyOtherBlockingRetryException.class)
            .backOff(new FixedBackOff(3_000, 5));
}
In combination with the global retryable topic’s fatal exceptions classification, you can configure the framework for any behavior you’d like, such as having some exceptions trigger both blocking and non-blocking retries, trigger only one kind or the other, or go straight to the DLT without retries of any kind.

Here’s an example with both configurations working together:

@Override
protected void configureBlockingRetries(BlockingRetriesConfigurer blockingRetries) {
    blockingRetries
            .retryOn(ShouldRetryOnlyBlockingException.class, ShouldRetryViaBothException.class)
            .backOff(new FixedBackOff(50, 3));
}

@Override
protected void manageNonBlockingFatalExceptions(List<Class<? extends Throwable>> nonBlockingFatalExceptions) {
    nonBlockingFatalExceptions.add(ShouldSkipBothRetriesException.class);
}

In this example:

  • ShouldRetryOnlyBlockingException.class would retry only via blocking and, if all retries fail, would go straight to the DLT.

  • ShouldRetryViaBothException.class would retry via blocking, and if all blocking retries fail would be forwarded to the next retry topic for another set of attempts.

  • ShouldSkipBothRetriesException.class would never be retried in any way and would go straight to the DLT if the first processing attempt failed.

Note that the blocking retries behavior is allowlist - you add the exceptions you do want to retry that way; while the non-blocking retries classification is geared towards FATAL exceptions and as such is denylist - you add the exceptions you don’t want to do non-blocking retries, but to send directly to the DLT instead.
The non-blocking exception classification behavior also depends on the specific topic’s configuration.