A different yet robust approach would be to use a state store. They are backed by Kafka as compacted changelog topics.
You can store failed messages in the state store and process them all by calling schedule (punctuate) and then delete all the successfully processed ones.
For example:
public class MyProcessor {
private final long schedulerIntervalMs = 60000;
private final String entityStoreName = "failed-message-store";
private KeyValueStore<String, Object> entityStore;
@Override
public void init(ProcessorContext context) {
this.entityStore = (KeyValueStore) context().getStateStore(entityStoreName);
context().schedule(Duration.ofMillis(this.schedulerIntervalMs), PunctuationType.WALL_CLOCK_TIME,
timestamp -> processFailedMessagesStore());
}
@Override
public void process(String key, Object value) {
boolean apiCallSuccessful = // call API
if (!apiCallSuccesfull) {
entityStore.put(key, value);
}
}
private void processFailedMessagesStore() {
try (KeyValueIterator<String, Object> allItems = entityStore.all()) {
allItems.forEachRemaining(item -> {
boolean successfullyProcessed = // re-process
if (successfullyProcessed) {
entityStore.delete(item.key);
}
});
}
}
}