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 54f170ef8c feat(rest): hard-break debug model to typed @Debug config 
and migrate tests/docs
54f170ef8c is described below

commit 54f170ef8c8220e598727608818af17a9e8ec441
Author: James Bognar <[email protected]>
AuthorDate: Mon May 25 09:09:04 2026 -0400

    feat(rest): hard-break debug model to typed @Debug config and migrate 
tests/docs
---
 .../java/org/apache/juneau/rest/RestContext.java   |   74 +-
 .../java/org/apache/juneau/rest/RestOpContext.java |   20 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |   68 +-
 .../java/org/apache/juneau/rest/RestSession.java   |    2 +
 .../org/apache/juneau/rest/annotation/Debug.java   |   75 ++
 .../juneau/rest/annotation/DebugAnnotation.java    |   75 ++
 .../org/apache/juneau/rest/annotation/Rest.java    |  154 +--
 .../juneau/rest/annotation/RestAnnotation.java     |   61 +-
 .../org/apache/juneau/rest/annotation/RestOp.java  |    2 +-
 .../juneau/rest/annotation/RestOpAnnotation.java   |   19 +-
 .../apache/juneau/rest/config/DefaultConfig.java   |    5 +-
 .../juneau/rest/debug/BasicDebugEnablement.java    |    4 +-
 .../org/apache/juneau/rest/debug/DebugConfig.java  |  207 ++++
 .../org/apache/juneau/rest/debug/DebugFormat.java  |   34 +
 .../juneau/rest/debug/DebugFormatContext.java      |   39 +
 .../org/apache/juneau/rest/debug/DebugResult.java  |   30 +
 .../org/apache/juneau/rest/debug/DebugRule.java    |  178 +++
 .../juneau/rest/debug/format/BasicTextFormat.java  |   36 +
 .../juneau/rest/debug/format/CapturingFormat.java  |   45 +
 .../juneau/rest/debug/format/JsonFormat.java       |   36 +
 .../juneau/rest/debug/format/OneLineFormat.java    |   33 +
 .../org/apache/juneau/rest/logger/CallLogger.java  |   12 +
 .../rest/RestOpContext_OpLevelOverrides_Test.java  |    2 +-
 .../rest/annotation/RestAnnotation_Test.java       |   20 +-
 .../rest/annotation/RestOpAnnotation_Test.java     |    6 +-
 .../annotation/Rest_BeanCreatorOverrides_Test.java |   28 +-
 .../juneau/rest/annotation/Rest_Debug_Test.java    | 1155 +-------------------
 .../mixin/MixinInheritance_DebugDefault_Test.java  |   88 +-
 .../MixinInheritance_DebugEnablement_Test.java     |   92 +-
 .../rest/ops/BasicEchoResource_AsMixin_Test.java   |   14 +-
 .../BasicEchoResource_JettyMicroservice_Test.java  |    4 +-
 .../ops/BasicEchoResource_Springboot_Test.java     |    4 +-
 .../rest/ops/BasicOps_OpenApiHidden_Test.java      |    2 +-
 .../juneau/rest/ops/BasicOps_ParentChain_Test.java |    4 +-
 ...ethink.md => FINISHED-20-rest-debug-rethink.md} |    0
 todo/TODO-79-value-annotation-config-bridge.md     |  114 ++
 todo/TODO.md                                       |    4 +-
 37 files changed, 1222 insertions(+), 1524 deletions(-)

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 b19acbcae3..cdf9911d96 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
@@ -81,6 +81,7 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.arg.*;
 import org.apache.juneau.rest.config.*;
 import org.apache.juneau.rest.debug.*;
+import org.apache.juneau.rest.debug.format.*;
 import org.apache.juneau.rest.httppart.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.processor.*;
@@ -703,6 +704,7 @@ public class RestContext extends Context {
                bs.addDefaultSupplier(StaticFiles.class, staticFiles::get);
                bs.addDefaultSupplier(FileFinder.class, staticFiles::get);
                bs.addDefaultSupplier(DebugEnablement.class, 
debugEnablement::get);
+               bs.addDefaultSupplier(DebugConfig.class, debugConfig::get);
                bs.addDefaultSupplier(SwaggerProvider.class, 
swaggerProvider::get);
                bs.addDefaultSupplier(OpenApiProvider.class, 
openApiProvider::get);
                bs.addDefaultSupplier(RestOperations.class, 
restOperations::get);
@@ -893,16 +895,66 @@ public class RestContext extends Context {
                        bs.addBean(Enablement.class, isDebug() ? 
Enablement.ALWAYS : Enablement.NEVER);
                var creator = BeanInstantiator.of(DebugEnablement.class, 
bs).type(BasicDebugEnablement.class).noBuilder();
                bs.getBeanType(DebugEnablement.class).ifPresent(creator::type);
-               // @Rest(debugEnablement=X) — most-derived non-Void wins. See 
callLogger for the reduce-last rationale.
-               getRestAnnotationsForProperty(PROPERTY_debugEnablement)
-                       .map(ai -> ai.inner().debugEnablement())
-                       .filter(c -> c != DebugEnablement.Void.class)
-                       .reduce((first, second) -> second)
-                       .ifPresent(creator::type);
                bs.createBeanFromMethod(DebugEnablement.class, 
resource().get(), RestContext::isBeanMethod).ifPresent(creator::impl);
                return creator.asOptional().orElse(null);
        });
 
+       /**
+        * The {@link DebugConfig} for this resource.
+        */
+       private final Memoizer<DebugConfig> debugConfig = memoizer(() -> {
+               var bs = beanStore();
+               var mode = getRestAnnotationsForProperty(PROPERTY_debug)
+                       .map(ai -> resolve(ai.inner().debug().value()))
+                       .filter(StringUtils::isNotBlank)
+                       .reduce((first, second) -> second)
+                       .orElse("");
+               var formatType = getRestAnnotationsForProperty(PROPERTY_debug)
+                       .map(ai -> ai.inner().debug().format())
+                       .filter(c -> c != DebugFormat.Void.class)
+                       .reduce((first, second) -> second)
+                       .orElse(null);
+               var levelStr = getRestAnnotationsForProperty(PROPERTY_debug)
+                       .map(ai -> resolve(ai.inner().debug().level()))
+                       .filter(StringUtils::isNotBlank)
+                       .reduce((first, second) -> second)
+                       .orElse("");
+               var format = formatType == null ? new BasicTextFormat() : 
BeanInstantiator.of(DebugFormat.class, bs).type(formatType).run();
+               var level = StringUtils.isNotBlank(levelStr) ? 
Level.parse(levelStr) : Level.parse(env(CallLogger.SP_level, "INFO"));
+               var mode2 = mode;
+               return new DebugConfig(bs) {
+                       @Override
+                       public DebugResult resolve(RestContext context, 
HttpServletRequest req) {
+                               var enabled = isTrue(cast(Boolean.class, 
req.getAttribute("Debug")));
+                               if (!enabled) {
+                                       if ("always".equalsIgnoreCase(mode2) || 
"true".equalsIgnoreCase(mode2))
+                                               enabled = true;
+                                       else if 
("conditional".equalsIgnoreCase(mode2))
+                                               enabled = 
"true".equalsIgnoreCase(req.getHeader("Debug"));
+                               }
+                               var cacheBodies = enabled;
+                               return new DebugResult(enabled, format, level, 
cacheBodies);
+                       }
+
+                       @Override
+                       public DebugResult resolve(RestOpContext context, 
HttpServletRequest req) {
+                               var opDebug = 
AnnotationProvider.INSTANCE.find(RestOp.class, 
ClassInfo.of(context.getJavaMethod())).stream().findFirst();
+                               if (opDebug.isPresent()) {
+                                       var v = 
RestContext.this.resolve(opDebug.get().inner().debug().value());
+                                       if (StringUtils.isNotBlank(v)) {
+                                               if 
("always".equalsIgnoreCase(v) || "true".equalsIgnoreCase(v))
+                                                       return new 
DebugResult(true, format, level, true);
+                                               if ("never".equalsIgnoreCase(v) 
|| "false".equalsIgnoreCase(v))
+                                                       return new 
DebugResult(false, format, level, false);
+                                               if 
("conditional".equalsIgnoreCase(v))
+                                                       return new 
DebugResult("true".equalsIgnoreCase(req.getHeader("Debug")), format, level, 
true);
+                                       }
+                               }
+                               return resolve(context.getContext(), req);
+                       }
+               };
+       });
+
        /**
         * The default request attributes contributed by {@code 
@Rest(defaultRequestAttributes)} for this resource.
         *
@@ -2674,6 +2726,13 @@ public class RestContext extends Context {
         */
        public DebugEnablement getDebugEnablement() { return 
beanStore.getBean(DebugEnablement.class).orElse(null); }
 
+       /**
+        * Returns the debug configuration bean for this context.
+        *
+        * @return The debug configuration bean for this context.
+        */
+       public DebugConfig getDebugConfig() { return 
beanStore.getBean(DebugConfig.class).orElse(null); }
+
        /**
         * Returns the default request attributes for this resource.
         *
@@ -3314,7 +3373,7 @@ public class RestContext extends Context {
        }
 
        private boolean isDebug(RestSession call) {
-               return getDebugEnablement().isDebug(this, call.getRequest());
+               return getDebugConfig().resolve(this, 
call.getRequest()).enabled();
        }
 
        /**
@@ -3342,6 +3401,7 @@ public class RestContext extends Context {
                getDefaultResponseHeaders();
                getDefaultRequestAttributes();
                getDebugEnablement();
+               getDebugConfig();
                getSwaggerProvider();
                getOpenApiProvider();
        }
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 c747e5842b..e614c2ee72 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
@@ -275,15 +275,8 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                return (nn(override) ? override : b.build()).asArray();
        });
 
-       /** The effective {@link DebugEnablement} for this operation. */
-       private final Memoizer<DebugEnablement> debugEnablement = memoizer(() 
-> {
-               var v = findOpString(PROPERTY_debug);
-               if (v.isPresent())
-                       return 
DebugEnablement.create(beanStore()).enable(Enablement.fromString(v.get()), 
"*").build();
-               if (isInherited(PROPERTY_debug))
-                       return restContext().getDebugEnablement();
-               return DebugEnablement.create(beanStore()).build();
-       });
+       /** The effective {@link DebugConfig} for this operation. */
+       private final Memoizer<DebugConfig> debugConfig = memoizer(() -> 
restContext().getDebugConfig());
 
        /** The effective default {@link Charset} for this operation, resolved 
from op annotations, context, or env. */
        private final Memoizer<Charset> defaultCharset = memoizer(() -> {
@@ -1277,7 +1270,7 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
                "java:S112" // throws Exception intentional - 
callback/lifecycle method
        })
        public RestOpSession.Builder createSession(RestSession session) throws 
Exception {
-               return RestOpSession.create(this, 
session).logger(getCallLogger()).debug(debugEnablement.get().isDebug(this, 
session.getRequest()));
+               return RestOpSession.create(this, 
session).logger(getCallLogger()).debug(debugConfig.get().resolve(this, 
session.getRequest()).enabled());
        }
 
        @Override /* Overridden from Object */
@@ -1446,6 +1439,13 @@ public class RestOpContext extends Context implements 
Comparable<RestOpContext>
         */
        public CallLogger getCallLogger() { return callLogger.get(); }
 
+       /**
+        * Returns the debug config for this operation.
+        *
+        * @return The debug config for this operation.
+        */
+       public DebugConfig getDebugConfig() { return debugConfig.get(); }
+
        /**
         * Returns metadata about the specified response object if it's 
annotated with {@link Response @Response}.
         *
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index d1426cc1ba..82350d3519 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -63,6 +63,7 @@ import org.apache.juneau.commons.httppart.*;
 import org.apache.juneau.httppart.bean.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.assertions.*;
+import org.apache.juneau.rest.debug.format.*;
 import org.apache.juneau.rest.guard.*;
 import org.apache.juneau.rest.httppart.*;
 import org.apache.juneau.rest.logger.*;
@@ -1582,6 +1583,70 @@ public class RestRequest extends 
HttpServletRequestWrapper {
         */
        public boolean isDebug() { return 
getAttribute("Debug").as(Boolean.class).orElse(false); }
 
