KubeMQ
Client SDKsJavaHow-to guidesError Handling

Graceful Shutdown

Gracefully shut down a KubeMQ Java SDK client, releasing connections and resources cleanly on application exit.

Overview

A graceful shutdown stops KubeMQ clients without dropping in-flight messages or leaking server-side subscription state. Killing a process outright, or closing a client mid-callback, can truncate a handler or leave the server thinking a consumer is still there. In a container platform that sends SIGTERM before force-killing a pod, an orderly shutdown sequence turns a rolling deploy into a clean handoff instead of a burst of errors.

The pattern is a fixed sequence: cancel subscriptions first so no new messages arrive, give pending callbacks a brief window to drain, then close each client. subscription.cancel() stops new deliveries while letting any callback already running finish. When an application holds several clients — here a PubSubClient, QueuesClient, and CQClient — they're closed in reverse construction order, so a client that might still reference another isn't torn down first.

Gotchas: a fixed Thread.sleep() after cancelling is a placeholder, not a guarantee — track in-flight callbacks with a CountDownLatch and wait on that instead. Closing clients out of order can leave one trying to use a transport another already shut down.

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

GracefulShutdownExample.java
package io.kubemq.example.errorhandling;

import io.kubemq.sdk.pubsub.*;
import io.kubemq.sdk.queues.*;
import io.kubemq.sdk.cq.*;

import java.util.ArrayList;
import java.util.List;

/**
 * Graceful Shutdown Example
 *
 * Demonstrates proper shutdown procedures for KubeMQ clients.
 */
public class GracefulShutdownExample {

    private static final String ADDRESS = "localhost:50000";
    private static final String CLIENT_ID = "java-errorhandling-graceful-shutdown-client";

    public static void main(String[] args) throws InterruptedException {
        System.out.println("=== Graceful Shutdown ===\n");

        // Create multiple clients
        PubSubClient pubSubClient = PubSubClient.builder()
                .address(ADDRESS).clientId(CLIENT_ID + "-pubsub").build();
        QueuesClient queuesClient = QueuesClient.builder()
                .address(ADDRESS).clientId(CLIENT_ID + "-queues").build();
        CQClient cqClient = CQClient.builder()
                .address(ADDRESS).clientId(CLIENT_ID + "-cq").build();

        System.out.println("Created 3 clients.");

        // Create channel and subscribe
        pubSubClient.createEventsChannel("java-errorhandling.shutdown-test");
        EventsSubscription sub = EventsSubscription.builder()
                .channel("java-errorhandling.shutdown-test")
                .onReceiveEventCallback(e -> {}).onErrorCallback(err -> {}).build();
        // Start the subscription
        pubSubClient.subscribeToEvents(sub);

        System.out.println("Subscription active.\n");

        // Send messages before shutdown
        for (int i = 1; i <= 3; i++) {
            pubSubClient.publishEvent(EventMessage.builder()
                    .channel("java-errorhandling.shutdown-test")
                    .body(("Message " + i).getBytes()).build());
        }
        Thread.sleep(300);

        // Graceful shutdown sequence
        System.out.println("--- Initiating Graceful Shutdown ---\n");

        // Step 1: Cancel subscriptions
        sub.cancel();
        System.out.println("1. Subscriptions cancelled.");

        // Step 2: Wait for pending operations
        Thread.sleep(200);
        System.out.println("2. Pending operations completed.");

        // Step 3: Clean up channels
        try { pubSubClient.deleteEventsChannel("java-errorhandling.shutdown-test"); } catch (Exception e) {}
        System.out.println("3. Channels cleaned up.");

        // Step 4: Close clients in reverse order
        cqClient.close();
        queuesClient.close();
        pubSubClient.close();
        System.out.println("4. All clients closed.\n");

        System.out.println("Graceful shutdown complete.");
    }
}

How It Works

  • The shutdown sequence follows four ordered steps: cancel subscriptions, wait for in-flight callbacks to drain, clean up channels, then close clients in reverse construction order.
  • subscription.cancel() signals the SDK to stop delivering new events to the callback; any callback already running completes before the subscription is fully torn down.
  • Thread.sleep(200) after cancel gives pending callbacks time to finish — in production replace this with a CountDownLatch or CompletableFuture tracked inside the callback.
  • Closing clients in reverse order (CQ → Queues → PubSub) prevents a scenario where a still-active client tries to publish on a transport that is already shut down.

Was this page helpful?

On this page