{"id":"1221fe260f850e81","repo":"apache/kafka","slug":"heartbeat-must-be-set-lower-than-the-session-timeo","errorCode":null,"errorMessage":"Heartbeat must be set lower than the session timeout","messagePattern":"Heartbeat must be set lower than the session timeout","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"clients/src/main/java/org/apache/kafka/clients/consumer/internals/Heartbeat.java","lineNumber":48,"sourceCode":" */\npublic final class Heartbeat {\n    private final int maxPollIntervalMs;\n    private final GroupRebalanceConfig rebalanceConfig;\n    private final Time time;\n    private final Timer heartbeatTimer;\n    private final Timer sessionTimer;\n    private final Timer pollTimer;\n    private final Logger log;\n    private final ExponentialBackoff retryBackoff;\n\n    private volatile long lastHeartbeatSend = 0L;\n    private volatile boolean heartbeatInFlight = false;\n    private volatile long heartbeatAttempts = 0L;\n\n    public Heartbeat(GroupRebalanceConfig config,\n                     Time time) {\n        if (config.heartbeatIntervalMs >= config.sessionTimeoutMs)\n            throw new IllegalArgumentException(\"Heartbeat must be set lower than the session timeout\");\n        this.rebalanceConfig = config;\n        this.time = time;\n        this.heartbeatTimer = time.timer(config.heartbeatIntervalMs);\n        this.sessionTimer = time.timer(config.sessionTimeoutMs);\n        this.maxPollIntervalMs = config.rebalanceTimeoutMs;\n        this.pollTimer = time.timer(maxPollIntervalMs);\n        this.retryBackoff = new ExponentialBackoff(rebalanceConfig.retryBackoffMs,\n                CommonClientConfigs.RETRY_BACKOFF_EXP_BASE,\n                rebalanceConfig.retryBackoffMaxMs,\n                CommonClientConfigs.RETRY_BACKOFF_JITTER);\n\n        final LogContext logContext = new LogContext(\"[Heartbeat groupID=\" + config.groupId + \"] \");\n        this.log = logContext.logger(getClass());\n    }\n\n    private void update(long now) {\n        heartbeatTimer.update(now);\n        sessionTimer.update(now);","sourceCodeStart":30,"sourceCodeEnd":66,"githubUrl":"https://github.com/apache/kafka/blob/c31c9215e131f8c17e79f8901b48c13ee6aa8e7a/clients/src/main/java/org/apache/kafka/clients/consumer/internals/Heartbeat.java#L30-L66","documentation":"IllegalArgumentException from the Heartbeat constructor validating that heartbeat.interval.ms is strictly less than session.timeout.ms. If the heartbeat fires as often as or more often than the session window, the protocol invariant that heartbeats keep the member alive within one session would be violated. The check runs once at consumer construction, so the failure surfaces immediately when the consumer is built rather than at runtime.","triggerScenarios":"Thrown when constructing a KafkaConsumer (or otherwise instantiating Heartbeat with a GroupRebalanceConfig) where heartbeat.intervalMs >= sessionTimeoutMs. Common when both are set explicitly in properties to equal or inverting values.","commonSituations":"Manual tuning of session.timeout.ms without also adjusting heartbeat.interval.ms; copy-pasted config that lowers session.timeout.ms for faster rebalance detection below the heartbeat interval; unit tests that construct Heartbeat directly with synthetic values.","solutions":["Set heartbeat.interval.ms to roughly one-third of session.timeout.ms (the documented rule of thumb).","Ensure session.timeout.ms > heartbeat.interval.ms in consumer properties, e.g. session=45000, heartbeat=15000.","Remove explicit heartbeat.interval.ms and let the client compute its default relative to session.timeout.ms."],"exampleFix":"// before\nprops.put(\"session.timeout.ms\", \"10000\");\nprops.put(\"heartbeat.interval.ms\", \"10000\");\n\n// after\nprops.put(\"session.timeout.ms\", \"45000\");\nprops.put(\"heartbeat.interval.ms\", \"15000\");","handlingStrategy":"validation","validationCode":"long heartbeatMs = (Long) props.getOrDefault(\"heartbeat.interval.ms\", 3000L);\nlong sessionMs  = (Long) props.getOrDefault(\"session.timeout.ms\", 45000L);\nif (heartbeatMs >= sessionMs) {\n    throw new IllegalArgumentException(\n        \"heartbeat.interval.ms (\" + heartbeatMs + \") must be strictly less than session.timeout.ms (\" + sessionMs + \")\");\n}","typeGuard":"private static boolean heartbeatFitsSession(long heartbeatMs, long sessionMs) {\n    return heartbeatMs > 0 && sessionMs > 0 && heartbeatMs < sessionMs;\n}","tryCatchPattern":null,"preventionTips":["Rule of thumb: set heartbeat.interval.ms to roughly one-third of session.timeout.ms.","Centralize consumer config in one builder/validator so this invariant is checked once at startup, not per consumer.","Bump session.timeout.ms when processing time per batch is long, and proportionally adjust heartbeat.interval.ms.","Add a unit test asserting heartbeat < session for every config profile shipped in your deployment.","Avoid copying broker-side keepalive settings into consumer configs; they are independent dials."],"tags":["kafka","consumer","config","heartbeat","session-timeout"],"analyzedSha":"c31c9215e131f8c17e79f8901b48c13ee6aa8e7a","analyzedAt":"2026-08-03T12:34:05.770Z","schemaVersion":2}