{"record":{"id":"e40b2c1b8c16c5cd","repo":"quarkusio/quarkus","slug":"cannot-use-let-s-encrypt-without-the-quarkus-vertx","errorCode":null,"errorMessage":"Cannot use Let's Encrypt without the quarkus-vertx-http extension","messagePattern":"Cannot use Let's Encrypt without the quarkus-vertx-http extension","errorType":"validation","errorClass":"ConfigurationException","httpStatus":null,"severity":"error","filePath":"extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/deployment/CertificatesProcessor.java","lineNumber":89,"sourceCode":"                .supplier(supplier)\n                .scope(Singleton.class)\n                .unremovable()\n                .setRuntimeInit();\n\n        syntheticBeans.produce(configurator.done());\n\n        return new TlsRegistryBuildItem(supplier);\n    }\n\n    @Record(ExecutionTime.RUNTIME_INIT)\n    @BuildStep(onlyIf = LetsEncryptEnabled.class)\n    void createManagementRoutes(BuildProducer<RouteBuildItem> routes,\n            LetsEncryptRecorder recorder,\n            TlsRegistryBuildItem registryBuildItem) {\n\n        // Check if Vert.x Web is present\n        if (!QuarkusClassLoader.isClassPresentAtRuntime(\"io.vertx.ext.web.Router\")) {\n            throw new ConfigurationException(\"Cannot use Let's Encrypt without the quarkus-vertx-http extension\");\n        }\n\n        recorder.initialize(registryBuildItem.registry());\n\n        // Route to handle the Let's Encrypt challenge - primary HTTP server\n        routes.produce(RouteBuildItem.newAbsoluteRoute(\"/.well-known/acme-challenge/:token\")\n                .withRequestHandler(recorder.challengeHandler())\n                .build());\n\n        // Route to configure the Let's Encrypt challenge - management server\n        routes.produce(RouteBuildItem.newManagementRoute(\"lets-encrypt/challenge\")\n                .withRequestHandler(recorder.chalengeAdminHandler())\n                .withRouteCustomizer(recorder.setupCustomizer())\n                .build());\n\n        // Route to refresh the certificates - management server\n        routes.produce(RouteBuildItem.newManagementRoute(\"lets-encrypt/certs\")\n                .withRequestHandler(recorder.reload())","sourceCodeStart":71,"sourceCodeEnd":107,"githubUrl":"https://github.com/quarkusio/quarkus/blob/e1c734241f34c7919086ceb4c9262b4a58f6de44/extensions/tls-registry/deployment/src/main/java/io/quarkus/tls/deployment/CertificatesProcessor.java#L71-L107","documentation":"During build, CertificatesProcessor.createManagementRoutes() wires an HTTP route for the ACME challenge. Let's Encrypt renewal requires serving HTTP-01 challenge responses, which is done through Vert.x Web's Router. If io.vertx.ext.web.Router is not present at runtime in the Quarkus classloader, the build fails with this ConfigurationException because the challenge route cannot be registered.","triggerScenarios":"An application enables Let's Encrypt certificate management (quarkus.tls.lets-contact/acme configuration) but does not include the quarkus-vertx-http ( Vert.x HTTP ) extension as a dependency, so QuarkusClassLoader.isClassPresentAtRuntime(\"io.vertx.ext.web.Router\") is false at build time.","commonSituations":"A non-HTTP (e.g. gRPC-only, or plain TLS without vertx-http) Quarkus app adding TLS registry Let's Encrypt config; removing quarkus-vertx-http during a cleanup and forgetting the ACME config remains; copy-pasting TLS config from a REST app into a background worker.","solutions":["Add the quarkus-vertx-http extension: ./mvnw quarkus:add-extension -Dextensions=\"vertx-http\"","If HTTP serving is impossible, disable Let's Encrypt management and provide the certificate out-of-band (quarkus.tls.*.key-store files)","Remove the Let's Encrypt/ACME config block if the app genuinely never terminates HTTP","Verify the dependency is in the runtime module, not only deployment, then rebuild"],"exampleFix":"// before (pom.xml)\n<dependency><groupId>io.quarkus</groupId><artifactId>quarkus-tls-registry</artifactId></dependency>\n// after\n<dependency><groupId>io.quarkus</groupId><artifactId>quarkus-tls-registry</artifactId></dependency>\n<dependency><groupId>io.quarkus</groupId><artifactId>quarkus-vertx-http</artifactId></dependency>","handlingStrategy":"validation","validationCode":"// build-time guard: ensure vertx-http is on the classpath before enabling ACME\nboolean hasVertxHttp = QuarkusClassLoader.isClassPresentAtRuntime(\"io.vertx.ext.web.Router\");\nboolean acmeEnabled = config.getOptionalValue(\"quarkus.tls.*.issuer-reference.acme.enabled\", Boolean.class).orElse(false);\nif (acmeEnabled && !hasVertxHttp) throw new IllegalStateException(\"Add the quarkus-vertx-http extension to use Let's Encrypt\");","typeGuard":"boolean letsEncryptUsable() {\n    return QuarkusClassLoader.isClassPresentAtRuntime(\"io.vertx.ext.web.Router\");\n}","tryCatchPattern":"// build-step failure cannot be caught at runtime; guard the build input\ntry {\n    build();\n} catch (ConfigurationException e) {\n    if (e.getMessage().contains(\"quarkus-vertx-http\")) {\n        throw new IllegalStateException(\"Add quarkus-vertx-http or disable Let's Encrypt config\", e);\n    }\n    throw e;\n}","preventionTips":["Add quarkus-vertx-http whenever quarkus-tls-registry with Let's Encrypt is used","Grep application.properties for lets-contact/acme entries before removing vertx-http","In extension templates, declare a runtime dependency so the build fails with a clear message early","Keep ACME config only in apps that serve HTTP (REST/gRPC over HTTP) workloads"],"tags":["build-time","configuration","acme","vertx-http","missing-dependency"],"backgroundTag":"missing-extension-dependency","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"}