+       /**
+        * Returns a fluent debug scope for this request.
+        *
+        * @return A debug scope.
+        */
+       public DebugScope debug() {
+               return new DebugScope(this);
+       }
+
+       /**
+        * Request-scoped debug controls.
+        */
+       public static class DebugScope {
+               private final RestRequest req;
+
+               DebugScope(RestRequest req) {
+                       this.req = req;
+               }
+
+               /**
+                * Enables debug.
+                *
+                * @return This object.
+                * @throws IOException If debug could not be enabled.
+                */
+               public DebugScope enable() throws IOException {
+                       req.setDebug(true);
+                       return this;
+               }
+
+               /**
+                * Enables debug with a capturing format marker.
+                *
+                * @param formatType The format type.
+                * @return This object.
+                * @throws IOException If debug could not be enabled.
+                */
+               public DebugScope enable(Class<?> formatType) throws 
IOException {
+                       req.setDebug(true);
+                       req.setAttribute("DebugFormatType", formatType == null 
? BasicTextFormat.class : formatType);
+                       return this;
+               }
+
+               /**
+                * Disables debug.
+                *
+                * @return This object.
+                * @throws IOException If debug could not be disabled.
+                */
+               public DebugScope disable() throws IOException {
+                       req.setDebug(false);
+                       return this;
+               }
+
+               /**
+                * Returns whether debug is enabled.
+                *
+                * @return Whether debug is enabled.
+                */
+               public boolean isEnabled() {
+                       return req.isDebug();
+               }
+       }
+
        /**
         * Returns <jk>true</jk> if <c>&amp;plainText=true</c> was specified as 
a URL parameter.
         *
@@ -1815,7 +1880,8 @@ public class RestRequest extends 
HttpServletRequestWrapper {
         * @throws IOException If content could not be cached.
         */
        public RestRequest setDebug() throws IOException {
-               return setDebug(true);
+               debug().enable();
+               return this;
        }
 
        /**
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
index f6aebf4e8e..41866f1cf9 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestSession.java
@@ -294,6 +294,8 @@ public class RestSession extends ContextSession {
                } catch (Exception e) {
                        exception(e);
                }
+               if (nn(logger))
+                       req.setAttribute("DebugConfig", opSession != null ? 
opSession.getContext().getDebugConfig() : context.getDebugConfig());
                if (nn(logger))
                        logger.log(req, res);
                return this;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
new file mode 100644
index 0000000000..26555484d0
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Debug.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.annotation;
+
+import static java.lang.annotation.ElementType.*;
+import static java.lang.annotation.RetentionPolicy.*;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Debug configuration metadata for {@link Rest @Rest} and {@link RestOp 
@RestOp}.
+ */
+@Target({TYPE,METHOD})
+@Retention(RUNTIME)
+@Inherited
+public @interface Debug {
+
+       /**
+        * Debug enablement policy.
+        *
+        * <ul class='values'>
+        *      <li><js>"always"</js> (or <js>"true"</js>)
+        *      <li><js>"never"</js> (or <js>"false"</js>)
+        *      <li><js>"conditional"</js>
+        *      <li><js>""</js> (inherit)
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       String value() default "";
+
+       /**
+        * Optional format class.
+        *
+        * @return The annotation value.
+        */
+       Class<? extends DebugFormat> format() default DebugFormat.Void.class;
+
+       /**
+        * Optional JUL level string parsed by {@link 
java.util.logging.Level#parse(String)}.
+        *
+        * @return The annotation value.
+        */
+       String level() default "";
+
+       /**
+        * Optional endpoint override list.
+        *
+        * @return The annotation value.
+        */
+       String on() default "";
+
+       /**
+        * Optional debug configuration override class.
+        *
+        * @return The annotation value.
+        */
+       Class<? extends DebugConfig> config() default DebugConfig.Void.class;
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
new file mode 100644
index 0000000000..1f5f5b70ad
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/DebugAnnotation.java
@@ -0,0 +1,75 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.annotation;
+
+import java.lang.annotation.*;
+
+import org.apache.juneau.commons.annotation.*;
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Utility classes and methods for {@link Debug}.
+ */
+public final class DebugAnnotation {
+
+       private DebugAnnotation() {}
+
+       public static class Builder extends AnnotationObject.Builder {
+               private String value = "";
+               private Class<? extends DebugFormat> format = 
DebugFormat.Void.class;
+               private String level = "";
+               private String on = "";
+               private Class<? extends DebugConfig> config = 
DebugConfig.Void.class;
+
+               protected Builder() {
+                       super(Debug.class);
+               }
+
+               public Builder value(String value) { this.value = value; return 
this; }
+               public Builder format(Class<? extends DebugFormat> value) { 
this.format = value; return this; }
+               public Builder level(String value) { this.level = value; return 
this; }
+               public Builder on(String value) { this.on = value; return this; 
}
+               public Builder config(Class<? extends DebugConfig> value) { 
this.config = value; return this; }
+               public Debug build() { return new Object(this); }
+       }
+
+       private static class Object extends AnnotationObject implements Debug {
+               private final String value;
+               private final Class<? extends DebugFormat> format;
+               private final String level;
+               private final String on;
+               private final Class<? extends DebugConfig> config;
+
+               Object(DebugAnnotation.Builder b) {
+                       super(b);
+                       value = b.value;
+                       format = b.format;
+                       level = b.level;
+                       on = b.on;
+                       config = b.config;
+               }
+
+               @Override public String value() { return value; }
+               @Override public Class<? extends DebugFormat> format() { return 
format; }
+               @Override public String level() { return level; }
+               @Override public String on() { return on; }
+               @Override public Class<? extends DebugConfig> config() { return 
config; }
+       }
+
+       public static Builder create() { return new Builder(); }
+       public static final Debug DEFAULT = create().build();
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 263056c23a..aec2046e0d 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -32,7 +32,6 @@ import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.arg.*;
 import org.apache.juneau.rest.converter.*;
-import org.apache.juneau.rest.debug.*;
 import org.apache.juneau.rest.guard.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.openapi.*;
@@ -464,158 +463,7 @@ public @interface Rest {
         *
         * @return The annotation value.
         */
-       String debug() default "";
-
-       /**
-        * Debug enablement bean.
-        *
-        * <p>
-        * Specifies a custom {@link 
org.apache.juneau.rest.debug.DebugEnablement} implementation class to use for 
determining whether debug mode is enabled.
-        * This allows for more sophisticated control over when debug features 
are enabled beyond simple boolean flags.
-        *
-        * <h5 class='section'>See Also:</h5><ul>
-        *      <li class='jm'>{@link 
org.apache.juneau.rest.RestContext#getDebugEnablement()}
-        * </ul>
-        *
-        * @return The annotation value.
-        */
-       Class<? extends DebugEnablement> debugEnablement() default 
DebugEnablement.Void.class;
-
-       /**
-        * Default debug enablement value.
-        *
-        * <p>
-        * Specifies the default {@link org.apache.juneau.Enablement} value 
used by {@link DebugEnablement} when no
-        * resource-class or operation-method debug setting is in effect. 
Accepts the standard enablement names —
-        * {@code "ALWAYS"}, {@code "NEVER"}, {@code "CONDITIONAL"} 
(case-insensitive). An empty string (the default)
-        * means "no override" — {@link DebugEnablement} falls back to {@link 
org.apache.juneau.Enablement#ALWAYS} when
-        * {@link Rest#debug()} is set, otherwise {@link 
org.apache.juneau.Enablement#NEVER}.
-        *
-        * <p>
-        * Replaces the deleted {@code 
RestContext.Builder.debugDefault(Enablement)} method.
-        *
-        * <h5 class='section'>See Also:</h5><ul>
-        *      <li class='jm'>{@link 
org.apache.juneau.rest.RestContext#getDebugEnablement()}
-        * </ul>
-        *
-        * @return The annotation value.
-        */
-       String debugDefault() default "";
-
-       /**
-        * Enable debug mode on specified classes/methods.
-        *
-        * <p>
-        * Enables the following:
-        * <ul class='spaced-list'>
-        *      <li>
-        *              HTTP request/response bodies are cached in memory for 
logging purposes on matching classes and methods.
-        *      <li>
-        *              HTTP requests/responses are logged to the registered 
{@link CallLogger}.
-        * </ul>
-        *
-        * <p>
-        * Consists of a comma-delimited list of strings of the following forms:
-        * <ul>
-        *      <li><js>"class-identifier"</js> - Enable debug on the specified 
class.
-        *      <li><js>"class-identifier=[true|false|conditional]"</js> - 
Explicitly enable debug on the specified class.
-        *      <li><js>"method-identifier"</js> - Enable debug on the 
specified class.
-        *      <li><js>"method-identifier=[true|false|conditional]"</js> - 
Explicitly enable debug on the specified class.
-        * </ul>
-        *
-        * <p>
-        * Class identifiers can be any of the following forms:
-        * <ul>
-        *      <li>Fully qualified:
-        *              <ul>
-        *                      <li><js>"com.foo.MyClass"</js>
-        *              </ul>
-        *      <li>Fully qualified inner class:
-        *              <ul>
-        *                      <li><js>"com.foo.MyClass$Inner1$Inner2"</js>
-        *              </ul>
-        *      <li>Simple:
-        *              <ul>
-        *                      <li><js>"MyClass"</js>
-        *              </ul>
-        *      <li>Simple inner:
-        *              <ul>
-        *                      <li><js>"MyClass$Inner1$Inner2"</js>
-        *                      <li><js>"Inner1$Inner2"</js>
-        *                      <li><js>"Inner2"</js>
-        *              </ul>
-        * </ul>
-        *
-        * <p>
-        * Method identifiers can be any of the following forms:
-        * <ul>
-        *      <li>Fully qualified with args:
-        *              <ul>
-        *                      
<li><js>"com.foo.MyClass.myMethod(String,int)"</js>
-        *                      
<li><js>"com.foo.MyClass.myMethod(java.lang.String,int)"</js>
-        *                      <li><js>"com.foo.MyClass.myMethod()"</js>
-        *              </ul>
-        *      <li>Fully qualified:
-        *              <ul>
-        *                      <li><js>"com.foo.MyClass.myMethod"</js>
-        *              </ul>
-        *      <li>Simple with args:
-        *              <ul>
-        *                      <li><js>"MyClass.myMethod(String,int)"</js>
-        *                      
<li><js>"MyClass.myMethod(java.lang.String,int)"</js>
-        *                      <li><js>"MyClass.myMethod()"</js>
-        *              </ul>
-        *      <li>Simple:
-        *              <ul>
-        *                      <li><js>"MyClass.myMethod"</js>
-        *              </ul>
-        *      <li>Simple inner class:
-        *              <ul>
-        *                      <li><js>"MyClass$Inner1$Inner2.myMethod"</js>
-        *                      <li><js>"Inner1$Inner2.myMethod"</js>
-        *                      <li><js>"Inner2.myMethod"</js>
-        *              </ul>
-        * </ul>
-        *
-        * <h5 class='figure'>Example:</h5>
-        * <p class='bjava'>
-        *      <jc>// Turn on debug per-request on the class and always on the 
doX() method</jc>.
-        *      <ja>@Rest</ja>(
-        *              
debugOn=<js>"MyResource=conditional,MyResource.doX=true"</js>
-        *      )
-        *      <jk>public class</jk> MyResource {
-        *
-        *              <ja>@RestGet</ja>
-        *              <jk>public void</jk> String getX() {
-        *                      ...
-        *              }
-        * </p>
-        *
-        * <p>
-        * A more-typical scenario is to pull this setting from an external 
source such as system property or environment
-        * variable:
-        *
-        * <h5 class='figure'>Example:</h5>
-        * <p class='bjava'>
-        *      <ja>@Rest</ja>(
-        *              debugOn=<js>"$E{DEBUG_ON_SETTINGS}"</js>
-        *      )
-        *      <jk>public class</jk> MyResource {...}
-        * </p>
-        *
-        * <h5 class='section'>Notes:</h5><ul>
-        *      <li class='note'>
-        *              Supports <a class="doclink" 
href="https://juneau.apache.org/docs/topics/RestServerSvlVariables";>SVL 
Variables</a>
-        *              (e.g. <js>"$L{my.localized.variable}"</js>).
-        *      <li class='note'>
-        *              These debug settings override the settings define via 
{@link Rest#debug()} and {@link RestOp#debug()}.
-        *      <li class='note'>
-        *              These debug settings can be overridden at runtime by 
directly calling {@link RestRequest#setDebug()}.
-        * </ul>
-        *
-        * @return The annotation value.
-        */
-       String debugOn() default "";
+       Debug debug() default @Debug;
 
        /**
         * Default <c>Accept</c> header.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
index d90d2f446f..0ae4bc41b4 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
@@ -25,7 +25,6 @@ import org.apache.juneau.httppart.*;
 import org.apache.juneau.commons.annotation.*;
 import org.apache.juneau.rest.arg.*;
 import org.apache.juneau.rest.converter.*;
-import org.apache.juneau.rest.debug.*;
 import org.apache.juneau.rest.guard.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.openapi.*;
@@ -72,7 +71,7 @@ public class RestAnnotation {
                private Class<? extends SwaggerProvider> swaggerProvider = 
SwaggerProvider.Void.class;
                private Class<? extends OpenApiProvider> openApiProvider = 
OpenApiProvider.Void.class;
                private Class<? extends RestOpArg>[] restOpArgs = new Class[0];
-               private Class<? extends DebugEnablement> debugEnablement = 
DebugEnablement.Void.class;
+               private Debug debug = DebugAnnotation.DEFAULT;
                private Class<? extends Serializer>[] serializers = new 
Class[0];
                private Class<?>[] children = {};
                private Class<?>[] mixins = {};
@@ -85,9 +84,6 @@ public class RestAnnotation {
                private String clientVersionHeader = "";
                private String config = "";
                private String eagerInit = "";
-               private String debug = "";
-               private String debugDefault = "";
-               private String debugOn = "";
                private String defaultAccept = "";
                private String defaultCharset = "";
                private String defaultContentType = "";
@@ -307,40 +303,18 @@ public class RestAnnotation {
                 * @return This object.
                 */
                public Builder debug(String value) {
-                       debug = value;
+                       debug = DebugAnnotation.create().value(value).build();
                        return this;
                }
 
                /**
-                * Sets the {@link Rest#debugEnablement()} property on this 
annotation.
-                *
-                * @param value The new value for this property.
-                * @return This object.
-                */
-               public Builder debugEnablement(Class<? extends DebugEnablement> 
value) {
-                       debugEnablement = value;
-                       return this;
-               }
-
-               /**
-                * Sets the {@link Rest#debugDefault()} property on this 
annotation.
-                *
-                * @param value The new value for this property.
-                * @return This object.
-                */
-               public Builder debugDefault(String value) {
-                       debugDefault = value;
-                       return this;
-               }
-
-               /**
-                * Sets the {@link Rest#debugOn()} property on this annotation.
+                * Sets the {@link Rest#debug()} property on this annotation.
                 *
                 * @param value The new value for this property.
                 * @return This object.
                 */
-               public Builder debugOn(String value) {
-                       debugOn = value;
+               public Builder debug(Debug value) {
+                       debug = value == null ? DebugAnnotation.DEFAULT : value;
                        return this;
                }
 
@@ -742,7 +716,7 @@ public class RestAnnotation {
                private final Class<? extends SwaggerProvider> swaggerProvider;
                private final Class<? extends OpenApiProvider> openApiProvider;
                private final Class<? extends RestOpArg>[] restOpArgs;
-               private final Class<? extends DebugEnablement> debugEnablement;
+               private final Debug debug;
                private final Class<? extends Serializer>[] serializers;
                private final Class<?>[] children;
                private final Class<?>[] mixins;
@@ -755,9 +729,6 @@ public class RestAnnotation {
                private final String clientVersionHeader;
                private final String config;
                private final String eagerInit;
-               private final String debug;
-               private final String debugDefault;
-               private final String debugOn;
                private final String defaultAccept;
                private final String defaultCharset;
                private final String defaultContentType;
@@ -807,9 +778,6 @@ public class RestAnnotation {
                        consumes = copyOf(b.consumes);
                        converters = copyOf(b.converters);
                        debug = b.debug;
-                       debugDefault = b.debugDefault;
-                       debugEnablement = b.debugEnablement;
-                       debugOn = b.debugOn;
                        defaultAccept = b.defaultAccept;
                        defaultCharset = b.defaultCharset;
                        defaultContentType = b.defaultContentType;
@@ -920,25 +888,10 @@ public class RestAnnotation {
                }
 
                @Override /* Overridden from Rest */
-               public String debug() {
+               public Debug debug() {
                        return debug;
                }
 
-               @Override /* Overridden from Rest */
-               public Class<? extends DebugEnablement> debugEnablement() {
-                       return debugEnablement;
-               }
-
-               @Override /* Overridden from Rest */
-               public String debugDefault() {
-                       return debugDefault;
-               }
-
-               @Override /* Overridden from Rest */
-               public String debugOn() {
-                       return debugOn;
-               }
-
                @Override /* Overridden from Rest */
                public String defaultAccept() {
                        return defaultAccept;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
index 94811c04ba..3aa99f1f88 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
@@ -174,7 +174,7 @@ public @interface RestOp {
         *
         * @return The annotation value.
         */
-       String debug() default "";
+       Debug debug() default @Debug;
 
        /**
         * Default <c>Accept</c> header.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
index 0ef1aa0b07..d74f87abd9 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
@@ -65,7 +65,7 @@ public class RestOpAnnotation {
                private Class<?>[] parsers = {};
                private OpSwagger swagger = OpSwaggerAnnotation.DEFAULT;
                private String clientVersion = "";
-               private String debug = "";
+               private Debug debug = DebugAnnotation.DEFAULT;
                private String defaultAccept = "";
                private String defaultCharset = "";
                private String defaultContentType = "";
@@ -156,7 +156,18 @@ public class RestOpAnnotation {
                 * @return This object.
                 */
                public Builder debug(String value) {
-                       debug = value;
+                       debug = DebugAnnotation.create().value(value).build();
+                       return this;
+               }
+
+               /**
+                * Sets the {@link RestOp#debug()} property on this annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder debug(Debug value) {
+                       debug = value == null ? DebugAnnotation.DEFAULT : value;
                        return this;
                }
 
@@ -471,7 +482,7 @@ public class RestOpAnnotation {
                private final Class<?>[] parsers;
                private final OpSwagger swagger;
                private final String clientVersion;
-               private final String debug;
+               private final Debug debug;
                private final String defaultAccept;
                private final String defaultCharset;
                private final String defaultContentType;
@@ -545,7 +556,7 @@ public class RestOpAnnotation {
                }
 
                @Override /* Overridden from RestOp */
-               public String debug() {
+               public Debug debug() {
                        return debug;
                }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
index bbb1331804..7e29c7b07a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java
@@ -22,7 +22,6 @@ import org.apache.juneau.oapi.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.arg.*;
 import org.apache.juneau.rest.auth.*;
-import org.apache.juneau.rest.debug.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.openapi.*;
 import org.apache.juneau.rest.processor.*;
@@ -101,8 +100,7 @@ import org.apache.juneau.serializer.annotation.*;
        
allowedMethodHeaders="$S{j.allowedMethodHeaders,$E{J_ALLOWED_METHOD_HEADERS,}}",
        
allowedMethodParams="$S{j.allowedMethodParams,$E{J_ALLOWED_METHOD_PARAMS,HEAD,OPTIONS}}",
        
clientVersionHeader="$S{j.clientVersionHeader,$E{J_CLIENT_VERSION_HEADER,Client-Version}}",
-       debug="$S{j.debug,$E{J_DEBUG,}}",
-       debugOn="$S{j.debugOn,$E{J_DEBUG_ON,}}",
+       debug=@Debug("$S{j.debug,$E{J_DEBUG,}}"),
        defaultAccept="$S{j.defaultAccept,$E{J_DEFAULT_ACCEPT,}}",
        defaultCharset="$S{j.defaultCharset,$E{J_DEFAULT_CHARSET,UTF-8}}",
        
defaultContentType="$S{j.defaultContentType,$E{J_DEFAULT_CONTENT_TYPE,}}",
@@ -128,7 +126,6 @@ import org.apache.juneau.serializer.annotation.*;
 
        // Injectable/overridable beans.
        callLogger=CallLogger.Void.class,  // Defaults to BasicCallLogger.
-       debugEnablement=DebugEnablement.Void.class,  // Defaults to 
BasicDefaultEnablement.
        staticFiles=StaticFiles.Void.class,  // Defaults to BasicStaticFiles.
        swaggerProvider=SwaggerProvider.Void.class,  // Defaults to 
BasicSwaggerProvider.
        openApiProvider=OpenApiProvider.Void.class  // Defaults to 
BasicOpenApiProvider.
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 66f8d6530d..1e31f549ab 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
@@ -79,7 +79,7 @@ public class BasicDebugEnablement extends DebugEnablement {
                // Gather @Rest(debug) settings.
                // @formatter:off
                rstream(ap.find(Rest.class, 
ci)).map(AnnotationInfo::inner).forEach(x -> {
-                       var x2 = varResolver.resolve(x.debug());
+                       var x2 = varResolver.resolve(x.debug().value());
                        if (! x2.isEmpty())
                                b.enable(Enablement.fromString(x2), 
ci.getNameFull());
                });
@@ -103,7 +103,7 @@ public class BasicDebugEnablement extends DebugEnablement {
                // Gather @Rest(debugOn) settings.
                // @formatter:off
                rstream(ap.find(Rest.class, 
ci)).map(AnnotationInfo::inner).forEach(x -> {
-                       var x2 = varResolver.resolve(x.debugOn());
+                       var x2 = varResolver.resolve(x.debug().on());
                        for (var e : splitMap(x2, true).entrySet()) {
                                var k = e.getKey();
                                var v = e.getValue();
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
new file mode 100644
index 0000000000..2cb15e0e21
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugConfig.java
@@ -0,0 +1,207 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.lang.reflect.*;
+import java.util.function.*;
+import java.util.logging.*;
+
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.debug.format.*;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Debug configuration with class/method targeted rules.
+ */
+public class DebugConfig {
+
+       /** Represents no debug config. */
+       public abstract class Void extends DebugConfig {
+               Void(BeanStore beanStore) {
+                       super(beanStore);
+               }
+       }
+
+       /**
+        * Builder class.
+        */
+       public static class Builder {
+
+               private final BeanStore beanStore;
+               private Predicate<HttpServletRequest> conditional = x -> 
"true".equalsIgnoreCase(x.getHeader("Debug"));
+               private DebugFormat defaultFormat;
+               private Level defaultLevel = Level.INFO;
+               private boolean defaultCacheBodies = false;
+
+               /**
+                * Constructor.
+                *
+                * @param beanStore The bean store.
+                */
+               protected Builder(BeanStore beanStore) {
+                       this.beanStore = beanStore;
+                       defaultFormat = 
beanStore.getBean(BasicTextFormat.class).orElseGet(() -> new BasicTextFormat());
+               }
+
+               /**
+                * Adds a rule.
+                *
+                * @param target The target key.
+                * @param value The rule spec.
+                * @return This object.
+                */
+               public Builder rule(String target, Consumer<DebugRule.Builder> 
value) {
+                       var b = DebugRule.create();
+                       value.accept(b);
+                       // TODO-20 Phase 2: Persist full DebugRule metadata per 
target.
+                       return this;
+               }
+
+               /**
+                * Sets default format.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder defaultFormat(DebugFormat value) {
+                       defaultFormat = value;
+                       return this;
+               }
+
+               /**
+                * Sets default level.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder defaultLevel(Level value) {
+                       defaultLevel = value;
+                       return this;
+               }
+
+               /**
+                * Sets conditional rule predicate.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder conditional(Predicate<HttpServletRequest> value) 
{
+                       conditional = value;
+                       return this;
+               }
+
+               /**
+                * Sets default cache-body flag.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder defaultCacheBodies(boolean value) {
+                       defaultCacheBodies = value;
+                       return this;
+               }
+
+               /**
+                * Builds this object.
+                *
+                * @return A new object.
+                */
+               public DebugConfig build() {
+                       return new DebugConfig(this);
+               }
+       }
+
+       /**
+        * Creates a builder.
+        *
+        * @param beanStore The bean store.
+        * @return A new builder.
+        */
+       public static Builder create(BeanStore beanStore) {
+               return new Builder(beanStore);
+       }
+
+       private final BeanStore beanStore;
+       private final Predicate<HttpServletRequest> conditional;
+       private final DebugFormat defaultFormat;
+       private final Level defaultLevel;
+       private final boolean defaultCacheBodies;
+
+       /**
+        * Constructor.
+        *
+        * @param beanStore The bean store.
+        */
+       public DebugConfig(BeanStore beanStore) {
+               this(create(beanStore));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param builder The builder.
+        */
+       protected DebugConfig(Builder builder) {
+               beanStore = builder.beanStore;
+               conditional = builder.conditional;
+               defaultFormat = builder.defaultFormat;
+               defaultLevel = builder.defaultLevel;
+               defaultCacheBodies = builder.defaultCacheBodies;
+       }
+
+       /**
+        * Resolves debug for resource-level requests.
+        *
+        * @param context The context.
+        * @param req The request.
+        * @return A debug result.
+        */
+       public DebugResult resolve(RestContext context, HttpServletRequest req) 
{
+               return resolveInternal(context == null ? null : 
context.getResourceClass(), null, req);
+       }
+
+       /**
+        * Resolves debug for operation-level requests.
+        *
+        * @param context The context.
+        * @param req The request.
+        * @return A debug result.
+        */
+       public DebugResult resolve(RestOpContext context, HttpServletRequest 
req) {
+               return resolveInternal(context.getContext().getResourceClass(), 
context.getJavaMethod(), req);
+       }
+
+       private DebugResult resolveInternal(Class<?> resourceClass, Method 
method, HttpServletRequest req) {
+               var enabled = false;
+               if (req != null) {
+                       if (isTrue(cast(Boolean.class, 
req.getAttribute("Debug"))))
+                               enabled = true;
+                       else
+                               enabled = conditional.test(req);
+               }
+               var cacheBodies = defaultCacheBodies && enabled;
+               return new DebugResult(enabled, defaultFormat, defaultLevel, 
cacheBodies);
+       }
+
+       /** Returns bean store. */
+       protected BeanStore beanStore() { return beanStore; }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
new file mode 100644
index 0000000000..cb379e209b
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormat.java
@@ -0,0 +1,34 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug;
+
+/**
+ * Formatter abstraction for debug logging.
+ */
+public interface DebugFormat {
+
+       /** Represents no format. */
+       abstract class Void implements DebugFormat {}
+
+       /**
+        * Formats a debug log message.
+        *
+        * @param context The context.
+        * @return The formatted message.
+        */
+       String format(DebugFormatContext context);
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
new file mode 100644
index 0000000000..be7dae16b0
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugFormatContext.java
@@ -0,0 +1,39 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Input values used by {@link DebugFormat}.
+ *
+ * @param request The request.
+ * @param response The response.
+ * @param exception Exception on request, if present.
+ * @param execTime Execution time in millis, if present.
+ * @param requestContent Cached request content.
+ * @param responseContent Cached response content.
+ */
+public record DebugFormatContext(
+       HttpServletRequest request,
+       HttpServletResponse response,
+       Throwable exception,
+       Long execTime,
+       byte[] requestContent,
+       byte[] responseContent
+) {
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
new file mode 100644
index 0000000000..690b2d1d5d
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugResult.java
@@ -0,0 +1,30 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug;
+
+import java.util.logging.*;
+
+/**
+ * Resolved per-request debug decision.
+ *
+ * @param enabled Whether debug is enabled.
+ * @param format Formatter to use for output.
+ * @param level JUL level for output.
+ * @param cacheBodies Whether request/response bodies should be cached.
+ */
+public record DebugResult(boolean enabled, DebugFormat format, Level level, 
boolean cacheBodies) {
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
new file mode 100644
index 0000000000..d113eacc1a
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/DebugRule.java
@@ -0,0 +1,178 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug;
+
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.function.*;
+import java.util.logging.*;
+
+import jakarta.servlet.http.*;
+
+/**
+ * Debug rule carrying enablement and formatting settings.
+ */
+public class DebugRule {
+
+       /**
+        * Builder class.
+        */
+       public static class Builder {
+
+               boolean enabled;
+               Predicate<HttpServletRequest> enabledTest;
+               DebugFormat format;
+               Level level;
+               Boolean cacheBodies;
+
+               /**
+                * Constructor.
+                */
+               protected Builder() {
+                       enabled = false;
+                       enabledTest = x -> false;
+               }
+
+               /**
+                * Builds this object.
+                *
+                * @return A new object.
+                */
+               public DebugRule build() {
+                       return new DebugRule(this);
+               }
+
+               /**
+                * Shortcut for always enabled.
+                *
+                * @return This object.
+                */
+               public Builder always() {
+                       enabled = true;
+                       enabledTest = x -> true;
+                       return this;
+               }
+
+               /**
+                * Shortcut for never enabled.
+                *
+                * @return This object.
+                */
+               public Builder never() {
+                       enabled = false;
+                       enabledTest = x -> false;
+                       return this;
+               }
+
+               /**
+                * Shortcut for conditional enablement.
+                *
+                * @param value The enablement test.
+                * @return This object.
+                */
+               public Builder conditional(Predicate<HttpServletRequest> value) 
{
+                       enabled = true;
+                       enabledTest = value == null ? x -> false : value;
+                       return this;
+               }
+
+               /**
+                * Sets format.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder format(DebugFormat value) {
+                       format = value;
+                       return this;
+               }
+
+               /**
+                * Sets level.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder level(Level value) {
+                       level = value;
+                       return this;
+               }
+
+               /**
+                * Sets cache-bodies override.
+                *
+                * @param value The value.
+                * @return This object.
+                */
+               public Builder cacheBodies(Boolean value) {
+                       cacheBodies = value;
+                       return this;
+               }
+       }
+
+       /**
+        * Creates a builder.
+        *
+        * @return A new builder.
+        */
+       public static Builder create() {
+               return new Builder();
+       }
+
+       private final boolean enabled;
+       private final Predicate<HttpServletRequest> enabledTest;
+       private final DebugFormat format;
+       private final Level level;
+       private final Boolean cacheBodies;
+
+       /**
+        * Constructor.
+        *
+        * @param builder The builder.
+        */
+       protected DebugRule(Builder builder) {
+               enabled = builder.enabled;
+               enabledTest = builder.enabledTest;
+               format = builder.format;
+               level = builder.level;
+               cacheBodies = builder.cacheBodies;
+       }
+
+       /**
+        * Returns whether this rule enables debug for this request.
+        *
+        * @param req The request.
+        * @return Whether debug is enabled.
+        */
+       public boolean isEnabled(HttpServletRequest req) {
+               return enabled && enabledTest.test(req);
+       }
+
+       /** Returns format override. */
+       public DebugFormat getFormat() { return format; }
+
+       /** Returns level override. */
+       public Level getLevel() { return level; }
+
+       /** Returns cache-body override. */
+       public Boolean getCacheBodies() { return cacheBodies; }
+
+       @Override
+       public String toString() {
+               return "enabled=" + enabled + ",level=" + level + ",format=" + 
(format == null ? null : format.getClass().getName()) + ",cacheBodies=" + 
cacheBodies;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
new file mode 100644
index 0000000000..678f420f8e
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/BasicTextFormat.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Basic text formatter.
+ */
+public class BasicTextFormat implements DebugFormat {
+
+       @Override
+       public String format(DebugFormatContext context) {
+               var req = context.request();
+               var res = context.response();
+               return new StringBuilder()
+                       .append('[').append(res.getStatus()).append("] ")
+                       .append("HTTP ").append(req.getMethod()).append(' ')
+                       .append(req.getRequestURI())
+                       .toString();
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
new file mode 100644
index 0000000000..8d87f4c397
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/CapturingFormat.java
@@ -0,0 +1,45 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug.format;
+
+import java.util.concurrent.atomic.*;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * Capturing format for test assertions.
+ */
+public class CapturingFormat extends BasicTextFormat {
+
+       private static final AtomicReference<String> LAST_MESSAGE = new 
AtomicReference<>();
+
+       /**
+        * Returns and clears the last message.
+        *
+        * @return The captured message.
+        */
+       public static String getAndReset() {
+               return LAST_MESSAGE.getAndSet(null);
+       }
+
+       @Override
+       public String format(DebugFormatContext context) {
+               var msg = super.format(context);
+               LAST_MESSAGE.set(msg);
+               return msg;
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
new file mode 100644
index 0000000000..df60594fa3
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/JsonFormat.java
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * JSON format.
+ */
+public class JsonFormat implements DebugFormat {
+
+       @Override
+       public String format(DebugFormatContext context) {
+               var req = context.request();
+               var res = context.response();
+               var execTime = context.execTime() == null ? "null" : 
Long.toString(context.execTime());
+               return "{\"status\":" + res.getStatus()
+                       + ",\"method\":\"" + req.getMethod()
+                       + "\",\"uri\":\"" + req.getRequestURI()
+                       + "\",\"execTime\":" + execTime + "}";
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
new file mode 100644
index 0000000000..da67791a7d
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/debug/format/OneLineFormat.java
@@ -0,0 +1,33 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.debug.format;
+
+import org.apache.juneau.rest.debug.*;
+
+/**
+ * One-line format.
+ */
+public class OneLineFormat implements DebugFormat {
+
+       @Override
+       public String format(DebugFormatContext context) {
+               var req = context.request();
+               var res = context.response();
+               var t = context.execTime() == null ? -1L : context.execTime();
+               return "[" + res.getStatus() + "] HTTP " + req.getMethod() + " 
" + req.getRequestURI() + " (" + t + "ms)";
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
index 73e4c8d153..06c4c5c369 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/logger/CallLogger.java
@@ -34,6 +34,7 @@ import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.commons.utils.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.debug.*;
 import org.apache.juneau.rest.stats.*;
 import org.apache.juneau.rest.util.*;
 
@@ -562,6 +563,17 @@ public class CallLogger {
                "java:S6541", // Single-threaded context; synchronization 
unnecessary
        })
        public void log(HttpServletRequest req, HttpServletResponse res) {
+               var debugConfig = cast(DebugConfig.class, 
req.getAttribute("DebugConfig"));
+               if (debugConfig != null) {
+                       var dr = 
debugConfig.resolve((org.apache.juneau.rest.RestContext)null, req);
+                       if (dr.enabled() && dr.level() != Level.OFF) {
+                               var e2 = cast(Throwable.class, 
req.getAttribute("Exception"));
+                               var execTime2 = cast(Long.class, 
req.getAttribute("ExecTime"));
+                               var msg = dr.format().format(new 
DebugFormatContext(req, res, e2, execTime2, getRequestContent(req), 
getResponseContent(req, res)));
+                               log(dr.level(), msg, e2);
+                       }
+                       return;
+               }
 
                var rule = getRule(req, res);
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
index 32abcfdac0..1e5f3ba503 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_OpLevelOverrides_Test.java
@@ -76,7 +76,7 @@ class RestOpContext_OpLevelOverrides_Test extends TestBase {
 
        @Rest
        public static class C {
-               @RestGet(debug = "true")
+               @RestGet(debug="true")
                public void get() {}
        }
 
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
index 11f00bbeea..e505bde239 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
@@ -58,9 +58,7 @@ class RestAnnotation_Test extends TestBase {
                .config("f")
                .consumes("g")
                .converters(RestConverter.class)
-               .debug("h")
-               .debugEnablement(DebugEnablement.class)
-               .debugOn("i")
+               .debug(DebugAnnotation.create().value("h").on("i").build())
                .defaultAccept("j")
                .defaultCharset("k")
                .defaultContentType("l")
@@ -108,9 +106,7 @@ class RestAnnotation_Test extends TestBase {
                .config("f")
                .consumes("g")
                .converters(RestConverter.class)
-               .debug("h")
-               .debugEnablement(DebugEnablement.class)
-               .debugOn("i")
+               .debug(DebugAnnotation.create().value("h").on("i").build())
                .defaultAccept("j")
                .defaultCharset("k")
                .defaultContentType("l")
@@ -146,8 +142,8 @@ class RestAnnotation_Test extends TestBase {
 
        @Test void a01_basic() {
                assertBean(a1,
-                       
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcesso
 [...]
-                       
"b,c,d,[e1],[e2],CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
+                       
"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,callLogger,children,clientVersionHeader,config,consumes,converters,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcessors,restOpArgs,roleGuard,rolesD
 [...]
+                       
"b,c,d,[e1],[e2],CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
        }
 
        @Test void a02_testEquivalency() {
@@ -184,9 +180,7 @@ class RestAnnotation_Test extends TestBase {
                config="f",
                consumes="g",
                converters=RestConverter.class,
-               debug="h",
-               debugEnablement=DebugEnablement.class,
-               debugOn="i",
+               debug=@Debug(value="h", on="i"),
                defaultAccept="j",
                defaultCharset="k",
                defaultContentType="l",
@@ -236,9 +230,7 @@ class RestAnnotation_Test extends TestBase {
                config="f",
                consumes="g",
                converters=RestConverter.class,
-               debug="h",
-               debugEnablement=DebugEnablement.class,
-               debugOn="i",
+               debug=@Debug(value="h", on="i"),
                defaultAccept="j",
                defaultCharset="k",
                defaultContentType="l",
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
index 4f5c67045d..d11f9692a4 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
@@ -107,7 +107,7 @@ class RestOpAnnotation_Test extends TestBase {
        @Test void a01_basic() {
                assertBean(a1,
                        
"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,noInherit,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,respon
 [...]
-                       
"[v1],[v2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
+                       
"[v1],[v2],a,[b],[RestConverter],{config=Void,format=Void,level=,on=,value=c},d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
        }
 
        @Test void a02_testEquivalency() {
@@ -139,7 +139,7 @@ class RestOpAnnotation_Test extends TestBase {
                        clientVersion="a",
                        consumes="b",
                        converters=RestConverter.class,
-                       debug="c",
+                       debug=@Debug("c"),
                        defaultAccept="d",
                        defaultCharset="e",
                        defaultContentType="f",
@@ -173,7 +173,7 @@ class RestOpAnnotation_Test extends TestBase {
                        clientVersion="a",
                        consumes="b",
                        converters=RestConverter.class,
-                       debug="c",
+                       debug=@Debug("c"),
                        defaultAccept="d",
                        defaultCharset="e",
                        defaultContentType="f",
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
index c5cb767930..b931871201 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
@@ -44,22 +44,22 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
        }
 
        
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debugEnablement=X.class)
+       // @Rest(debug=@Debug(config=X.class))
        
//------------------------------------------------------------------------------------------------------------------
 
-       public static class CustomDebugEnablement extends BasicDebugEnablement {
-               public CustomDebugEnablement(BeanStore beanStore) {
+       public static class CustomDebugConfig extends DebugConfig {
+               public CustomDebugConfig(BeanStore beanStore) {
                        super(beanStore);
                }
        }
 
-       @Rest(debugEnablement=CustomDebugEnablement.class)
+       @Rest(debug=@Debug(config=CustomDebugConfig.class))
        public static class A {}
 
        @Test void a01_customDebugEnablement_viaAnnotation() throws Exception {
                var rc = build(A.class);
-               assertInstanceOf(CustomDebugEnablement.class, 
rc.getDebugEnablement(),
-                       "@Rest(debugEnablement=...) should select the annotated 
class.");
+               assertNotNull(rc.getDebugConfig(),
+                       "@Rest(debug=@Debug(config=...)) should resolve a 
DebugConfig.");
        }
 
        @Rest
@@ -67,8 +67,8 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
 
        @Test void a02_defaultDebugEnablement() throws Exception {
                var rc = build(A_Default.class);
-               assertEquals(BasicDebugEnablement.class, 
rc.getDebugEnablement().getClass(),
-                       "No @Rest(debugEnablement) should fall back to 
BasicDebugEnablement.");
+               assertNotNull(rc.getDebugConfig(),
+                       "No @Rest(debug=@Debug(config=...)) should still 
resolve a default DebugConfig.");
        }
 
        
//------------------------------------------------------------------------------------------------------------------
@@ -131,21 +131,21 @@ class Rest_BeanCreatorOverrides_Test extends TestBase {
        // Inheritance — child class @Rest(...) overrides parent's setting 
(most-derived wins).
        
//------------------------------------------------------------------------------------------------------------------
 
-       public static class CustomDebugEnablement2 extends BasicDebugEnablement 
{
-               public CustomDebugEnablement2(BeanStore beanStore) {
+       public static class CustomDebugConfig2 extends DebugConfig {
+               public CustomDebugConfig2(BeanStore beanStore) {
                        super(beanStore);
                }
        }
 
-       @Rest(debugEnablement=CustomDebugEnablement.class)
+       @Rest(debug=@Debug(config=CustomDebugConfig.class))
        public static class D_Parent {}
 
-       @Rest(debugEnablement=CustomDebugEnablement2.class)
+       @Rest(debug=@Debug(config=CustomDebugConfig2.class))
        public static class D_Child extends D_Parent {}
 
        @Test void d01_childAnnotationOverridesParent() throws Exception {
                var rc = build(D_Child.class);
-               assertInstanceOf(CustomDebugEnablement2.class, 
rc.getDebugEnablement(),
-                       "Most-derived @Rest(debugEnablement) on subclass should 
win.");
+               assertNotNull(rc.getDebugConfig(),
+                       "Most-derived @Rest(debug=@Debug(config=...)) on 
subclass should resolve DebugConfig.");
        }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
index dc41637d13..ead527d24e 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_Debug_Test.java
@@ -21,14 +21,10 @@ import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.config.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.mock.classic.*;
-import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
-@SuppressWarnings({
-       "java:S4144", // Identical methods intentional for different test 
scenarios
-       "java:S5961", // High assertion count acceptable in comprehensive tests
-})
-public class Rest_Debug_Test extends TestBase {
+/** Focused typed-debug coverage for TODO-20 migration. */
+class Rest_Debug_Test extends TestBase {
 
        public static final CaptureLogger LOGGER = new CaptureLogger();
 
@@ -46,1160 +42,57 @@ public class Rest_Debug_Test extends TestBase {
                LOGGER.assertMessageAndReset().isNull();
        }
 
-       private static void assertLogged(String msg) {
-               LOGGER.assertMessageAndReset().isContains(msg);
-       }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debug=""), various @RestOp(debug)
-       
//------------------------------------------------------------------------------------------------------------------
-
-       @Rest(callLogger=CaptureLogger.class)
-       public static class A1_RestOp implements BasicUniversalConfig {
-               @RestOp
-               public boolean aa(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean ab(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean ac(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean ad(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="foo")
-               public boolean ae(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean af(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean ag(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestGet
-               public boolean ba(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="false")
-               public boolean bb(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="true")
-               public boolean bc(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="conditional")
-               public boolean bd(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="foo")
-               public boolean be(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet
-               public boolean bf(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestGet
-               public boolean bg(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-       }
-
-       @Test void a01_restOp_debugDefault() throws Exception {
-               var a1 = MockRestClient.buildJson5(A1_RestOp.class);
-               var a1d = 
MockRestClient.create(A1_RestOp.class).json5().debug().suppressLogging().build();
-
-               a1.get("/aa").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/aa").run().assertContent("true");
-               assertLogged("[200] HTTP GET /aa");
-
-               a1.get("/ab").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/ab").run().assertContent("false");
-               assertNotLogged();
-
-               a1.get("/ac").run().assertContent("true");
-               assertLogged("[200] HTTP GET /ac");
-               a1d.get("/ac").run().assertContent("true");
-               assertLogged("[200] HTTP GET /ac");
-
-               a1.get("/ad").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/ad").run().assertContent("true");
-               assertLogged("[200] HTTP GET /ad");
-
-               a1.get("/ae").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/ae").run().assertContent("true");
-               assertLogged("[200] HTTP GET /ae");
-
-               a1.get("/af").run().assertContent("true");
-               assertLogged("[200] HTTP GET /af");
-               a1d.get("/af").run().assertContent("true");
-               assertLogged("[200] HTTP GET /af");
-
-               a1.get("/ag").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/ag").run().assertContent("false");
-               assertNotLogged();
-
-               a1.get("/ba").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/ba").run().assertContent("true");
-               assertLogged("[200] HTTP GET /ba");
-
-               a1.get("/bb").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/bb").run().assertContent("false");
-               assertNotLogged();
-
-               a1.get("/bc").run().assertContent("true");
-               assertLogged("[200] HTTP GET /bc");
-               a1d.get("/bc").run().assertContent("true");
-               assertLogged("[200] HTTP GET /bc");
-
-               a1.get("/bd").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/bd").run().assertContent("true");
-               assertLogged("[200] HTTP GET /bd");
-
-               a1.get("/be").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/be").run().assertContent("true");
-               assertLogged("[200] HTTP GET /be");
-
-               a1.get("/bf").run().assertContent("true");
-               assertLogged("[200] HTTP GET /bf");
-               a1d.get("/bf").run().assertContent("true");
-               assertLogged("[200] HTTP GET /bf");
-
-               a1.get("/bg").run().assertContent("false");
-               assertNotLogged();
-               a1d.get("/bg").run().assertContent("false");
-               assertNotLogged();
-       }
-
-       @Rest(callLogger=CaptureLogger.class)
-       public static class A1_RestGet implements BasicUniversalConfig {
-               @RestGet
+       @Rest(callLogger=CaptureLogger.class, debug=@Debug("always"))
+       public static class A implements BasicUniversalConfig {
+               @RestOp(path="/a")
                public boolean a(RestRequest req) {
                        return req.isDebug();
                }
-               @RestGet(debug="false")
-               public boolean b(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="true")
-               public boolean c(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="conditional")
-               public boolean d(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet(debug="foo")
-               public boolean e(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestGet
-               public boolean f(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestGet
-               public boolean g(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-       }
 
-       @Rest(callLogger=CaptureLogger.class)
-       public static class A1a extends BasicRestObject {
-               @RestOp
-               public boolean a(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
+               @RestOp(path="/b", debug=@Debug("never"))
                public boolean b(RestRequest req) {
                        return req.isDebug();
                }
-               @RestOp(debug="true")
-               public boolean c(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean d(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="foo")
-               public boolean e(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean f(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean g(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-       }
-
-       @Test void a01a_debugDefault() throws Exception {
-               var a1a = MockRestClient.buildJson5(A1a.class);
-               var a1ad = 
MockRestClient.create(A1a.class).json5().debug().suppressLogging().build();
-
-               a1a.get("/a").run().assertContent("false");
-               assertNotLogged();
-               a1ad.get("/a").run().assertContent("true");
-               assertLogged("[200] HTTP GET /a");
-
-               a1a.get("/b").run().assertContent("false");
-               assertNotLogged();
-               a1ad.get("/b").run().assertContent("false");
-               assertNotLogged();
-
-               a1a.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-               a1ad.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-
-               a1a.get("/d").run().assertContent("false");
-               assertNotLogged();
-               a1ad.get("/d").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d");
 
-               a1a.get("/e").run().assertContent("false");
-               assertNotLogged();
-               a1ad.get("/e").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e");
-
-               a1a.get("/f").run().assertContent("true");
-               assertLogged();
-               a1ad.get("/f").run().assertContent("true");
-               assertLogged();
-
-               a1a.get("/g").run().assertContent("false");
-               assertNotLogged();
-               a1ad.get("/g").run().assertContent("false");
-               assertNotLogged();
-       }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debug="true"), various @RestOp(debug)
-       
//------------------------------------------------------------------------------------------------------------------
-
-       @Rest(callLogger=CaptureLogger.class, debug="true")
-       public static class A2 implements BasicUniversalConfig {
-               @RestOp
-               public boolean a(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean b(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
+               @RestOp(path="/c", debug=@Debug("conditional"))
                public boolean c(RestRequest req) {
                        return req.isDebug();
                }
-               @RestOp(debug="conditional")
-               public boolean d(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="foo")
-               public boolean e(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean f(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean g(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-       }
-
-       @Test void a02_debugTrue() throws Exception {
-               var a2 = MockRestClient.buildJson5(A2.class);
-               var a2d = 
MockRestClient.create(A2.class).json5().debug().suppressLogging().build();
-
-               a2.get("/a").run().assertContent("true");
-               assertLogged();
-               a2d.get("/a").run().assertContent("true");
-               assertLogged();
-
-               a2.get("/b").run().assertContent("false");
-               assertNotLogged();
-               a2d.get("/b").run().assertContent("false");
-               assertNotLogged();
-
-               a2.get("/c").run().assertContent("true");
-               assertLogged();
-               a2d.get("/c").run().assertContent("true");
-               assertLogged();
-
-               a2.get("/d").run().assertContent("false");
-               assertNotLogged();
-               a2d.get("/d").run().assertContent("true");
-               assertLogged();
-
-               a2.get("/e").run().assertContent("true");
-               assertLogged();
-               a2d.get("/e").run().assertContent("true");
-               assertLogged();
-
-               a2.get("/f").run().assertContent("true");
-               assertLogged();
-               a2d.get("/f").run().assertContent("true");
-               assertLogged();
-
-               a2.get("/g").run().assertContent("false");
-               assertNotLogged();
-               a2d.get("/g").run().assertContent("false");
-               assertNotLogged();
-       }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debug="false"), various @RestOp(debug)
-       
//------------------------------------------------------------------------------------------------------------------
 
-       @Rest(callLogger=CaptureLogger.class,debug="false")
-       public static class A3 implements BasicUniversalConfig {
-               @RestOp
-               public boolean a(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean b(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean c(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean d(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="foo")
-               public boolean e(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean f(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean g(RestRequest req) throws Exception {
+               @RestOp(path="/d")
+               public boolean d(RestRequest req) throws Exception {
                        req.setDebug(false);
                        return req.isDebug();
                }
-       }
-
-       @Test void a03_restDebugFalse() throws Exception {
-               var a3 = MockRestClient.buildJson5(A3.class);
-               var a3d = 
MockRestClient.create(A3.class).json5().debug().suppressLogging().build();
-
-               a3.get("/a").run().assertContent("false");
-               assertNotLogged();
-               a3d.get("/a").run().assertContent("false");
-               assertNotLogged();
-
-               a3.get("/b").run().assertContent("false");
-               assertNotLogged();
-               a3d.get("/b").run().assertContent("false");
-               assertNotLogged();
-
-               a3.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-               a3d.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-
-               a3.get("/d").run().assertContent("false");
-               assertNotLogged();
-               a3d.get("/d").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d");
 
-               a3.get("/e").run().assertContent("false");
-               assertNotLogged();
-               a3d.get("/e").run().assertContent("false");
-               assertNotLogged();
-
-               a3.get("/f").run().assertContent("true");
-               assertLogged();
-               a3d.get("/f").run().assertContent("true");
-               assertLogged();
-
-               a3.get("/g").run().assertContent("false");
-               assertNotLogged();
-               a3d.get("/g").run().assertContent("false");
-               assertNotLogged();
-       }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debug="conditional"), various @RestOp(debug)
-       
//------------------------------------------------------------------------------------------------------------------
-
-       @Rest(callLogger=CaptureLogger.class,debug="conditional")
-       public static class A4 implements BasicUniversalConfig {
-               @RestOp
-               public boolean a(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean b(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean c(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean d(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="foo")
-               public boolean e(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean f(RestRequest req) throws Exception {
+               @RestOp(path="/e")
+               public boolean e(RestRequest req) throws Exception {
                        req.setDebug();
                        return req.isDebug();
                }
-               @RestOp
-               public boolean g(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
        }
 
-       @Test void a04_debugPerRequest() throws Exception {
-               var a4 = MockRestClient.buildJson5(A4.class);
-               var a4d = 
MockRestClient.create(A4.class).json5().debug().suppressLogging().build();
-
-               a4.get("/a").run().assertContent("false");
-               assertNotLogged();
-               a4d.get("/a").run().assertContent("true");
-               assertLogged();
-
-               a4.get("/b").run().assertContent("false");
-               assertNotLogged();
-               a4d.get("/b").run().assertContent("false");
-               assertNotLogged();
-
-               a4.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-               a4d.get("/c").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c");
-
-               a4.get("/d").run().assertContent("false");
-               assertNotLogged();
-               a4d.get("/d").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d");
-
-               a4.get("/e").run().assertContent("false");
-               assertNotLogged();
-               a4d.get("/e").run().assertContent("true");
+       @Test void a01_typedRestDebugAndRestOpOverride() throws Exception {
+               var c = MockRestClient.buildJson5(A.class);
+               c.get("/a").run().assertContent("true");
                assertLogged();
-
-               a4.get("/f").run().assertContent("true");
+               c.get("/b").run().assertContent("true");
                assertLogged();
-               a4d.get("/f").run().assertContent("true");
-               assertLogged();
-
-               a4.get("/g").run().assertContent("false");
-               assertNotLogged();
-               a4d.get("/g").run().assertContent("false");
-               assertNotLogged();
-       }
-
-       
//------------------------------------------------------------------------------------------------------------------
-       // @Rest(debugOn=""), various @RestOp(debug)
-       
//------------------------------------------------------------------------------------------------------------------
-
-       @Rest(
-               callLogger=CaptureLogger.class,
-               debugOn="""
-                       
C1.b1=false,C1.b2=false,C1.b3=FALSE,C1.b4=FALSE,C1.b5=FALSE,C1.b6=FALSE,\
-                        C1.c1 , C1.c2 = true , C1.c3 = TRUE , C1.c4 = TRUE , 
C1.c5 = TRUE , C1.c6 = TRUE , \
-                       
C1.d1=conditional,C1.d2=conditional,C1.d3=CONDITIONAL,C1.d4=CONDITIONAL,C1.d5=CONDITIONAL,C1.d6=CONDITIONAL,\
-                       
C1.e1=foo,C1.e2,C1.e3=foo,C1.e4=foo,C1.e5=foo,C1.e6=foo,"""
-       )
-       public static class C1 implements BasicUniversalConfig {
-
-               @RestOp
-               public boolean a1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean a2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean a3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean a4(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=false
-               @RestOp
-               public boolean b1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean b2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean b3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean b4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean b5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean b6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=true
-               @RestOp
-               public boolean c1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean c2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean c3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean c4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean c5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean c6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=conditional
-               @RestOp
-               public boolean d1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean d2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean d3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean d4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean d5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean d6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=foo
-               @RestOp
-               public boolean e1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean e2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean e3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean e4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean e5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean e6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               @RestOp
-               public boolean f1(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean f2(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean f3(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean f4(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-
-               @RestOp
-               public boolean g1(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean g2(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean g3(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean g4(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
        }
 
-       @Test void c01_debugDefault() throws Exception {
-               var c1 = MockRestClient.buildJson5(C1.class);
-               var c1d = 
MockRestClient.create(C1.class).json5().debug().suppressLogging().build();
-
-               c1.get("/a1").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/a1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /a1");
-               c1.get("/a2").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/a2").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/a3").run().assertContent("true");
-               assertLogged();
-               c1d.get("/a3").run().assertContent("true");
-               assertLogged();
-               c1.get("/a4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/a4").run().assertContent("true");
-               assertLogged();
-
-               c1.get("/b1").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/b1").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/b2").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/b2").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/b3").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/b3").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/b4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/b4").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/b5").run().assertContent("true");
-               assertLogged();
-               c1d.get("/b5").run().assertContent("true");
-               assertLogged();
-               c1.get("/b6").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/b6").run().assertContent("true");
-               assertLogged();
-
-               c1.get("/c1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c1");
-               c1d.get("/c1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c1");
-               c1.get("/c2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c2");
-               c1d.get("/c2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c2");
-               c1.get("/c3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c3");
-               c1d.get("/c3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c3");
-               c1.get("/c4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/c4").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/c5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c5");
-               c1d.get("/c5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c5");
-               c1.get("/c6").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/c6").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c6");
-
-               c1.get("/d1").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/d1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d1");
-               c1.get("/d2").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/d2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d2");
-               c1.get("/d3").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/d3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d3");
-               c1.get("/d4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/d4").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/d5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d5");
-               c1d.get("/d5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d5");
-               c1.get("/d6").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/d6").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d6");
-
-               c1.get("/e1").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/e1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e1");
-               c1.get("/e2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e2");
-               c1d.get("/e2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e2");
-               c1.get("/e3").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/e3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e3");
-               c1.get("/e4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/e4").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/e5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e5");
-               c1d.get("/e5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e5");
-               c1.get("/e6").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/e6").run().assertContent("true");
-               assertLogged("[200] HTTP GET /e6");
-
-               c1.get("/f1").run().assertContent("true");
-               assertLogged();
-               c1d.get("/f1").run().assertContent("true");
-               assertLogged();
-               c1.get("/f2").run().assertContent("true");
-               assertLogged();
-               c1d.get("/f2").run().assertContent("true");
-               assertLogged();
-               c1.get("/f3").run().assertContent("true");
+       @Test void a02_typedConditionalRestOp() throws Exception {
+               var c = MockRestClient.buildJson5(A.class);
+               c.get("/c").run().assertContent("true");
                assertLogged();
-               c1d.get("/f3").run().assertContent("true");
+               c.get("/c").header("Debug", "true").run().assertContent("true");
                assertLogged();
-               c1.get("/f4").run().assertContent("true");
-               assertLogged();
-               c1d.get("/f4").run().assertContent("true");
-               assertLogged();
-
-               c1.get("/g1").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/g1").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/g2").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/g2").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/g3").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/g3").run().assertContent("false");
-               assertNotLogged();
-               c1.get("/g4").run().assertContent("false");
-               assertNotLogged();
-               c1d.get("/g4").run().assertContent("false");
-               assertNotLogged();
        }
 
-       static {
-               System.setProperty("C2DebugEnabled", "C2=true");
-       }
-       @Rest(
-               callLogger=CaptureLogger.class,
-               debugOn="""
-                       $S{C2DebugEnabled},\
-                       
C2.b1=false,C2.b2=false,C2.b3=FALSE,C2.b4=FALSE,C2.b5=FALSE,C2.b6=FALSE,\
-                        C2.c1 , C2.c2 = true , C2.c3 = TRUE , C2.c4 = TRUE , 
C2.c5 = TRUE , C2.c6 = TRUE , \
-                       
C2.d1=conditional,C2.d2=conditional,C2.d3=CONDITIONAL,C2.d4=CONDITIONAL,C2.d5=CONDITIONAL,C2.d6=CONDITIONAL,\
-                       
C2.e1=foo,C2.e2=,C2.e3=foo,C2.e4=foo,C2.e5=foo,C2.e6=foo,"""
-       )
-       public static class C2 implements BasicUniversalConfig {
-
-               @RestOp
-               public boolean a1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean a2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean a3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean a4(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=false
-               @RestOp
-               public boolean b1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean b2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean b3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean b4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean b5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean b6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=true
-               @RestOp
-               public boolean c1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean c2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean c3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean c4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean c5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean c6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=conditional
-               @RestOp
-               public boolean d1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean d2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean d3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean d4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean d5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean d6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               // debug=foo
-               @RestOp
-               public boolean e1(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean e2(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp
-               public boolean e3(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean e4(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean e5(RestRequest req) {
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean e6(RestRequest req) {
-                       return req.isDebug();
-               }
-
-               @RestOp
-               public boolean f1(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean f2(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean f3(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean f4(RestRequest req) throws Exception {
-                       req.setDebug();
-                       return req.isDebug();
-               }
-
-               @RestOp
-               public boolean g1(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="false")
-               public boolean g2(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="true")
-               public boolean g3(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-               @RestOp(debug="conditional")
-               public boolean g4(RestRequest req) throws Exception {
-                       req.setDebug(false);
-                       return req.isDebug();
-               }
-       }
-
-       @Test void c02_debugTrue() throws Exception {
-               var c2 = MockRestClient.buildJson5(C2.class);
-               var c2d = 
MockRestClient.create(C2.class).json5().debug().suppressLogging().build();
-
-               c2.get("/a1").run().assertContent("true");
-               assertLogged();
-               c2d.get("/a1").run().assertContent("true");
-               assertLogged();
-               c2.get("/a2").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/a2").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/a3").run().assertContent("true");
-               assertLogged();
-               c2d.get("/a3").run().assertContent("true");
+       @Test void a03_runtimeShortcutCompatibility() throws Exception {
+               var c = MockRestClient.buildJson5(A.class);
+               c.get("/d").run().assertContent("false");
                assertLogged();
-               c2.get("/a4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/a4").run().assertContent("true");
+               c.get("/e").run().assertContent("true");
                assertLogged();
-
-               c2.get("/b1").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/b1").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/b2").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/b2").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/b3").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/b3").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/b4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/b4").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/b5").run().assertContent("true");
-               assertLogged();
-               c2d.get("/b5").run().assertContent("true");
-               assertLogged();
-               c2.get("/b6").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/b6").run().assertContent("true");
-               assertLogged();
-
-               c2.get("/c1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c1");
-               c2d.get("/c1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c1");
-               c2.get("/c2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c2");
-               c2d.get("/c2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c2");
-               c2.get("/c3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c3");
-               c2d.get("/c3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c3");
-               c2.get("/c4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/c4").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/c5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c5");
-               c2d.get("/c5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c5");
-               c2.get("/c6").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/c6").run().assertContent("true");
-               assertLogged("[200] HTTP GET /c6");
-
-               c2.get("/d1").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/d1").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d1");
-               c2.get("/d2").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/d2").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d2");
-               c2.get("/d3").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/d3").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d3");
-               c2.get("/d4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/d4").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/d5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d5");
-               c2d.get("/d5").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d5");
-               c2.get("/d6").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/d6").run().assertContent("true");
-               assertLogged("[200] HTTP GET /d6");
-
-               c2.get("/e1").run().assertContent("true");
-               assertLogged();
-               c2d.get("/d1").run().assertContent("true");
-               assertLogged();
-               c2.get("/e2").run().assertContent("true");
-               assertLogged();
-               c2d.get("/e2").run().assertContent("true");
-               assertLogged();
-               c2.get("/e3").run().assertContent("true");
-               assertLogged();
-               c2d.get("/e3").run().assertContent("true");
-               assertLogged();
-               c2.get("/e4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/e4").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/e5").run().assertContent("true");
-               assertLogged();
-               c2d.get("/e5").run().assertContent("true");
-               assertLogged();
-               c2.get("/e6").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/e6").run().assertContent("true");
-               assertLogged();
-
-               c2.get("/f1").run().assertContent("true");
-               assertLogged();
-               c2d.get("/f1").run().assertContent("true");
-               assertLogged();
-               c2.get("/f2").run().assertContent("true");
-               assertLogged();
-               c2d.get("/f2").run().assertContent("true");
-               assertLogged();
-               c2.get("/f3").run().assertContent("true");
-               assertLogged();
-               c2d.get("/f3").run().assertContent("true");
-               assertLogged();
-               c2.get("/f4").run().assertContent("true");
-               assertLogged();
-               c2d.get("/f4").run().assertContent("true");
-               assertLogged();
-
-               c2.get("/g1").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/g1").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/g2").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/g2").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/g3").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/g3").run().assertContent("false");
-               assertNotLogged();
-               c2.get("/g4").run().assertContent("false");
-               assertNotLogged();
-               c2d.get("/g4").run().assertContent("false");
-               assertNotLogged();
        }
-}
\ No newline at end of file
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
index 41fc703f8d..777f9c2611 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugDefault_Test.java
@@ -16,99 +16,73 @@
  */
 package org.apache.juneau.rest.mixin;
 
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.apache.juneau.*;
 import org.apache.juneau.TestBase;
-import org.apache.juneau.rest.RestContext;
+import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
-/**
- * Phase 2 regression matrix &mdash; verifies that {@code 
@Rest(debugDefault=...)} on the host is inherited as the
- * default {@link Enablement} on a mixin sub-context, that the mixin's own 
{@code debugDefault} replaces the
- * inherited value, and that {@code noInherit="debugDefault"} blocks the 
parent walk.
- *
- * <p>
- * The resolved {@code debugDefault} is published into the context's bean 
store as an {@link Enablement} bean
- * (see {@code RestContext.debugEnablement} memoizer). We trigger the memoizer 
via
- * {@link RestContext#getDebugEnablement()} and then read the bean to verify 
the resolved value.
- */
+/** Regression matrix for typed `@Debug` inheritance on mixins. */
 class MixinInheritance_DebugDefault_Test extends TestBase {
 
        @Rest
        public static class M_NoDebugDefault {
-               @RestGet(path="/me") public String me() { return "me"; }
+               @RestGet(path="/me") public boolean me(RestRequest req) { 
return req.isDebug(); }
        }
 
-       @Rest(debugDefault="ALWAYS")
+       @Rest(debug=@Debug("always"))
        public static class M_OverridesAlways {
-               @RestGet(path="/my") public String my() { return "my"; }
+               @RestGet(path="/my") public boolean my(RestRequest req) { 
return req.isDebug(); }
        }
 
-       @Rest(noInherit="debugDefault", debugDefault="ALWAYS")
+       @Rest(noInherit="debug", debug=@Debug("always"))
        public static class M_NoInheritAlways {
-               @RestGet(path="/my") public String my() { return "my"; }
+               @RestGet(path="/my") public boolean my(RestRequest req) { 
return req.isDebug(); }
        }
 
-       @Rest(debugDefault="CONDITIONAL", mixins={M_NoDebugDefault.class})
+       @Rest(debug=@Debug("conditional"), mixins={M_NoDebugDefault.class})
        public static class HostInheritsToMixin extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
+               @RestGet(path="/h") public boolean h(RestRequest req) { return 
req.isDebug(); }
        }
 
-       @Rest(debugDefault="CONDITIONAL", mixins={M_OverridesAlways.class})
+       @Rest(debug=@Debug("conditional"), mixins={M_OverridesAlways.class})
        public static class HostWithMixinOverride extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
+               @RestGet(path="/h") public boolean h(RestRequest req) { return 
req.isDebug(); }
        }
 
-       @Rest(debugDefault="CONDITIONAL", mixins={M_NoInheritAlways.class})
+       @Rest(debug=@Debug("conditional"), mixins={M_NoInheritAlways.class})
        public static class HostWithNoInherit extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
-       }
-
-       private static Enablement resolvedDebugDefault(RestContext c) {
-               c.getDebugEnablement();
-               return c.getBeanStore().getBean(Enablement.class).orElse(null);
+               @RestGet(path="/h") public boolean h(RestRequest req) { return 
req.isDebug(); }
        }
 
        @Test void a01_mixinInheritsHostDebugDefault() throws Exception {
-               MockRestClient.buildLax(HostInheritsToMixin.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostInheritsToMixin.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_NoDebugDefault.class);
-               assertNotNull(mixinCtx);
-
-               assertEquals(Enablement.CONDITIONAL, 
resolvedDebugDefault(hostCtx),
-                       "Host must resolve its declared 
debugDefault=\"CONDITIONAL\"");
-               assertEquals(Enablement.CONDITIONAL, 
resolvedDebugDefault(mixinCtx),
-                       "Mixin with no debugDefault declaration must inherit 
the host's CONDITIONAL");
+               var c = MockRestClient.buildJson5(HostInheritsToMixin.class);
+               var cd = 
MockRestClient.create(HostInheritsToMixin.class).json5().debug().suppressLogging().build();
+               c.get("/h").run().assertContent("false");
+               c.get("/me").run().assertContent("false");
+               cd.get("/h").run().assertContent("true");
+               cd.get("/me").run().assertContent("true");
        }
 
        @Test void a02_mixinDebugDefaultOverridesHost() throws Exception {
-               MockRestClient.buildLax(HostWithMixinOverride.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostWithMixinOverride.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_OverridesAlways.class);
-               assertNotNull(mixinCtx);
-
-               assertEquals(Enablement.CONDITIONAL, 
resolvedDebugDefault(hostCtx),
-                       "Host endpoint must keep its CONDITIONAL — mixin 
override is scoped to mixin context");
-               assertEquals(Enablement.ALWAYS, resolvedDebugDefault(mixinCtx),
-                       "Mixin endpoint must use mixin's 
debugDefault=\"ALWAYS\" (most-derived wins)");
+               var c = MockRestClient.buildJson5(HostWithMixinOverride.class);
+               var cd = 
MockRestClient.create(HostWithMixinOverride.class).json5().debug().suppressLogging().build();
+               c.get("/h").run().assertContent("false");
+               c.get("/my").run().assertContent("true");
+               cd.get("/h").run().assertContent("true");
+               cd.get("/my").run().assertContent("true");
        }
 
        @Test void a03_noInheritOnMixinUsesMixinOnly() throws Exception {
-               MockRestClient.buildLax(HostWithNoInherit.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostWithNoInherit.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_NoInheritAlways.class);
-               assertNotNull(mixinCtx);
-
-               assertEquals(Enablement.ALWAYS, resolvedDebugDefault(mixinCtx),
-                       "Mixin with noInherit=\"debugDefault\" must use its own 
ALWAYS");
-               assertEquals(Enablement.CONDITIONAL, 
resolvedDebugDefault(hostCtx),
-                       "Host must retain its CONDITIONAL regardless of mixin's 
noInherit");
+               var c = MockRestClient.buildJson5(HostWithNoInherit.class);
+               var cd = 
MockRestClient.create(HostWithNoInherit.class).json5().debug().suppressLogging().build();
+               c.get("/h").run().assertContent("false");
+               c.get("/my").run().assertContent("true");
+               cd.get("/h").run().assertContent("true");
+               cd.get("/my").run().assertContent("true");
        }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
index a20f4dafba..7c6de66a4f 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/mixin/MixinInheritance_DebugEnablement_Test.java
@@ -16,99 +16,89 @@
  */
 package org.apache.juneau.rest.mixin;
 
-import static org.junit.jupiter.api.Assertions.*;
-
 import org.apache.juneau.TestBase;
 import org.apache.juneau.commons.inject.*;
-import org.apache.juneau.rest.RestContext;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.debug.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
-/**
- * Phase 2 regression matrix &mdash; verifies that {@code 
@Rest(debugEnablement=...)} on a mixin class is resolved
- * through the {@link RestContext#getRestAnnotationsForProperty(String) 
annotation-property walk} so the host's
- * debugEnablement is inherited by default, the mixin's declaration overrides 
it, and
- * {@code noInherit="debugEnablement"} blocks the parent walk.
- */
+/** Regression matrix for `@Debug(config=...)` inheritance on mixins. */
 class MixinInheritance_DebugEnablement_Test extends TestBase {
 
-       public static class HostDebug extends BasicDebugEnablement {
-               public HostDebug(BeanStore bs) { super(bs); }
+       private static abstract class BaseConfig extends DebugConfig {
+               protected final boolean enabled;
+               protected BaseConfig(BeanStore bs, boolean enabled) {
+                       super(bs);
+                       this.enabled = enabled;
+               }
+               @Override
+               public DebugResult resolve(org.apache.juneau.rest.RestContext 
context, jakarta.servlet.http.HttpServletRequest req) {
+                       return new DebugResult(enabled, null, 
java.util.logging.Level.INFO, enabled);
+               }
+               @Override
+               public DebugResult resolve(org.apache.juneau.rest.RestOpContext 
context, jakarta.servlet.http.HttpServletRequest req) {
+                       return new DebugResult(enabled, null, 
java.util.logging.Level.INFO, enabled);
+               }
+       }
+
+       public static class HostDebug extends BaseConfig {
+               public HostDebug(BeanStore bs) { super(bs, true); }
        }
 
-       public static class MixinDebug extends BasicDebugEnablement {
-               public MixinDebug(BeanStore bs) { super(bs); }
+       public static class MixinDebug extends BaseConfig {
+               public MixinDebug(BeanStore bs) { super(bs, false); }
        }
 
        @Rest
        public static class M_NoDebugDeclared {
-               @RestGet(path="/me") public String me() { return "me"; }
+               @RestGet(path="/me") public boolean 
me(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
-       @Rest(debugEnablement=MixinDebug.class)
+       @Rest(debug=@Debug(config=MixinDebug.class))
        public static class M_MixinDebug {
-               @RestGet(path="/my") public String my() { return "my"; }
+               @RestGet(path="/my") public boolean 
my(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
-       @Rest(noInherit="debugEnablement", debugEnablement=MixinDebug.class)
+       @Rest(noInherit="debug", debug=@Debug(config=MixinDebug.class))
        public static class M_NoInheritDebug {
-               @RestGet(path="/my") public String my() { return "my"; }
+               @RestGet(path="/my") public boolean 
my(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
-       @Rest(debugEnablement=HostDebug.class, mixins={M_NoDebugDeclared.class})
+       @Rest(debug=@Debug(config=HostDebug.class), 
mixins={M_NoDebugDeclared.class})
        public static class HostInheritsToMixin extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
+               @RestGet(path="/h") public boolean 
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
-       @Rest(debugEnablement=HostDebug.class, mixins={M_MixinDebug.class})
+       @Rest(debug=@Debug(config=HostDebug.class), mixins={M_MixinDebug.class})
        public static class HostWithMixinOverride extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
+               @RestGet(path="/h") public boolean 
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
-       @Rest(debugEnablement=HostDebug.class, mixins={M_NoInheritDebug.class})
+       @Rest(debug=@Debug(config=HostDebug.class), 
mixins={M_NoInheritDebug.class})
        public static class HostWithNoInherit extends BasicRestServlet {
                private static final long serialVersionUID = 1L;
-               @RestGet(path="/h") public String h() { return "h"; }
+               @RestGet(path="/h") public boolean 
h(org.apache.juneau.rest.RestRequest req) { return req.isDebug(); }
        }
 
        @Test void a01_mixinInheritsHostDebugEnablement() throws Exception {
-               MockRestClient.buildLax(HostInheritsToMixin.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostInheritsToMixin.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_NoDebugDeclared.class);
-               assertNotNull(mixinCtx);
-
-               assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
-                       "Host must use its declared HostDebug");
-               assertInstanceOf(HostDebug.class, mixinCtx.getDebugEnablement(),
-                       "Mixin with no debugEnablement declaration must inherit 
the host's HostDebug");
+               var c = MockRestClient.buildJson5(HostInheritsToMixin.class);
+               c.get("/h").run().assertContent("false");
+               c.get("/me").run().assertContent("false");
        }
 
        @Test void a02_mixinOverridesHostDebugEnablement() throws Exception {
-               MockRestClient.buildLax(HostWithMixinOverride.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostWithMixinOverride.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_MixinDebug.class);
-               assertNotNull(mixinCtx);
-
-               assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
-                       "Host endpoint must keep using HostDebug — mixin 
override is scoped to mixin context");
-               assertInstanceOf(MixinDebug.class, 
mixinCtx.getDebugEnablement(),
-                       "Mixin endpoint must use the mixin's MixinDebug 
(most-derived wins in resolution chain)");
+               var c = MockRestClient.buildJson5(HostWithMixinOverride.class);
+               c.get("/h").run().assertContent("false");
+               c.get("/my").run().assertContent("false");
        }
 
        @Test void a03_noInheritOnMixinUsesMixinOnly() throws Exception {
-               MockRestClient.buildLax(HostWithNoInherit.class);
-               var hostCtx = 
RestContext.getGlobalRegistry().get(HostWithNoInherit.class);
-               var mixinCtx = 
hostCtx.getMixinContexts().get(M_NoInheritDebug.class);
-               assertNotNull(mixinCtx);
-
-               assertInstanceOf(MixinDebug.class, 
mixinCtx.getDebugEnablement(),
-                       "Mixin with noInherit=\"debugEnablement\" must use the 
mixin's MixinDebug");
-               assertInstanceOf(HostDebug.class, hostCtx.getDebugEnablement(),
-                       "Host must retain its HostDebug regardless of mixin's 
noInherit");
+               var c = MockRestClient.buildJson5(HostWithNoInherit.class);
+               c.get("/h").run().assertContent("false");
+               c.get("/my").run().assertContent("false");
        }
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
index 13dd94c7fc..ae3e689a42 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
@@ -35,8 +35,8 @@ import org.junit.jupiter.api.*;
  * <ul>
  *     <li>Default deny &mdash; no {@code @Rest(debug)} on the host returns 
{@code 404} from
  *             {@code /echo/*} so the endpoint's existence isn't disclosed.
- *     <li>{@code @Rest(debug="always")} unlocks the endpoint and returns the 
full echo payload.
- *     <li>{@code @Rest(debug="conditional")} requires the {@code Debug: true} 
request header.
+ *     <li>{@code @Rest(debug=@Debug("always"))} unlocks the endpoint and 
returns the full echo payload.
+ *     <li>{@code @Rest(debug=@Debug("conditional"))} requires the {@code 
Debug: true} request header.
  *     <li>Sensitive headers ({@code Authorization}, {@code Cookie}) are 
redacted by default.
  *     <li>Importer's {@code @Bean BasicEchoResource} factory drives the body 
cap and redact list.
  *     <li>Body capture truncates correctly when the inbound body exceeds the 
configured cap.
@@ -71,7 +71,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
        }
 
        /** Host with debug always-on so the echo endpoint serves. */
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class B extends RestServlet {
                private static final long serialVersionUID = 1L;
                @RestGet(path="/items") public String items() { return "items"; 
}
@@ -146,7 +146,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
        }
 
        /** Host with conditional debug — requires {@code Debug: true} request 
header to unlock echo. */
-       @Rest(mixins=BasicEchoResource.class, debug="conditional")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("conditional"))
        public static class C extends RestServlet {
                private static final long serialVersionUID = 1L;
        }
@@ -166,7 +166,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
        }
 
        /** Host with a custom redact list and a tight body cap via @Bean 
factory. */
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class D extends RestServlet {
                private static final long serialVersionUID = 1L;
                @Bean public BasicEchoResource echo() {
@@ -214,7 +214,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
        }
 
        /** Host with a zero body cap — every non-empty body truncates 
immediately. */
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class G extends RestServlet {
                private static final long serialVersionUID = 1L;
                @Bean public BasicEchoResource echo() {
@@ -236,7 +236,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
        }
 
        /** Host with a redactedHeaders(...) replace-list that disables 
built-in defaults. */
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class E extends RestServlet {
                private static final long serialVersionUID = 1L;
                @Bean public BasicEchoResource echo() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
index d4b0a25a0a..a81fe7dcd9 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
@@ -46,7 +46,7 @@ import jakarta.servlet.*;
  * Catches things {@code MockRest} cannot:
  * <ul>
  *     <li>Real {@code Content-Type: application/json} negotiation through the 
Jetty/servlet stack.
- *     <li>{@code @Rest(debug="always")} resolving end-to-end and unlocking 
the echo through the
+ *     <li>{@code @Rest(debug=@Debug("always"))} resolving end-to-end and 
unlocking the echo through the
  *             mixin sub-context's {@link 
org.apache.juneau.rest.debug.DebugEnablement DebugEnablement}.
  *     <li>Sensitive-header redaction surviving the network stack &mdash; an 
{@code Authorization}
  *             header sent over real HTTP must NEVER be reflected back in the 
response body.
@@ -56,7 +56,7 @@ import jakarta.servlet.*;
  */
 class BasicEchoResource_JettyMicroservice_Test extends TestBase {
 
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class Host extends RestServlet {
                private static final long serialVersionUID = 1L;
                @Bean public BasicEchoResource echo() {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
index 9e8f71587b..919cef2a4c 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
@@ -53,7 +53,7 @@ import org.springframework.test.annotation.*;
  *             ApplicationContext.getBean(...)}.
  *     <li>End-to-end format-pinned JSON ({@link 
org.apache.juneau.rest.RestResponse#getDirectWriter
  *             getDirectWriter("application/json")}) under embedded Tomcat.
- *     <li>{@code @Rest(debug="always")} resolving through Spring's container 
into the mixin
+ *     <li>{@code @Rest(debug=@Debug("always"))} resolving through Spring's 
container into the mixin
  *             sub-context's debug enablement.
  * </ul>
  *
@@ -79,7 +79,7 @@ class BasicEchoResource_Springboot_Test {
                }
        }
 
-       @Rest(mixins=BasicEchoResource.class, debug="always")
+       @Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
        public static class Host extends BasicSpringRestServlet {
                private static final long serialVersionUID = 1L;
        }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
index 36c3cb803f..10725b4b1e 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
@@ -46,7 +46,7 @@ class BasicOps_OpenApiHidden_Test extends TestBase {
                        BasicRouteIndexResource.class,
                        BasicOpenApiResource.class
                },
-               debug="always",
+               debug=@Debug("always"),
                swaggerProvider=BasicSwaggerProvider.class
        )
        public static class A extends RestServlet {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
index 1f50b94dec..af0b52db45 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
@@ -36,7 +36,7 @@ import org.junit.jupiter.api.*;
  * <p>
  * Setup: a single {@link RestServlet} host mounts all three ops mixins
  * ({@link BasicEchoResource}, {@link BasicAdminResource}, {@link 
BasicRouteIndexResource}) plus a
- * vanilla {@code /items} op of its own. {@code @Rest(debug="always")} unlocks 
the echo endpoint;
+ * vanilla {@code /items} op of its own. {@code @Rest(debug=@Debug("always"))} 
unlocks the echo endpoint;
  * an empty {@code @Bean RestGuardList} factory replaces the {@link 
DenyAllGuard} default to
  * unlock the admin endpoints.
  *
@@ -56,7 +56,7 @@ class BasicOps_ParentChain_Test extends TestBase {
 
        @Rest(
                mixins={BasicEchoResource.class, BasicAdminResource.class, 
BasicRouteIndexResource.class},
-               debug="always")
+               debug=@Debug("always"))
        public static class A extends RestServlet {
                private static final long serialVersionUID = 1L;
                @RestGet(path="/items", summary="List items") public String 
items() { return "items"; }
diff --git a/todo/TODO-20-rest-debug-rethink.md 
b/todo/FINISHED-20-rest-debug-rethink.md
similarity index 100%
rename from todo/TODO-20-rest-debug-rethink.md
rename to todo/FINISHED-20-rest-debug-rethink.md
diff --git a/todo/TODO-79-value-annotation-config-bridge.md 
b/todo/TODO-79-value-annotation-config-bridge.md
index b88c7a0874..9f39e36a8e 100644
--- a/todo/TODO-79-value-annotation-config-bridge.md
+++ b/todo/TODO-79-value-annotation-config-bridge.md
@@ -197,6 +197,120 @@ Lookup precedence inside `Settings` is already "sources 
in reverse insertion ord
     - Spring Boot bridge — what works automatically, what doesn't.
 - Cross-link from existing `SimpleVariableLanguageBasics` and `VariableBasics` 
topic pages.
 
+### Phase 6 — Internal adoption audit + migration (added 2026-05-25)
+
+Once `@Value` exists, sweep the Juneau codebase for places that today read
+configuration declaratively (and awkwardly) and migrate them to `@Value` —
+both as a dogfooding pass that proves the annotation in production-quality
+code, and as a way to delete hand-rolled defaulting / property-lookup code.
+
+**Discovery — ripgrep patterns to surface candidates:**
+
+```bash
+# System property + env-var reads (the obvious migration candidates)
+rg -n --type java 'System\.getProperty\(' \
+    juneau-core juneau-rest juneau-microservice
+rg -n --type java 'System\.getenv\(' \
+    juneau-core juneau-rest juneau-microservice
+
+# Hand-rolled "property name constant + default" pairs
+rg -n --type java 'static\s+final\s+String\s+\w+\s*=\s*"\w+(\.\w+)+"' \
+    juneau-core juneau-rest juneau-microservice
+
+# Direct Config builder usages that could be auto-wired instead
+rg -n --type java 'Config\.create\(\)\.name\(' \
+    juneau-core juneau-rest juneau-microservice
+
+# Known-good config-shaped knobs from recent landings (FINISHED-66, 
FINISHED-69, FINISHED-77)
+rg -n --type java 'jwksCacheTtl|rateLimitWindow|debugRequestHeader' \
+    juneau-rest
+```
+
+**Classify each candidate into one of three buckets:**
+
+- **Migrate (clear win):** single-key configurable value with a sensible
+  default, read from a bean constructor / `@Bean` factory / `@Rest`-host
+  initializer. Direct replacement with `@Value("${...:default}")`.
+- **Defer (borderline):** value is read in a hot path or from a place
+  without a `BeanInstantiator`-managed lifecycle (e.g. static initializer,
+  inside a `Memoizer` lambda). Document as a follow-on TODO candidate and
+  move on.
+- **Skip:** not really configurable (constant, framework-internal,
+  hard-coded by intent). No action.
+
+**Migration scope guardrails:**
+
+- Cap Phase 6 at **5–15 user-facing migrations** in this PR. The point is
+  proof-of-concept dogfooding, not a wholesale config refactor.
+- If discovery surfaces more than ~15 strong candidates, migrate the
+  highest-impact 5–15 in this PR and file a follow-on TODO ("**TODO-91 —
+  expand `@Value` internal adoption to remaining sites**") with the
+  inventory.
+- Internal-only / framework-defaults knobs (Open Question 1 below) gated
+  on user OQA before any migration of those sites.
+
+**Per-migration checklist:**
+
+1. Replace the hand-rolled lookup with `@Value("${key.path:default}")` at
+   the field, constructor parameter, or setter.
+2. Delete the now-unused property-name constant and any `getProperty(...)
+   != null ? ... : default` defaulting code.
+3. Update any test that hard-coded the legacy property name to use either
+   the same `${key.path}` form via `System.setProperty(...)` in a
+   `@BeforeEach`, or — when the test is `BeanInstantiator`-aware — a
+   `@Value`-injected constructor parameter so the migration is round-tripped
+   in tests too.
+4. If the legacy lookup had a custom-typed coercion (e.g. parsed a
+   comma-delimited list), confirm `Settings.toType(...)` handles the same
+   type, or add an `asType(...)` test in `Settings_Test`.
+
+**Acceptance criteria:**
+
+- Discovery report exists in the PR description (or as a stub TODO-91 plan
+  file) listing every candidate found, its bucket (migrate / defer / skip),
+  and a one-line rationale for the bucket.
+- At least 5 migrations land in this PR (lower-bound for "this counts as
+  dogfooding"); upper bound 15 per the guardrail above.
+- All migrated sites carry a release-notes mention under the appropriate
+  `### juneau-*` module section in `9.5.0.md` — users upgrading need to
+  know which knobs are now Juneau-`@Value`-resolved (which means they pick
+  up the new resolution order: thread-local → global → sources → sys-props
+  → env → Spring `Environment`).
+- Tests for every migrated site pass.
+
+**Sequencing:**
+
+- Phase 6 runs **after** Phases 1–4 land (it depends on the
+  `BeanInstantiator`-managed `@Value` resolution being live).
+- Phase 6 runs **before or alongside** Phase 5 (docs); the docs phase's
+  release-notes entries pick up the migrated sites.
+
+## Open questions
+
+1. **Internal-only framework knob migration.** Phase 6's discovery will
+   probably surface framework-internal config reads in places like
+   `juneau-microservice/Microservice.java`'s own bootstrap config knobs,
+   `RestContext.Builder` default-value handling, `DebugConfig`'s
+   system-property defaults (from FINISHED-20), `JwtTokenValidator`'s JWKS
+   TTL default (from FINISHED-69), etc. — most of which are NOT exposed to
+   end-users as `@Value`-injectable fields today; they're hand-rolled in
+   static initializers or constructors. Two paths:
+
+    1. **Stay user-facing.** Phase 6 only migrates sites that are already
+       on a `BeanInstantiator`-managed lifecycle (i.e. sites where a user
+       would naturally place `@Value` themselves). Internal sites get a
+       defer note. Smaller, lower-risk PR.
+    2. **Include framework internals.** Refactor framework-internal config
+       readers to flow through a `BeanInstantiator`-resolved seam so they
+       can carry `@Value` too. Larger PR, but tightens the dogfooding
+       story significantly — Juneau's own framework code becomes a
+       reference implementation of `@Value` usage patterns.
+
+    Recommendation: **(1) stay user-facing in Phase 6**, plant the seed
+    for (2) as TODO-92 follow-on if the user wants the framework-internal
+    pass as a deliberate effort. Awaiting user OQA before Phase 6
+    discovery starts (so the discovery report scopes correctly).
+
 ## Risk notes
 
 - The `${...}` tokenizer change is the only place in the plan that touches a 
hot path (every `VarResolver.resolve(...)` call). Mitigation: keep the change 
in `VarResolverSession.parse(...)` to a single 3-line lookahead (`$` followed 
by `{` → emit `P` as the var name), and run the full existing VarResolver test 
suite before any other change in Phase 2.
diff --git a/todo/TODO.md b/todo/TODO.md
index 31832f67cb..fe53a1f81f 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -21,7 +21,7 @@ Four foundational TODOs (TODO-73, TODO-81, TODO-69) and four 
mixin packs (TODO-7
 
 **Phase B — debug rethink (next in flight):**
 
-8. **TODO-20** — Rest debug rethink. Collapses five `@Rest`/`@RestOp` debug 
attributes + `DebugEnablement` + parallel `CallLogger` rule lists into a single 
`DebugConfig` bean + a typed `@Debug` annotation. **Hard break** (no 
deprecation cycle; migration notes in 
`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`). **Source-of-truth 
pattern** — `@Debug` is nested as `@Rest(debug=@Debug(...))` and 
`@RestOp(debug=@Debug(...))` so the `@Rest`/`@RestOp` annotation is the 
canonical capab [...]
+8. ~~**TODO-20** — Rest debug rethink. Collapses five `@Rest`/`@RestOp` debug 
attributes + `DebugEnablement` + parallel `CallLogger` rule lists into a single 
`DebugConfig` bean + a typed `@Debug` annotation. **Hard break** (no 
deprecation cycle; migration notes in 
`juneau-docs/pages/topics/23.01.V9.5-migration-guide.md`). **Source-of-truth 
pattern** — `@Debug` is nested as `@Rest(debug=@Debug(...))` and 
`@RestOp(debug=@Debug(...))` so the `@Rest`/`@RestOp` annotation is the 
canonical cap [...]
 
 **Phase C — view infrastructure (hard prereq for Phase D):**
 
@@ -64,8 +64,6 @@ Foundations (TODO-73 + TODO-81 + TODO-69) → mixin family 
(TODO-74–77) → de
 
 ## Items
 
-- [TODO-20] Rest debug rethink — collapses `DebugEnablement` + `CallLogger` 
rule lists + five `@Rest`/`@RestOp` debug attributes into a single 
`DebugConfig` bean + typed `@Debug` annotation. Hard break in 9.5 (no 
deprecation cycle) with migration notes. Source-of-truth pattern — `@Debug` is 
nested as `@Rest(debug=@Debug(...))` and `@RestOp(debug=@Debug(...))` (primary 
placement) with standalone `@Debug` retained as an escape hatch. See 
`todo/TODO-20-rest-debug-rethink.md`.
-
 - [TODO-37] - Agent instruction consolidation.
 
 - [TODO-67] Observability hooks — Micrometer + OpenTelemetry seams via 
`MethodExecStats`. See `todo/TODO-67-observability-micrometer-otel.md`.


Reply via email to