{"record":{"id":"b1182604c144ad77","repo":"quarkusio/quarkus","slug":"the-certificate-chain-cannot-be-null-or-empty","errorCode":null,"errorMessage":"The certificate chain cannot be null or empty","messagePattern":"The certificate chain cannot be null or empty","errorType":"exception","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java","lineNumber":46,"sourceCode":"import io.smallrye.certs.CertificateUtils;\nimport io.vertx.core.json.JsonObject;\n\npublic class LetsEncryptHelpers {\n\n    public static final String DEFAULT_ACME_URL = \"https://acme-v02.api.letsencrypt.org/directory\";\n    public static final String DEFAULT_ACME_STAGING_URL = \"https://acme-staging-v02.api.letsencrypt.org/directory\";\n    public static final String TLS_AUDIT_LOG = \"io.quarkus.tls.audit\";\n\n    static Logger LOGGER = Logger.getLogger(LetsEncryptHelpers.class);\n    public static Logger AUDIT = Logger.getLogger(LetsEncryptHelpers.TLS_AUDIT_LOG);\n\n    public static void writePrivateKeyAndCertificateChainsAsPem(PrivateKey pk, X509Certificate[] chain, File privateKeyFile,\n            File certificateChainFile) throws Exception {\n        if (pk == null) {\n            throw new IllegalArgumentException(\"The private key cannot be null\");\n        }\n        if (chain == null || chain.length == 0) {\n            throw new IllegalArgumentException(\"The certificate chain cannot be null or empty\");\n        }\n\n        AUDIT.debug(\"Writing private key to file: \" + privateKeyFile.getAbsolutePath());\n        CertificateUtils.writePrivateKeyToPem(pk, null, privateKeyFile);\n\n        AUDIT.debug(\"Writing certificate chain to file: \" + certificateChainFile.getAbsolutePath());\n\n        if (chain.length == 1) {\n            CertificateUtils.writeCertificateToPEM(chain[0], certificateChainFile);\n            return;\n        }\n\n        // For some reason the method from CertificateUtils distinguishes the first certificate and the rest of the chain\n        X509Certificate[] restOfTheChain = new X509Certificate[chain.length - 1];\n        System.arraycopy(chain, 1, restOfTheChain, 0, chain.length - 1);\n        CertificateUtils.writeCertificateToPEM(chain[0], certificateChainFile, restOfTheChain);\n    }\n","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/tls-registry/cli/src/main/java/io/quarkus/tls/cli/letsencrypt/LetsEncryptHelpers.java#L28-L64","documentation":"writePrivateKeyAndCertificateChainsAsPem also validates the X509Certificate[] argument and throws IllegalArgumentException when it is null or zero-length. A certificate chain is mandatory to write the chain PEM; an empty result means the ACME order returned no certificates, so there is nothing usable to persist.","triggerScenarios":"Calling writePrivateKeyAndCertificateChainsAsPem(pk, null, ...) or with new X509Certificate[0], or via issueCertificate when obtainCertificateChain's X509CertificateChainAndSigningKey.getCertificateChain() is null/empty (failed ACME authorization, order still pending/invalid, custom server bug).","commonSituations":"Renewing while the previous order is still pending at the CA; domain authorization failed so the CA returned no chain; tests/tools calling the helper with placeholder data; a custom ACME directory that returns a malformed finalize response.","solutions":["Null/empty-check getCertificateChain() before writing; if empty, treat the order as failed and re-run lets-encrypt issue/renew.","Inspect the ACME order state (pending/invalid/valid) — wait for pending orders or fix authorization failures (DNS/HTTP-01 reachability) before retrying.","Test with the Let's Encrypt staging URL to rule out a custom-server incompatibility.","Ensure the domain in the CLI command resolves and is reachable over HTTP 80 so the HTTP-01 challenge can succeed and a chain is actually issued."],"exampleFix":"// before\nX509Certificate[] chain = certChainAndPrivateKey.getCertificateChain();\nLetsEncryptHelpers.writePrivateKeyAndCertificateChainsAsPem(key, chain, keyPem, chainPem);\n\n// after\nX509Certificate[] chain = certChainAndPrivateKey.getCertificateChain();\nif (chain == null || chain.length == 0) {\n    throw new IllegalStateException(\"No certificate chain returned by ACME; order likely failed\");\n}\nLetsEncryptHelpers.writePrivateKeyAndCertificateChainsAsPem(key, chain, keyPem, chainPem);","handlingStrategy":"validation","validationCode":"X509Certificate[] chain = certChainAndPrivateKey.getCertificateChain();\nif (chain == null || chain.length == 0) {\n    throw new IllegalStateException(\"ACME returned no certificate chain; order failed or pending\");\n}\nLetsEncryptHelpers.writePrivateKeyAndCertificateChainsAsPem(pk, chain, keyPem, chainPem);","typeGuard":"static boolean hasCertificateChain(X509CertificateChainAndSigningKey r) {\n    return r != null && r.getCertificateChain() != null && r.getCertificateChain().length > 0;\n}","tryCatchPattern":"try {\n    LetsEncryptHelpers.writePrivateKeyAndCertificateChainsAsPem(pk, chain, keyPem, chainPem);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage() != null && e.getMessage().contains(\"certificate chain cannot be null\")) {\n        // no chain issued: check ACME order state / authorization, then retry issuance\n    } else {\n        throw e;\n    }\n}","preventionTips":["Confirm the HTTP-01 challenge succeeded (domain reachable on port 80) before expecting a chain.","Check ACME order status; retry only after it is valid, and wait out pending orders.","Null/length-check getCertificateChain() at every call site of the helper.","Validate issuance end-to-end against the staging ACME URL before production."],"tags":["acme","lets-encrypt","pem","certificate-chain","null-argument"],"backgroundTag":"null-argument-precondition","analyzedSha":"e1c734241f34c7919086ceb4c9262b4a58f6de44","analyzedAt":"2026-09-05T17:01:29.979Z","contentChangedAt":"2026-09-05T17:01:29.979Z","schemaVersion":2},"datasetVersion":"2026-09-12T22:17:10.623Z"}