title: Exponential backoff with message order guarantee using spring-kafka
body: <p>I'm trying to implement a Spring Boot-based Kafka consumer that has some very strong message delivery guarentees, even in a case of an error.</p>
  <ul>
  <li>messages from a partition must be processed in order,</li>
  <li>if message processing fails, the consumption of the particular partition should be suspended,</li>
  <li>the processing should be retried with a backoff, until it succeeds.</li>
  </ul>
  <p>Our current implementation fulfills these requirements:</p>
  <pre class="lang-java prettyprint-override"><code>@Bean
  public KafkaListenerContainerFactory&lt;ConcurrentMessageListenerContainer&lt;String, String&gt;&gt; kafkaListenerContainerFactory() {
    ConcurrentKafkaListenerContainerFactory&lt;String, String&gt; factory = new ConcurrentKafkaListenerContainerFactory&lt;&gt;();
    factory.setConsumerFactory(consumerFactory());
    factory.setRetryTemplate(retryTemplate());
  
    final ContainerProperties containerProperties = factory.getContainerProperties();
    containerProperties.setAckMode(AckMode.MANUAL_IMMEDIATE);
    containerProperties.setErrorHandler(errorHandler());
  
    return factory;
  }
  
  @Bean
  public RetryTemplate retryTemplate() {
  
    final ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
    backOffPolicy.setInitialInterval(1000);
    backOffPolicy.setMultiplier(1.5);
  
    final RetryTemplate template = new RetryTemplate();
    template.setRetryPolicy(new AlwaysRetryPolicy());    
    template.setBackOffPolicy(backOffPolicy);
  
    return template;
  }
  
  @Bean
  public ErrorHandler errorHandler() {
    return new SeekToCurrentErrorHandler();
  }
  </code></pre>
  <p>However, here, the record is <em>locked</em> by the consumer forever. At some point, the processing time will exceed <code>max.poll.interval.ms</code> and the server will reassign the partition to some other consumer, thus creating a duplicate.</p>
  <p>Assuming <code>max.poll.interval.ms</code> equal to 5 mins (default) and the failure lasting 30 mins, this will cause the message to be processed ca. 6 times.</p>
  <p>Another possiblity is to return the messages to the queue after N retries (e.g. 3 attempts), by using <code>SimpleRetryPolicy</code>. Then, the message will be replayed (thanks to <code>SeekToCurrentErrorHandler</code>) and the processing will start from scratch, again up to 5 attempts. This results in delays forming a series e.g.</p>
  <pre><code>10 secs -&gt; 30 secs -&gt; 90 secs -&gt; 10 secs -&gt; 30 secs -&gt; 90 secs -&gt; ...
  </code></pre>
  <p>which is less desired than an constantly rising one :)</p>
  <p>Is there any third scenario which could keep the delays forming an ascending series and, at the same time, not creating duplicates in the aforementioned example?</p>