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 f8486683b1 Add fluent programmatic builder API for REST resources and 
mixins
f8486683b1 is described below

commit f8486683b13c7c3744568a05c76dcdf5207a5e65
Author: James Bognar <[email protected]>
AuthorDate: Mon Jun 1 14:56:33 2026 -0400

    Add fluent programmatic builder API for REST resources and mixins
    
    Reintroduces a public, fluent builder surface (RestBuilder / 
AbstractRestBuilder)
    for configuring @Rest resources and mixins programmatically (the Spring 
@Bean use
    case) with builder-set values winning over @Rest annotation values.
    
    - RestBuilder<SELF> / AbstractRestBuilder<R, SELF>: full self-type CRTP 
spine
      (the builder is the override bag); 55 @Rest members + mdcAsyncPropagation 
+
      the generic set() escape hatch.
    - Three flavor builders (RestServlet/RestResource/RestMixin) leave SELF 
open for
      true covariant chaining, each with a concrete DefaultBuilder<R> leaf.
    - Constructor trio (no-arg + Foo(RestBuilder) + Foo.Builder) with a 
package-private
      restBuilder stash; RestContext.Args carries it and RestContext prepends a
      synthesized @Rest so builder values beat annotations (rung-1 precedence).
    - OQ-11 mirror-and-forward for the worker-backed multi-flavor capabilities
      (FaviconMixin -> FaviconProvider, VersionMixin -> VersionProvider) via
      composition; WellKnownMixin/SeoMixin re-parented for a uniform surface.
    - BeanInstantiator exact-type builder preference (Option D): strict-typed 
builder
      candidates are preferred and weak supertype-only candidates are declined 
when a
      usable direct constructor exists, resolving the builder(Class) 
autodetection
      collision in mixin sub-context instantiation.
    
    Adds RestBuilder_Test and BeanInstantiator_OptionD_Test; updates the 
heuristic-
    pinning BeanInstantiator_Test.w09 to the new stricter selection. Also adds
    constructor-rationale Javadoc to the worker+builder constructors.
---
 .../juneau/commons/inject/BeanInstantiator.java    | 104 +++-
 .../apache/juneau/rest/AbstractRestBuilder.java    | 353 +++++++++++++
 .../java/org/apache/juneau/rest/RestBuilder.java   | 561 +++++++++++++++++++++
 .../java/org/apache/juneau/rest/RestContext.java   | 107 +++-
 .../juneau/rest/convention/FaviconMixin.java       |  53 +-
 .../apache/juneau/rest/convention/SeoMixin.java    |  21 +-
 .../juneau/rest/convention/VersionMixin.java       |  44 +-
 .../juneau/rest/convention/WellKnownMixin.java     |  21 +-
 .../org/apache/juneau/rest/servlet/RestMixin.java  |  97 +++-
 .../apache/juneau/rest/servlet/RestResource.java   |  84 +++
 .../apache/juneau/rest/servlet/RestServlet.java    |  95 +++-
 .../inject/BeanInstantiator_OptionD_Test.java      | 165 ++++++
 .../commons/inject/BeanInstantiator_Test.java      |  19 +-
 .../org/apache/juneau/rest/RestBuilder_Test.java   | 164 ++++++
 juneau-utest/test-run-history.tsv                  |   1 +
 15 files changed, 1845 insertions(+), 44 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanInstantiator.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanInstantiator.java
index c09f8aa89a..6dc0641063 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanInstantiator.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/BeanInstantiator.java
@@ -2129,17 +2129,30 @@ public class BeanInstantiator<T> {
                }
 
                // Priority 3: Autodetect
-               // 3a: Look for static create/builder method that returns a 
potential builder type
-               r = beanSubType.getPublicMethods().stream()
+               // 3a: Look for static create/builder method that returns a 
potential builder type.
+               var staticBuilderTypes = beanSubType.getPublicMethods().stream()
                        .filter(x -> x.isAll(STATIC, NOT_DEPRECATED, 
NOT_SYNTHETIC, NOT_BRIDGE))
                        .filter(x -> 
builderMethodNames.contains(x.getNameSimple()))
                        .filter(x -> ! x.hasReturnType(beanSubType)) // Must 
not return the bean type itself
                        .filter(x -> isValidBuilderType(x.getReturnType()))
