quarkusio/quarkus · error · RestClientDefinitionException

Ambiguous @HttpMethod definition on type ${typeDef}

Error message

Ambiguous @HttpMethod definition on type ${typeDef}

What it means

Thrown by QuarkusRestClientBuilder.verifyInterface (during build) as a RestClientDefinitionException when a resource interface method carries more than one HTTP method annotation (@GET, @POST, @PUT, @DELETE, etc.). Each interface method must declare exactly one HTTP verb.

Source

Thrown at extensions/resteasy-classic/resteasy-client/runtime/src/main/java/io/quarkus/restclient/runtime/QuarkusRestClientBuilder.java:590

                paramMap.put(name, "foobar");
            }

        }
    }

    private <T> void verifyInterface(Class<T> typeDef) {

        Method[] methods = resolveMethods(typeDef);

        // multiple verbs
        for (Method method : methods) {
            boolean hasHttpMethod = false;
            for (Annotation annotation : method.getAnnotations()) {
                boolean isHttpMethod = (annotation.annotationType().getAnnotation(HttpMethod.class) != null);
                if (!hasHttpMethod && isHttpMethod) {
                    hasHttpMethod = true;
                } else if (hasHttpMethod && isHttpMethod) {
                    throw new RestClientDefinitionException("Ambiguous @HttpMethod definition on type " + typeDef);
                }
            }
        }

        // invalid parameter
        Path classPathAnno = typeDef.getAnnotation(Path.class);

        ResteasyUriBuilder template = null;
        for (Method method : methods) {
            Path methodPathAnno = method.getAnnotation(Path.class);
            if (methodPathAnno != null) {
                template = classPathAnno == null
                        ? (ResteasyUriBuilder) new ResteasyUriBuilderImpl().path(methodPathAnno.value())
                        : (ResteasyUriBuilder) new ResteasyUriBuilderImpl()
                                .path(classPathAnno.value() + "/" + methodPathAnno.value());
            } else if (classPathAnno != null) {
                template = (ResteasyUriBuilder) new ResteasyUriBuilderImpl().path(classPathAnno.value());
            } else {

View on GitHub (pinned to e1c734241f)

Solutions

  1. Keep exactly one HTTP method annotation per interface method — remove the extra one
  2. If a custom verb is needed, ensure it doesn't stack with a standard annotation
  3. Verify with the MicroProfile spec: one @HttpMethod meta-annotated annotation per resource method

Example fix

// before
@GET
@POST
@Path("/items")
List<Item> getItems();
// after
@GET
@Path("/items")
List<Item> getItems();
Defensive patterns

Strategy: validation

Validate before calling

for (Method m : MyClient.class.getMethods()) {
    long count = Arrays.stream(m.getAnnotations())
        .filter(a -> a.annotationType().isAnnotationPresent(HttpMethod.class))
        .count();
    if (count > 1) throw new IllegalStateException(m + " has multiple HTTP method annotations");
}

Try / catch

try {
    client = builder.build(MyClient.class);
} catch (RestClientDefinitionException e) {
    if (e.getMessage().startsWith("Ambiguous @HttpMethod definition")) {
        log.error("Interface method has two HTTP verb annotations: {}", e.getMessage());
    }
    throw e;
}

Prevention

When it happens

Trigger: Annotating one client interface method with two HTTP annotations (e.g. @GET and @POST); meta-annotations that accidentally both carry @HttpMethod; copy-paste leaving an old verb annotation in place.

Common situations: Refactoring @GET to @POST and keeping both; combining custom verb annotations with standard ones on the same method; IDE auto-import adding a second annotation.

Related errors


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