{"record":{"id":"a37fd1aa48dd8264","repo":"apache/rocketmq","slug":"datetime-is-null","errorCode":null,"errorMessage":"datetime is null.","messagePattern":"datetime is null\\.","errorType":"exception","errorClass":"AuthenticationException","httpStatus":null,"severity":"error","filePath":"auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java","lineNumber":59,"sourceCode":"\npublic class DefaultAuthenticationContextBuilder implements AuthenticationContextBuilder<DefaultAuthenticationContext> {\n\n    private static final String CREDENTIAL = \"Credential\";\n    private static final String SIGNATURE = \"Signature\";\n\n    @Override\n    public DefaultAuthenticationContext build(Metadata metadata, GeneratedMessageV3 request) {\n        try {\n            DefaultAuthenticationContext context = new DefaultAuthenticationContext();\n            context.setChannelId(metadata.get(GrpcConstants.CHANNEL_ID));\n            context.setRpcCode(request.getDescriptorForType().getFullName());\n            String authorization = metadata.get(GrpcConstants.AUTHORIZATION);\n            if (StringUtils.isEmpty(authorization)) {\n                return context;\n            }\n            String datetime = metadata.get(GrpcConstants.DATE_TIME);\n            if (StringUtils.isEmpty(datetime)) {\n                throw new AuthenticationException(\"datetime is null.\");\n            }\n\n            String[] result = authorization.split(CommonConstants.SPACE, 2);\n            if (result.length != 2) {\n                throw new AuthenticationException(\"authentication header is incorrect.\");\n            }\n            String[] keyValues = result[1].split(CommonConstants.COMMA);\n            for (String keyValue : keyValues) {\n                String[] kv = keyValue.trim().split(CommonConstants.EQUAL, 2);\n                int kvLength = kv.length;\n                if (kv.length != 2) {\n                    throw new AuthenticationException(\"authentication keyValues length is incorrect, actual length={}.\", kvLength);\n                }\n                String authItem = kv[0];\n                if (CREDENTIAL.equals(authItem)) {\n                    String[] credential = kv[1].split(CommonConstants.SLASH);\n                    int credentialActualLength = credential.length;\n                    if (credentialActualLength == 0) {","sourceCodeStart":41,"sourceCodeEnd":77,"githubUrl":"https://github.com/apache/rocketmq/blob/293f5885719fc4aa3619446a1900f58ccfcfdd29/auth/src/main/java/org/apache/rocketmq/auth/authentication/builder/DefaultAuthenticationContextBuilder.java#L41-L77","documentation":"Thrown while building the gRPC authentication context: the Authorization metadata header is present but the companion datetime metadata (GrpcConstants.DATE_TIME) is missing or empty. The datetime string is the content that gets HMAC-signed, so without it the server cannot verify the client signature. The builder therefore refuses to construct the context before any signature check happens.","triggerScenarios":"A gRPC client (e.g. RocketMQ 5.x remoting gRPC SDK) sends the 'authorization' metadata key but omits the 'datetime' metadata key, or sends it with an empty value. Triggered on any authenticated gRPC RPC once AUTHORIZATION is set, because datetime is checked immediately after a non-empty authorization header.","commonSituations":"Hand-rolled gRPC clients or custom interceptors that only copy the authorization header; SDK versions where the signer sets datetime but a proxy/loan balancer strips unknown metadata keys; client code that builds metadata manually and forgets the datetime entry that the signature was computed over.","solutions":["Set the datetime metadata on every gRPC call, e.g. metadata.put(GrpcConstants.DATE_TIME, datetime) where datetime is the same string used when computing the client signature (typically formatted with DateTimeFormatter ofPattern('yyyyMMddHHmmss') in UTC).","Use the official RocketMQ gRPC client SDK, which sets both authorization and datetime automatically in its credentials interceptor, instead of hand-writing the metadata.","Verify no intermediary (gRPC proxy, custom ClientInterceptor, metadata whitelist) strips the datetime key between client and broker."],"exampleFix":"// before\nMetadata metadata = new Metadata();\nmetadata.put(Metadata.Key.of(\"authorization\", ASCII_STRING_MARSHALLER), authHeader);\n// signature computed over a local datetime variable\n\n// after\nString datetime = LocalDateTime.now(ZoneOffset.UTC).format(DateTimeFormatter.ofPattern(\"yyyyMMddHHmmss\"));\nMetadata metadata = new Metadata();\nmetadata.put(Metadata.Key.of(\"datetime\", ASCII_STRING_MARSHALLER), datetime);\nmetadata.put(Metadata.Key.of(\"authorization\", ASCII_STRING_MARSHALLER), \"RocketMQ Credential=\" + username + \",Signature=\" + signature + \",DateTime=\" + datetime);","handlingStrategy":"validation","validationCode":"// Before the gRPC call: ensure datetime metadata accompanies the authorization header\nString datetime = DateTimeFormatter.ofPattern(\"yyyyMMddHHmmss\")\n        .withZone(ZoneOffset.UTC).format(Instant.now());\nif (authorizationHeader != null && !authorizationHeader.isEmpty()\n        && (datetime == null || datetime.isEmpty())) {\n    throw new IllegalStateException(\"datetime metadata is required when authorization is set\");\n}","typeGuard":null,"tryCatchPattern":"catch (AuthenticationException e) when e.getMessage().contains(\"datetime is null\") -> client-side: attach datetime metadata and retry once; surface otherwise.","preventionTips":["Centralize metadata construction in one client interceptor that always sets authorization and datetime together","Compute the signature over the exact datetime string you put into metadata","Add a unit test asserting both metadata keys are present on every outgoing call"],"tags":["rocketmq","authentication","grpc","metadata"],"backgroundTag":null,"analyzedSha":"293f5885719fc4aa3619446a1900f58ccfcfdd29","analyzedAt":"2026-08-14T11:50:13.822Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}