KubeMQ
Client SDKsJavaHow-to guidesQueues

Ack Range

Selectively acknowledge specific messages from a polled batch by sequence with the Java SDK.

Which to use

This page is about selective/per-message settlement of a polled batch — acking or rejecting individual messages by sequence while leaving the rest pending. For settling an entire polled batch in one call, see Ack All.

Overview

A single poll response often bundles several messages into one batch, but "successfully processed" rarely applies to all of them uniformly — one handler might fail while its siblings succeed. Settling the whole batch together forces an all-or-nothing outcome: either you redeliver work you already finished, or you silently drop work you didn't. Per-message settlement lets each message's outcome reflect what actually happened to it, instead of the worst result in the batch.

Each QueueMessageReceived returned by receiveQueueMessages carries its own broker-assigned getSequence(). Calling msg.ack() on one message settles only that message; calling msg.reject() on another explicitly returns it to the queue for redelivery. Messages you don't touch at all are left pending — still redeliverable — until their own ack()/reject() is called or the visibility timeout expires.

Gotchas: messages you never touch aren't automatically fine — once the visibility timeout elapses, anything left unsettled goes back to the queue, so a handler that forgets to settle a message isn't "done," it's "will retry." Every reject() increments that message's getReceiveCount(), which can trigger dead-letter routing if maxReceiveCount is configured — so a bug that rejects indiscriminately can drain a message's retry budget fast. And selective settlement only works with messages fetched with autoAck disabled — with auto-ack on, the broker settles the entire batch the moment it's delivered, before your code runs.

Prerequisites

  • KubeMQ server running on localhost:50000
  • Java SDK installed (implementation 'io.kubemq.sdk:kubemq-sdk-Java:3.1.1' (Gradle) or Maven dependency from Getting Started)

Code

AckRangeExample.java
package io.kubemq.example.queuesstream;

import io.kubemq.sdk.queues.*;
import java.util.UUID;

/**
 * AckRangeExample for Queues Stream
 *
 * Demonstrates selectively acknowledging specific messages using individual
 * message ack()/reject() calls. This allows fine-grained control over which
 * messages in a batch are acknowledged.
 */
public class AckRangeExample {
    private static final String ADDRESS = "localhost:50000";
    private static final String CLIENT_ID = "java-queues-ack-range-client";
    private static final String CHANNEL = "java-queues.ack-range";

    public static void main(String[] args) {
        // Create a client connected to the KubeMQ server
        try (QueuesClient client = QueuesClient.builder().address(ADDRESS).clientId(CLIENT_ID).build()) {
            // Create the queue channel
            client.createQueuesChannel(CHANNEL);

            // Send messages to the queue
            for (int i = 1; i <= 5; i++) {
                client.sendQueueMessage(QueueMessage.builder()
                        .channel(CHANNEL).body(("Range msg " + i).getBytes()).build());
            }

            // Poll for a batch of messages with manual ack
            QueuesPollResponse response = client.receiveQueueMessages(QueuesPollRequest.builder()
                    .channel(CHANNEL).pollMaxMessages(5).pollWaitTimeoutInSeconds(5).build());

            System.out.println("Received " + response.getMessages().size() + " messages.");

            // Selectively settle by sequence: ack even sequences, reject odd ones
            for (QueueMessageReceived msg : response.getMessages()) {
                long sequence = msg.getSequence();
                if (sequence % 2 == 0) {
                    msg.ack();
                    System.out.println("  Acked: seq=" + sequence);
                } else {
                    msg.reject();
                    System.out.println("  Rejected: seq=" + sequence);
                }
            }

            // Clean up rejected messages (receive and auto-ack)
            QueuesPollResponse cleanup = client.receiveQueueMessages(QueuesPollRequest.builder()
                    .channel(CHANNEL).pollMaxMessages(10).pollWaitTimeoutInSeconds(1).autoAckMessages(true).build());
            System.out.println("\nRemaining messages cleaned up: " + cleanup.getMessages().size());

            // Clean up resources
            client.deleteQueuesChannel(CHANNEL);
        } catch (Exception e) {
            System.err.println("Error: " + e.getMessage());
        }
    }
}

How It Works

  • pollMaxMessages(5) fetches up to 5 messages in one call with manual ack, so each QueueMessageReceived must be settled explicitly via ack() or reject().
  • Settlement decisions are made per-message using the broker-assigned msg.getSequence() (even = ack, odd = reject in this example).
  • Rejected messages are returned to the queue for redelivery; their getReceiveCount() increments, which can trigger dead-letter routing if maxReceiveCount is set.
  • This per-message settlement pattern is the foundation for selective processing: only confirmed-successful messages are removed from the queue, while the rest remain redeliverable.

Was this page helpful?

On this page