# Cached Query (/sdks/java/how-to/rpc/query-cached)



## Overview [#overview]

**Query response caching** lets the broker answer repeat requests without re-running your handler — useful when a query is expensive to compute (a database lookup, an aggregation, a downstream call) but the same input is asked for repeatedly in a short window. Only the first request pays the processing cost; every other caller gets the same answer straight from the broker.

Set `cacheKey(...)` and `cacheTtlInSeconds(...)` on the `QueryMessage`. The first query with a given key is a miss: it reaches the handler, and the broker stores the response under that key for the TTL. A subsequent query with the same key is a hit — the broker returns the stored response directly, and the handler is not invoked again until the TTL expires.

**Gotchas:** the cache is keyed by the string you choose, not by the query body — if the underlying data changes mid-TTL, callers can get a stale answer until it expires. Keys are scoped per channel, so the same key on another channel is a separate entry. Setting `cacheTtlInSeconds(0)` or omitting `cacheKey` disables caching for that request even on a caching channel.

## Prerequisites [#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](/sdks/java))

## Code [#code]

```java title="CachedQueryExample.java"
package io.kubemq.example.queries;

import io.kubemq.sdk.cq.*;

public class CachedQueryExample {
    private static final String ADDRESS = "localhost:50000";
    private static final String CLIENT_ID = "java-queries-cached-query-client";
    private static final String CHANNEL = "java-queries.cached-query";

    public static void main(String[] args) throws InterruptedException {
        // Create a client connected to the KubeMQ server
        CQClient client = CQClient.builder().address(ADDRESS).clientId(CLIENT_ID).build();
        client.ping();
        // Create the queries channel
        client.createQueriesChannel(CHANNEL);

        // Subscribe to handle queries (responses may be cached)
        QueriesSubscription sub = QueriesSubscription.builder()
                .channel(CHANNEL)
                .onReceiveQueryCallback(query -> {
                    System.out.println("Handler invoked for: " + query.getId());
                    client.sendResponseMessage(QueryResponseMessage.builder()
                            .queryReceived(query).isExecuted(true)
                            .body("cached-result".getBytes()).build());
                })
                .onErrorCallback(err -> System.err.println("Error: " + err.getMessage()))
                .build();
        // Start the query handler subscription
        client.subscribeToQueries(sub);
        Thread.sleep(1000);

        // Build query with cache key and TTL
        QueryMessage q = QueryMessage.builder()
                .channel(CHANNEL).body("get-price".getBytes()).metadata("product=xyz")
                .timeoutInSeconds(10).cacheKey("price:xyz").cacheTtlInSeconds(60).build();

        // First request hits the handler; second may be served from cache
        QueryResponseMessage r1 = client.sendQuery(q);
        System.out.println("Response 1: " + new String(r1.getBody()));

        QueryResponseMessage r2 = client.sendQuery(q);
        System.out.println("Response 2: " + new String(r2.getBody()) + " (may be cached)");

        // Clean up resources
        sub.cancel();
        client.deleteQueriesChannel(CHANNEL);
        client.close();
    }
}

```

## How It Works [#how-it-works]

* `cacheKey("price:xyz")` and `cacheTtlInSeconds(60)` instruct the broker to store the first response for this key and serve it directly for subsequent identical requests within 60 seconds, bypassing the handler entirely.
* The first `sendQuery()` reaches the handler (you see "Handler invoked"); the second call returns the cached response — the handler is not invoked again until the TTL expires.
* Cache keys are scoped to the channel; the same key on different channels is independent.
* Set `cacheTtlInSeconds(0)` or omit `cacheKey` to disable caching for a specific request even on a channel that supports it.

## Related [#related]

* [Pattern overview](/learn/rpc/getting-started)
* [Java SDK Reference](/sdks/java/reference/rpc)
* [Send Query](/sdks/java/tutorials/query-send)
* [Query Group](/sdks/java/how-to/rpc/query-group)
