7474import org .springframework .kafka .event .NonResponsiveConsumerEvent ;
7575import org .springframework .kafka .listener .ConsumerSeekAware .ConsumerSeekCallback ;
7676import org .springframework .kafka .listener .ContainerProperties .AckMode ;
77+ import org .springframework .kafka .listener .ContainerProperties .AssignmentCommitOption ;
7778import org .springframework .kafka .support .Acknowledgment ;
7879import org .springframework .kafka .support .KafkaUtils ;
7980import org .springframework .kafka .support .LogIfLevelEnabled ;
@@ -570,6 +571,10 @@ private final class ListenerConsumer implements SchedulingAwareRunnable, Consume
570571 private final Duration authorizationExceptionRetryInterval =
571572 this .containerProperties .getAuthorizationExceptionRetryInterval ();
572573
574+ private final AssignmentCommitOption autoCommitOption = this .containerProperties .getAssignmentCommitOption ();
575+
576+ private final boolean commitCurrentOnAssignment ;
577+
573578 private Map <TopicPartition , OffsetMetadata > definedPartitions ;
574579
575580 private int count ;
@@ -614,6 +619,7 @@ private final class ListenerConsumer implements SchedulingAwareRunnable, Consume
614619 this .transactionTemplate = determineTransactionTemplate ();
615620 this .genericListener = listener ;
616621 this .consumerSeekAwareListener = checkConsumerSeekAware (listener );
622+ this .commitCurrentOnAssignment = determineCommitCurrent (consumerProperties );
617623 subscribeOrAssignTopics (this .consumer );
618624 GenericErrorHandler <?> errHandler = KafkaMessageListenerContainer .this .getGenericErrorHandler ();
619625 if (listener instanceof BatchMessageListener ) {
@@ -680,6 +686,20 @@ else if (listener instanceof MessageListener) {
680686 this .micrometerHolder = obtainMicrometerHolder ();
681687 }
682688
689+ private boolean determineCommitCurrent (Properties consumerProperties ) {
690+ if (AssignmentCommitOption .NEVER .equals (this .autoCommitOption )) {
691+ return false ;
692+ }
693+ if (!this .autoCommit && AssignmentCommitOption .ALWAYS .equals (this .autoCommitOption )) {
694+ return true ;
695+ }
696+ String autoOffsetReset = consumerProperties .getProperty (ConsumerConfig .AUTO_OFFSET_RESET_CONFIG );
697+ return !this .autoCommit
698+ && (autoOffsetReset == null || autoOffsetReset .equals ("latest" ))
699+ && (AssignmentCommitOption .LATEST_ONLY .equals (this .autoCommitOption )
700+ || AssignmentCommitOption .LATEST_ONLY_NO_TX .equals (this .autoCommitOption ));
701+ }
702+
683703 private long obtainMaxPollInterval (Properties consumerProperties ) {
684704 Object timeout = consumerProperties .get (ConsumerConfig .MAX_POLL_INTERVAL_MS_CONFIG );
685705 if (timeout == null ) {
@@ -2223,8 +2243,8 @@ public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
22232243 ListenerConsumer .this .logger .warn ("Paused consumer resumed by Kafka due to rebalance; "
22242244 + "consumer paused again, so the initial poll() will never return any records" );
22252245 }
2226- ListenerConsumer .this .assignedPartitions = partitions ;
2227- if (! ListenerConsumer .this .autoCommit ) {
2246+ ListenerConsumer .this .assignedPartitions = new LinkedList <>( partitions ) ;
2247+ if (ListenerConsumer .this .commitCurrentOnAssignment ) {
22282248 // Commit initial positions - this is generally redundant but
22292249 // it protects us from the case when another consumer starts
22302250 // and rebalance would cause it to reset at the end
@@ -2241,51 +2261,7 @@ public void onPartitionsAssigned(Collection<TopicPartition> partitions) {
22412261 return ;
22422262 }
22432263 }
2244- ListenerConsumer .this .commitLogger .log (() -> "Committing on assignment: " + offsetsToCommit );
2245- if (ListenerConsumer .this .transactionTemplate != null &&
2246- ListenerConsumer .this .kafkaTxManager != null ) {
2247- try {
2248- offsetsToCommit .forEach ((partition , offsetAndMetadata ) -> {
2249- TransactionSupport .setTransactionIdSuffix (
2250- zombieFenceTxIdSuffix (partition .topic (), partition .partition ()));
2251- ListenerConsumer .this .transactionTemplate
2252- .execute (new TransactionCallbackWithoutResult () {
2253-
2254- @ SuppressWarnings ({ UNCHECKED , RAWTYPES })
2255- @ Override
2256- protected void doInTransactionWithoutResult (TransactionStatus status ) {
2257- KafkaResourceHolder holder =
2258- (KafkaResourceHolder ) TransactionSynchronizationManager
2259- .getResource (
2260- ListenerConsumer .this .kafkaTxManager
2261- .getProducerFactory ());
2262- if (holder != null ) {
2263- holder .getProducer ()
2264- .sendOffsetsToTransaction (
2265- Collections .singletonMap (partition ,
2266- offsetAndMetadata ),
2267- ListenerConsumer .this .consumerGroupId );
2268- }
2269- }
2270-
2271- });
2272- });
2273- }
2274- finally {
2275- TransactionSupport .clearTransactionIdSuffix ();
2276- }
2277- }
2278- else {
2279- ContainerProperties containerProps = KafkaMessageListenerContainer .this .getContainerProperties ();
2280- if (containerProps .isSyncCommits ()) {
2281- ListenerConsumer .this .consumer .commitSync (offsetsToCommit ,
2282- containerProps .getSyncCommitTimeout ());
2283- }
2284- else {
2285- ListenerConsumer .this .consumer .commitAsync (offsetsToCommit ,
2286- containerProps .getCommitCallback ());
2287- }
2288- }
2264+ commitCurrentOffsets (offsetsToCommit );
22892265 }
22902266 if (ListenerConsumer .this .genericListener instanceof ConsumerSeekAware ) {
22912267 seekPartitions (partitions , false );
@@ -2298,6 +2274,55 @@ protected void doInTransactionWithoutResult(TransactionStatus status) {
22982274 }
22992275 }
23002276
2277+ private void commitCurrentOffsets (Map <TopicPartition , OffsetAndMetadata > offsetsToCommit ) {
2278+ ListenerConsumer .this .commitLogger .log (() -> "Committing on assignment: " + offsetsToCommit );
2279+ if (ListenerConsumer .this .transactionTemplate != null
2280+ && ListenerConsumer .this .kafkaTxManager != null
2281+ && !AssignmentCommitOption .LATEST_ONLY_NO_TX .equals (ListenerConsumer .this .autoCommitOption )) {
2282+ try {
2283+ offsetsToCommit .forEach ((partition , offsetAndMetadata ) -> {
2284+ TransactionSupport .setTransactionIdSuffix (
2285+ zombieFenceTxIdSuffix (partition .topic (), partition .partition ()));
2286+ ListenerConsumer .this .transactionTemplate
2287+ .execute (new TransactionCallbackWithoutResult () {
2288+
2289+ @ SuppressWarnings ({ UNCHECKED , RAWTYPES })
2290+ @ Override
2291+ protected void doInTransactionWithoutResult (TransactionStatus status ) {
2292+ KafkaResourceHolder holder =
2293+ (KafkaResourceHolder ) TransactionSynchronizationManager
2294+ .getResource (
2295+ ListenerConsumer .this .kafkaTxManager
2296+ .getProducerFactory ());
2297+ if (holder != null ) {
2298+ holder .getProducer ()
2299+ .sendOffsetsToTransaction (
2300+ Collections .singletonMap (partition ,
2301+ offsetAndMetadata ),
2302+ ListenerConsumer .this .consumerGroupId );
2303+ }
2304+ }
2305+
2306+ });
2307+ });
2308+ }
2309+ finally {
2310+ TransactionSupport .clearTransactionIdSuffix ();
2311+ }
2312+ }
2313+ else {
2314+ ContainerProperties containerProps = KafkaMessageListenerContainer .this .getContainerProperties ();
2315+ if (containerProps .isSyncCommits ()) {
2316+ ListenerConsumer .this .consumer .commitSync (offsetsToCommit ,
2317+ containerProps .getSyncCommitTimeout ());
2318+ }
2319+ else {
2320+ ListenerConsumer .this .consumer .commitAsync (offsetsToCommit ,
2321+ containerProps .getCommitCallback ());
2322+ }
2323+ }
2324+ }
2325+
23012326 }
23022327
23032328 private final class InitialOrIdleSeekCallback implements ConsumerSeekCallback {
0 commit comments