{"record":{"id":"b8f67dc390152fcc","repo":"alibaba/canal","slug":"clientidentity-s-should-subscribe-first","errorCode":null,"errorMessage":"ClientIdentity:%s should subscribe first","messagePattern":"ClientIdentity:(.+?) should subscribe first","errorType":"exception","errorClass":"CanalServerException","httpStatus":null,"severity":"error","filePath":"server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java","lineNumber":527,"sourceCode":"            return eventStore.tryGet(start, batchSize);\n        } else {\n            try {\n                if (timeout <= 0) {\n                    return eventStore.get(start, batchSize);\n                } else {\n                    return eventStore.get(start, batchSize, timeout, unit);\n                }\n            } catch (Exception e) {\n                throw new CanalServerException(e);\n            }\n        }\n    }\n\n    private void checkSubscribe(ClientIdentity clientIdentity) {\n        CanalInstance canalInstance = canalInstances.get(clientIdentity.getDestination());\n        boolean hasSubscribe = canalInstance.getMetaManager().hasSubscribe(clientIdentity);\n        if (!hasSubscribe) {\n            throw new CanalServerException(String.format(\"ClientIdentity:%s should subscribe first\",\n                clientIdentity.toString()));\n        }\n    }\n\n    private void checkStart(String destination) {\n        if (!isStart(destination)) {\n            throw new CanalServerException(String.format(\"destination:%s should start first\", destination));\n        }\n    }\n\n    private void loadCanalMetrics() {\n        ServiceLoader<CanalMetricsProvider> providers = ServiceLoader.load(CanalMetricsProvider.class);\n        List<CanalMetricsProvider> list = new ArrayList<>();\n        for (CanalMetricsProvider provider : providers) {\n            list.add(provider);\n        }\n\n        if (list.isEmpty()) {","sourceCodeStart":509,"sourceCodeEnd":545,"githubUrl":"https://github.com/alibaba/canal/blob/87be50e87686a3e8af08c368d0e1ffd1f59eb04a/server/src/main/java/com/alibaba/otter/canal/server/embedded/CanalServerWithEmbedded.java#L509-L545","documentation":"Thrown by the private checkSubscribe() guard, which is invoked at the top of get()/ack()/rollback() before any batch operation. It asks MetaManager.hasSubscribe(clientIdentity); if false it aborts because batch tracking and cursor positioning require an active subscription for that destination+clientId.","triggerScenarios":"Calling get(), ack(), or rollback() on a ClientIdentity whose subscribe(destination, filter) was never called, was called for a different destination, or whose subscription was cleared. Also fires if the MetaManager lost the subscription record (e.g. in-memory MetaManager after a server restart).","commonSituations":"Client connects and immediately calls get() without subscribe(); client subscribes to destination A then queries destination B; server restart with a memory-backed MetaManager (no ZooKeeper) wipes subscriptions; typo in destination name between subscribe and get.","solutions":["Call server.subscribe(clientIdentity) and wait for it to succeed before any get/ack/rollback.","Verify the destination string in the ClientIdentity matches exactly between subscribe and subsequent calls.","If using a memory MetaManager, re-subscribe after every server restart; use ZooKeeper MetaManager for persistence.","Add a subscription readiness check in client startup before entering the consume loop."],"exampleFix":"// before: get without subscribe\nClientIdentity cid = new ClientIdentity(destination, clientId);\nMessage msg = server.get(cid, 1000, 1L, TimeUnit.SECONDS);\n// after: subscribe first\nClientIdentity cid = new ClientIdentity(destination, (short) 1001);\nserver.subscribe(cid); // register subscription\nMessage msg = server.get(cid, 1000, 1L, TimeUnit.SECONDS);","handlingStrategy":"validation","validationCode":"// verify subscription exists before any batch operation\nif (!metaManager.hasSubscribe(clientIdentity)) {\n    server.subscribe(clientIdentity);\n}","typeGuard":null,"tryCatchPattern":"try {\n    server.get(clientId, batchSize, timeout, unit);\n} catch (CanalServerException e) {\n    if (e.getMessage().contains(\"should subscribe first\")) {\n        server.subscribe(clientId);\n        return server.get(clientId, batchSize, timeout, unit);\n    }\n    throw e;\n}","preventionTips":["Centralize client connect logic so subscribe always precedes get/ack/rollback.","Use a ZooKeeper-backed MetaManager so subscriptions survive server restarts.","Assert destination name consistency between subscribe and consume paths in tests."],"tags":["embedded-server","subscription","lifecycle","ordering"],"backgroundTag":null,"analyzedSha":"87be50e87686a3e8af08c368d0e1ffd1f59eb04a","analyzedAt":"2026-08-14T04:30:11.918Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}