diff --git a/all/pom.xml b/all/pom.xml
index 8d9c5eb02da..10d54a39059 100644
--- a/all/pom.xml
+++ b/all/pom.xml
@@ -474,6 +474,14 @@
io.helidon.openapi
helidon-openapi
+
+ io.helidon.openapi
+ helidon-openapi-31
+
+
+ io.helidon.openapi
+ helidon-openapi-32
+
io.helidon.logging
helidon-logging-common
diff --git a/bom/pom.xml b/bom/pom.xml
index 98810318fff..7d66a887a2a 100644
--- a/bom/pom.xml
+++ b/bom/pom.xml
@@ -647,6 +647,16 @@
helidon-openapi
${helidon.version}
+
+ io.helidon.openapi
+ helidon-openapi-31
+ ${helidon.version}
+
+
+ io.helidon.openapi
+ helidon-openapi-32
+ ${helidon.version}
+
diff --git a/codegen/codegen/src/main/java/io/helidon/codegen/TypeHierarchy.java b/codegen/codegen/src/main/java/io/helidon/codegen/TypeHierarchy.java
index 8bb8b274429..501152689c5 100644
--- a/codegen/codegen/src/main/java/io/helidon/codegen/TypeHierarchy.java
+++ b/codegen/codegen/src/main/java/io/helidon/codegen/TypeHierarchy.java
@@ -24,6 +24,7 @@
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.stream.Collectors;
import io.helidon.common.Api;
@@ -177,6 +178,83 @@ public static List hierarchyAnnotations(CodegenContext ctx, TypeInfo
return List.copyOf(annotations.values());
}
+ /**
+ * Find all distinct effective occurrences of any of the annotation types on matching methods in a method's type
+ * hierarchy, grouped by the declaring method. Meta-annotations are included. Unlike
+ * {@link #hierarchyAnnotations(CodegenContext, TypeInfo, TypedElementInfo)}, this method does not apply
+ * annotation-type precedence between declarations.
+ * A candidate-bearing declaration excludes candidates on declarations it overrides. A declaration without matching
+ * candidates does not exclude candidates inherited from its ancestors. Candidates on unrelated hierarchy branches
+ * are retained. The provided method itself is excluded. The outer list contains distinct declaration multisets; each
+ * inner list preserves candidate order and multiplicity.
+ *
+ * @param ctx codegen context
+ * @param type type info owning the method
+ * @param method method element
+ * @param annotationTypes annotation types to find
+ * @return distinct annotation candidates grouped by declaring method
+ */
+ @Api.Internal
+ public static List> hierarchyAnnotationCandidates(CodegenContext ctx,
+ TypeInfo type,
+ TypedElementInfo method,
+ Set annotationTypes) {
+ Objects.requireNonNull(ctx, "ctx is null");
+ Objects.requireNonNull(type, "type is null");
+ Objects.requireNonNull(method, "method is null");
+ Set candidateTypes = Set.copyOf(Objects.requireNonNull(annotationTypes, "annotationTypes is null"));
+ if (method.kind() != ElementKind.METHOD) {
+ throw new CodegenException("Only method elements have hierarchy annotation candidates: " + method.kind());
+ }
+
+ List prototypes = new ArrayList<>();
+ BiConsumer collector = (declaringType, inheritedMethod) ->
+ prototypes.add(new HierarchyMethod(declaringType, inheritedMethod));
+ Set processedTypes = new HashSet<>();
+ String packageName = type.typeName().packageName();
+ type.superTypeInfo().ifPresent(it -> collectInheritedMethods(
+ processedTypes,
+ collector,
+ it,
+ method,
+ packageName));
+ type.interfaceTypeInfo().forEach(it -> collectInheritedMethods(
+ processedTypes,
+ collector,
+ it,
+ method,
+ packageName));
+
+ List annotationCandidates = new ArrayList<>();
+ for (HierarchyMethod prototype : prototypes) {
+ List candidates = new ArrayList<>();
+ prototype.method().annotations()
+ .forEach(it -> collectAnnotationCandidates(ctx,
+ candidateTypes,
+ candidates,
+ it,
+ new HashSet<>()));
+ if (!candidates.isEmpty()) {
+ annotationCandidates.add(new HierarchyAnnotationCandidate(prototype.declaringType(),
+ List.copyOf(candidates)));
+ }
+ }
+
+ List> result = new ArrayList<>();
+ Set