This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 54635bc626 org.apache.juneau.common.reflect API improvements
54635bc626 is described below

commit 54635bc626c348ddf6287d8e32bfff0778170f64
Author: James Bognar <[email protected]>
AuthorDate: Wed Nov 19 16:44:11 2025 -0500

    org.apache.juneau.common.reflect API improvements
---
 .../juneau/common/reflect/AnnotationProvider.java  | 448 ++++++++++++++++++++-
 .../juneau/common/reflect/AnnotationTraversal.java |  21 +
 .../juneau/common/reflect/ExecutableInfo.java      |   3 +-
 .../apache/juneau/common/reflect/MethodInfo.java   |  18 -
 .../src/main/java/org/apache/juneau/Context.java   |   9 +-
 .../httppart/bean/RequestBeanPropertyMeta.java     |   5 +-
 .../juneau/httppart/bean/ResponseBeanMeta.java     |   5 +-
 .../rest/client/remote/RemoteOperationMeta.java    | 128 +-----
 .../rest/client/remote/RemoteOperationReturn.java  |   5 +-
 .../java/org/apache/juneau/rest/RestContext.java   |   6 +-
 .../java/org/apache/juneau/rest/RestOpContext.java |  58 +--
 .../juneau/rest/debug/BasicDebugEnablement.java    |   3 +-
 12 files changed, 539 insertions(+), 170 deletions(-)

diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
index 32c1766c43..928651b803 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationProvider.java
@@ -518,6 +518,101 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * Streams annotations from a class that belong to the specified 
annotation group.
+        *
+        * <p>
+        * This method is similar to {@link #find(Class, ClassInfo, 
AnnotationTraversal...)} but filters
+        * annotations based on their {@link 
org.apache.juneau.annotation.AnnotationGroup} membership rather
+        * than their exact type.
+        *
+        * <p>
+        * An annotation belongs to a group if it has an {@code 
@AnnotationGroup} meta-annotation with the
+        * specified group class. This allows you to find all annotations that 
are logically related.
+        *
+        * <h5 class='section'>Supported Traversal Types:</h5>
+        * <ul>
+        *      <li>{@link AnnotationTraversal#SELF SELF} - Annotations 
declared directly on this class
+        *      <li>{@link AnnotationTraversal#PARENTS PARENTS} - Parent 
classes and interfaces (child-to-parent order)
+        *      <li>{@link AnnotationTraversal#PACKAGE PACKAGE} - The package 
annotations
+        * </ul>
+        *
+        * <p>
+        * <b>Default:</b> If no traversals are specified, defaults to: {@code 
PARENTS, PACKAGE}
+        * <br>Note: {@code PARENTS} includes the class itself plus all parent 
classes and interfaces.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all bean-related annotations (that belong to the 
Bean group)</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              annotationProvider.findGroup(Bean.<jk>class</jk>, 
<jv>classInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param clazz The class to search.
+        * @param traversals
+        *      The traversal options. If not specified, defaults to {@code 
PARENTS, PACKAGE}.
+        *      <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link 
AnnotationTraversal#PARENTS PARENTS}, 
+        *      {@link AnnotationTraversal#PACKAGE PACKAGE}
+        * @return A stream of {@link AnnotationInfo} objects belonging to the 
specified group. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroup(Class<A> group, ClassInfo clazz, AnnotationTraversal... traversals) {
+               assertArgNotNull("group", group);
+               assertArgNotNull("clazz", clazz);
+               if (traversals.length == 0)
+                       traversals = a(PARENTS, PACKAGE);
+
+               return Arrays.stream(traversals)
+                       
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+                       .flatMap(traversal -> {
+                               if (traversal == SELF) {
+                                       return concat(
+                                               
classAnnnotations.get(clazz.inner()).stream(),
+                                               
clazz.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                       )
+                                       .filter(a -> a.isInGroup(group));
+                               } else if (traversal == PARENTS) {
+                                       return 
clazz.getParentsAndInterfaces().stream().flatMap(x ->
+                                               concat(
+                                                       
classAnnnotations.get(x.inner()).stream(),
+                                                       
x.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                               ).filter(a -> 
a.isInGroup(group))
+                                       );
+                               } else if (traversal == PACKAGE) {
+                                       return opt(clazz.getPackage()).map(x -> 
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a -> 
a.isInGroup(group))).orElse(Stream.empty());
+                               }
+                               throw illegalArg("Invalid traversal type for 
class annotations: {0}", traversal);
+                       });
+       }
+
+       /**
+        * Streams annotations from a class that belong to the specified 
annotation group in parent-first order.
+        *
+        * <p>
+        * This is equivalent to calling {@link #findGroup(Class, ClassInfo, 
AnnotationTraversal...)}
+        * and reversing the result.
+        *
+        * <p>
+        * Use this when you need parent annotations to take precedence over 
child annotations.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Get bean-group annotations in parent-first order</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              
annotationProvider.findGroupTopDown(Bean.<jk>class</jk>, <jv>classInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param clazz The class to search.
+        * @param traversals The traversal options.
+        * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroupTopDown(Class<A> group, ClassInfo clazz, AnnotationTraversal... 
traversals) {
+               return rstream(findGroup(group, clazz, traversals).toList());
+       }
+
        /**
         * Streams all annotations from a class using configurable traversal 
options, without filtering by annotation type.
         *
@@ -686,6 +781,8 @@ public class AnnotationProvider {
                                                        
m.getDeclaredAnnotations().stream()
                                                ).filter(a -> 
a.isType(type)).map(a -> (AnnotationInfo<A>)a)
                                        );
+                               } else if (traversal == DECLARING_CLASS) {
+                                       return find(type, 
method.getDeclaringClass(), PARENTS);
                                } else if (traversal == RETURN_TYPE) {
                                        return find(type, 
method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
                                } else if (traversal == PACKAGE) {
@@ -695,6 +792,106 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * Streams annotations from a method that belong to the specified 
annotation group.
+        *
+        * <p>
+        * This method is similar to {@link #find(Class, MethodInfo, 
AnnotationTraversal...)} but filters
+        * annotations based on their {@link 
org.apache.juneau.annotation.AnnotationGroup} membership rather
+        * than their exact type.
+        *
+        * <p>
+        * An annotation belongs to a group if it has an {@code 
@AnnotationGroup} meta-annotation with the
+        * specified group class. This allows you to find all annotations that 
are logically related.
+        *
+        * <h5 class='section'>Supported Traversal Types:</h5>
+        * <ul>
+        *      <li>{@link AnnotationTraversal#SELF SELF} - Annotations 
declared directly on this method
+        *      <li>{@link AnnotationTraversal#MATCHING_METHODS 
MATCHING_METHODS} - Matching methods in parent classes (child-to-parent)
+        *      <li>{@link AnnotationTraversal#DECLARING_CLASS DECLARING_CLASS} 
- The declaring class hierarchy
+        *      <li>{@link AnnotationTraversal#RETURN_TYPE RETURN_TYPE} - The 
return type hierarchy (includes class parents and package)
+        *      <li>{@link AnnotationTraversal#PACKAGE PACKAGE} - The declaring 
class's package annotations
+        * </ul>
+        *
+        * <p>
+        * <b>Default:</b> If no traversals are specified, defaults to: {@code 
SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE}
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all REST operation annotations (that belong to the 
RestOp group)</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              annotationProvider.findGroup(RestOp.<jk>class</jk>, 
<jv>methodInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param method The method to search.
+        * @param traversals
+        *      The traversal options. If not specified, defaults to {@code 
SELF, MATCHING_METHODS, RETURN_TYPE, PACKAGE}.
+        *      <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link 
AnnotationTraversal#MATCHING_METHODS MATCHING_METHODS}, 
+        *      {@link AnnotationTraversal#DECLARING_CLASS DECLARING_CLASS}, 
{@link AnnotationTraversal#RETURN_TYPE RETURN_TYPE}, 
+        *      {@link AnnotationTraversal#PACKAGE PACKAGE}
+        * @return A stream of {@link AnnotationInfo} objects belonging to the 
specified group. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroup(Class<A> group, MethodInfo method, AnnotationTraversal... traversals) 
{
+               assertArgNotNull("group", group);
+               assertArgNotNull("method", method);
+               if (traversals.length == 0)
+                       traversals = a(SELF, MATCHING_METHODS, RETURN_TYPE, 
PACKAGE);
+
+               return Arrays.stream(traversals)
+                       
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+                       .flatMap(traversal -> {
+                               if (traversal == SELF) {
+                                       return concat(
+                                               
methodAnnotations.get(method.inner()).stream(),
+                                               
method.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                       ).filter(a -> a.isInGroup(group));
+                               } else if (traversal == MATCHING_METHODS) {
+                                       return 
method.getMatchingMethods().stream().skip(1).flatMap(m ->
+                                               concat(
+                                                       
methodAnnotations.get(m.inner()).stream(),
+                                                       
m.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                               ).filter(a -> 
a.isInGroup(group))
+                                       );
+                               } else if (traversal == DECLARING_CLASS) {
+                                       return findGroup(group, 
method.getDeclaringClass(), PARENTS);
+                               } else if (traversal == RETURN_TYPE) {
+                                       return findGroup(group, 
method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
+                               } else if (traversal == PACKAGE) {
+                                       return 
opt(method.getDeclaringClass().getPackage()).map(x -> 
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a -> 
a.isInGroup(group))).orElse(Stream.empty());
+                               }
+                               throw illegalArg("Invalid traversal type for 
method annotations: {0}", traversal);
+                       });
+       }
+
+       /**
+        * Streams annotations from a method that belong to the specified 
annotation group in parent-first order.
+        *
+        * <p>
+        * This is equivalent to calling {@link #findGroup(Class, MethodInfo, 
AnnotationTraversal...)}
+        * and reversing the result.
+        *
+        * <p>
+        * Use this when you need parent annotations to take precedence over 
child annotations.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Get REST operation annotations in parent-first order</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              
annotationProvider.findGroupTopDown(RestOp.<jk>class</jk>, <jv>methodInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param method The method to search.
+        * @param traversals The traversal options.
+        * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroupTopDown(Class<A> group, MethodInfo method, AnnotationTraversal... 
traversals) {
+               return rstream(findGroup(group, method, traversals).toList());
+       }
+
        /**
         * Streams all annotations from a method using configurable traversal 
options, without filtering by annotation type.
         *
@@ -720,7 +917,7 @@ public class AnnotationProvider {
        public Stream<AnnotationInfo<Annotation>> find(MethodInfo method, 
AnnotationTraversal... traversals) {
                assertArgNotNull("method", method);
                if (traversals.length == 0)
-                       traversals = a(SELF, MATCHING_METHODS, RETURN_TYPE, 
PACKAGE);
+                       traversals = a(SELF, MATCHING_METHODS, DECLARING_CLASS, 
RETURN_TYPE, PACKAGE);
 
                return Arrays.stream(traversals)
                        
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
@@ -737,6 +934,8 @@ public class AnnotationProvider {
                                                        
m.getDeclaredAnnotations().stream()
                                                );
                                        });
+                               } else if (traversal == DECLARING_CLASS) {
+                                       return find(method.getDeclaringClass(), 
PARENTS);
                                } else if (traversal == RETURN_TYPE) {
                                        return 
find(method.getReturnType().unwrap(Value.class, Optional.class), PARENTS);
                                } else if (traversal == PACKAGE) {
@@ -878,6 +1077,91 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * Streams annotations from a parameter that belong to the specified 
annotation group.
+        *
+        * <p>
+        * This method is similar to {@link #find(Class, ParameterInfo, 
AnnotationTraversal...)} but filters
+        * annotations based on their {@link 
org.apache.juneau.annotation.AnnotationGroup} membership rather
+        * than their exact type.
+        *
+        * <p>
+        * An annotation belongs to a group if it has an {@code 
@AnnotationGroup} meta-annotation with the
+        * specified group class. This allows you to find all annotations that 
are logically related.
+        *
+        * <h5 class='section'>Supported Traversal Types:</h5>
+        * <ul>
+        *      <li>{@link AnnotationTraversal#SELF SELF} - Annotations 
declared directly on this parameter
+        *      <li>{@link AnnotationTraversal#MATCHING_PARAMETERS 
MATCHING_PARAMETERS} - Matching parameters in parent methods/constructors 
(child-to-parent)
+        *      <li>{@link AnnotationTraversal#PARAMETER_TYPE PARAMETER_TYPE} - 
The parameter's type hierarchy (includes class parents and package)
+        * </ul>
+        *
+        * <p>
+        * <b>Default:</b> If no traversals are specified, defaults to: {@code 
SELF, MATCHING_PARAMETERS, PARAMETER_TYPE}
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all REST parameter annotations (that belong to a 
group)</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              annotationProvider.findGroup(Query.<jk>class</jk>, 
<jv>parameterInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param parameter The parameter to search.
+        * @param traversals
+        *      The traversal options. If not specified, defaults to {@code 
SELF, MATCHING_PARAMETERS, PARAMETER_TYPE}.
+        *      <br>Valid values: {@link AnnotationTraversal#SELF SELF}, {@link 
AnnotationTraversal#MATCHING_PARAMETERS MATCHING_PARAMETERS}, 
+        *      {@link AnnotationTraversal#PARAMETER_TYPE PARAMETER_TYPE}
+        * @return A stream of {@link AnnotationInfo} objects belonging to the 
specified group. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroup(Class<A> group, ParameterInfo parameter, AnnotationTraversal... 
traversals) {
+               assertArgNotNull("group", group);
+               assertArgNotNull("parameter", parameter);
+               if (traversals.length == 0)
+                       traversals = a(SELF, MATCHING_PARAMETERS, 
PARAMETER_TYPE);
+
+               return Arrays.stream(traversals)
+                       
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+                       .flatMap(traversal -> {
+                               if (traversal == SELF) {
+                                       return 
parameter.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a -> 
a.isInGroup(group));
+                               } else if (traversal == MATCHING_PARAMETERS) {
+                                       return 
parameter.getMatchingParameters().stream().skip(1).flatMap(x -> 
x.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a).filter(a -> 
a.isInGroup(group)));
+                               } else if (traversal == PARAMETER_TYPE) {
+                                       return findGroup(group, 
parameter.getParameterType().unwrap(Value.class, Optional.class), PARENTS, 
PACKAGE);
+                               }
+                               throw illegalArg("Invalid traversal type for 
parameter annotations: {0}", traversal);
+                       });
+       }
+
+       /**
+        * Streams annotations from a parameter that belong to the specified 
annotation group in parent-first order.
+        *
+        * <p>
+        * This is equivalent to calling {@link #findGroup(Class, 
ParameterInfo, AnnotationTraversal...)}
+        * and reversing the result.
+        *
+        * <p>
+        * Use this when you need parent annotations to take precedence over 
child annotations.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Get parameter annotations in parent-first order</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              
annotationProvider.findGroupTopDown(Query.<jk>class</jk>, 
<jv>parameterInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param parameter The parameter to search.
+        * @param traversals The traversal options.
+        * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroupTopDown(Class<A> group, ParameterInfo parameter, 
AnnotationTraversal... traversals) {
+               return rstream(findGroup(group, parameter, 
traversals).toList());
+       }
+
        /**
         * Streams annotations from a parameter using configurable traversal 
options in parent-to-child order.
         *
@@ -1066,6 +1350,87 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * Streams annotations from a field that belong to the specified 
annotation group.
+        *
+        * <p>
+        * This method is similar to {@link #find(Class, FieldInfo, 
AnnotationTraversal...)} but filters
+        * annotations based on their {@link 
org.apache.juneau.annotation.AnnotationGroup} membership rather
+        * than their exact type.
+        *
+        * <p>
+        * An annotation belongs to a group if it has an {@code 
@AnnotationGroup} meta-annotation with the
+        * specified group class. This allows you to find all annotations that 
are logically related.
+        *
+        * <h5 class='section'>Supported Traversal Types:</h5>
+        * <ul>
+        *      <li>{@link AnnotationTraversal#SELF SELF} - Annotations 
declared directly on this field
+        * </ul>
+        *
+        * <p>
+        * <b>Default:</b> If no traversals are specified, defaults to: {@code 
SELF}
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all bean property annotations (that belong to the 
Beanp group)</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              annotationProvider.findGroup(Beanp.<jk>class</jk>, 
<jv>fieldInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param field The field to search.
+        * @param traversals
+        *      The traversal options. If not specified, defaults to {@code 
SELF}.
+        *      <br>Valid values: {@link AnnotationTraversal#SELF SELF}
+        * @return A stream of {@link AnnotationInfo} objects belonging to the 
specified group. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroup(Class<A> group, FieldInfo field, AnnotationTraversal... traversals) {
+               assertArgNotNull("group", group);
+               assertArgNotNull("field", field);
+               if (traversals.length == 0)
+                       traversals = a(SELF);
+
+               return Arrays.stream(traversals)
+                       
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+                       .flatMap(traversal -> {
+                               if (traversal == SELF) {
+                                       return concat(
+                                               
fieldAnnotations.get(field.inner()).stream(),
+                                               
field.getAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                       ).filter(a -> a.isInGroup(group));
+                               }
+                               throw illegalArg("Invalid traversal type for 
field annotations: {0}", traversal);
+                       });
+       }
+
+       /**
+        * Streams annotations from a field that belong to the specified 
annotation group in parent-first order.
+        *
+        * <p>
+        * This is equivalent to calling {@link #findGroup(Class, FieldInfo, 
AnnotationTraversal...)}
+        * and reversing the result.
+        *
+        * <p>
+        * Use this when you need parent annotations to take precedence over 
child annotations.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Get bean property annotations in parent-first order</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              
annotationProvider.findGroupTopDown(Beanp.<jk>class</jk>, <jv>fieldInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param field The field to search.
+        * @param traversals The traversal options.
+        * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroupTopDown(Class<A> group, FieldInfo field, AnnotationTraversal... 
traversals) {
+               return rstream(findGroup(group, field, traversals).toList());
+       }
+
        /**
         * Streams all annotations from a field using configurable traversal 
options, without filtering by annotation type.
         *
@@ -1208,6 +1573,87 @@ public class AnnotationProvider {
                        });
        }
 
+       /**
+        * Streams annotations from a constructor that belong to the specified 
annotation group.
+        *
+        * <p>
+        * This method is similar to {@link #find(Class, ConstructorInfo, 
AnnotationTraversal...)} but filters
+        * annotations based on their {@link 
org.apache.juneau.annotation.AnnotationGroup} membership rather
+        * than their exact type.
+        *
+        * <p>
+        * An annotation belongs to a group if it has an {@code 
@AnnotationGroup} meta-annotation with the
+        * specified group class. This allows you to find all annotations that 
are logically related.
+        *
+        * <h5 class='section'>Supported Traversal Types:</h5>
+        * <ul>
+        *      <li>{@link AnnotationTraversal#SELF SELF} - Annotations 
declared directly on this constructor
+        * </ul>
+        *
+        * <p>
+        * <b>Default:</b> If no traversals are specified, defaults to: {@code 
SELF}
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Find all bean-related annotations on constructor</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              annotationProvider.findGroup(Bean.<jk>class</jk>, 
<jv>constructorInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param constructor The constructor to search.
+        * @param traversals
+        *      The traversal options. If not specified, defaults to {@code 
SELF}.
+        *      <br>Valid values: {@link AnnotationTraversal#SELF SELF}
+        * @return A stream of {@link AnnotationInfo} objects belonging to the 
specified group. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroup(Class<A> group, ConstructorInfo constructor, AnnotationTraversal... 
traversals) {
+               assertArgNotNull("group", group);
+               assertArgNotNull("constructor", constructor);
+               if (traversals.length == 0)
+                       traversals = a(SELF);
+
+               return Arrays.stream(traversals)
+                       
.sorted(Comparator.comparingInt(AnnotationTraversal::getOrder))
+                       .flatMap(traversal -> {
+                               if (traversal == SELF) {
+                                       return concat(
+                                               
constructorAnnotations.get(constructor.inner()).stream(),
+                                               
constructor.getDeclaredAnnotations().stream().map(a -> (AnnotationInfo<?>)a)
+                                       ).filter(a -> a.isInGroup(group));
+                               }
+                               throw illegalArg("Invalid traversal type for 
constructor annotations: {0}", traversal);
+                       });
+       }
+
+       /**
+        * Streams annotations from a constructor that belong to the specified 
annotation group in parent-first order.
+        *
+        * <p>
+        * This is equivalent to calling {@link #findGroup(Class, 
ConstructorInfo, AnnotationTraversal...)}
+        * and reversing the result.
+        *
+        * <p>
+        * Use this when you need parent annotations to take precedence over 
child annotations.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Get bean-related annotations in parent-first order</jc>
+        *      Stream&lt;AnnotationInfo&lt;?&gt;&gt; <jv>annotations</jv> = 
+        *              
annotationProvider.findGroupTopDown(Bean.<jk>class</jk>, 
<jv>constructorInfo</jv>);
+        * </p>
+        *
+        * @param <A> The annotation group type.
+        * @param group The annotation group class to filter by.
+        * @param constructor The constructor to search.
+        * @param traversals The traversal options.
+        * @return A stream of {@link AnnotationInfo} objects in parent-first 
order. Never <jk>null</jk>.
+        */
+       public <A extends Annotation> Stream<AnnotationInfo<?>> 
findGroupTopDown(Class<A> group, ConstructorInfo constructor, 
AnnotationTraversal... traversals) {
+               return rstream(findGroup(group, constructor, 
traversals).toList());
+       }
+
        /**
         * Streams all annotations from a constructor using configurable 
traversal options, without filtering by annotation type.
         *
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
index a9a9afb4ab..85c18eb4e1 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/AnnotationTraversal.java
@@ -159,6 +159,27 @@ public enum AnnotationTraversal {
         */
        RETURN_TYPE(30),
 
+       /**
+        * Include the declaring class hierarchy in the traversal.
+        *
+        * <p>
+        * For methods, fields, and constructors: Searches annotations on the 
declaring class and its parent hierarchy.
+        * Automatically includes the declaring class and all its parents and 
interfaces.
+        *
+        * <h5 class='section'>Applicable to:</h5>
+        * Methods, fields, and constructors.
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jc>// Given: class Child extends Parent { void method() {...} 
}</jc>
+        *      <jc>// Searches: Child hierarchy (Child, Parent, interfaces, 
etc.)</jc>
+        * </p>
+        *
+        * <h5 class='section'>Order:</h5>
+        * Precedence: 35
+        */
+       DECLARING_CLASS(35),
+
        /**
         * Include the parameter type in the traversal.
         *
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
index 0640d58312..f8c03b3e8a 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/ExecutableInfo.java
@@ -19,6 +19,7 @@ package org.apache.juneau.common.reflect;
 import static org.apache.juneau.common.reflect.ClassArrayFormat.*;
 import static org.apache.juneau.common.reflect.ClassNameFormat.*;
 import static org.apache.juneau.common.utils.AssertionUtils.*;
+import static org.apache.juneau.common.utils.ClassUtils.*;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.Utils.*;
@@ -64,7 +65,7 @@ public abstract class ExecutableInfo extends AccessibleInfo {
                this.isConstructor = inner instanceof Constructor;
                this.parameters = memoize(this::findParameters);
                this.exceptions = memoize(() -> 
stream(inner.getExceptionTypes()).map(ClassInfo::of).toList());
-               this.declaredAnnotations = memoize(() -> 
stream(inner.getDeclaredAnnotations()).map(a -> 
AnnotationInfo.of((Annotatable)this, a)).toList());
+               this.declaredAnnotations = memoize(() -> 
stream(inner.getDeclaredAnnotations()).flatMap(a -> streamRepeated(a)).map(a -> 
AnnotationInfo.of((Annotatable)this, a)).toList());
                this.shortName = memoize(() -> f("{0}({1})", getSimpleName(), 
getParameters().stream().map(p -> 
p.getParameterType().getNameSimple()).collect(joining(","))));
                this.fullName = memoize(this::findFullName);
        }
diff --git 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
index 5bd6ff5a62..cd6cff422a 100644
--- 
a/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
+++ 
b/juneau-core/juneau-common/src/main/java/org/apache/juneau/common/reflect/MethodInfo.java
@@ -223,24 +223,6 @@ public class MethodInfo extends ExecutableInfo implements 
Comparable<MethodInfo>
                return allAnnotationInfos.get();
        }
 
-       /**
-        * Returns all annotations of the specified type on the declaring 
class, this method and parent overridden methods, return type, and declaring 
class's package.
-        *
-        * <p>
-        *      See {@link #getAllAnnotations()} for ordering details.
-        *
-        * @param <A> The annotation type.
-        * @param type The annotation type to filter by.
-        * @return A stream of matching annotation infos.
-        */
-       @SuppressWarnings("unchecked")
-       public <A extends Annotation> Stream<AnnotationInfo<A>> 
getAllAnnotations(Class<A> type) {
-               assertArgNotNull("type", type);
-               return getAllAnnotations().stream()
-                       .filter(a -> a.isType(type))
-                       .map(a -> (AnnotationInfo<A>)a);
-       }
-
        private List<MethodInfo> findMatchingMethods() {
                var result = new ArrayList<MethodInfo>();
                result.add(this); // 1. This method
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
index 2d29052855..c8f4992485 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Context.java
@@ -669,15 +669,16 @@ public abstract class Context {
                }
 
                private static AnnotationWorkList traverse(AnnotationWorkList 
work, Object x) {
+                       AnnotationProvider ap = AnnotationProvider.INSTANCE;
                        CollectionUtils.traverse(x, y -> {
                                if (x instanceof Class<?> x2)
-                                       
work.add(rstream(ClassInfo.of(x2).getAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai
 -> (AnnotationInfo<?>)ai));
+                                       
work.add(ap.findTopDown(ClassInfo.of(x2)).filter(CONTEXT_APPLY_FILTER).map(ai 
-> (AnnotationInfo<?>)ai));
                                else if (x instanceof ClassInfo x2)
-                                       
work.add(rstream(x2.getAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai -> 
(AnnotationInfo<?>)ai));
+                                       
work.add(ap.findTopDown(x2).filter(CONTEXT_APPLY_FILTER).map(ai -> 
(AnnotationInfo<?>)ai));
                                else if (x instanceof Method x2)
-                                       
work.add(rstream(MethodInfo.of(x2).getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai
 -> (AnnotationInfo<?>)ai));
+                                       
work.add(ap.findTopDown(MethodInfo.of(x2)).filter(CONTEXT_APPLY_FILTER).map(ai 
-> (AnnotationInfo<?>)ai));
                                else if (x instanceof MethodInfo x2)
-                                       
work.add(rstream(x2.getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai -> 
(AnnotationInfo<?>)ai));
+                                       
work.add(ap.findTopDown(x2).filter(CONTEXT_APPLY_FILTER).map(ai -> 
(AnnotationInfo<?>)ai));
                                else
                                        illegalArg("Invalid type passed to 
applyAnnotations:  {0}", cn(x));
                        });
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
index f6749143ca..8b3bcf1fd0 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/RequestBeanPropertyMeta.java
@@ -65,8 +65,9 @@ public class RequestBeanPropertyMeta {
 
        static RequestBeanPropertyMeta.Builder create(HttpPartType partType, 
Class<? extends Annotation> c, MethodInfo m) {
                HttpPartSchema.Builder sb = 
HttpPartSchema.create().name(m.getPropertyName());
-               rstream(m.getAllAnnotations()).map(x -> 
x.cast(Schema.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> sb.apply(x));
-               rstream(m.getAllAnnotations()).map(x -> 
x.cast(c)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x -> 
sb.apply(x));
+               AnnotationProvider ap = AnnotationProvider.INSTANCE;
+               ap.findTopDown(m).map(x -> 
x.cast(Schema.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> sb.apply(x));
+               ap.findTopDown(m).map(x -> 
x.cast(c)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x -> 
sb.apply(x));
                return new 
Builder().partType(partType).schema(sb.build()).getter(m.inner());
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
index 8a4e54f6c8..f0757bfee8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/bean/ResponseBeanMeta.java
@@ -127,8 +127,9 @@ public class ResponseBeanMeta {
                        return null;
                var b = new Builder(annotations);
                b.apply(m.getReturnType().unwrap(Value.class, 
Optional.class).innerType());
-               rstream(m.getAllAnnotations()).map(x -> 
x.cast(Response.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> b.apply(x));
-               rstream(m.getAllAnnotations()).map(x -> 
x.cast(StatusCode.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> b.apply(x));
+               AnnotationProvider ap = AnnotationProvider.INSTANCE;
+               ap.findTopDown(m).map(x -> 
x.cast(Response.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> b.apply(x));
+               ap.findTopDown(m).map(x -> 
x.cast(StatusCode.class)).filter(Objects::nonNull).map(AnnotationInfo::inner).forEach(x
 -> b.apply(x));
                return b.build();
        }
 
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
index 9808de8688..430fef4ba8 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationMeta.java
@@ -63,8 +63,9 @@ public class RemoteOperationMeta {
                Builder(String parentPath, Method m, String defaultMethod) {
 
                        var mi = MethodInfo.of(m);
+                       AnnotationProvider ap = AnnotationProvider.INSTANCE;
 
-                       List<AnnotationInfo<?>> al = 
rstream(mi.getAllAnnotations()).filter(REMOTE_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+                       List<AnnotationInfo<?>> al = 
ap.findTopDown(mi).filter(REMOTE_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
                        if (al.isEmpty())
                                al = 
rstream(mi.getReturnType().unwrap(Value.class, 
Optional.class).getAnnotations()).filter(REMOTE_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
 
@@ -145,138 +146,49 @@ public class RemoteOperationMeta {
                // These handle both individual annotations and repeated 
annotation arrays
 
                private void processContentDefaults(MethodInfo mi) {
-                       rstream(mi.getAllAnnotations())
+                       AnnotationProvider.INSTANCE.find(mi)
                                .map(x -> x.cast(Content.class))
                                .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(c -> {
-                                       String def = c.def();
-                                       if (isNotEmpty(def)) {
-                                               contentDefault = def;
-                                       }
-                               });
-                       rstream(mi.getAllAnnotations())
-                               .map(x -> x.cast(ContentAnnotation.Array.class))
-                               .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(x -> {
-                                       for (var c : x.value()) {
-                                               String def = c.def();
-                                               if (isNotEmpty(def)) {
-                                                       contentDefault = def;
-                                               }
-                                       }
-                               });
+                               .map(x -> x.inner().def())
+                               .filter(x -> isNotBlank(x))
+                               .findFirst()
+                               .ifPresent(x -> contentDefault = x);
                }
 
                private static void processFormDataDefaults(MethodInfo mi, 
Map<String,String> defaults) {
-                       rstream(mi.getAllAnnotations())
+                       AnnotationProvider.INSTANCE.findTopDown(mi)
                                .map(x -> x.cast(FormData.class))
                                .filter(Objects::nonNull)
                                .map(AnnotationInfo::inner)
-                               .forEach(fd -> {
-                                       String name = firstNonEmpty(fd.name(), 
fd.value());
-                                       String def = fd.def();
-                                       if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                               defaults.put(name, def);
-                                       }
-                               });
-                       rstream(mi.getAllAnnotations())
-                               .map(x -> 
x.cast(FormDataAnnotation.Array.class))
-                               .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(x -> {
-                                       for (var fd : x.value()) {
-                                               String name = 
firstNonEmpty(fd.name(), fd.value());
-                                               String def = fd.def();
-                                               if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                                       defaults.put(name, def);
-                                               }
-                                       }
-                               });
+                               .filter(x -> isAnyNotEmpty(x.name(), x.value()) 
&& isNotEmpty(x.def()))
+                               .forEach(x -> 
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
                }
 
                private static void processHeaderDefaults(MethodInfo mi, 
Map<String,String> defaults) {
-                       // Check for individual @Header annotations
-                       rstream(mi.getAllAnnotations())
+                       AnnotationProvider.INSTANCE.findTopDown(mi)
                                .map(x -> x.cast(Header.class))
                                .filter(Objects::nonNull)
                                .map(AnnotationInfo::inner)
-                               .forEach(h -> {
-                                       String name = firstNonEmpty(h.name(), 
h.value());
-                                       String def = h.def();
-                                       if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                               defaults.put(name, def);
-                                       }
-                               });
-                       // Check for @Header.Array (repeated annotations)
-                       rstream(mi.getAllAnnotations())
-                               .map(x -> x.cast(HeaderAnnotation.Array.class))
-                               .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(x -> {
-                                       for (var h : x.value()) {
-                                               String name = 
firstNonEmpty(h.name(), h.value());
-                                               String def = h.def();
-                                               if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                                       defaults.put(name, def);
-                                               }
-                                       }
-                               });
+                               .filter(x -> isAnyNotEmpty(x.name(), x.value()) 
&& isNotEmpty(x.def()))
+                               .forEach(x -> 
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
                }
-       
+
                private static void processPathDefaults(MethodInfo mi, 
Map<String,String> defaults) {
-                       rstream(mi.getAllAnnotations())
+                       AnnotationProvider.INSTANCE.findTopDown(mi)
                                .map(x -> x.cast(Path.class))
                                .filter(Objects::nonNull)
                                .map(AnnotationInfo::inner)
-                               .forEach(p -> {
-                                       String name = firstNonEmpty(p.name(), 
p.value());
-                                       String def = p.def();
-                                       if (isNotEmpty(name) && ne(NONE, def)) {
-                                               defaults.put(name, def);
-                                       }
-                               });
-                       rstream(mi.getAllAnnotations())
-                               .map(x -> x.cast(PathAnnotation.Array.class))
-                               .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(x -> {
-                                       for (var p : x.value()) {
-                                               String name = 
firstNonEmpty(p.name(), p.value());
-                                               String def = p.def();
-                                               if (isNotEmpty(name) && 
ne(NONE, def)) {
-                                                       defaults.put(name, def);
-                                               }
-                                       }
-                               });
+                               .filter(x -> isAnyNotEmpty(x.name(), x.value()) 
&& ne(NONE, x.def()))
+                               .forEach(x -> 
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
                }
 
                private static void processQueryDefaults(MethodInfo mi, 
Map<String,String> defaults) {
-                       rstream(mi.getAllAnnotations())
+                       AnnotationProvider.INSTANCE.findTopDown(mi)
                                .map(x -> x.cast(Query.class))
                                .filter(Objects::nonNull)
                                .map(AnnotationInfo::inner)
-                               .forEach(q -> {
-                                       String name = firstNonEmpty(q.name(), 
q.value());
-                                       String def = q.def();
-                                       if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                               defaults.put(name, def);
-                                       }
-                               });
-                       rstream(mi.getAllAnnotations())
-                               .map(x -> x.cast(QueryAnnotation.Array.class))
-                               .filter(Objects::nonNull)
-                               .map(AnnotationInfo::inner)
-                               .forEach(x -> {
-                                       for (var q : x.value()) {
-                                               String name = 
firstNonEmpty(q.name(), q.value());
-                                               String def = q.def();
-                                               if (isNotEmpty(name) && 
isNotEmpty(def)) {
-                                                       defaults.put(name, def);
-                                               }
-                                       }
-                               });
+                               .filter(x -> isAnyNotEmpty(x.name(), x.value()) 
&& isNotEmpty(x.def()))
+                               .forEach(x -> 
defaults.put(firstNonEmpty(x.name(), x.value()), x.def()));
                }
        }
 
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
index 05149ec5e2..4525f54135 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/remote/RemoteOperationReturn.java
@@ -49,9 +49,8 @@ public class RemoteOperationReturn {
        RemoteOperationReturn(MethodInfo m) {
                ClassInfo rt = m.getReturnType();
 
-               List<AnnotationInfo<?>> al = 
rstream(m.getAllAnnotations()).filter(REMOTE_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
-               if (al.isEmpty())
-                       al = rstream(m.getReturnType().unwrap(Value.class, 
Optional.class).getAnnotations()).filter(REMOTE_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+               var ap = AnnotationProvider.INSTANCE;
+               var al = ap.findTopDown(m).filter(REMOTE_OP_GROUP).toList();
 
                RemoteReturn rv = null;
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index bdce59ca4d..90cef99500 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -201,11 +201,12 @@ public class RestContext extends Context {
                private static <T extends Annotation> MethodList 
getAnnotatedMethods(Supplier<?> resource, Class<T> annotation, Predicate<T> 
predicate) {
                        Map<String,Method> x = map();
                        var r = resource.get();
+                       AnnotationProvider ap = AnnotationProvider.INSTANCE;
 
                        // @formatter:off
                        ClassInfo.ofProxy(r).getAllMethodsTopDown().stream()
                                .filter(y -> y.hasAnnotation(annotation))
-                               .forEach(y -> 
rstream(y.getAllAnnotations()).map(ai -> 
ai.cast(annotation)).filter(Objects::nonNull).map(AnnotationInfo::inner)
+                               .forEach(y -> ap.findTopDown(y).map(ai -> 
ai.cast(annotation)).filter(Objects::nonNull).map(AnnotationInfo::inner)
                                        .filter(z -> predicate == null || 
predicate.test(z))
                                        .forEach(z -> x.put(y.getSignature(), 
y.accessible().inner())));
                        // @formatter:on
@@ -4566,6 +4567,7 @@ public class RestContext extends Context {
 
                        // Default value.
                        Value<RestOperations.Builder> v = 
Value.of(RestOperations.create(beanStore));
+                       var ap = 
restContext.getBeanContext().getAnnotationProvider();
 
                        var rci = ClassInfo.of(resource.get());
 
@@ -4581,7 +4583,7 @@ public class RestContext extends Context {
                        // @formatter:on
 
                        for (var mi : rci.getPublicMethods()) {
-                               List<AnnotationInfo<?>> al = 
rstream(mi.getAllAnnotations()).filter(REST_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
+                               List<AnnotationInfo<?>> al = 
ap.findTopDown(mi).filter(REST_OP_GROUP).map(ai -> 
(AnnotationInfo<?>)ai).collect(Collectors.toList());
 
                                // Also include methods on @Rest-annotated 
interfaces.
                                if (al.isEmpty()) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 65e8d96592..13ac3d41b5 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -17,6 +17,7 @@
 package org.apache.juneau.rest;
 
 import static org.apache.juneau.collections.JsonMap.*;
+import static org.apache.juneau.common.reflect.AnnotationTraversal.*;
 import static org.apache.juneau.common.utils.CollectionUtils.*;
 import static org.apache.juneau.common.utils.StringUtils.*;
 import static org.apache.juneau.common.utils.StringUtils.compare;
@@ -126,6 +127,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                        this.restMethod = method;
 
                        this.beanStore = BeanStore.of(context.getBeanStore(), 
context.builder.resource().get()).addBean(java.lang.reflect.Method.class, 
method);
+                       var ap = 
context.getBeanContext().getAnnotationProvider();
 
                        var mi = MethodInfo.of(context.getResourceClass(), 
method);
 
@@ -133,7 +135,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                                var vr = context.getVarResolver();
                                var vrs = vr.createSession();
 
-                               var work = AnnotationWorkList.of(vrs, 
rstream(mi.getAllAnnotations()).filter(CONTEXT_APPLY_FILTER).map(ai -> 
(AnnotationInfo<?>)ai));
+                               var work = AnnotationWorkList.of(vrs, 
ap.findTopDown(mi, SELF, MATCHING_METHODS, DECLARING_CLASS, RETURN_TYPE, 
PACKAGE).filter(CONTEXT_APPLY_FILTER).map(ai -> (AnnotationInfo<?>)ai));
 
                                apply(work);
 
@@ -1921,37 +1923,37 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                 *
                 * @return The path matchers for this method.
                 */
-       protected UrlPathMatcherList getPathMatchers() {
+               protected UrlPathMatcherList getPathMatchers() {
 
-               var v = Value.of(UrlPathMatcherList.create());
+                       var v = Value.of(UrlPathMatcherList.create());
 
-               if (nn(path)) {
-                       for (var p : path) {
-                                       if (dotAll && ! p.endsWith("/*"))
-                                               p += "/*";
-                                       v.get().add(UrlPathMatcher.of(p));
+                       if (nn(path)) {
+                               for (var p : path) {
+                                               if (dotAll && ! 
p.endsWith("/*"))
+                                                       p += "/*";
+                                               
v.get().add(UrlPathMatcher.of(p));
+                                       }
                                }
-                       }
 
-               if (v.get().isEmpty()) {
-                       var mi = MethodInfo.of(restMethod);
-                       String p = null;
-                       String httpMethod = null;
-                       if (mi.hasAnnotation(RestGet.class))
-                               httpMethod = "get";
-                       else if (mi.hasAnnotation(RestPut.class))
-                               httpMethod = "put";
-                       else if (mi.hasAnnotation(RestPost.class))
-                               httpMethod = "post";
-                       else if (mi.hasAnnotation(RestDelete.class))
-                               httpMethod = "delete";
-                       else if (mi.hasAnnotation(RestOp.class)) {
-                               var _httpMethod = Value.<String>empty();
-                               rstream(mi.getAllAnnotations()).map(x -> 
x.cast(RestOp.class)).filter(Objects::nonNull).map(AnnotationInfo::inner)
-                                       .filter(x -> isNotEmpty(x.method()))
-                                       .forEach(x -> 
_httpMethod.set(x.method()));
-                               httpMethod = _httpMethod.orElse(null);
-                       }
+                       if (v.get().isEmpty()) {
+                               var mi = MethodInfo.of(restMethod);
+                               String p = null;
+                               String httpMethod = null;
+                               if (mi.hasAnnotation(RestGet.class))
+                                       httpMethod = "get";
+                               else if (mi.hasAnnotation(RestPut.class))
+                                       httpMethod = "put";
+                               else if (mi.hasAnnotation(RestPost.class))
+                                       httpMethod = "post";
+                               else if (mi.hasAnnotation(RestDelete.class))
+                                       httpMethod = "delete";
+                               else if (mi.hasAnnotation(RestOp.class)) {
+                                       httpMethod = 
AnnotationProvider.INSTANCE.find(RestOp.class, mi)
+                                               .map(x -> x.inner().method())
+                                               .filter(x -> isNotEmpty(x))
+                                               .findFirst()
+                                               .orElse(null);
+                               }
 
                                p = HttpUtils.detectHttpPath(restMethod, 
httpMethod);
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
index eac63cc40e..5e9e5b8fc3 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/BasicDebugEnablement.java
@@ -64,6 +64,7 @@ public class BasicDebugEnablement extends DebugEnablement {
                RestContext.Builder builder = 
beanStore.getBean(RestContext.Builder.class).get();
                ResourceSupplier resource = 
beanStore.getBean(ResourceSupplier.class).get();
                VarResolver varResolver = 
beanStore.getBean(VarResolver.class).get();
+               var ap = AnnotationProvider.INSTANCE;
 
                // Default debug enablement if not overridden at class/method 
level.
                Enablement debugDefault = defaultSettings.get(Enablement.class, 
"RestContext.debugDefault").orElse(builder.isDebug() ? Enablement.ALWAYS : 
Enablement.NEVER);
@@ -83,7 +84,7 @@ public class BasicDebugEnablement extends DebugEnablement {
                // Gather @RestOp(debug) settings.
                // @formatter:off
                ci.getPublicMethods().stream()
-                       .forEach(x -> 
+                       .forEach(x ->
                                rstream(x.getAllAnnotations())
                                        .filter(REST_OP_GROUP)
                                        .flatMap(ai -> 
ai.getValue(String.class, "debug").stream())


Reply via email to