-                       .findFirst()
-                       .map(MethodInfo::getReturnType);
-               if (r.isPresent()) {
-                       log("Found builder via static method: %s", 
r.get().getName());
-                       return r.get();
+                       .map(MethodInfo::getReturnType)
+                       .toList();
+               if (! staticBuilderTypes.isEmpty()) {
+                       // TODO-143 (Option D): prefer a factory whose builder 
builds the EXACT requested type (or a
+                       // subtype) over one that only promises a supertype — 
e.g. an inherited generic builder(Class)
+                       // whose build() erases to a base class. A 
supertype-only factory must not displace a stricter path.
+                       var strict = 
staticBuilderTypes.stream().filter(this::isStrictBuilderType).findFirst();
+                       if (strict.isPresent()) {
+                               log("Found builder via static method (strict): 
%s", strict.get().getName());
+                               return strict.get();
+                       }
+                       var weak = gateWeakBuilder(staticBuilderTypes.get(0));
+                       if (weak != null) {
+                               log("Found builder via static method: %s", 
weak.getName());
+                               return weak;
+                       }
+                       // Weak/supertype-only static factory rejected in favor 
of a direct constructor; fall through to
+                       // 3b/3c (a stricter inner Builder may still exist) and 
ultimately the constructor path.
                }
 
                // 3b: Look for inner Builder class
@@ -2150,12 +2163,17 @@ public class BeanInstantiator<T> {
                if (r.isPresent()) {
                        var builderClass = r.get();
                        if (isValidBuilderType(builderClass)) {
-                               log("Found builder via inner class: %s", 
builderClass.getName());
+                               var gated = isStrictBuilderType(builderClass) ? 
builderClass : gateWeakBuilder(builderClass);
+                               if (gated != null) {
+                                       log("Found builder via inner class: 
%s", gated.getName());
+                                       return gated;
+                               }
+                               // Weak inner builder rejected in favor of a 
direct constructor; fall through.
+                       } else {
+                               // Still return it so we can provide a better 
error message when trying to use it
+                               log("Found builder class via inner class but it 
is not valid: %s", builderClass.getName());
                                return builderClass;
                        }
-                       // Still return it so we can provide a better error 
message when trying to use it
-                       log("Found builder class via inner class but it is not 
valid: %s", builderClass.getName());
-                       return builderClass;
                }
 
                // 3c: Look for builder in parent classes and implemented 
interfaces (skip beanSubType itself,
@@ -2170,8 +2188,13 @@ public class BeanInstantiator<T> {
                        if (r.isPresent()) {
                                var builderClass = r.get();
                                if (isValidBuilderType(builderClass)) {
-                                       log("Found builder via parent inner 
class: %s", builderClass.getName());
-                                       return builderClass;
+                                       var gated = 
isStrictBuilderType(builderClass) ? builderClass : 
gateWeakBuilder(builderClass);
+                                       if (gated != null) {
+                                               log("Found builder via parent 
inner class: %s", gated.getName());
+                                               return gated;
+                                       }
+                                       // Weak parent inner builder rejected 
in favor of a direct constructor; keep scanning.
+                                       continue;
                                }
                                // Still return it so we can provide a better 
error message when trying to use it
                                log("Found builder class via parent inner class 
but it is not valid: %s", builderClass.getName());
@@ -2184,6 +2207,61 @@ public class BeanInstantiator<T> {
                return null;
        }
 
+       /**
+        * Tests whether a builder candidate builds the <b>exact</b> requested 
bean type (or a subtype of it),
+        * as opposed to only promising a supertype.
+        *
+        * <p>
+        * Used by {@link #findBuilderType()} (TODO-143 Option D) to prefer a 
precise builder over an inherited
+        * generic base builder whose {@code build()} erases to a parent class 
(e.g. a self-typed REST
+        * {@code DefaultBuilder} whose {@code build()} returns {@code 
RestMixin}).
+        *
+        * @param builderCandidate The builder type to test.
+        * @return <jk>true</jk> if the candidate has a 0-arg (or {@code 
@Inject}) build/create/get method whose
+        *      return type is {@code beanSubType} or a subtype of it.
+        */
+       private boolean isStrictBuilderType(ClassInfo builderCandidate) {
+               return builderCandidate.getPublicMethods().stream()
+                       .filter(x -> x.isAll(NOT_STATIC, NOT_DEPRECATED, 
NOT_SYNTHETIC, NOT_BRIDGE))
+                       .filter(x -> 
buildMethodNames.contains(x.getNameSimple()))
+                       .filter(x -> { var rt = x.getReturnType(); return 
rt.is(beanSubType.inner()) || beanSubType.isParentOf(rt); })
+                       .anyMatch(x -> 
x.getAnnotations().stream().anyMatch(JsrSupport::isInjectAnnotation) || 
x.getParameterCount() == 0);
+       }
+
+       /**
+        * TODO-143 Option D gate: a builder candidate that only builds a 
<i>supertype</i> of the requested bean
+        * type must not be used when the requested type is concretely 
instantiable via a direct constructor.
+        *
+        * <p>
+        * Returns the candidate unchanged when it is safe to use, or {@code 
null} to signal "prefer the
+        * constructor path". This is only invoked for non-strict 
(supertype-only) candidates.
+        *
+        * @param builderCandidate The (weak) builder candidate.
+        * @return The candidate, or {@code null} to prefer the 
direct-constructor path.
+        */
+       private ClassInfo gateWeakBuilder(ClassInfo builderCandidate) {
+               if (hasUsableDirectConstructor()) {
+                       log("Builder candidate %s only builds a supertype of %s 
and a usable direct constructor exists; preferring the constructor.", 
builderCandidate.getName(), beanSubType.getName());
+                       return null;
+               }
+               return builderCandidate;
+       }
+
+       /**
+        * Tests whether the requested bean type is concretely instantiable via 
a non-private constructor whose
+        * parameters can all be resolved from the bean store (a no-arg 
constructor always qualifies).
+        *
+        * @return <jk>true</jk> if a usable direct constructor exists on 
{@code beanSubType}.
+        */
+       private boolean hasUsableDirectConstructor() {
+               if (beanSubType.isAbstract() || beanSubType.isInterface())
+                       return false;
+               return beanSubType.getDeclaredConstructors().stream()
+                       .filter(x -> x.isAll(NOT_DEPRECATED, NOT_PRIVATE))
+                       .filter(x -> x.isDeclaringClass(beanSubType))
+                       .anyMatch(x -> x.canResolveAllParameters(store, 
enclosingInstance));
+       }
+
        /**
         * Finds all builder types, including the primary builder type and any 
builder types found in the builder's parent hierarchy.
         *
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/AbstractRestBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/AbstractRestBuilder.java
new file mode 100644
index 0000000000..4296c9cda0
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/AbstractRestBuilder.java
@@ -0,0 +1,353 @@
+/*
+ * 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;
+
+import static org.apache.juneau.commons.utils.AssertionUtils.*;
+
+import java.util.*;
+
+import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.arg.*;
+import org.apache.juneau.rest.converter.*;
+import org.apache.juneau.rest.guard.*;
+import org.apache.juneau.rest.logger.*;
+import org.apache.juneau.rest.openapi.*;
+import org.apache.juneau.rest.processor.*;
+import org.apache.juneau.rest.staticfile.*;
+import org.apache.juneau.rest.swagger.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * Shared abstract base for the {@link RestBuilder} flavor builders ({@code 
RestServlet.Builder},
+ * {@code RestResource.Builder}, {@code RestMixin.Builder}).
+ *
+ * <p>
+ * Each fluent {@code @Rest}-member setter is implemented <b>once</b> here and 
forwards into a backing
+ * {@link RestAnnotation.Builder} &mdash; the override bag.  At {@link 
RestContext} construction time the bag is
+ * turned into a synthetic, highest-priority {@code @Rest} annotation that is 
prepended to the resource's
+ * {@code @Rest} chain, so builder-supplied values <b>take precedence</b> over 
the class's own
+ * {@link org.apache.juneau.rest.annotation.Rest @Rest} annotation values.
+ *
+ * <h5 class='section'>Self type (CRTP):</h5>
+ *
+ * <p>
+ * {@code SELF} is the concrete builder type, left open through the flavor 
builders and their user subclasses so
+ * that bespoke setters on a subclass chain with true covariant returns.  
Setters return {@link #self()}.
+ *
+ * @param <R> The resource type produced by {@link #build()}.
+ * @param <SELF> The concrete builder type (self type).
+ * @since 9.5.0
+ */
+@SuppressWarnings({
+       "unchecked", // CRTP self-type cast in self() is safe by construction.
+       "java:S1452"  // Wildcard return on getResourceType() is intentional.
+})
+public abstract class AbstractRestBuilder<R, SELF extends 
AbstractRestBuilder<R, SELF>> implements RestBuilder<SELF> {
+
+       private final Class<R> resourceType;
+       private final RestAnnotation.Builder anno = RestAnnotation.create();
+       private final Map<String,Object> extras = new LinkedHashMap<>();
+       private Boolean mdcAsyncPropagation;
+
+       /**
+        * Constructor.
+        *
+        * @param resourceType The resource type produced by {@link #build()}.  
Must not be <jk>null</jk>.
+        */
+       protected AbstractRestBuilder(Class<R> resourceType) {
+               this.resourceType = assertArgNotNull("resourceType", 
resourceType);
+       }
+
+       /**
+        * Returns this builder cast to the self type.
+        *
+        * @return This object.
+        */
+       protected final SELF self() {
+               return (SELF)this;
+       }
+
+       /**
+        * Builds the configured resource instance.
+        *
+        * @return A new resource instance configured by this builder.
+        */
+       public abstract R build();
+
+       /**
+        * Reflectively instantiates the resource type, preferring a {@code 
(RestBuilder)} constructor (constructor
+        * injection &mdash; TODO-145 &sect;2.4 constructor trio) and falling 
back to the no-arg constructor.
+        *
+        * <p>
+        * Flavor builder {@code build()} implementations call this then stash 
{@code this} on the returned instance.
+        *
+        * @return A new, uninitialized resource instance.
+        */
+       protected R createResource() {
+               var ci = ClassInfo.of(resourceType);
+               var ctor = ci.getDeclaredConstructor(x -> 
x.hasParameterTypes(RestBuilder.class)).orElse(null);
+               if (ctor != null)
+                       return ctor.accessible().newInstance(this);
+               var noArg = ci.getNoArgConstructor(Visibility.PRIVATE)
+                       .orElseThrow(() -> new IllegalStateException("Resource 
class " + resourceType.getName() + " has no no-arg or RestBuilder 
constructor."));
+               return noArg.accessible().newInstance();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Override-bag accessors (consumed by RestContext during construction)
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Returns the resource type produced by this builder.
+        *
+        * @return The resource type.  Never <jk>null</jk>.
+        */
+       public Class<?> getResourceType() {
+               return resourceType;
+       }
+
+       /**
+        * Returns the synthetic {@code @Rest} annotation carrying all 
builder-set members, for use as the
+        * highest-priority contributor of the {@code @Rest} resolution chain.
+        *
+        * @return The synthetic annotation.  Never <jk>null</jk>.
+        */
+       public Rest toRestAnnotation() {
+               return anno.build();
+       }
+
+       /**
+        * Returns the programmatic MDC async-propagation override, or 
<jk>null</jk> if not set.
+        *
+        * @return The override, or <jk>null</jk>.
+        */
+       public Boolean getMdcAsyncPropagation() {
+               return mdcAsyncPropagation;
+       }
+
+       /**
+        * Returns the forward-compat extras set via {@link #set(String, 
Object)}.
+        *
+        * @return An unmodifiable view of the extras map.  Never <jk>null</jk>.
+        */
+       public Map<String,Object> getExtras() {
+               return Collections.unmodifiableMap(extras);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Identity & mounting
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF path(String value) { anno.path(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF paths(String... value) { anno.paths(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF children(Class<?>... value) { anno.children(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF mixins(Class<?>... value) { anno.mixins(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF uriAuthority(String value) { anno.uriAuthority(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF uriContext(String value) { anno.uriContext(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF uriRelativity(String value) { anno.uriRelativity(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF uriResolution(String value) { anno.uriResolution(value); 
return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Marshalling
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF serializers(Class<? extends Serializer>... value) { 
anno.serializers(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF parsers(Class<?>... value) { anno.parsers(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF encoders(Class<? extends Encoder>... value) { 
anno.encoders(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF partSerializer(Class<? extends HttpPartSerializer> value) { 
anno.partSerializer(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF partParser(Class<? extends HttpPartParser> value) { 
anno.partParser(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF consumes(String... value) { anno.consumes(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF produces(String... value) { anno.produces(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF responseProcessors(Class<? extends ResponseProcessor>... 
value) { anno.responseProcessors(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF allowedSerializerOptions(String... value) { 
anno.allowedSerializerOptions(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF allowedParserOptions(String... value) { 
anno.allowedParserOptions(value); return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Request behavior
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF allowedHeaderParams(String value) { 
anno.allowedHeaderParams(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF allowedMethodHeaders(String value) { 
anno.allowedMethodHeaders(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF allowedMethodParams(String value) { 
anno.allowedMethodParams(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF clientVersionHeader(String value) { 
anno.clientVersionHeader(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultAccept(String value) { anno.defaultAccept(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultContentType(String value) { 
anno.defaultContentType(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultCharset(String value) { anno.defaultCharset(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultRequestAttributes(String... value) { 
anno.defaultRequestAttributes(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultRequestHeaders(String... value) { 
anno.defaultRequestHeaders(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF defaultResponseHeaders(String... value) { 
anno.defaultResponseHeaders(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF disableContentParam(String value) { 
anno.disableContentParam(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF maxInput(String value) { anno.maxInput(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF restOpArgs(Class<? extends RestOpArg>... value) { 
anno.restOpArgs(value); return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Security
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF guards(Class<? extends RestGuard>... value) { 
anno.guards(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF roleGuard(String value) { anno.roleGuard(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF rolesDeclared(String value) { anno.rolesDeclared(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF converters(Class<? extends RestConverter>... value) { 
anno.converters(value); return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Lifecycle / perf
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF eagerInit(String value) { anno.eagerInit(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF lazyChildren(String value) { anno.lazyChildren(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF virtualThreads(String value) { anno.virtualThreads(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF asyncTimeoutMillis(String value) { 
anno.asyncTimeoutMillis(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF asyncCompletionExecutor(String value) { 
anno.asyncCompletionExecutor(value); return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Observability / logging / errors
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF callLogger(Class<? extends CallLogger> value) { 
anno.callLogger(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF debug(String value) { anno.debug(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF observability(String value) { anno.observability(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF renderResponseStackTraces(String value) { 
anno.renderResponseStackTraces(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF problemDetails(String value) { anno.problemDetails(value); 
return self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Docs / metadata / i18n / static files
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF title(String... value) { anno.title(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF description(String... value) { anno.description(value); 
return self(); }
+
+       @Override /* RestBuilder */
+       public SELF siteName(String value) { anno.siteName(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF swaggerProvider(Class<? extends SwaggerProvider> value) { 
anno.swaggerProvider(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF openApiProvider(Class<? extends OpenApiProvider> value) { 
anno.openApiProvider(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF messages(String value) { anno.messages(value); return 
self(); }
+
+       @Override /* RestBuilder */
+       public SELF config(String value) { anno.config(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF staticFiles(Class<? extends StaticFiles> value) { 
anno.staticFiles(value); return self(); }
+
+       @Override /* RestBuilder */
+       public SELF noInherit(String... value) { anno.noInherit(value); return 
self(); }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Programmatic-only knob & escape hatch
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Override /* RestBuilder */
+       public SELF mdcAsyncPropagation(boolean value) { mdcAsyncPropagation = 
value; return self(); }
+
+       @Override /* RestBuilder */
+       public SELF set(String key, Object value) { 
extras.put(assertArgNotNull("key", key), value); return self(); }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestBuilder.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestBuilder.java
new file mode 100644
index 0000000000..0a0faa600d
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestBuilder.java
@@ -0,0 +1,561 @@
+/*
+ * 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;
+
+import org.apache.juneau.encoders.*;
+import org.apache.juneau.httppart.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.arg.*;
+import org.apache.juneau.rest.converter.*;
+import org.apache.juneau.rest.guard.*;
+import org.apache.juneau.rest.logger.*;
+import org.apache.juneau.rest.openapi.*;
+import org.apache.juneau.rest.processor.*;
+import org.apache.juneau.rest.staticfile.*;
+import org.apache.juneau.rest.swagger.*;
+import org.apache.juneau.serializer.*;
+
+/**
+ * Public, fluent, self-typed configuration surface for programmatically 
configuring a {@code @Rest} resource,
+ * child resource, or mixin instead of (or in addition to) the {@link Rest 
@Rest} annotation.
+ *
+ * <p>
+ * This is the user-facing builder surface introduced for programmatic 
resource/mixin configuration (e.g.
+ * instantiating a configured resource bean in a Spring {@code @Bean} method). 
 Builder-supplied values take
+ * <b>precedence</b> over {@link Rest @Rest} annotation values &mdash; they 
slot in as the highest-priority
+ * (rung&nbsp;1) contributor of the runtime-override resolution chain 
documented on
+ * {@link RestContext#getPaths()}, generalized to every {@code @Rest} member.
+ *
+ * <p class='bjava'>
+ *     <jc>// Configure a resource programmatically; builder values win over 
the class's @Rest annotation.</jc>
+ *     MyRest <jv>r</jv> = 
MyRest.<jsm>builder</jsm>().path(<js>"/foo"</js>).allowedHeaderParams(<js>"foo"</js>).build();
+ * </p>
+ *
+ * <h5 class='section'>Self type (CRTP):</h5>
+ *
+ * <p>
+ * The {@code SELF} type parameter is the concrete builder type, so every 
fluent setter returns the most-derived
+ * builder type and bespoke setters added by a builder subclass chain with 
true covariant returns.  This mirrors
+ * the project-wide {@code SELF}/{@code self()} self-type convention.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link AbstractRestBuilder}
+ *     <li class='jc'>{@link 
org.apache.juneau.rest.servlet.RestServlet.Builder}
+ *     <li class='jc'>{@link 
org.apache.juneau.rest.servlet.RestResource.Builder}
+ *     <li class='jc'>{@link org.apache.juneau.rest.servlet.RestMixin.Builder}
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics";>@Rest-Annotated
 Class Basics</a>
+ * </ul>
+ *
+ * @param <SELF> The concrete builder type (self type).
+ * @since 9.5.0
+ */
+public interface RestBuilder<SELF extends RestBuilder<SELF>> {
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Identity & mounting
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#path() path} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF path(String value);
+
+       /**
+        * Specifies the {@link Rest#paths() top-level mount paths} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF paths(String... value);
+
+       /**
+        * Specifies the {@link Rest#children() child resources} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF children(Class<?>... value);
+
+       /**
+        * Specifies the {@link Rest#mixins() mixins} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF mixins(Class<?>... value);
+
+       /**
+        * Specifies the {@link Rest#uriAuthority() uriAuthority} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF uriAuthority(String value);
+
+       /**
+        * Specifies the {@link Rest#uriContext() uriContext} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF uriContext(String value);
+
+       /**
+        * Specifies the {@link Rest#uriRelativity() uriRelativity} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF uriRelativity(String value);
+
+       /**
+        * Specifies the {@link Rest#uriResolution() uriResolution} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF uriResolution(String value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Marshalling
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#serializers() serializers} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF serializers(Class<? extends Serializer>... value);
+
+       /**
+        * Specifies the {@link Rest#parsers() parsers} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF parsers(Class<?>... value);
+
+       /**
+        * Specifies the {@link Rest#encoders() encoders} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF encoders(Class<? extends Encoder>... value);
+
+       /**
+        * Specifies the {@link Rest#partSerializer() partSerializer} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF partSerializer(Class<? extends HttpPartSerializer> value);
+
+       /**
+        * Specifies the {@link Rest#partParser() partParser} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF partParser(Class<? extends HttpPartParser> value);
+
+       /**
+        * Specifies the {@link Rest#consumes() consumes} media types for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF consumes(String... value);
+
+       /**
+        * Specifies the {@link Rest#produces() produces} media types for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF produces(String... value);
+
+       /**
+        * Specifies the {@link Rest#responseProcessors() responseProcessors} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF responseProcessors(Class<? extends ResponseProcessor>... value);
+
+       /**
+        * Specifies the {@link Rest#allowedSerializerOptions() 
allowedSerializerOptions} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF allowedSerializerOptions(String... value);
+
+       /**
+        * Specifies the {@link Rest#allowedParserOptions() 
allowedParserOptions} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF allowedParserOptions(String... value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Request behavior / params
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#allowedHeaderParams() allowedHeaderParams} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF allowedHeaderParams(String value);
+
+       /**
+        * Specifies the {@link Rest#allowedMethodHeaders() 
allowedMethodHeaders} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF allowedMethodHeaders(String value);
+
+       /**
+        * Specifies the {@link Rest#allowedMethodParams() allowedMethodParams} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF allowedMethodParams(String value);
+
+       /**
+        * Specifies the {@link Rest#clientVersionHeader() clientVersionHeader} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF clientVersionHeader(String value);
+
+       /**
+        * Specifies the {@link Rest#defaultAccept() defaultAccept} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultAccept(String value);
+
+       /**
+        * Specifies the {@link Rest#defaultContentType() defaultContentType} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultContentType(String value);
+
+       /**
+        * Specifies the {@link Rest#defaultCharset() defaultCharset} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultCharset(String value);
+
+       /**
+        * Specifies the {@link Rest#defaultRequestAttributes() 
defaultRequestAttributes} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultRequestAttributes(String... value);
+
+       /**
+        * Specifies the {@link Rest#defaultRequestHeaders() 
defaultRequestHeaders} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultRequestHeaders(String... value);
+
+       /**
+        * Specifies the {@link Rest#defaultResponseHeaders() 
defaultResponseHeaders} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF defaultResponseHeaders(String... value);
+
+       /**
+        * Specifies the {@link Rest#disableContentParam() disableContentParam} 
flag for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF disableContentParam(String value);
+
+       /**
+        * Specifies the {@link Rest#maxInput() maxInput} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF maxInput(String value);
+
+       /**
+        * Specifies the {@link Rest#restOpArgs() restOpArgs} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF restOpArgs(Class<? extends RestOpArg>... value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Security
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#guards() guards} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF guards(Class<? extends RestGuard>... value);
+
+       /**
+        * Specifies the {@link Rest#roleGuard() roleGuard} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF roleGuard(String value);
+
+       /**
+        * Specifies the {@link Rest#rolesDeclared() rolesDeclared} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF rolesDeclared(String value);
+
+       /**
+        * Specifies the {@link Rest#converters() converters} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF converters(Class<? extends RestConverter>... value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Lifecycle / perf
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#eagerInit() eagerInit} flag for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF eagerInit(String value);
+
+       /**
+        * Specifies the {@link Rest#lazyChildren() lazyChildren} flag for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF lazyChildren(String value);
+
+       /**
+        * Specifies the {@link Rest#virtualThreads() virtualThreads} flag for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF virtualThreads(String value);
+
+       /**
+        * Specifies the {@link Rest#asyncTimeoutMillis() asyncTimeoutMillis} 
for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF asyncTimeoutMillis(String value);
+
+       /**
+        * Specifies the {@link Rest#asyncCompletionExecutor() 
asyncCompletionExecutor} bean name for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF asyncCompletionExecutor(String value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Observability / logging / errors
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#callLogger() callLogger} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF callLogger(Class<? extends CallLogger> value);
+
+       /**
+        * Specifies the {@link Rest#debug() debug} mode for this resource.
+        *
+        * @param value The new value for this property (e.g. {@code "true"}, 
{@code "conditional"}).
+        * @return This object.
+        */
+       SELF debug(String value);
+
+       /**
+        * Specifies the {@link Rest#observability() observability} flag for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF observability(String value);
+
+       /**
+        * Specifies the {@link Rest#renderResponseStackTraces() 
renderResponseStackTraces} flag for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF renderResponseStackTraces(String value);
+
+       /**
+        * Specifies the {@link Rest#problemDetails() problemDetails} flag for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF problemDetails(String value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Docs / metadata / i18n / static files
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies the {@link Rest#title() title} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF title(String... value);
+
+       /**
+        * Specifies the {@link Rest#description() description} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF description(String... value);
+
+       /**
+        * Specifies the {@link Rest#siteName() siteName} for this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF siteName(String value);
+
+       /**
+        * Specifies the {@link Rest#swaggerProvider() swaggerProvider} for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF swaggerProvider(Class<? extends SwaggerProvider> value);
+
+       /**
+        * Specifies the {@link Rest#openApiProvider() openApiProvider} for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF openApiProvider(Class<? extends OpenApiProvider> value);
+
+       /**
+        * Specifies the {@link Rest#messages() messages} bundle location for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF messages(String value);
+
+       /**
+        * Specifies the {@link Rest#config() config} location for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF config(String value);
+
+       /**
+        * Specifies the {@link Rest#staticFiles() staticFiles} for this 
resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF staticFiles(Class<? extends StaticFiles> value);
+
+       /**
+        * Specifies the {@link Rest#noInherit() noInherit} property names for 
this resource.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF noInherit(String... value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Programmatic-only knob (no @Rest member)
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Specifies whether MDC async propagation is enabled for this resource.
+        *
+        * <p>
+        * This is a <b>programmatic-only</b> knob &mdash; it has no {@link 
Rest @Rest} annotation member (its default is
+        * env-driven).  Setting it here overrides the {@code 
RestContext.mdcAsyncPropagation} env-driven default.
+        *
+        * @param value The new value for this property.
+        * @return This object.
+        */
+       SELF mdcAsyncPropagation(boolean value);
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Forward-compat escape hatch
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       /**
+        * Generic forward-compatible override setter, keyed by the {@code 
@Rest} property name (e.g.
+        * {@code "allowContentParam"}, {@code "defaultRequestHeaders"}).
+        *
+        * <p>
+        * Use this escape hatch for members not yet exposed as a dedicated 
fluent method.  Keys reuse the existing
+        * {@code @Rest} member names.
+        *
+        * @param key The {@code @Rest} property name.
+        * @param value The override value.
+        * @return This object.
+        */
+       SELF set(String key, Object value);
+}
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 ecd0ee19eb..895480d266 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
@@ -89,6 +89,9 @@ import org.apache.juneau.rest.debug.format.*;
 import org.apache.juneau.rest.httppart.*;
 import org.apache.juneau.rest.logger.*;
 import org.apache.juneau.rest.metrics.*;
+import org.apache.juneau.rest.servlet.RestMixin;
+import org.apache.juneau.rest.servlet.RestResource;
+import org.apache.juneau.rest.servlet.RestServlet;
 import org.apache.juneau.rest.processor.*;
 import org.apache.juneau.rest.rrpc.*;
 import org.apache.juneau.rest.servlet.*;
@@ -238,7 +241,8 @@ public class RestContext extends Context {
                Consumer<WritableBeanStore> beanStoreConfigurer,
                BeanStore overridingParent,
                String[] paths,
-               boolean mixinContext
+               boolean mixinContext,
+               RestBuilder restBuilder
        ) {
 
                /**
@@ -252,6 +256,27 @@ public class RestContext extends Context {
                        if (beanStoreConfigurer == null)
                                beanStoreConfigurer = bs -> {};
                }
+
+               /**
+                * Back-compatible constructor without the {@code restBuilder} 
component (defaults it to <jk>null</jk>).
+                *
+                * <p>
+                * The {@code restBuilder} (TODO-143) is then resolved during 
{@link RestContext} construction from the
+                * resource instance's stashed builder, so call sites that 
don't carry one keep working unchanged.
+                *
+                * @param resourceClass The resource class.
+                * @param parentContext The parent context, or <jk>null</jk>.
+                * @param servletConfig The servlet config, or <jk>null</jk>.
+                * @param resource The resource supplier.
+                * @param path The mount path override, or <jk>null</jk>.
+                * @param beanStoreConfigurer The bean-store configurer, or 
<jk>null</jk>.
+                * @param overridingParent The overriding parent bean store, or 
<jk>null</jk>.
+                * @param paths The programmatic mount-paths override, or 
<jk>null</jk>.
+                * @param mixinContext Whether this is a mixin sub-context.
+                */
+               public Args(Class<?> resourceClass, RestContext parentContext, 
ServletConfig servletConfig, Supplier<?> resource, String path, 
Consumer<WritableBeanStore> beanStoreConfigurer, BeanStore overridingParent, 
String[] paths, boolean mixinContext) {
+                       this(resourceClass, parentContext, servletConfig, 
resource, path, beanStoreConfigurer, overridingParent, paths, mixinContext, 
null);
+               }
        }
 
        /**
@@ -693,6 +718,22 @@ public class RestContext extends Context {
        private final Supplier<?> resource;
        private AnnotationWorkList annotationWork;
 
+       /**
+        * The programmatic configuration builder for this resource (TODO-143), 
or <jk>null</jk> when configured purely
+        * by annotation.  Resolved during construction from {@link 
Args#restBuilder()} or, failing that, from the
+        * resource instance's stashed builder.  When non-<jk>null</jk>, a 
synthetic highest-priority {@code @Rest}
+        * annotation built from its set members is prepended to {@link 
#getRestAnnotations()} so builder-supplied
+        * values take precedence over the resource class's own {@code @Rest} 
annotation values.
+        */
+       private final RestBuilder restBuilder;
+
+       /**
+        * The synthetic {@code @Rest} annotation built from {@link 
#restBuilder}'s set members, or <jk>null</jk> when
+        * there is no programmatic builder.  Prepended at the most-derived 
(child) position of
+        * {@link #getRestAnnotations()} so builder-supplied values win over 
the resource class's own annotation.
+        */
+       private final Rest builderRestAnnotation;
+
        // Private accessors used by memoizer lambdas to satisfy Java's 
definite-assignment rules for blank final fields.
        private WritableBeanStore beanStore() { return beanStore; }
        private Supplier<?> resource() { return resource; }
@@ -2096,6 +2137,24 @@ public class RestContext extends Context {
                        var rs = new ResourceSupplier(resourceClass, 
assertArgNotNull("resource", builder.args.resource()));
                        resource = rs;
 
+                       // TODO-143: resolve the programmatic configuration 
builder.  Prefer the one carried on Args (set by
+                       // RestServlet.init()); otherwise read the builder 
stashed on the resource instance (non-reflective) so
+                       // programmatic construction via MockRestClient, child 
mounting, and mixin composition all honor it.
+                       var rb = builder.args.restBuilder();
+                       if (rb == null)
+                               rb = stashedRestBuilder(rs.get());
+                       restBuilder = rb;
+                       if (restBuilder instanceof AbstractRestBuilder<?,?> 
arb) {
+                               // Synthetic, highest-priority @Rest carrying 
the builder-set members; prepended (most-derived
+                               // child position) to the @Rest chain by the 
restAnnotations memoizer so builder values win.
+                               builderRestAnnotation = arb.toRestAnnotation();
+                               // Programmatic mdcAsyncPropagation knob (no 
@Rest member) wins over the env-driven default.
+                               if (arb.getMdcAsyncPropagation() != null && 
builder.mdcAsyncPropagation == null)
+                                       builder.mdcAsyncPropagation = 
arb.getMdcAsyncPropagation();
+                       } else {
+                               builderRestAnnotation = null;
+                       }
+
                        // --- beanStore setup (May 2026 refactor; 
precedence-flipped 9.5) ---
 
                        // Determine the parent (bootstrap) store: inherited 
from parent resource if present.
@@ -2438,7 +2497,15 @@ public class RestContext extends Context {
         * a host's explicit per-property values for any property the mixin 
doesn't itself declare (e.g.
         * {@code partSerializer}, {@code partParser}).
         */
-       private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = 
memoizer(() -> {
+       private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = 
memoizer(() -> prependBuilderRestAnnotation(computeRawRestAnnotations()));
+
+       /**
+        * Computes the raw {@code @Rest} annotation chain (most-derived 
first), folding in the framework
+        * {@code DefaultConfig} annotations for non-mixin contexts.  Does not 
include the synthetic builder annotation.
+        *
+        * @return The raw annotation chain.
+        */
+       private List<AnnotationInfo<Rest>> computeRawRestAnnotations() {
                var raw = getAnnotationProvider().find(Rest.class, 
ClassInfo.of(getResourceClass()));
                if (isMixinContextField())
                        return raw;
@@ -2451,7 +2518,41 @@ public class RestContext extends Context {
                var combined = new ArrayList<>(raw);
                combined.addAll(defaultConfigAnnotations);
                return Collections.unmodifiableList(combined);
-       });
+       }
+
+       /**
+        * Prepends the synthetic builder-supplied {@code @Rest} annotation 
(TODO-143) at the most-derived (child)
+        * position so its set members win in both child-first ({@code 
findFirst}) and parent-to-child
+        * ({@code reduce-last}) resolution walks.  Returns {@code base} 
unchanged when there is no programmatic builder.
+        *
+        * @param base The raw annotation chain.
+        * @return The chain with the synthetic annotation prepended (when 
present).
+        */
+       private List<AnnotationInfo<Rest>> 
prependBuilderRestAnnotation(List<AnnotationInfo<Rest>> base) {
+               if (builderRestAnnotation == null)
+                       return base;
+               var combined = new ArrayList<AnnotationInfo<Rest>>(base.size() 
+ 1);
+               
combined.add(AnnotationInfo.of(ClassInfo.of(getResourceClass()), 
builderRestAnnotation));
+               combined.addAll(base);
+               return Collections.unmodifiableList(combined);
+       }
+
+       /**
+        * Non-reflectively reads the programmatic configuration builder 
stashed on a resource/mixin instance
+        * (TODO-143 &sect;2.4), or returns <jk>null</jk> when the instance 
carries none or is not a builder-aware base type.
+        *
+        * @param r The resource instance.
+        * @return The stashed builder, or <jk>null</jk>.
+        */
+       private static RestBuilder stashedRestBuilder(Object r) {
+               if (r instanceof RestServlet x)
+                       return x.getRestBuilder();
+               if (r instanceof RestResource x)
+                       return x.getRestBuilder();
+               if (r instanceof RestMixin x)
+                       return x.getRestBuilder();
+               return null;
+       }
 
        /**
         * The {@code @Rest} annotation list for this resource in 
parent-to-child (top-down) order.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
index e574a94cdc..7edaf75efd 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
@@ -17,7 +17,9 @@
 package org.apache.juneau.rest.convention;
 
 import org.apache.juneau.http.*;
+import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
 
 /**
  * Mixin that serves a {@code favicon.ico} icon at {@code /favicon.ico}.
@@ -101,7 +103,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class FaviconMixin {
+public class FaviconMixin extends RestMixin {
 
        /** Default {@code Cache-Control} header value: {@code max-age=2592000, 
public} (30 days). */
        public static final String DEFAULT_CACHE_CONTROL = 
FaviconProvider.DEFAULT_CACHE_CONTROL;
@@ -136,6 +138,35 @@ public class FaviconMixin {
                this.worker = worker;
        }
 
+       /**
+        * Worker + builder constructor (TODO-143 OQ-11 / &sect;2.4).
+        *
+        * <p>
+        * Used by {@link Builder#build()} to both delegate to the shared 
{@link FaviconProvider} worker and stash the
+        * programmatic {@link RestBuilder} (carrying any {@code @Rest}-level 
overrides such as {@code path}) so those
+        * values take precedence over the mixin class's own {@link Rest @Rest} 
annotation.
+        *
+        * <h5 class='section'>Why worker + builder, not the flavor 
Builder:</h5>
+        * <ul class='spaced-list'>
+        *      <li>Takes the already-built <b>worker bean</b> (not the flavor 
{@link Builder}) so the flavor can be
+        *              constructed from ANY independently-supplied worker 
&mdash; e.g. a user's own {@code @Bean FaviconProvider}
+        *              or BeanStore bean, per the delegate-bean model &mdash; 
not only via this flavor's own builder.
+        *      <li>Takes the generic {@link RestBuilder} (here {@code this} 
from {@link Builder#build()}) so it honors the
+        *              uniform &sect;2.4 {@code Foo(RestBuilder)} injection 
contract the base class and DI resolution key on; the
+        *              base knows nothing about the concrete flavor builder or 
the worker type.
+        *      <li>Holds the finished worker product (the {@code final} {@link 
FaviconProvider} field), not a transient
+        *              builder; the worker is materialized exactly once at 
{@link Builder#build()} time.
+        *      <li>Keeps the capability worker and the REST-level config as 
two distinct inputs.
+        * </ul>
+        *
+        * @param worker The shared {@link FaviconProvider} worker this flavor 
delegates to. Must not be <jk>null</jk>.
+        * @param builder The programmatic configuration builder. May be 
<jk>null</jk>.
+        */
+       protected FaviconMixin(FaviconProvider worker, RestBuilder builder) {
+               super(builder);
+               this.worker = worker;
+       }
+
        /**
         * [GET /favicon.ico] &mdash; serve the configured favicon bytes.
         *
@@ -158,13 +189,23 @@ public class FaviconMixin {
         * Mirrors {@link FaviconProvider.Builder}'s configuration methods on 
the mixin's own surface and
         * forwards each call to an underlying {@link FaviconProvider.Builder}, 
which builds the shared worker
         * the mixin delegates to (TODO-145 &sect;2.3.1 / OQ-11).
+        *
+        * <p>
+        * Extends {@link RestMixin.Builder} (TODO-143 Option B) so the mixin's 
bespoke worker-config setters
+        * ({@link #bytes(byte[])}, {@link #classpath(String)}, {@link 
#cacheControl(String)}) chain with true
+        * covariant returns alongside the inherited {@link RestBuilder} 
surface (e.g. {@code path}, {@code roleGuard}).
+        * This is how a multi-flavor capability avoids triplicating its 
REST-level config: the worker config is
+        * forwarded once into {@link FaviconProvider.Builder}, and the REST 
config is inherited once from
+        * {@link AbstractRestBuilder}.
         */
-       public static class Builder {
+       public static class Builder extends RestMixin.Builder<FaviconMixin, 
Builder> {
 
                private final FaviconProvider.Builder worker = 
FaviconProvider.create();
 
                /** Constructor &mdash; package access for {@link 
FaviconMixin#create()}. */
-               protected Builder() {}
+               protected Builder() {
+                       super(FaviconMixin.class);
+               }
 
                /**
                 * Sets the raw favicon bytes.
@@ -213,12 +254,14 @@ public class FaviconMixin {
                }
 
                /**
-                * Builds a {@link FaviconMixin} instance.
+                * Builds a {@link FaviconMixin} instance, delegating to the 
shared {@link FaviconProvider} worker and
+                * stashing this builder so its {@code @Rest}-level overrides 
take precedence over the annotation.
                 *
                 * @return A configured instance.
                 */
+               @Override /* AbstractRestBuilder */
                public FaviconMixin build() {
-                       return new FaviconMixin(worker.build());
+                       return new FaviconMixin(worker.build(), this);
                }
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
index f09d0e6776..1edebc5015 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
@@ -23,6 +23,7 @@ import java.util.*;
 
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
 
 /**
  * Mixin that serves SEO-conventional endpoints {@code /robots.txt} and {@code 
/sitemap.xml}.
@@ -106,7 +107,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class SeoMixin {
+public class SeoMixin extends RestMixin {
 
        /** Default robots policy: deny everything. */
        public static final String DEFAULT_ROBOTS = "User-agent: *\nDisallow: 
/\n";
@@ -131,9 +132,15 @@ public class SeoMixin {
        /**
         * Builder constructor.
         *
+        * <p>
+        * Stashes the programmatic {@link RestBuilder} (the builder itself, 
carrying any {@code @Rest}-level
+        * overrides such as {@code path}) via {@code super(builder)} so those 
values take precedence over this
+        * mixin's own {@link Rest @Rest} annotation (TODO-143 &sect;2.4).
+        *
         * @param builder The builder.
         */
        protected SeoMixin(Builder builder) {
+               super(builder);
                robotsTxt = builder.buildRobots();
                sitemapEntries = List.copyOf(builder.sitemapEntries);
        }
@@ -215,15 +222,22 @@ public class SeoMixin {
 
        /**
         * Builder for {@link SeoMixin} instances.
+        *
+        * <p>
+        * Extends {@link RestMixin.Builder} (TODO-143 Option B) so the mixin's 
bespoke robots/sitemap setters chain
+        * with true covariant returns alongside the inherited {@link 
RestBuilder} surface (e.g. {@code path},
+        * {@code roleGuard}).
         */
-       public static class Builder {
+       public static class Builder extends RestMixin.Builder<SeoMixin, 
Builder> {
 
                private final List<RobotsRule> robotsRules = new ArrayList<>();
                private final List<SitemapEntry> sitemapEntries = new 
ArrayList<>();
                private String customRobotsTxt;
 
                /** Constructor &mdash; package access for {@link 
SeoMixin#create()}. */
-               protected Builder() {}
+               protected Builder() {
+                       super(SeoMixin.class);
+               }
 
                /**
                 * Adds an {@code Allow} rule to the robots policy.
@@ -305,6 +319,7 @@ public class SeoMixin {
                 *
                 * @return A configured instance.
                 */
+               @Override /* AbstractRestBuilder */
                public SeoMixin build() {
                        return new SeoMixin(this);
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
index 023cb821aa..f74d2b74ab 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
@@ -23,6 +23,7 @@ import java.util.jar.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
 
 /**
  * Mixin that serves deployment-introspection metadata at {@code /version} 
(configurable).
@@ -136,7 +137,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class VersionMixin {
+public class VersionMixin extends RestMixin {
 
        /** Sentinel value returned for entries that the worker couldn't 
resolve. */
        public static final String UNKNOWN = VersionProvider.UNKNOWN;
@@ -184,6 +185,35 @@ public class VersionMixin {
                this.worker = worker;
        }
 
+       /**
+        * Worker + builder constructor (TODO-143 OQ-11 / &sect;2.4).
+        *
+        * <p>
+        * Used by {@link Builder#build()} to both delegate to the shared 
{@link VersionProvider} worker and stash the
+        * programmatic {@link RestBuilder} (carrying any {@code @Rest}-level 
overrides such as {@code path}) so those
+        * values take precedence over the mixin class's own {@link Rest @Rest} 
annotation.
+        *
+        * <h5 class='section'>Why worker + builder, not the flavor 
Builder:</h5>
+        * <ul class='spaced-list'>
+        *      <li>Takes the already-built <b>worker bean</b> (not the flavor 
{@link Builder}) so the flavor can be
+        *              constructed from ANY independently-supplied worker 
&mdash; e.g. a user's own {@code @Bean VersionProvider}
+        *              or BeanStore bean, per the delegate-bean model &mdash; 
not only via this flavor's own builder.
+        *      <li>Takes the generic {@link RestBuilder} (here {@code this} 
from {@link Builder#build()}) so it honors the
+        *              uniform &sect;2.4 {@code Foo(RestBuilder)} injection 
contract the base class and DI resolution key on; the
+        *              base knows nothing about the concrete flavor builder or 
the worker type.
+        *      <li>Holds the finished worker product (the {@code final} {@link 
VersionProvider} field), not a transient
+        *              builder; the worker is materialized exactly once at 
{@link Builder#build()} time.
+        *      <li>Keeps the capability worker and the REST-level config as 
two distinct inputs.
+        * </ul>
+        *
+        * @param worker The shared {@link VersionProvider} worker this flavor 
delegates to. Must not be <jk>null</jk>.
+        * @param builder The programmatic configuration builder. May be 
<jk>null</jk>.
+        */
+       protected VersionMixin(VersionProvider worker, RestBuilder builder) {
+               super(builder);
+               this.worker = worker;
+       }
+
        /**
         * [GET /version] &mdash; emit the assembled metadata as a JSON map.
         *
@@ -216,13 +246,20 @@ public class VersionMixin {
         * Mirrors {@link VersionProvider.Builder}'s configuration methods on 
the mixin's own surface and
         * forwards each call to an underlying {@link VersionProvider.Builder}, 
which builds the shared worker
         * the mixin delegates to (TODO-145 &sect;2.3.1 / OQ-11).
+        *
+        * <p>
+        * Extends {@link RestMixin.Builder} (TODO-143 Option B) so the mixin's 
bespoke worker-config setters chain
+        * with true covariant returns alongside the inherited {@link 
RestBuilder} surface (e.g. {@code path},
+        * {@code roleGuard}). The worker config is forwarded once into {@link 
VersionProvider.Builder} and the REST
+        * config is inherited once from {@link AbstractRestBuilder} &mdash; no 
triplication across the Version flavors.
         */
-       public static class Builder {
+       public static class Builder extends RestMixin.Builder<VersionMixin, 
Builder> {
 
                private final VersionProvider.Builder worker;
 
                /** Constructor &mdash; protected access for {@link 
VersionMixin#create()}. */
                protected Builder(VersionProvider.Builder worker) {
+                       super(VersionMixin.class);
                        this.worker = worker;
                }
 
@@ -325,8 +362,9 @@ public class VersionMixin {
                 *
                 * @return A configured instance.
                 */
+               @Override /* AbstractRestBuilder */
                public VersionMixin build() {
-                       return new VersionMixin(worker.build());
+                       return new VersionMixin(worker.build(), this);
                }
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
index 13412ce23e..e4fa4167d3 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
@@ -21,6 +21,7 @@ import java.io.*;
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
 
 /**
  * Mixin that serves <a href="https://www.rfc-editor.org/rfc/rfc8615";>RFC 
8615</a>
@@ -103,7 +104,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class WellKnownMixin {
+public class WellKnownMixin extends RestMixin {
 
        /**
         * Creates a new builder.
@@ -124,9 +125,15 @@ public class WellKnownMixin {
        /**
         * Builder constructor.
         *
+        * <p>
+        * Stashes the programmatic {@link RestBuilder} (the builder itself, 
carrying any {@code @Rest}-level
+        * overrides such as {@code path}) via {@code super(builder)} so those 
values take precedence over this
+        * mixin's own {@link Rest @Rest} annotation (TODO-143 &sect;2.4).
+        *
         * @param builder The builder.
         */
        protected WellKnownMixin(Builder builder) {
+               super(builder);
                securityTxt = builder.securityTxt;
        }
 
@@ -163,13 +170,20 @@ public class WellKnownMixin {
 
        /**
         * Builder for {@link WellKnownMixin} instances.
+        *
+        * <p>
+        * Extends {@link RestMixin.Builder} (TODO-143 Option B) so the mixin's 
bespoke {@link #securityTxt(String)}
+        * setter chains with true covariant returns alongside the inherited 
{@link RestBuilder} surface (e.g.
+        * {@code path}, {@code roleGuard}).
         */
-       public static class Builder {
+       public static class Builder extends RestMixin.Builder<WellKnownMixin, 
Builder> {
 
                private String securityTxt;
 
                /** Constructor &mdash; package access for {@link 
WellKnownMixin#create()}. */
-               protected Builder() {}
+               protected Builder() {
+                       super(WellKnownMixin.class);
+               }
 
                /**
                 * Sets the {@code security.txt} body that will be served at
@@ -193,6 +207,7 @@ public class WellKnownMixin {
                 *
                 * @return A configured instance.
                 */
+               @Override /* AbstractRestBuilder */
                public WellKnownMixin build() {
                        return new WellKnownMixin(this);
                }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
index 9e8f0f8456..85c9033def 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
@@ -41,14 +41,13 @@ import org.apache.juneau.rest.annotation.*;
  * base-less; the new single-responsibility op-mixins extend this base to make 
the triad membership
  * explicit.
  *
- * <h5 class='section'>Builder support (deferred):</h5>
+ * <h5 class='section'>Builder support:</h5>
  *
  * <p>
- * The fluent programmatic-builder surface ({@code RestBuilder}/{@code 
RestMixin.Builder}) that would let
- * a mixin be configured programmatically rather than by annotation is 
<b>not</b> part of this base yet
- * &mdash; it is deferred along with the resource/servlet builder work. Until 
then, {@code RestMixin}
- * carries no builder or stashed-builder state and mixins are configured 
purely by their {@code @Rest} /
- * {@code @RestOp} annotations.
+ * The fluent programmatic-builder surface ({@link RestBuilder} / {@link 
Builder}) lets a mixin be configured
+ * programmatically rather than (or in addition to) by annotation &mdash; 
builder-supplied values take precedence
+ * over {@link Rest @Rest} annotation values.  Use {@link #builder(Class)} for 
the common case, or subclass
+ * {@link Builder} for capability mixins that add their own setters (TODO-143 
Option B).
  *
  * <h5 class='section'>Reaching the host resource:</h5>
  *
@@ -87,6 +86,49 @@ public abstract class RestMixin {
         */
        private final AtomicReference<RestContext> context = new 
AtomicReference<>();
 
+       /**
+        * The programmatic configuration builder stashed on this instance 
(TODO-143 &sect;2.4), or <jk>null</jk> when the
+        * mixin was constructed without a builder.  Mutable so it can be 
written by either the
+        * {@link #RestMixin(RestBuilder)} constructor or {@link 
Builder#build()}.  Read non-reflectively by
+        * {@link RestContext} during mixin sub-context construction so 
builder-supplied values take precedence over
+        * {@link Rest @Rest} annotation values.
+        */
+       RestBuilder restBuilder;
+
+       /**
+        * Default constructor.
+        */
+       protected RestMixin() {}
+
+       /**
+        * Builder-injection constructor (TODO-145 &sect;2.4 constructor trio).
+        *
+        * @param builder The programmatic configuration builder.  May be 
<jk>null</jk>.
+        */
+       protected RestMixin(RestBuilder builder) {
+               this.restBuilder = builder;
+       }
+
+       /**
+        * Returns the programmatic configuration builder stashed on this 
mixin, or <jk>null</jk> if none.
+        *
+        * @return The stashed builder, or <jk>null</jk>.
+        */
+       public RestBuilder getRestBuilder() {
+               return restBuilder;
+       }
+
+       /**
+        * Creates a new fluent builder for programmatically configuring an 
instance of the specified mixin type.
+        *
+        * @param <R> The mixin type.
+        * @param type The mixin type to build.  Must not be <jk>null</jk>.
+        * @return A new builder.
+        */
+       public static <R extends RestMixin> DefaultBuilder<R> builder(Class<R> 
type) {
+               return new DefaultBuilder<>(type);
+       }
+
        /**
         * Captures the per-mixin {@link RestContext} sub-context this mixin 
instance is bound to.
         *
@@ -131,4 +173,47 @@ public abstract class RestMixin {
                var c = context.get();
                return c == null ? null : c.getParentContext();
        }
+
+       /**
+        * Fluent builder for programmatically configuring a {@link RestMixin} 
subclass.
+        *
+        * <p>
+        * Subclassable, self-typed (CRTP) flavor builder (TODO-143 Option B).  
Capability mixins (e.g.
+        * {@code FaviconMixin}) extend this and add their own worker-config 
setters, which chain with true covariant
+        * returns alongside the inherited {@link RestBuilder} surface.  For 
the common (non-subclassed) case use
+        * {@link RestMixin#builder(Class)} which returns the concrete {@link 
DefaultBuilder} leaf.
+        *
+        * @param <R> The mixin type produced by {@link #build()}.
+        * @param <SELF> The concrete builder type (self type).
+        */
+       public static class Builder<R extends RestMixin, SELF extends 
Builder<R, SELF>> extends AbstractRestBuilder<R, SELF> {
+
+               /**
+                * Constructor.
+                *
+                * @param type The mixin type produced by {@link #build()}.  
Must not be <jk>null</jk>.
+                */
+               protected Builder(Class<R> type) {
+                       super(type);
+               }
+
+               @Override /* AbstractRestBuilder */
+               public R build() {
+                       var r = createResource();
+                       r.restBuilder = this;
+                       return r;
+               }
+       }
+
+       /**
+        * Concrete default leaf builder returned by {@link 
RestMixin#builder(Class)} for the common (non-subclassed)
+        * case.
+        *
+        * @param <R> The mixin type produced by {@link #build()}.
+        */
+       public static final class DefaultBuilder<R extends RestMixin> extends 
Builder<R, DefaultBuilder<R>> {
+               DefaultBuilder(Class<R> type) {
+                       super(type);
+               }
+       }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
index 69c72d48a3..9f24562fa0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
@@ -45,6 +45,49 @@ public abstract class RestResource {
 
        private AtomicReference<RestContext> context = new AtomicReference<>();
 
+       /**
+        * The programmatic configuration builder stashed on this instance 
(TODO-143 &sect;2.4), or <jk>null</jk> when the
+        * resource was constructed without a builder.  Mutable so it can be 
written by either the
+        * {@link #RestResource(RestBuilder)} constructor or {@link 
Builder#build()}.  Read non-reflectively by
+        * {@link RestContext} during construction so builder-supplied values 
take precedence over {@code @Rest}
+        * annotation values.
+        */
+       RestBuilder restBuilder;
+
+       /**
+        * Default constructor.
+        */
+       protected RestResource() {}
+
+       /**
+        * Builder-injection constructor (TODO-145 &sect;2.4 constructor trio).
+        *
+        * @param builder The programmatic configuration builder.  May be 
<jk>null</jk>.
+        */
+       protected RestResource(RestBuilder builder) {
+               this.restBuilder = builder;
+       }
+
+       /**
+        * Returns the programmatic configuration builder stashed on this 
resource, or <jk>null</jk> if none.
+        *
+        * @return The stashed builder, or <jk>null</jk>.
+        */
+       public RestBuilder getRestBuilder() {
+               return restBuilder;
+       }
+
+       /**
+        * Creates a new fluent builder for programmatically configuring an 
instance of the specified resource type.
+        *
+        * @param <R> The resource type.
+        * @param type The resource type to build.  Must not be <jk>null</jk>.
+        * @return A new builder.
+        */
+       public static <R extends RestResource> DefaultBuilder<R> 
builder(Class<R> type) {
+               return new DefaultBuilder<>(type);
+       }
+
        /**
         * Returns the current thread-local HTTP request.
         *
@@ -177,4 +220,45 @@ public abstract class RestResource {
        protected void setContext(RestContext context) throws ServletException {
                this.context.set(context);
        }
+
+       /**
+        * Fluent builder for programmatically configuring a {@link 
RestResource} subclass.
+        *
+        * <p>
+        * Subclassable, self-typed (CRTP) flavor builder (TODO-143 Option B).  
For the common (non-subclassed) case
+        * use {@link RestResource#builder(Class)} which returns the concrete 
{@link DefaultBuilder} leaf.
+        *
+        * @param <R> The resource type produced by {@link #build()}.
+        * @param <SELF> The concrete builder type (self type).
+        */
+       public static class Builder<R extends RestResource, SELF extends 
Builder<R, SELF>> extends AbstractRestBuilder<R, SELF> {
+
+               /**
+                * Constructor.
+                *
+                * @param type The resource type produced by {@link #build()}.  
Must not be <jk>null</jk>.
+                */
+               protected Builder(Class<R> type) {
+                       super(type);
+               }
+
+               @Override /* AbstractRestBuilder */
+               public R build() {
+                       var r = createResource();
+                       r.restBuilder = this;
+                       return r;
+               }
+       }
+
+       /**
+        * Concrete default leaf builder returned by {@link 
RestResource#builder(Class)} for the common (non-subclassed)
+        * case.
+        *
+        * @param <R> The resource type produced by {@link #build()}.
+        */
+       public static final class DefaultBuilder<R extends RestResource> 
extends Builder<R, DefaultBuilder<R>> {
+               DefaultBuilder(Class<R> type) {
+                       super(type);
+               }
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
index 133b33e379..a72fd7901f 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestServlet.java
@@ -78,6 +78,54 @@ public abstract class RestServlet extends HttpServlet {
        private final AtomicReference<RestContext> context = new 
AtomicReference<>();
        private final AtomicReference<Exception> initException = new 
AtomicReference<>();
 
+       /**
+        * The programmatic configuration builder stashed on this instance 
(TODO-143 &sect;2.4), or <jk>null</jk> when the
+        * resource was constructed without a builder.  Mutable so it can be 
written by either the
+        * {@link #RestServlet(RestBuilder)} constructor or {@link 
Builder#build()} (the no-arg-only setter-stash path).
+        * Read non-reflectively by {@link RestContext} during construction so 
builder-supplied values take precedence
+        * over {@link Rest @Rest} annotation values.
+        */
+       RestBuilder restBuilder;
+
+       /**
+        * Default constructor.
+        */
+       protected RestServlet() {}
+
+       /**
+        * Builder-injection constructor (TODO-145 &sect;2.4 constructor trio).
+        *
+        * @param builder The programmatic configuration builder.  May be 
<jk>null</jk>.
+        */
+       protected RestServlet(RestBuilder builder) {
+               this.restBuilder = builder;
+       }
+
+       /**
+        * Returns the programmatic configuration builder stashed on this 
resource, or <jk>null</jk> if none.
+        *
+        * @return The stashed builder, or <jk>null</jk>.
+        * @since 9.5.0
+        */
+       public RestBuilder getRestBuilder() {
+               return restBuilder;
+       }
+
+       /**
+        * Creates a new fluent builder for programmatically configuring an 
instance of the specified resource type.
+        *
+        * <p>
+        * Builder-supplied values take precedence over the resource class's 
own {@link Rest @Rest} annotation values.
+        *
+        * @param <R> The resource type.
+        * @param type The resource type to build.  Must not be <jk>null</jk>.
+        * @return A new builder.
+        * @since 9.5.0
+        */
+       public static <R extends RestServlet> DefaultBuilder<R> 
builder(Class<R> type) {
+               return new DefaultBuilder<>(type);
+       }
+
        @Override /* Overridden from GenericServlet */
        public synchronized void destroy() {
                if (nn(context.get()))
@@ -200,7 +248,7 @@ public abstract class RestServlet extends HttpServlet {
                        if (nn(context.get()))
                                return;
                        super.init(servletConfig);
-                       context.set(new RestContext(new 
RestContext.Args(this.getClass(), null, servletConfig, () -> this, "", null, 
null, null, false)));
+                       context.set(new RestContext(new 
RestContext.Args(this.getClass(), null, servletConfig, () -> this, "", null, 
null, null, false, restBuilder)));
                        context.get().postInit();
                        context.get().postInitChildFirst();
                } catch (ServletException e) {
@@ -302,4 +350,49 @@ public abstract class RestServlet extends HttpServlet {
                        this.context.set(context);
                }
        }
+
+       /**
+        * Fluent builder for programmatically configuring a {@link 
RestServlet} subclass.
+        *
+        * <p>
+        * This is the subclassable, self-typed (CRTP) flavor builder.  Its 
{@code SELF} type parameter is left open so
+        * a user subclass's bespoke setters chain with true covariant returns 
alongside the inherited
+        * {@link RestBuilder} surface (TODO-143 Option B).  For the common 
case where the builder is not subclassed,
+        * use {@link RestServlet#builder(Class)} which returns the concrete 
{@link DefaultBuilder} leaf.
+        *
+        * @param <R> The resource type produced by {@link #build()}.
+        * @param <SELF> The concrete builder type (self type).
+        * @since 9.5.0
+        */
+       public static class Builder<R extends RestServlet, SELF extends 
Builder<R, SELF>> extends AbstractRestBuilder<R, SELF> {
+
+               /**
+                * Constructor.
+                *
+                * @param type The resource type produced by {@link #build()}.  
Must not be <jk>null</jk>.
+                */
+               protected Builder(Class<R> type) {
+                       super(type);
+               }
+
+               @Override /* AbstractRestBuilder */
+               public R build() {
+                       var r = createResource();
+                       r.restBuilder = this;
+                       return r;
+               }
+       }
+
+       /**
+        * Concrete default leaf builder returned by {@link 
RestServlet#builder(Class)} for the common (non-subclassed)
+        * case, so callers are not forced to spell the {@code SELF} type 
parameter.
+        *
+        * @param <R> The resource type produced by {@link #build()}.
+        * @since 9.5.0
+        */
+       public static final class DefaultBuilder<R extends RestServlet> extends 
Builder<R, DefaultBuilder<R>> {
+               DefaultBuilder(Class<R> type) {
+                       super(type);
+               }
+       }
 }
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_OptionD_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_OptionD_Test.java
new file mode 100644
index 0000000000..605c00da1a
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_OptionD_Test.java
@@ -0,0 +1,165 @@
+/*
+ * 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.commons.inject;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-143 (Option D) acceptance tests for {@link BeanInstantiator}'s 
stricter builder-candidate selection.
+ *
+ * <p>
+ * A builder candidate whose {@code build()} only promises a <b>supertype</b> 
of the requested bean type (e.g.
+ * an inherited generic self-typed base builder, or one reached via a {@code 
Class}-parameterized factory) must
+ * not displace a stricter instantiation path — a usable direct constructor, 
or a builder that builds the exact
+ * requested type. These tests verify: (a) an exact-typed builder still wins; 
(b) a parent-only builder loses to
+ * a direct constructor; (c) a {@code Class}-parameterized parent-only factory 
loses to a direct constructor;
+ * (d) a subtype's own exact builder still wins even when a constructor 
exists; and (e) plain POJOs are
+ * unaffected.
+ */
+@SuppressWarnings("java:S2094") // Intentionally empty helper bean.
+class BeanInstantiator_OptionD_Test extends TestBase {
+
+       private BasicBeanStore beanStore;
+
+       @BeforeEach
+       void setUp() {
+               beanStore = new BasicBeanStore(null);
+               A_Base.builderUsed = false;
+               C_Base.factoryUsed = false;
+       }
+
+       private <T> T run(Class<T> c) {
+               return BeanInstantiator.of(c, beanStore).run();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // a — exact-typed builder still wins; parent-only builder loses to a 
direct constructor.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class A_Base {
+               static boolean builderUsed;
+               public static Builder create() { return new Builder(); }
+               public A_Base() {}
+               public static class Builder {
+                       public A_Base build() { builderUsed = true; return new 
A_Base(); }
+               }
+       }
+
+       // Subtype with a usable no-arg constructor and NO builder of its own; 
it only inherits A_Base.create(),
+       // whose build() returns the supertype A_Base.
+       public static class A_Sub extends A_Base {
+               public boolean viaCtor;
+               public A_Sub() { this.viaCtor = true; }
+       }
+
+       @Test
+       void a01_exactTypedBuilderWins() {
+               var bean = run(A_Base.class);
+               assertNotNull(bean);
+               assertTrue(A_Base.builderUsed, "Exact-typed builder should have 
been used for A_Base.");
+       }
+
+       @Test
+       void a02_parentOnlyBuilderLosesToDirectConstructor() {
+               var bean = run(A_Sub.class);
+               assertNotNull(bean);
+               assertInstanceOf(A_Sub.class, bean);
+               assertTrue(bean.viaCtor, "A_Sub should be built via its direct 
constructor.");
+               assertFalse(A_Base.builderUsed, "The supertype-only builder 
must not be used when a direct constructor exists.");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // b — Class-parameterized parent-only factory loses to a direct 
constructor (mirrors the REST builder(Class) shape).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class C_Base {
+               static boolean factoryUsed;
+               public static <R extends C_Base> DefaultBuilder<R> 
builder(Class<R> type) { return new DefaultBuilder<>(type); }
+               public C_Base() {}
+               public static class DefaultBuilder<R extends C_Base> {
+                       private final Class<R> type;
+                       DefaultBuilder(Class<R> type) { this.type = type; }
+                       public R build() {
+                               factoryUsed = true;
+                               try { return 
type.getDeclaredConstructor().newInstance(); }
+                               catch (ReflectiveOperationException e) { throw 
new RuntimeException(e); }
+                       }
+               }
+       }
+
+       public static class C_Sub extends C_Base {
+               public boolean viaCtor;
+               public C_Sub() { this.viaCtor = true; }
+       }
+
+       @Test
+       void b01_classParameterizedParentOnlyFactoryLosesToCtor() {
+               // Make the Class parameter resolvable so that, absent the 
Option-D rule, the weak factory would be selected.
+               beanStore.add(Class.class, C_Sub.class);
+               var bean = run(C_Sub.class);
+               assertNotNull(bean);
+               assertInstanceOf(C_Sub.class, bean);
+               assertTrue(bean.viaCtor, "C_Sub should be built via its direct 
constructor.");
+               assertFalse(C_Base.factoryUsed, "The Class-parameterized 
supertype-only factory must not displace the constructor.");
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // c — a subtype's OWN exact builder still wins even when a constructor 
exists (gate only fires for weak builders).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class D_Base {
+               public D_Base() {}
+       }
+
+       public static class D_Sub extends D_Base {
+               boolean viaBuilder;
+               boolean viaCtor;
+               public static Builder create() { return new Builder(); }
+               public D_Sub() { this.viaCtor = true; }
+               D_Sub(boolean fromBuilder) { this.viaBuilder = fromBuilder; }
+               public static class Builder {
+                       public D_Sub build() { return new D_Sub(true); }
+               }
+       }
+
+       @Test
+       void c01_subtypeExactBuilderWinsOverConstructor() {
+               var bean = run(D_Sub.class);
+               assertNotNull(bean);
+               assertTrue(bean.viaBuilder, "D_Sub's own exact-typed builder 
should win even though a constructor exists.");
+               assertFalse(bean.viaCtor);
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // d — plain POJO with no builder is unaffected (constructor path).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class E_Pojo {
+               public boolean viaCtor;
+               public E_Pojo() { this.viaCtor = true; }
+       }
+
+       @Test
+       void d01_plainPojoUsesConstructor() {
+               var bean = run(E_Pojo.class);
+               assertNotNull(bean);
+               assertTrue(bean.viaCtor);
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_Test.java
index 37f184ff6c..817e47ff21 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BeanInstantiator_Test.java
@@ -4588,15 +4588,20 @@ class BeanInstantiator_Test extends TestBase {
                }
 
                @Test
-               @DisplayName("W09 - Builder inner class is discovered when 
declared on a parent class (Priority 3c)")
+               @DisplayName("W09 - Supertype-only parent inner builder loses 
to the subtype's direct constructor (TODO-143 Option D)")
                void w09_builderInParentClass() {
-                       // W_BeanWithParentInnerBuilder has no declared 
@Builder, no static factory, and no inner
-                       // class.  Priority 3c walks the parent chain and finds 
W_BeanWithInnerBuilder.Builder.
+                       // W_BeanWithParentInnerBuilder has no declared 
@Builder, no static factory, and no inner class of
+                       // its own.  Priority 3c does find 
W_BeanWithInnerBuilder.Builder on the parent, BUT that builder's
+                       // build() only promises the *supertype* 
W_BeanWithInnerBuilder.  Under TODO-143 Option D a
+                       // supertype-only builder candidate must not displace a 
usable direct constructor on the more-specific
+                       // requested type, so selection is declined in favor of 
the no-arg constructor.  (Previously the
+                       // parent-only builder was reported as discovered even 
though its output — a parent instance — was
+                       // always rejected and discarded in favor of this same 
constructor; that was heuristic-pinning, not a
+                       // functional path.)
                        var b = bc(W_BeanWithParentInnerBuilder.class);
-                       b.run();
-                       var builderType = b.getBuilderType();
-                       assertNotNull(builderType, "Builder type must be 
discovered via parent inner class");
-                       assertTrue(builderType.getName().endsWith("$Builder"));
+                       var bean = b.run();
+                       assertNull(b.getBuilderType(), "Supertype-only parent 
inner builder must not be selected when a direct constructor exists.");
+                       assertEquals("parent-default", bean.origin, "Instance 
must be built via the direct constructor.");
                }
 
                public static class W_BeanWithSupplierSetter {
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/rest/RestBuilder_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/rest/RestBuilder_Test.java
new file mode 100644
index 0000000000..b16e8e3146
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/RestBuilder_Test.java
@@ -0,0 +1,164 @@
+/*
+ * 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;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.convention.*;
+import org.apache.juneau.rest.servlet.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * TODO-143 acceptance tests for the fluent {@link RestBuilder} / {@link 
AbstractRestBuilder} configuration surface:
+ * builder-set values override {@code @Rest} annotation values; the 
constructor trio (no-arg, {@code Foo(RestBuilder)},
+ * {@code Foo.Builder}); subclass builders chaining with true covariant 
returns (Option B); and OQ-11
+ * mirror-and-forward per-flavor builders (FaviconMixin) gaining the full REST 
surface via {@link RestMixin.Builder}.
+ */
+class RestBuilder_Test extends TestBase {
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Test resources
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Rest(path="annpath", clientVersionHeader="Ann-Version", 
allowedHeaderParams="AnnHdr")
+       public static class A extends RestResource {}
+
+       @Rest(path="annpath")
+       public static class B extends RestResource {
+               final boolean viaBuilderCtor;
+               public B() { this.viaBuilderCtor = false; }
+               public B(RestBuilder builder) { super(builder); 
this.viaBuilderCtor = true; }
+       }
+
+       private static RestContext ctx(RestResource r) throws Exception {
+               return new RestContext(new RestContext.Args(r.getClass(), null, 
null, () -> r, "", null, null, null, false))
+                       .postInit().postInitChildFirst();
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Builder values override @Rest annotation values (precedence).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void a01_builderOverridesAnnotation_scalar() throws Exception {
+               var r = 
RestResource.builder(A.class).path("bldpath").clientVersionHeader("Bld-Version").build();
+               var c = ctx(r);
+               assertEquals("bldpath", c.getFullPath());
+               assertEquals("Bld-Version", c.getClientVersionHeader());
+       }
+
+       @Test
+       void a02_builderOverridesAnnotation_set() throws Exception {
+               var r = 
RestResource.builder(A.class).allowedHeaderParams("BldHdr").build();
+               assertTrue(ctx(r).getAllowedHeaderParams().contains("BldHdr"));
+       }
+
+       @Test
+       void a03_annotationUsedWhenNoBuilder() throws Exception {
+               // Control: a plain instance (no builder) falls through to the 
@Rest annotation value.
+               assertEquals("annpath", ctx(new A()).getFullPath());
+       }
+
+       @Test
+       void a04_unsetBuilderMembersFallThroughToAnnotation() throws Exception {
+               // Only path is overridden; clientVersionHeader should still 
resolve from the annotation.
+               var r = RestResource.builder(A.class).path("bldpath").build();
+               var c = ctx(r);
+               assertEquals("bldpath", c.getFullPath());
+               assertEquals("Ann-Version", c.getClientVersionHeader());
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Constructor trio (no-arg, Foo(RestBuilder), Foo.Builder).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void b01_noArgConstructor() {
+               assertFalse(new B().viaBuilderCtor);
+               assertNull(new B().getRestBuilder());
+       }
+
+       @Test
+       void b02_builderConstructorInjection() {
+               // B declares B(RestBuilder); createResource() must prefer it 
over the no-arg constructor.
+               var builder = RestResource.builder(B.class).path("x");
+               var r = builder.build();
+               assertTrue(r.viaBuilderCtor);
+               assertSame(builder, r.getRestBuilder());
+       }
+
+       @Test
+       void b03_builderProducesStashedInstance() {
+               var builder = RestResource.builder(A.class).path("x");
+               var r = builder.build();
+               assertSame(builder, r.getRestBuilder());
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // Subclass builders chain with true covariant returns (Option B).
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       public static class CustomBuilder extends RestResource.Builder<A, 
CustomBuilder> {
+               String custom;
+               public CustomBuilder() { super(A.class); }
+               public CustomBuilder custom(String value) { this.custom = 
value; return self(); }
+       }
+
+       @Test
+       void c01_subclassCovariantChaining() {
+               // If any inherited setter returned the base type, the trailing 
.custom(...) would not compile.
+               CustomBuilder b = new 
CustomBuilder().path("/p").custom("a").allowedHeaderParams("h").custom("b");
+               assertEquals("b", b.custom);
+               var r = b.build();
+               assertSame(b, r.getRestBuilder());
+       }
+
+       
//-----------------------------------------------------------------------------------------------------------------
+       // OQ-11 mirror-and-forward per-flavor builder (FaviconMixin) + mixin 
builder support.
+       
//-----------------------------------------------------------------------------------------------------------------
+
+       @Test
+       void d01_faviconMirrorForwardChainsWithRestSurface() {
+               // Worker-config setters (bytes/cacheControl) and the inherited 
REST surface (path) chain covariantly.
+               var fm = FaviconMixin.create()
+                       .path("/icons")
+                       .cacheControl("max-age=10")
+                       .bytes(new byte[]{1,2,3})
+                       .build();
+               assertNotNull(fm);
+               // The worker was configured (serves without error).
+               assertNotNull(fm.getFavicon());
+               // The inherited REST-surface override flowed into the stashed 
builder (would win over @Rest).
+               var rb = fm.getRestBuilder();
+               assertNotNull(rb);
+               assertEquals("/icons", 
((AbstractRestBuilder<?,?>)rb).toRestAnnotation().path());
+       }
+
+       @Rest
+       public static class M extends RestMixin {}
+
+       @Test
+       void d02_mixinBuilderViaFactory() {
+               var builder = RestMixin.builder(M.class).path("/p");
+               var m = builder.build();
+               assertNotNull(m);
+               assertSame(builder, m.getRestBuilder());
+               assertSame(RestMixin.DefaultBuilder.class, 
m.getRestBuilder().getClass());
+       }
+}
diff --git a/juneau-utest/test-run-history.tsv 
b/juneau-utest/test-run-history.tsv
index 719f3ff55a..716f92e14d 100644
--- a/juneau-utest/test-run-history.tsv
+++ b/juneau-utest/test-run-history.tsv
@@ -60,3 +60,4 @@ timestamp     git_sha branch  tests_run       failures        
errors  skipped surefire_sec    wall_sec
 2026-06-01T14:07:35Z   40a74b4f9452    master  126135  0       0       26      
186
 2026-06-01T15:50:15Z   f167a9dccbc7    master  126165  0       0       26      
180
 2026-06-01T17:10:12Z   6f9df91cec12    master  126175  0       0       26      
181
+2026-06-01T18:55:33Z   8eceb21bb5b9    master  126190  0       0       26      
185


Reply via email to