quarkusio/quarkus · error · DeploymentException

${exposedEndpoint} is declared by :${endpointConfigs}

Error message

${exposedEndpoint} is declared by :${endpointConfigs}

What it means

Quarkus RESTEasy Reactive detects when the same HTTP method + path combination is declared by more than one resource method (including via class-level @Path prefixes). The aggregated message names the duplicated endpoint and the competing resource/method configurations. If the REST config failOnDuplicate is enabled, deployment throws this DeploymentException; otherwise it only logs a warning.

Source

Thrown at extensions/resteasy-reactive/rest/deployment/src/main/java/io/quarkus/resteasy/reactive/server/deployment/ResteasyReactiveProcessor.java:1686

    @BuildStep
    @Record(value = ExecutionTime.STATIC_INIT)
    public FilterBuildItem addDefaultAuthFailureHandler(ResteasyReactiveRecorder recorder,
            ResteasyReactiveDeploymentBuildItem deployment,
            Optional<ObservabilityIntegrationBuildItem> observabilityIntegrationBuildItem) {
        // replace default auth failure handler added by vertx-http so that our exception mappers can customize response
        return new FilterBuildItem(
                recorder.defaultAuthFailureHandler(deployment.getDeployment(), observabilityIntegrationBuildItem.isPresent()),
                SecurityHandlerPriorities.AUTHENTICATION - 1);
    }

    private void checkForDuplicateEndpoint(ResteasyReactiveConfig config, Map<String, List<EndpointConfig>> allMethods) {
        String message = allMethods.values().stream()
                .map(this::getDuplicateEndpointMessage)
                .filter(Objects::nonNull)
                .collect(Collectors.joining());
        if (!message.isEmpty()) {
            if (config.failOnDuplicate()) {
                throw new DeploymentException(message);
            }
            log.warn(message);
        }
    }

    private void addResourceMethodByPath(Map<String, List<EndpointConfig>> allMethods, String path, ClassInfo info,
            ResourceMethod rm) {
        allMethods.computeIfAbsent(getEndpointClassifier(rm, path), key -> new ArrayList<>())
                .addAll(getEndpointConfigs(path, info, rm));
    }

    private String getEndpointClassifier(ResourceMethod resourceMethod, String path) {
        String fullPath = (path.equals("/") ? "" : path) + resourceMethod.getPath();
        return resourceMethod.getHttpMethod() + " " + normalizePathParamNames(fullPath);
    }

    /**
     * Replaces path parameter names with a fixed placeholder so that structurally equivalent

View on GitHub (pinned to e1c734241f)

Solutions

  1. Change the @Path of one of the conflicting methods so each endpoint is unique
  2. Change the HTTP verb of one method if the duplication was unintended
  3. Remove the dead/duplicate method if both implementations are identical
  4. If duplicates are acceptable, disable the strict behavior via the RESTEasy Reactive failOnDuplicate config option (deployment then only warns)
  5. Identify the offending classes from the endpointConfigs listed in the message and refactor path structure

Example fix

// before
@Path("/pets")
class A { @GET @Path("{id}") Pet get(long id) {...} }
class B { @GET @Path("/pets/{id}") Pet find(long id) {...} }
// after
class B { @GET @Path("/pets/details/{id}") Pet find(long id) {...} }
Defensive patterns

Strategy: validation

Validate before calling

// pre-build scan for duplicate method+path declarations
Map<String, List<String>> seen = new HashMap<>();
for (Class<?> resource : resourceClasses) {
    String basePath = resource.getAnnotation(jakarta.ws.rs.Path.class).value();
    for (Method m : resource.getDeclaredMethods()) {
        jakarta.ws.rs.Path p = m.getAnnotation(jakarta.ws.rs.Path.class);
        for (String verb : List.of("GET","POST","PUT","DELETE")) {
            try {
                if (m.isAnnotationPresent((Class) Class.forName("jakarta.ws.rs." + verb))) {
                    String key = verb + " " + basePath + (p == null ? "" : p.value());
                    seen.computeIfAbsent(key, k -> new ArrayList<>()).add(resource.getName() + "#" + m.getName());
                }
            } catch (ClassNotFoundException ignored) {}
        }
    }
}
seen.entrySet().stream().filter(e -> e.getValue().size() > 1)
    .forEach(e -> System.err.println("Duplicate endpoint: " + e.getKey() + " -> " + e.getValue()));

Prevention

When it happens

Trigger: Two resource methods (possibly in different classes) resolve to the same HTTP verb and request path, and the duplicate-endpoint failure config option (failOnDuplicate in the RESTEasy Reactive server config) is true. The check runs on the fully aggregated endpoint map during the build.

Common situations: Copy-pasting a resource method without changing the @Path value; merging modules where two services declare the same route; Locator/Path-interface combinations that accidentally collide; enabling the fail-on-duplicate flag for the first time and surfacing pre-existing collisions.

Related errors


AI-assisted analysis of quarkusio/quarkus@e1c734241f (2026-09-05). Data as JSON: /api/errors/b1c848aae37ba714. Report an issue: GitHub.