You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/05/04 20:17:09 UTC

(juneau) branch master updated: refactor: align/memoize rest context fields and resolve Sonar issues

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 282b88f127 refactor: align/memoize rest context fields and resolve Sonar issues
282b88f127 is described below

commit 282b88f1272e74df3dbcda8addc4b2bbd71a2c9d
Author: James Bognar <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
AuthorDate: Mon May 4 16:16:57 2026 -0400

    refactor: align/memoize rest context fields and resolve Sonar issues
---
 .../java/org/apache/juneau/rest/RestContext.java   | 850 +++++++++++----------
 .../org/apache/juneau/rest/RestContextInit.java    |   3 +-
 .../java/org/apache/juneau/rest/RestOpContext.java | 700 ++++++++---------
 .../org/apache/juneau/rest/RestOperations.java     |   1 -
 4 files changed, 818 insertions(+), 736 deletions(-)

diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 30deb4a74a..1b9c08cc0d 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
@@ -164,7 +164,7 @@ public class RestContext extends Context {
 	 * Builder class.
 	 *
 	 * <p>
-	 * Demoted to package-private in TODO-16 Phase D-4a (2026-04-19). User code should construct a
+	 * Demoted to package-private in the April 2026 refactor (2026-04-19). User code should construct a
 	 * {@link RestContext} via the public {@link #RestContext(RestContextInit)} constructor; this Builder
 	 * exists only as internal bootstrap state for the framework and is slated for inlining in a later phase.
 	 */
@@ -280,7 +280,7 @@ public class RestContext extends Context {
 		 * Package-private constructor.
 		 *
 		 * <p>
-		 * Demoted from {@code protected} to package-private in TODO-16 Phase D-4a (2026-04-19). Only
+		 * Demoted from {@code protected} to package-private in the April 2026 refactor (2026-04-19). Only
 		 * {@link RestContext#toBuilder(RestContextInit)} instantiates this type now.
 		 *
 		 * @param resourceClass
@@ -709,7 +709,7 @@ public class RestContext extends Context {
 			// @formatter:off
 			// Note: pre-9.5 this also called .addBean(Builder.class, this) so user code could request the
 			// in-flight RestContext.Builder via @RestInit method parameters or @RestInject method-finder
-			// resolution. That injection protocol was deleted in TODO-16 Phase C-3 (zero non-test callers
+			// resolution. That injection protocol was deleted in the April 2026 refactor (zero non-test callers
 			// across the entire codebase) — the Builder is now a private staging detail of RestContext
 			// construction, not a user-visible bean.
 			beanStore = createBeanStore(resource)
@@ -1185,8 +1185,8 @@ public class RestContext extends Context {
 			// @formatter:off
 			// Note: pre-9.5 this filter also excluded `y.hasParameter(RestOpContext.Builder.class)` because the
 			// per-op `@RestInit(RestOpContext.Builder)` flow handled those separately. That flow was deleted in
-			// TODO-16 Phase C-3 Route B (zero real-world callers), so the exclusion is gone too. Likewise, the
-			// class-level `@RestInit(RestContext.Builder)` injection protocol was deleted in TODO-16 Phase C-3
+			// April 2026 refactor Route B (zero real-world callers), so the exclusion is gone too. Likewise, the
+			// class-level `@RestInit(RestContext.Builder)` injection protocol was deleted in the same refactor
 			// (zero non-test callers anywhere) — `RestContext.Builder` is no longer added to the bean store, so
 			// any straggling `@RestInit` method that still declares either Builder type as a parameter will now
 			// surface a "missing prerequisites" error here, which is the desired loud-failure signal.
@@ -1547,7 +1547,8 @@ public class RestContext extends Context {
 			beanStore.getBean(HttpPartParser.class).ifPresent(x -> v.get().impl(x));
 
 			// Replace with this bean.
-			opt(HttpPartParser.class.isInstance(resource.get()) ? HttpPartParser.class.cast(resource.get()) : null).ifPresent(x -> v.get().impl(x));
+			var r = resource.get();
+			opt(r instanceof HttpPartParser x ? x : null).ifPresent(x -> v.get().impl(x));
 
 			// Specify the bean type if its set as a default.
 			beanStore.getBeanType(HttpPartParser.class).ifPresent(x -> v.get().type(x));
@@ -1583,7 +1584,8 @@ public class RestContext extends Context {
 			beanStore.getBean(HttpPartSerializer.class).ifPresent(x -> v.get().impl(x));
 
 			// Replace with this bean.
-			opt(HttpPartSerializer.class.isInstance(resource.get()) ? HttpPartSerializer.class.cast(resource.get()) : null).ifPresent(x -> v.get().impl(x));
+			var r = resource.get();
+			opt(r instanceof HttpPartSerializer x ? x : null).ifPresent(x -> v.get().impl(x));
 
 			// Specify the bean type if its set as a default.
 			beanStore.getBeanType(HttpPartSerializer.class).ifPresent(x -> v.get().type(x));
@@ -1863,7 +1865,7 @@ public class RestContext extends Context {
 
 			var rci = ClassInfo.of(resource.get());
 
-			// TODO-16 Phase C-3 (Route B, 2026-04-19): The per-op `@RestInit(RestOpContext.Builder)` protocol
+			// April 2026 refactor (Route B, 2026-04-19): The per-op `@RestInit(RestOpContext.Builder)` protocol
 			// was deleted. Audit confirmed zero real-world callers (only stale javadoc/docs examples). Users who
 			// previously injected `RestOpContext.Builder` per op now configure operations declaratively via
 			// `@RestOp(...)` attributes, `@RestInject`-named beans, or the class-level `@RestInit(RestContext.Builder)`
@@ -2046,7 +2048,7 @@ public class RestContext extends Context {
 	 *
 	 * <p>
 	 * Wired into the annotation-processing machinery via {@link org.apache.juneau.annotation.ContextApply @ContextApply}
-	 * on {@link Rest}. Moved into {@code RestContext} during TODO-16 Phase D-4a (2026-04-19) so that
+	 * on {@link Rest}. Moved into {@code RestContext} during the April 2026 refactor (2026-04-19) so that
 	 * {@link Builder} can be demoted to package-private without breaking the cross-package reference
 	 * that used to live in {@code RestAnnotation.RestContextApply}.
 	 */
@@ -2112,6 +2114,15 @@ public class RestContext extends Context {
 	protected final String path;
 	protected final ThreadLocal<RestSession> localSession = new ThreadLocal<>();
 	protected final UrlPathMatcher pathMatcher;
+	private final Supplier<?> resource;
+
+	// Private accessors used by memoizer lambdas to satisfy Java's definite-assignment rules for blank final fields.
+	private BasicBeanStore beanStore() { return beanStore; }
+	private Builder builder() { return builder; }
+	private Supplier<?> resource() { return resource; }
+	private Class<?> resourceClass() { return resourceClass; }
+	private RestContext parentContext() { return parentContext; }
+	private RestOperations restOperations() { return restOperations; }
 
 	private static final class LifecycleInvokerPair {
 		final MethodList methods;
@@ -2123,97 +2134,37 @@ public class RestContext extends Context {
 		}
 	}
 
-	private final Memoizer<LifecycleInvokerPair> destroyInvokerPair = memoizer(this::findDestroyLifecycle);
-	private final Memoizer<LifecycleInvokerPair> endCallInvokerPair = memoizer(this::findEndCallLifecycle);
-	private final Memoizer<LifecycleInvokerPair> postInitInvokerPair = memoizer(this::findPostInitLifecycle);
-	private final Memoizer<LifecycleInvokerPair> postInitChildFirstInvokerPair = memoizer(this::findPostInitChildFirstLifecycle);
-	private final Memoizer<LifecycleInvokerPair> startCallInvokerPair = memoizer(this::findStartCallLifecycle);
-	private final Memoizer<MethodList> preCallMethodsMemo = memoizer(this::findPreCallMethodsList);
-	private final Memoizer<MethodList> postCallMethodsMemo = memoizer(this::findPostCallMethodsList);
-
-	private final Memoizer<Logger> loggerMemo = memoizer(this::findLogger);
-
-	private Logger findLogger() {
-		var v = Value.of(Logger.getLogger(cn(resourceClass)));
-		beanStore.getBean(Logger.class).ifPresent(v::set);
-		new BeanCreateMethodFinder<>(Logger.class, resource.get(), beanStore).addBean(Logger.class, v.get()).find(Builder::isRestInjectMethod).run(v::set);
-		return v.get();
-	}
-
-	// Bootstrap-time resolver — no Messages, no Config bean. Cached on the builder so that init()
-	// and findMessages()/findConfig() see the same instance during construction.
-	private final Memoizer<VarResolver> bootstrapVarResolverMemo = memoizer(this::findBootstrapVarResolver);
-
-	private VarResolver findBootstrapVarResolver() {
-		return builder.bootstrapVarResolver();
-	}
-
-	private final Memoizer<Messages> messagesMemo = memoizer(this::findMessages);
-
-	private Messages findMessages() {
-		var b = Messages.create(resourceClass);
-		// Walk @Rest annotations parent-to-child; child wins because location() prepends.
-		var anns = new ArrayList<>(getRestAnnotations());
-		Collections.reverse(anns);
-		// Resolve location strings against the simple resolver — full resolver isn't available yet
-		// (it depends on getMessages()).
-		var vrs = getBootstrapVarResolver().createSession();
-		anns.forEach(ai -> ai.getString(PROPERTY_messages).filter(StringUtils::isNotBlank).ifPresent(s -> b.location(vrs.resolve(s))));
-		beanStore.getBean(Messages.class).ifPresent(b::impl);
-		new BeanCreateMethodFinder<>(Messages.class, resource.get(), beanStore).addBean(Messages.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
-		return b.build();
-	}
-
-	private final Memoizer<ThrownStore> thrownStoreMemo = memoizer(this::findThrownStore);
-
-	private ThrownStore findThrownStore() {
-		var b = ThrownStore.create(beanStore).impl(parentContext == null ? null : parentContext.getThrownStore());
-		beanStore.getBeanType(ThrownStore.class).ifPresent(b::type);
-		beanStore.getBean(ThrownStore.class).ifPresent(b::impl);
-		new BeanCreateMethodFinder<>(ThrownStore.class, resource.get(), beanStore).addBean(ThrownStore.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
-		return b.build();
-	}
-
-	// Depends on getThrownStore() for thrownStoreOnce wiring.
-	private final Memoizer<MethodExecStore> methodExecStoreMemo = memoizer(this::findMethodExecStore);
-
-	private MethodExecStore findMethodExecStore() {
-		var b = MethodExecStore.create(beanStore).thrownStoreOnce(getThrownStore());
-		beanStore.getBeanType(MethodExecStore.class).ifPresent(b::type);
-		beanStore.getBean(MethodExecStore.class).ifPresent(b::impl);
-		new BeanCreateMethodFinder<>(MethodExecStore.class, resource.get(), beanStore).addBean(MethodExecStore.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
-		return b.build();
-	}
-
-	// Runtime resolver — wraps the simple resolver and adds Messages + Config beans.
-	// Depends on getBootstrapVarResolver() and getMessages(); pulls the bootstrap Config from the builder
-	// to avoid an infinite recursion with the runtime Config (which wraps the bootstrap Config in a
-	// session backed by *this* resolver).
-	private final Memoizer<VarResolver> varResolverMemo = memoizer(this::findVarResolver);
-
-	private VarResolver findVarResolver() {
-		var b = getBootstrapVarResolver().copy()
-			.bean(Messages.class, getMessages())
-			.bean(Config.class, builder.config());
-		beanStore.getBean(VarResolver.class).ifPresent(b::impl);
-		new BeanCreateMethodFinder<>(VarResolver.class, resource.get(), beanStore).addBean(VarResolver.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
-		return b.build();
-	}
-
-	private final Memoizer<Config> configMemo = memoizer(this::findConfig);
-
-	private Config findConfig() {
-		// builder.config() returns the unresolved config (already populated in Builder.init() via createConfig());
-		// wrapping with .resolving(varResolver.createSession()) yields the resolving variant exposed by getConfig().
-		return builder.config().resolving(getVarResolver().createSession());
-	}
+	/**
+	 * The {@link BeanContext} for this resource.
+	 *
+	 * <p>
+	 * Built from the builder's sub-builder, which is lazy-created and then mutated by the annotation
+	 * apply pass during {@code init()} before this memoizer fires — so the builder is in its final
+	 * configured state by the time {@code build()} is called.
+	 */
+	private final Memoizer<BeanContext> beanContext = memoizer(() -> builder().beanContext().build());
 
-	private final Memoizer<CallLogger> callLoggerMemo = memoizer(this::findCallLogger);
+	/**
+	 * The bootstrap-time {@link VarResolver} — no {@link Messages} or {@link Config} bean available yet.
+	 *
+	 * <p>
+	 * Cached on the builder so that {@code init()} and the {@link #messages} / {@link #config}
+	 * memoizers all see the same instance during construction.
+	 */
+	private final Memoizer<VarResolver> bootstrapVarResolver = memoizer(() -> builder().bootstrapVarResolver());
 
-	private CallLogger findCallLogger() {
-		var creator = BeanCreator.of(CallLogger.class, beanStore).type(BasicCallLogger.class);
+	/**
+	 * The {@link CallLogger} for this resource.
+	 *
+	 * <p>
+	 * Defaults to {@link BasicCallLogger}. {@code @Rest(callLogger=X)} most-derived non-{@code Void} class wins.
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<CallLogger> callLogger = memoizer(() -> {
+		var bs = beanStore();
+		var creator = BeanCreator.of(CallLogger.class, bs).type(BasicCallLogger.class);
 		// Order matters — annotations override defaults so they need to be applied last.
-		beanStore.getBeanType(CallLogger.class).ifPresent(creator::type);
+		bs.getBeanType(CallLogger.class).ifPresent(creator::type);
 		// @Rest(callLogger=X) — most-derived non-Void wins.
 		// getRestAnnotationsForProperty(...) yields parent-to-child order (rstream reversal); reduce-last
 		// keeps the most-derived value, mirroring the prior "apply each annotation, child overrides parent" semantics.
@@ -2222,74 +2173,209 @@ public class RestContext extends Context {
 			.filter(c -> c != CallLogger.Void.class)
 			.reduce((first, second) -> second)
 			.ifPresent(creator::type);
-		beanStore.getBean(CallLogger.class).ifPresent(creator::impl);
-		new BeanCreateMethodFinder<>(CallLogger.class, resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+		bs.getBean(CallLogger.class).ifPresent(creator::impl);
+		new BeanCreateMethodFinder<>(CallLogger.class, resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
 		return creator.orElse(null);
-	}
+	});
 
-	private final Memoizer<DebugEnablement> debugEnablementMemo = memoizer(this::findDebugEnablement);
+	/**
+	 * The resolving {@link Config} for this resource.
+	 *
+	 * <p>
+	 * Wraps the builder's unresolved config with a {@link VarResolver} session so that SVL variables
+	 * in config values are expanded on access.
+	 */
+	private final Memoizer<Config> config = memoizer(() -> builder().config().resolving(getVarResolver().createSession()));
 
-	private DebugEnablement findDebugEnablement() {
+	/**
+	 * The supported request content types ({@code Content-Type} media types) for this resource.
+	 *
+	 * <p>
+	 * Walks {@code @Rest(consumes)} annotations parent-to-child and returns the union. When no annotation
+	 * declares types, falls back to the intersection of all op-level parser supported media types.
+	 */
+	private final Memoizer<List<MediaType>> consumes = memoizer(() -> {
+		// Walk @Rest(consumes=...) chain (parent-to-child); concat all entries in chain order.
+		var fromAnnotations = new ArrayList<MediaType>();
+		getRestAnnotationsForProperty(PROPERTY_consumes)
+			.forEach(ai -> stream(ai.inner().consumes()).map(MediaType::of).forEach(fromAnnotations::add));
+		if (! fromAnnotations.isEmpty())
+			return u(fromAnnotations);
+		// Fall back to the intersection of opContexts' parser-supported media types — matches legacy behavior.
+		var opContexts = restOperations().getOpContexts();
+		Set<MediaType> s = opContexts.isEmpty() ? emptySet() : toSet(opContexts.get(0).getParsers().getSupportedMediaTypes());
+		opContexts.forEach(x -> s.retainAll(x.getParsers().getSupportedMediaTypes()));
+		return u(toList(s));
+	});
+
+	/**
+	 * The {@link DebugEnablement} for this resource.
+	 *
+	 * <p>
+	 * Resolved from {@code @Rest(debugDefault)} (most-derived non-blank wins), falling back to
+	 * {@code @Rest(debug=true|false)}. The resolved {@link Enablement} is published into the bean store
+	 * so that {@link BasicDebugEnablement} subclasses can pick it up. Defaults to {@link BasicDebugEnablement}.
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<DebugEnablement> debugEnablement = memoizer(() -> {
 		// @Rest(debugDefault="ALWAYS|NEVER|CONDITIONAL") — most-derived non-blank value wins, with parent inheritance
 		// gated by @Rest(noInherit={"debugDefault"}). Resolved value is published as an Enablement bean so that
-		// BasicDebugEnablement.init() (and any subclass) can pick it up via beanStore.getBean(Enablement.class).
+		// BasicDebugEnablement.init() (and any subclass) can pick it up via beanStore().getBean(Enablement.class).
 		// Annotation value overrides any pre-registered Enablement bean (e.g. mock-client default).
 		// If neither a debugDefault annotation value NOR a pre-registered Enablement bean is present, fall back
-		// to the @Rest(debug=true|false) boolean flag — ALWAYS when set, NEVER otherwise. This used to be done
-		// inside BasicDebugEnablement.init() by reading RestContext.Builder.isDebug() out of the bean store, but
-		// that protocol is gone (TODO-16 Phase C-3, 2026-04-19): no Enablement.class lookup escapes this method
-		// without a value, so subclasses no longer need access to the in-flight Builder.
+		// to the @Rest(debug=true|false) boolean flag — ALWAYS when set, NEVER otherwise.
+		var bs = beanStore();
 		String debugDefaultStr = mergeReplacedStringAttribute(PROPERTY_debugDefault, env("RestContext.debugDefault").orElse(null));
 		Enablement resolvedDebugDefault = null;
 		if (nn(debugDefaultStr) && !debugDefaultStr.isBlank())
 			resolvedDebugDefault = Enablement.fromString(debugDefaultStr);
 		if (nn(resolvedDebugDefault))
-			beanStore.addBean(Enablement.class, resolvedDebugDefault);
-		else if (beanStore.getBean(Enablement.class).isEmpty())
-			beanStore.addBean(Enablement.class, builder.isDebug() ? Enablement.ALWAYS : Enablement.NEVER);
-		var creator = BeanCreator.of(DebugEnablement.class, beanStore).type(BasicDebugEnablement.class);
+			bs.addBean(Enablement.class, resolvedDebugDefault);
+		else if (bs.getBean(Enablement.class).isEmpty())
+			bs.addBean(Enablement.class, builder().isDebug() ? Enablement.ALWAYS : Enablement.NEVER);
+		var creator = BeanCreator.of(DebugEnablement.class, bs).type(BasicDebugEnablement.class);
 		// Order matters — annotations override defaults so they need to be applied last.
-		beanStore.getBeanType(DebugEnablement.class).ifPresent(creator::type);
-		// @Rest(debugEnablement=X) — most-derived non-Void wins. See findCallLogger() for the reduce-last rationale.
+		bs.getBeanType(DebugEnablement.class).ifPresent(creator::type);
+		// @Rest(debugEnablement=X) — most-derived non-Void wins. See callLogger for the reduce-last rationale.
 		getRestAnnotationsForProperty(PROPERTY_debugEnablement)
 			.map(ai -> ai.inner().debugEnablement())
 			.filter(c -> c != DebugEnablement.Void.class)
 			.reduce((first, second) -> second)
 			.ifPresent(creator::type);
-		beanStore.getBean(DebugEnablement.class).ifPresent(creator::impl);
-		new BeanCreateMethodFinder<>(DebugEnablement.class, resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+		bs.getBean(DebugEnablement.class).ifPresent(creator::impl);
+		new BeanCreateMethodFinder<>(DebugEnablement.class, resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
 		return creator.orElse(null);
-	}
+	});
 
-	private final Memoizer<StaticFiles> staticFilesMemo = memoizer(this::findStaticFiles);
+	/**
+	 * The default request attributes contributed by {@code @Rest(defaultRequestAttributes)} for this resource.
+	 */
+	private final Memoizer<NamedAttributeMap> defaultRequestAttributes = memoizer(() -> builder().defaultRequestAttributes());
 
-	private StaticFiles findStaticFiles() {
-		var creator = BeanCreator.of(StaticFiles.class, beanStore).type(BasicStaticFiles.class);
-		// Order matters — annotations override defaults so they need to be applied last.
-		beanStore.getBeanType(StaticFiles.class).ifPresent(creator::type);
-		// @Rest(staticFiles=X) — most-derived non-Void wins. See findCallLogger() for the reduce-last rationale.
-		getRestAnnotationsForProperty(PROPERTY_staticFiles)
-			.map(ai -> ai.inner().staticFiles())
-			.filter(c -> c != StaticFiles.Void.class)
-			.reduce((first, second) -> second)
-			.ifPresent(creator::type);
-		beanStore.getBean(StaticFiles.class).ifPresent(creator::impl);
-		new BeanCreateMethodFinder<>(StaticFiles.class, resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
-		return creator.orElse(null);
-	}
+	/**
+	 * The default request headers contributed by {@code @Rest(defaultRequestHeaders)} for this resource.
+	 */
+	private final Memoizer<HeaderList> defaultRequestHeaders = memoizer(() -> builder().defaultRequestHeaders());
+
+	/**
+	 * The default response headers contributed by {@code @Rest(defaultResponseHeaders)} for this resource.
+	 */
+	private final Memoizer<HeaderList> defaultResponseHeaders = memoizer(() -> builder().defaultResponseHeaders());
 
-	private final Memoizer<BeanContext> beanContextMemo = memoizer(this::findBeanContext);
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestDestroy @RestDestroy} and their invokers.
+	 */
+	private final Memoizer<LifecycleInvokerPair> destroyInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> builder().createDestroyMethods(beanStore(), resource())));
 
-	private BeanContext findBeanContext() {
-		// Builder.beanContext() lazy-creates from BeanContext.create() seeded with bean-store overrides
-		// (BeanContext.Builder / BeanContext) and is mutated by init()'s apply(work) call before the ctor
-		// triggers this memoizer — so by the time we build() the builder it's in its final configured state.
-		return builder.beanContext().build();
-	}
+	/**
+	 * The {@link EncoderSet} for this resource, built from the builder's encoder sub-builder.
+	 */
+	private final Memoizer<EncoderSet> encoders = memoizer(() -> builder().encoders().build());
+
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestEndCall @RestEndCall} and their invokers.
+	 */
+	private final Memoizer<LifecycleInvokerPair> endCallInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> builder().createEndCallMethods(beanStore(), resource())));
+
+	/**
+	 * The {@link JsonSchemaGenerator} for this resource, built from the builder's JSON-schema sub-builder.
+	 */
+	private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = memoizer(() -> builder().jsonSchemaGenerator().build());
+
+	/**
+	 * The {@link Logger} for this resource.
+	 *
+	 * <p>
+	 * Defaults to {@code Logger.getLogger(resourceClass.getName())}. A bean-store override or
+	 * {@code @RestInject} factory method REPLACES the default.
+	 */
+	private final Memoizer<Logger> logger = memoizer(() -> {
+		var v = Value.of(Logger.getLogger(cn(resourceClass())));
+		beanStore().getBean(Logger.class).ifPresent(v::set);
+		new BeanCreateMethodFinder<>(Logger.class, resource().get(), beanStore()).addBean(Logger.class, v.get()).find(Builder::isRestInjectMethod).run(v::set);
+		return v.get();
+	});
+
+	/**
+	 * The {@link Messages} bundle for this resource.
+	 *
+	 * <p>
+	 * Walks {@code @Rest} annotations parent-to-child, resolving each {@code messages} location string
+	 * against the bootstrap resolver (full resolver not yet available — it depends on {@code getMessages()}).
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<Messages> messages = memoizer(() -> {
+		var b = Messages.create(resourceClass());
+		// Walk @Rest annotations parent-to-child; child wins because location() prepends.
+		var anns = new ArrayList<>(getRestAnnotations());
+		Collections.reverse(anns);
+		// Resolve location strings against the simple resolver — full resolver isn't available yet
+		// (it depends on getMessages()).
+		var vrs = getBootstrapVarResolver().createSession();
+		anns.forEach(ai -> ai.getString(PROPERTY_messages).filter(StringUtils::isNotBlank).ifPresent(s -> b.location(vrs.resolve(s))));
+		beanStore().getBean(Messages.class).ifPresent(b::impl);
+		new BeanCreateMethodFinder<>(Messages.class, resource().get(), beanStore()).addBean(Messages.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
+		return b.build();
+	});
+
+	/**
+	 * The {@link MethodExecStore} for this resource, wired to the {@link ThrownStore}.
+	 *
+	 * <p>
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<MethodExecStore> methodExecStore = memoizer(() -> {
+		var bs = beanStore();
+		var b = MethodExecStore.create(bs).thrownStoreOnce(getThrownStore());
+		bs.getBeanType(MethodExecStore.class).ifPresent(b::type);
+		bs.getBean(MethodExecStore.class).ifPresent(b::impl);
+		new BeanCreateMethodFinder<>(MethodExecStore.class, resource().get(), bs).addBean(MethodExecStore.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
+		return b.build();
+	});
+
+	/**
+	 * The {@link ParserSet} for this resource, built from the builder's parser sub-builder.
+	 */
+	private final Memoizer<ParserSet> parsers = memoizer(() -> builder().parsers().build());
+
+	/**
+	 * The {@link HttpPartParser} for this resource, created from the builder's part-parser creator.
+	 */
+	private final Memoizer<HttpPartParser> partParser = memoizer(() -> builder().partParser().create());
+
+	/**
+	 * The {@link HttpPartSerializer} for this resource, created from the builder's part-serializer creator.
+	 */
+	private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() -> builder().partSerializer().create());
+
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestPostCall @RestPostCall}.
+	 */
+	private final Memoizer<MethodList> postCallMethods = memoizer(() -> builder().createPostCallMethods(beanStore(), resource()));
+
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestPostInit @RestPostInit}{@code (childFirst=true)} and their invokers.
+	 */
+	private final Memoizer<LifecycleInvokerPair> postInitChildFirstInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> builder().createPostInitChildFirstMethods(beanStore(), resource())));
+
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestPostInit @RestPostInit} and their invokers.
+	 */
+	private final Memoizer<LifecycleInvokerPair> postInitInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> builder().createPostInitMethods(beanStore(), resource())));
 
-	private final Memoizer<List<MediaType>> producesMemo = memoizer(this::findProduces);
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestPreCall @RestPreCall}.
+	 */
+	private final Memoizer<MethodList> preCallMethods = memoizer(() -> builder().createPreCallMethods(beanStore(), resource()));
 
-	private List<MediaType> findProduces() {
+	/**
+	 * The supported response content types ({@code Accept} media types) for this resource.
+	 *
+	 * <p>
+	 * Walks {@code @Rest(produces)} annotations parent-to-child and returns the union. When no annotation
+	 * declares types, falls back to the intersection of all op-level serializer supported media types.
+	 */
+	private final Memoizer<List<MediaType>> produces = memoizer(() -> {
 		// Walk @Rest(produces=...) chain (parent-to-child); concat all entries in chain order.
 		var fromAnnotations = new ArrayList<MediaType>();
 		getRestAnnotationsForProperty(PROPERTY_produces)
@@ -2297,139 +2383,152 @@ public class RestContext extends Context {
 		if (! fromAnnotations.isEmpty())
 			return u(fromAnnotations);
 		// Fall back to the intersection of opContexts' serializer-supported media types — matches legacy behavior.
-		var opContexts = restOperations.getOpContexts();
+		var opContexts = restOperations().getOpContexts();
 		Set<MediaType> s = opContexts.isEmpty() ? emptySet() : toSet(opContexts.get(0).getSerializers().getSupportedMediaTypes());
 		opContexts.forEach(x -> s.retainAll(x.getSerializers().getSupportedMediaTypes()));
 		return u(toList(s));
-	}
-
-	private final Memoizer<List<MediaType>> consumesMemo = memoizer(this::findConsumes);
+	});
 
-	private List<MediaType> findConsumes() {
-		// Walk @Rest(consumes=...) chain (parent-to-child); concat all entries in chain order.
-		var fromAnnotations = new ArrayList<MediaType>();
-		getRestAnnotationsForProperty(PROPERTY_consumes)
-			.forEach(ai -> stream(ai.inner().consumes()).map(MediaType::of).forEach(fromAnnotations::add));
-		if (! fromAnnotations.isEmpty())
-			return u(fromAnnotations);
-		// Fall back to the intersection of opContexts' parser-supported media types — matches legacy behavior.
-		var opContexts = restOperations.getOpContexts();
-		Set<MediaType> s = opContexts.isEmpty() ? emptySet() : toSet(opContexts.get(0).getParsers().getSupportedMediaTypes());
-		opContexts.forEach(x -> s.retainAll(x.getParsers().getSupportedMediaTypes()));
-		return u(toList(s));
-	}
-
-	private final Memoizer<ResponseProcessor[]> responseProcessorsMemo = memoizer(this::findResponseProcessors);
-
-	private ResponseProcessor[] findResponseProcessors() {
+	/**
+	 * The ordered array of {@link ResponseProcessor} instances for this resource.
+	 *
+	 * <p>
+	 * Walks {@code @Rest(responseProcessors)} annotations parent-to-child (append order). A bean-store
+	 * override or {@code @RestInject} factory method REPLACES the entire list.
+	 */
+	private final Memoizer<ResponseProcessor[]> responseProcessors = memoizer(() -> {
 		// Walk @Rest(responseProcessors=...) chain (parent-to-child via getRestAnnotationsForProperty).
 		// DefaultConfig contributes the framework defaults at the top of the chain; resource-class entries append.
 		// ResponseProcessorList.Builder.add(...) uses addAll (append) — final order: [DefaultConfig, parent, child].
-		var v = Value.of(ResponseProcessorList.create(beanStore));
+		var bs = beanStore();
+		var v = Value.of(ResponseProcessorList.create(bs));
 		getRestAnnotationsForProperty(PROPERTY_responseProcessors)
 			.forEach(ai -> v.get().add(ai.inner().responseProcessors()));
 		// Bean-store override REPLACES the entire annotation-derived list.
-		beanStore.getBean(ResponseProcessorList.class).ifPresent(x -> v.get().impl(x));
+		bs.getBean(ResponseProcessorList.class).ifPresent(x -> v.get().impl(x));
 		// @RestInject method override REPLACES the entire annotation-derived list.
-		new BeanCreateMethodFinder<>(ResponseProcessorList.class, resource.get(), beanStore)
+		new BeanCreateMethodFinder<>(ResponseProcessorList.class, resource().get(), bs)
 			.addBean(ResponseProcessorList.Builder.class, v.get())
 			.find(Builder::isRestInjectMethod)
 			.run(x -> v.get().impl(x));
 		return v.get().build().toArray();
-	}
-
-	private final Memoizer<Class<? extends RestOpArg>[]> restOpArgsMemo = memoizer(this::findRestOpArgs);
+	});
 
-	private Class<? extends RestOpArg>[] findRestOpArgs() {
+	/**
+	 * The ordered array of {@link RestOpArg} argument-resolver types for this resource.
+	 *
+	 * <p>
+	 * Walks {@code @Rest(restOpArgs)} annotations parent-to-child (prepend order, so child entries
+	 * take priority). A bean-store override or {@code @RestInject} factory method REPLACES the entire list.
+	 */
+	private final Memoizer<Class<? extends RestOpArg>[]> restOpArgs = memoizer(() -> {
 		// Walk @Rest(restOpArgs=...) chain (parent-to-child via getRestAnnotationsForProperty).
 		// DefaultConfig contributes the framework arg-resolver list; resource-class entries override.
 		// RestOpArgList.Builder.add(...) uses prependAll — applying per-annotation in chain order yields
 		// final order: [child, parent, DefaultConfig], matching the legacy apply-pass behavior.
-		var v = Value.of(RestOpArgList.create(beanStore));
+		var bs = beanStore();
+		var v = Value.of(RestOpArgList.create(bs));
 		getRestAnnotationsForProperty(PROPERTY_restOpArgs)
 			.forEach(ai -> v.get().add(ai.inner().restOpArgs()));
 		// Bean-store override REPLACES the entire annotation-derived list.
-		beanStore.getBean(RestOpArgList.class).ifPresent(x -> v.get().impl(x));
+		bs.getBean(RestOpArgList.class).ifPresent(x -> v.get().impl(x));
 		// @RestInject method override REPLACES the entire annotation-derived list.
-		new BeanCreateMethodFinder<>(RestOpArgList.class, resource.get(), beanStore)
+		new BeanCreateMethodFinder<>(RestOpArgList.class, resource().get(), bs)
 			.addBean(RestOpArgList.Builder.class, v.get())
 			.find(Builder::isRestInjectMethod)
 			.run(x -> v.get().impl(x));
 		return v.get().build().asArray();
-	}
-
-	private final Memoizer<SerializerSet> serializersMemo = memoizer(this::findSerializers);
-
-	private SerializerSet findSerializers() {
-		return builder.serializers().build();
-	}
+	});
 
-	private final Memoizer<ParserSet> parsersMemo = memoizer(this::findParsers);
-
-	private ParserSet findParsers() {
-		return builder.parsers().build();
-	}
-
-	private final Memoizer<HttpPartSerializer> partSerializerMemo = memoizer(this::findPartSerializer);
-
-	private HttpPartSerializer findPartSerializer() {
-		return builder.partSerializer().create();
-	}
-
-	private final Memoizer<HttpPartParser> partParserMemo = memoizer(this::findPartParser);
-
-	private HttpPartParser findPartParser() {
-		return builder.partParser().create();
-	}
-
-	private final Memoizer<JsonSchemaGenerator> jsonSchemaGeneratorMemo = memoizer(this::findJsonSchemaGenerator);
-
-	private JsonSchemaGenerator findJsonSchemaGenerator() {
-		return builder.jsonSchemaGenerator().build();
-	}
-
-	private final Memoizer<EncoderSet> encodersMemo = memoizer(this::findEncoders);
-
-	private EncoderSet findEncoders() {
-		return builder.encoders().build();
-	}
-
-	private final Memoizer<HeaderList> defaultRequestHeadersMemo = memoizer(this::findDefaultRequestHeaders);
-
-	private HeaderList findDefaultRequestHeaders() {
-		return builder.defaultRequestHeaders();
-	}
-
-	private final Memoizer<HeaderList> defaultResponseHeadersMemo = memoizer(this::findDefaultResponseHeaders);
-
-	private HeaderList findDefaultResponseHeaders() {
-		return builder.defaultResponseHeaders();
-	}
-
-	private final Memoizer<NamedAttributeMap> defaultRequestAttributesMemo = memoizer(this::findDefaultRequestAttributes);
+	/**
+	 * The {@link SerializerSet} for this resource, built from the builder's serializer sub-builder.
+	 */
+	private final Memoizer<SerializerSet> serializers = memoizer(() -> builder().serializers().build());
 
-	private NamedAttributeMap findDefaultRequestAttributes() {
-		return builder.defaultRequestAttributes();
-	}
+	/**
+	 * Methods annotated with {@link org.apache.juneau.rest.annotation.RestStartCall @RestStartCall} and their invokers.
+	 */
+	private final Memoizer<LifecycleInvokerPair> startCallInvokerPair = memoizer(() -> buildLifecycleInvokerPair(() -> builder().createStartCallMethods(beanStore(), resource())));
 
-	private final Memoizer<SwaggerProvider> swaggerProviderMemo = memoizer(this::findSwaggerProvider);
+	/**
+	 * The {@link StaticFiles} provider for this resource.
+	 *
+	 * <p>
+	 * Defaults to {@link BasicStaticFiles}. {@code @Rest(staticFiles=X)} most-derived non-{@code Void} class wins.
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<StaticFiles> staticFiles = memoizer(() -> {
+		var bs = beanStore();
+		var creator = BeanCreator.of(StaticFiles.class, bs).type(BasicStaticFiles.class);
+		// Order matters — annotations override defaults so they need to be applied last.
+		bs.getBeanType(StaticFiles.class).ifPresent(creator::type);
+		// @Rest(staticFiles=X) — most-derived non-Void wins. See callLogger for the reduce-last rationale.
+		getRestAnnotationsForProperty(PROPERTY_staticFiles)
+			.map(ai -> ai.inner().staticFiles())
+			.filter(c -> c != StaticFiles.Void.class)
+			.reduce((first, second) -> second)
+			.ifPresent(creator::type);
+		bs.getBean(StaticFiles.class).ifPresent(creator::impl);
+		new BeanCreateMethodFinder<>(StaticFiles.class, resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
+		return creator.orElse(null);
+	});
 
-	private SwaggerProvider findSwaggerProvider() {
-		var creator = BeanCreator.of(SwaggerProvider.class, beanStore).type(BasicSwaggerProvider.class);
+	/**
+	 * The {@link SwaggerProvider} for this resource.
+	 *
+	 * <p>
+	 * Defaults to {@link BasicSwaggerProvider}. {@code @Rest(swaggerProvider=X)} most-derived
+	 * non-{@code Void} class wins. A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<SwaggerProvider> swaggerProvider = memoizer(() -> {
+		var bs = beanStore();
+		var creator = BeanCreator.of(SwaggerProvider.class, bs).type(BasicSwaggerProvider.class);
 		// Order matters — annotations override defaults so they need to be applied last.
-		beanStore.getBeanType(SwaggerProvider.class).ifPresent(creator::type);
-		// @Rest(swaggerProvider=X) — most-derived non-Void wins. See findCallLogger() for the reduce-last rationale.
+		bs.getBeanType(SwaggerProvider.class).ifPresent(creator::type);
+		// @Rest(swaggerProvider=X) — most-derived non-Void wins. See callLogger for the reduce-last rationale.
 		getRestAnnotationsForProperty(PROPERTY_swaggerProvider)
 			.map(ai -> ai.inner().swaggerProvider())
 			.filter(c -> c != SwaggerProvider.Void.class)
 			.reduce((first, second) -> second)
 			.ifPresent(creator::type);
-		beanStore.getBean(SwaggerProvider.class).ifPresent(creator::impl);
-		new BeanCreateMethodFinder<>(SwaggerProvider.class, resource.get(), beanStore).find(Builder::isRestInjectMethod).run(creator::impl);
+		bs.getBean(SwaggerProvider.class).ifPresent(creator::impl);
+		new BeanCreateMethodFinder<>(SwaggerProvider.class, resource().get(), bs).find(Builder::isRestInjectMethod).run(creator::impl);
 		return creator.orElse(null);
-	}
+	});
 
-	private final Supplier<?> resource;
+	/**
+	 * The {@link ThrownStore} for this resource, used to track exception statistics.
+	 *
+	 * <p>
+	 * Inherits from the parent context's store when one is present. A bean-store override or
+	 * {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<ThrownStore> thrownStore = memoizer(() -> {
+		var bs = beanStore();
+		var b = ThrownStore.create(bs).impl(parentContext() == null ? null : parentContext().getThrownStore());
+		bs.getBeanType(ThrownStore.class).ifPresent(b::type);
+		bs.getBean(ThrownStore.class).ifPresent(b::impl);
+		new BeanCreateMethodFinder<>(ThrownStore.class, resource().get(), bs).addBean(ThrownStore.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
+		return b.build();
+	});
+
+	/**
+	 * The runtime {@link VarResolver} — wraps the bootstrap resolver and adds {@link Messages} and {@link Config} beans.
+	 *
+	 * <p>
+	 * The bootstrap {@link Config} is pulled from the builder to avoid a circular dependency: the runtime
+	 * {@link Config} wraps the bootstrap config in a session backed by this resolver.
+	 * A bean-store override or {@code @RestInject} factory method REPLACES the result.
+	 */
+	private final Memoizer<VarResolver> varResolver = memoizer(() -> {
+		var bs = beanStore();
+		var b = getBootstrapVarResolver().copy()
+			.bean(Messages.class, getMessages())
+			.bean(Config.class, builder().config());
+		bs.getBean(VarResolver.class).ifPresent(b::impl);
+		new BeanCreateMethodFinder<>(VarResolver.class, resource().get(), bs).addBean(VarResolver.Builder.class, b).find(Builder::isRestInjectMethod).run(b::impl);
+		return b.build();
+	});
 
 	/**
 	 * Constructor — record-based entry point.
@@ -2462,7 +2561,7 @@ public class RestContext extends Context {
 	 * Internal constructor.
 	 *
 	 * <p>
-	 * Privatized in TODO-16 Phase D-4a (2026-04-19). External callers must use
+	 * Privatized in the April 2026 refactor (2026-04-19). External callers must use
 	 * {@link #RestContext(RestContextInit)} instead; this constructor only services the internal
 	 * {@link #toBuilder(RestContextInit)} helper.
 	 *
@@ -2532,8 +2631,8 @@ public class RestContext extends Context {
 			restChildren = builder.restChildren(this).build();
 			bs.addBean(SwaggerProvider.class, getSwaggerProvider());
 
-			// produces/consumes are resolved lazily via the producesMemo/consumesMemo memoizers below
-			// (TODO-16 Phase D-1, 2026-04-19) — they walk the @Rest(produces=...) / @Rest(consumes=...)
+			// produces/consumes are resolved lazily via the produces/consumes memoizers below
+			// (April 2026 refactor, 2026-04-19) — they walk the @Rest(produces=...) / @Rest(consumes=...)
 			// chain first, then fall back to deriving the intersection of opContexts' supported media types.
 
 		} catch (BasicHttpException e) {
@@ -2556,6 +2655,40 @@ public class RestContext extends Context {
 	// Memoized allowlist fields
 	//---------------------------------------------------------------------------------------------
 
+	/**
+	 * Memoized parser session-option keys from {@code @Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
+	 *
+	 * <p>
+	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
+	 * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive tokens.
+	 */
+	private final Memoizer<SortedSet<String>> allowedParserOptions = memoizer(() -> {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedParserOptions;
+		var pc = parentContext();
+		if (isInherited(p) && pc != null)
+			l.addAll(pc.getAllowedParserOptions());
+		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
+		return u(treeSetCi(removeNegations(l)));
+	});
+
+	/**
+	 * Memoized serializer session-option keys from {@code @Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
+	 *
+	 * <p>
+	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
+	 * Leading-hyphen tokens remove earlier positive tokens.
+	 */
+	private final Memoizer<SortedSet<String>> allowedSerializerOptions = memoizer(() -> {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedSerializerOptions;
+		var pc = parentContext();
+		if (isInherited(p) && pc != null)
+			l.addAll(pc.getAllowedSerializerOptions());
+		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
+		return u(treeSetCi(removeNegations(l)));
+	});
+
 	/**
 	 * Memoized value of the {@code noInherit} annotation attribute from the nearest {@code @Rest} annotation.
 	 *
@@ -2581,9 +2714,7 @@ public class RestContext extends Context {
 	 * {@code DefaultConfig} (transitively or directly) won't get a duplicate entry — the list is dedup'd by the
 	 * {@link Class} the annotation was declared on.
 	 */
-	private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = memoizer(this::findRestAnnotations);
-
-	private List<AnnotationInfo<Rest>> findRestAnnotations() {
+	private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = memoizer(() -> {
 		var raw = getAnnotationProvider().find(Rest.class, ClassInfo.of(getResourceClass()));
 		var hasDefaultConfig = raw.stream().anyMatch(ai -> ai.getAnnotatable() instanceof ClassInfo ci && DefaultConfig.class.equals(ci.inner()));
 		if (hasDefaultConfig)
@@ -2594,43 +2725,7 @@ public class RestContext extends Context {
 		var combined = new ArrayList<>(raw);
 		combined.addAll(defaultConfigAnnotations);
 		return Collections.unmodifiableList(combined);
-	}
-
-	/**
-	 * Memoized parser session-option keys from {@code @Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
-	 *
-	 * <p>
-	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
-	 * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive tokens.
-	 */
-	private final Memoizer<SortedSet<String>> allowedParserOptions = memoizer(this::findAllowedParserOptions);
-
-	private SortedSet<String> findAllowedParserOptions() {
-		var l = new ArrayList<String>();
-		var p = PROPERTY_allowedParserOptions;
-		if (isInherited(p) && parentContext != null)
-			l.addAll(parentContext.getAllowedParserOptions());
-		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
-		return u(treeSetCi(removeNegations(l)));
-	}
-
-	/**
-	 * Memoized serializer session-option keys from {@code @Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
-	 *
-	 * <p>
-	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
-	 * Leading-hyphen tokens remove earlier positive tokens.
-	 */
-	private final Memoizer<SortedSet<String>> allowedSerializerOptions = memoizer(this::findAllowedSerializerOptions);
-
-	private SortedSet<String> findAllowedSerializerOptions() {
-		var l = new ArrayList<String>();
-		var p = PROPERTY_allowedSerializerOptions;
-		if (isInherited(p) && parentContext != null)
-			l.addAll(parentContext.getAllowedSerializerOptions());
-		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
-		return u(treeSetCi(removeNegations(l)));
-	}
+	});
 
 	private Stream<AnnotationInfo<Rest>> restAnnotationsForPropertySortedByRank(String propertyName) {
 		return getRestAnnotationsForProperty(propertyName).sorted(Comparator.comparingInt(AnnotationInfo::getRank));
@@ -2679,45 +2774,27 @@ public class RestContext extends Context {
 		return v.get();
 	}
 
-	private final Memoizer<Set<String>> allowedHeaderParams = memoizer(this::findAllowedHeaderParams);
-
-	private Set<String> findAllowedHeaderParams() {
-		return Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams, env("RestContext.allowedHeaderParams", "Accept,Content-Type"))));
-	}
-
-	private final Memoizer<Set<String>> allowedMethodHeaders = memoizer(this::findAllowedMethodHeaders);
+	/** Header names that may be passed via URL query parameter; resolved from {@code @Rest(allowedHeaderParams)}, default {@code "Accept,Content-Type"}. */
+	private final Memoizer<Set<String>> allowedHeaderParams      = memoizer(() -> Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedHeaderParams, env("RestContext.allowedHeaderParams", "Accept,Content-Type")))));
+	/** HTTP method names that may be specified via a request header; resolved from {@code @Rest(allowedMethodHeaders)}, default empty. */
+	private final Memoizer<Set<String>> allowedMethodHeaders     = memoizer(() -> Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodHeaders, env("RestContext.allowedMethodHeaders").orElse("")))));
+	/** HTTP method names that may be specified via URL query parameter; resolved from {@code @Rest(allowedMethodParams)}, default {@code "HEAD,OPTIONS"}. */
+	private final Memoizer<Set<String>> allowedMethodParams      = memoizer(() -> Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodParams, env("RestContext.allowedMethodParams", "HEAD,OPTIONS")))));
+	/** Whether a {@code &content=} URL parameter may override the request body; inverse of {@code @Rest(disableContentParam)}. */
+	private final Memoizer<Boolean>     allowContentParam        = memoizer(() -> !mergeReplacedBooleanAttribute(PROPERTY_disableContentParam, env("RestContext.disableContentParam", false)));
+	/** Whether exception stack traces are rendered in error responses; resolved from {@code @Rest(renderResponseStackTraces)}. */
+	private final Memoizer<Boolean>     renderResponseStackTraces = memoizer(() -> mergeReplacedBooleanAttribute(PROPERTY_renderResponseStackTraces, env("RestContext.renderResponseStackTraces", false)));
+	/** The request header used for client-version matching; resolved from {@code @Rest(clientVersionHeader)}, default {@code "Client-Version"}. */
+	private final Memoizer<String>      clientVersionHeader      = memoizer(() -> mergeReplacedStringAttribute(PROPERTY_clientVersionHeader, env("RestContext.clientVersionHeader", "Client-Version")));
 
-	private Set<String> findAllowedMethodHeaders() {
-		return Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodHeaders, env("RestContext.allowedMethodHeaders").orElse(""))));
-	}
-
-	private final Memoizer<Set<String>> allowedMethodParams = memoizer(this::findAllowedMethodParams);
-
-	private Set<String> findAllowedMethodParams() {
-		return Collections.unmodifiableSet(newCaseInsensitiveSet(mergeReplacedStringAttribute(PROPERTY_allowedMethodParams, env("RestContext.allowedMethodParams", "HEAD,OPTIONS"))));
-	}
-
-	private final Memoizer<Boolean> allowContentParam = memoizer(this::findAllowContentParam);
-
-	private boolean findAllowContentParam() {
-		return ! mergeReplacedBooleanAttribute(PROPERTY_disableContentParam, env("RestContext.disableContentParam", false));
-	}
-
-	private final Memoizer<Boolean> renderResponseStackTraces = memoizer(this::findRenderResponseStackTraces);
-
-	private boolean findRenderResponseStackTraces() {
-		return mergeReplacedBooleanAttribute(PROPERTY_renderResponseStackTraces, env("RestContext.renderResponseStackTraces", false));
-	}
-
-	private final Memoizer<String> clientVersionHeader = memoizer(this::findClientVersionHeader);
-
-	private String findClientVersionHeader() {
-		return mergeReplacedStringAttribute(PROPERTY_clientVersionHeader, env("RestContext.clientVersionHeader", "Client-Version"));
-	}
-
-	private final Memoizer<UriRelativity> uriRelativity = memoizer(this::findUriRelativity);
-
-	private UriRelativity findUriRelativity() {
+	/**
+	 * The {@link UriRelativity} strategy for URI resolution in this resource.
+	 *
+	 * <p>
+	 * Resolved from {@code @Rest(uriRelativity)} annotations (most-derived non-blank wins), with env-var
+	 * fallback. Defaults to {@link UriRelativity#RESOURCE}.
+	 */
+	private final Memoizer<UriRelativity> uriRelativity = memoizer(() -> {
 		var v = new AtomicReference<>(
 			parseEnumConstant(UriRelativity.class, resolve(emptyIfNull(env("RestContext.uriRelativity").get())))
 				.orElse(UriRelativity.RESOURCE)
@@ -2726,11 +2803,46 @@ public class RestContext extends Context {
 			parseEnumConstant(UriRelativity.class, resolve(s)).ifPresent(v::set)
 		));
 		return v.get();
-	}
+	});
 
-	private final Memoizer<UriResolution> uriResolution = memoizer(this::findUriResolution);
+	/**
+	 * The URI authority (scheme + host + port) override for this resource.
+	 *
+	 * <p>
+	 * Resolved from {@code @Rest(uriAuthority)} annotations and env-var; inherited from parent when not
+	 * blocked by {@code noInherit}. {@code null} means no override.
+	 */
+	private final Memoizer<String> uriAuthority = memoizer(() -> {
+		String local = mergeReplacedStringAttribute(PROPERTY_uriAuthority, env("RestContext.uriAuthority").orElse(null));
+		if (nn(local))
+			return local;
+		var pc = parentContext();
+		return isInherited(PROPERTY_uriAuthority) && pc != null ? pc.getUriAuthority() : null;
+	});
+
+	/**
+	 * The URI context path override for this resource.
+	 *
+	 * <p>
+	 * Resolved from {@code @Rest(uriContext)} annotations and env-var; inherited from parent when not
+	 * blocked by {@code noInherit}. {@code null} means no override.
+	 */
+	private final Memoizer<String> uriContext = memoizer(() -> {
+		String local = mergeReplacedStringAttribute(PROPERTY_uriContext, env("RestContext.uriContext").orElse(null));
+		if (nn(local))
+			return local;
+		var pc = parentContext();
+		return isInherited(PROPERTY_uriContext) && pc != null ? pc.getUriContext() : null;
+	});
 
-	private UriResolution findUriResolution() {
+	/**
+	 * The {@link UriResolution} strategy for URI resolution in this resource.
+	 *
+	 * <p>
+	 * Resolved from {@code @Rest(uriResolution)} annotations (most-derived non-blank wins), with env-var
+	 * fallback. Defaults to {@link UriResolution#ROOT_RELATIVE}.
+	 */
+	private final Memoizer<UriResolution> uriResolution = memoizer(() -> {
 		var v = new AtomicReference<>(
 			parseEnumConstant(UriResolution.class, resolve(emptyIfNull(env("RestContext.uriResolution").get())))
 				.orElse(UriResolution.ROOT_RELATIVE)
@@ -2739,25 +2851,7 @@ public class RestContext extends Context {
 			parseEnumConstant(UriResolution.class, resolve(s)).ifPresent(v::set)
 		));
 		return v.get();
-	}
-
-	private final Memoizer<String> uriAuthority = memoizer(this::findUriAuthority);
-
-	private String findUriAuthority() {
-		String local = mergeReplacedStringAttribute(PROPERTY_uriAuthority, env("RestContext.uriAuthority").orElse(null));
-		if (nn(local))
-			return local;
-		return isInherited(PROPERTY_uriAuthority) ? parentContext.getUriAuthority() : null;
-	}
-
-	private final Memoizer<String> uriContext = memoizer(this::findUriContext);
-
-	private String findUriContext() {
-		String local = mergeReplacedStringAttribute(PROPERTY_uriContext, env("RestContext.uriContext").orElse(null));
-		if (nn(local))
-			return local;
-		return isInherited(PROPERTY_uriContext) ? parentContext.getUriContext() : null;
-	}
+	});
 
 	/**
 	 * Returns the {@link Rest} annotations on the resource class hierarchy for the specified property,
@@ -2899,7 +2993,7 @@ public class RestContext extends Context {
 
 		// Must be careful not to bleed thread-locals.
 		if (nn(localSession.get()))
-			LOG.warning("WARNING:  Thread-local call object was not cleaned up from previous request.  {}, thread=[{}]", this, Thread.currentThread().getId());
+			LOG.warning("WARNING:  Thread-local call object was not cleaned up from previous request.  {}, thread=[{}]", this, Thread.currentThread().getName());
 
 		RestSession.Builder sb = createSession().resource(resource).req(r1).res(r2).logger(getCallLogger());
 
@@ -3039,7 +3133,7 @@ public class RestContext extends Context {
 	 *
 	 * @return The bean store associated with this context.
 	 */
-	public BeanContext getBeanContext() { return beanContextMemo.get(); }
+	public BeanContext getBeanContext() { return beanContext.get(); }
 
 	BeanContext.Builder         getBeanContextBuilder()          { return builder.beanContext(); }
 	EncoderSet.Builder          getEncodersBuilder()             { return builder.encoders(); }
@@ -3086,7 +3180,7 @@ public class RestContext extends Context {
 	 * 	The call logger to use for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public CallLogger getCallLogger() { return callLoggerMemo.get(); }
+	public CallLogger getCallLogger() { return callLogger.get(); }
 
 	/**
 	 * Returns the name of the client version header name used by this resource.
@@ -3111,7 +3205,7 @@ public class RestContext extends Context {
 	 * 	The resolving config file associated with this servlet.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public Config getConfig() { return configMemo.get(); }
+	public Config getConfig() { return config.get(); }
 
 	/**
 	 * Returns the explicit list of supported content types for this resource.
@@ -3126,14 +3220,14 @@ public class RestContext extends Context {
 	 * 	An unmodifiable list of supported <c>Content-Type</c> header values for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public List<MediaType> getConsumes() { return consumesMemo.get(); }
+	public List<MediaType> getConsumes() { return consumes.get(); }
 
 	/**
 	 * Returns the debug enablement bean for this context.
 	 *
 	 * @return The debug enablement bean for this context.
 	 */
-	public DebugEnablement getDebugEnablement() { return debugEnablementMemo.get(); }
+	public DebugEnablement getDebugEnablement() { return debugEnablement.get(); }
 
 	/**
 	 * Returns the default request attributes for this resource.
@@ -3146,7 +3240,7 @@ public class RestContext extends Context {
 	 * 	The default request headers for this resource in an unmodifiable list.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public NamedAttributeMap getDefaultRequestAttributes() { return defaultRequestAttributesMemo.get(); }
+	public NamedAttributeMap getDefaultRequestAttributes() { return defaultRequestAttributes.get(); }
 
 	/**
 	 * Returns the default request headers for this resource.
@@ -3159,7 +3253,7 @@ public class RestContext extends Context {
 	 * 	The default request headers for this resource in an unmodifiable list.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public HeaderList getDefaultRequestHeaders() { return defaultRequestHeadersMemo.get(); }
+	public HeaderList getDefaultRequestHeaders() { return defaultRequestHeaders.get(); }
 
 	/**
 	 * Returns the default response headers for this resource.
@@ -3172,14 +3266,14 @@ public class RestContext extends Context {
 	 * 	The default response headers for this resource in an unmodifiable list.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public HeaderList getDefaultResponseHeaders() { return defaultResponseHeadersMemo.get(); }
+	public HeaderList getDefaultResponseHeaders() { return defaultResponseHeaders.get(); }
 
 	/**
 	 * Returns the encoders associated with this context.
 	 *
 	 * @return The encoders associated with this context.
 	 */
-	public EncoderSet getEncoders() { return encodersMemo.get(); }
+	public EncoderSet getEncoders() { return encoders.get(); }
 
 	/**
 	 * Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or
@@ -3203,7 +3297,7 @@ public class RestContext extends Context {
 	 * 	The HTTP-part serializer associated with this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public JsonSchemaGenerator getJsonSchemaGenerator() { return jsonSchemaGeneratorMemo.get(); }
+	public JsonSchemaGenerator getJsonSchemaGenerator() { return jsonSchemaGenerator.get(); }
 
 	/**
 	 * Returns the HTTP call for the current request.
@@ -3225,7 +3319,7 @@ public class RestContext extends Context {
 	 * 	The logger for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public Logger getLogger() { return loggerMemo.get(); }
+	public Logger getLogger() { return logger.get(); }
 
 	/**
 	 * Returns the resource bundle used by this resource.
@@ -3234,21 +3328,21 @@ public class RestContext extends Context {
 	 * 	The resource bundle for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public Messages getMessages() { return messagesMemo.get(); }
+	public Messages getMessages() { return messages.get(); }
 
 	/**
 	 * Returns the timing statistics on all method executions on this class.
 	 *
 	 * @return The timing statistics on all method executions on this class.
 	 */
-	public MethodExecStore getMethodExecStore() { return methodExecStoreMemo.get(); }
+	public MethodExecStore getMethodExecStore() { return methodExecStore.get(); }
 
 	/**
 	 * Returns the parsers associated with this context.
 	 *
 	 * @return The parsers associated with this context.
 	 */
-	public ParserSet getParsers() { return parsersMemo.get(); }
+	public ParserSet getParsers() { return parsers.get(); }
 
 	/**
 	 * Returns the HTTP-part parser associated with this resource.
@@ -3257,7 +3351,7 @@ public class RestContext extends Context {
 	 * 	The HTTP-part parser associated with this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public HttpPartParser getPartParser() { return partParserMemo.get(); }
+	public HttpPartParser getPartParser() { return partParser.get(); }
 
 	/**
 	 * Returns the HTTP-part serializer associated with this resource.
@@ -3266,7 +3360,7 @@ public class RestContext extends Context {
 	 * 	The HTTP-part serializer associated with this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public HttpPartSerializer getPartSerializer() { return partSerializerMemo.get(); }
+	public HttpPartSerializer getPartSerializer() { return partSerializer.get(); }
 
 	/**
 	 * Returns the path for this resource as defined by the {@link Rest#path() @Rest(path)} annotation or
@@ -3303,7 +3397,7 @@ public class RestContext extends Context {
 	 * 	An unmodifiable list of supported <c>Accept</c> header values for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public List<MediaType> getProduces() { return producesMemo.get(); }
+	public List<MediaType> getProduces() { return produces.get(); }
 
 	/**
 	 * Returns the resource object.
@@ -3341,7 +3435,7 @@ public class RestContext extends Context {
 	 * 	The response processors for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public ResponseProcessor[] getResponseProcessors() { return responseProcessorsMemo.get(); }
+	public ResponseProcessor[] getResponseProcessors() { return responseProcessors.get(); }
 
 	/**
 	 * Returns the {@link RestOpArg} classes registered on this resource.
@@ -3359,7 +3453,7 @@ public class RestContext extends Context {
 	 * 	The REST-op-arg classes for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public Class<? extends RestOpArg>[] getRestOpArgs() { return restOpArgsMemo.get(); }
+	public Class<? extends RestOpArg>[] getRestOpArgs() { return restOpArgs.get(); }
 
 	/**
 	 * Returns the child resources associated with this servlet.
@@ -3397,7 +3491,7 @@ public class RestContext extends Context {
 	 *
 	 * @return The serializers associated with this context.
 	 */
-	public SerializerSet getSerializers() { return serializersMemo.get(); }
+	public SerializerSet getSerializers() { return serializers.get(); }
 
 	/**
 	 * Returns the servlet init parameter returned by {@link ServletConfig#getInitParameter(String)}.
@@ -3416,7 +3510,7 @@ public class RestContext extends Context {
 	 * 	The static files for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public StaticFiles getStaticFiles() { return staticFilesMemo.get(); }
+	public StaticFiles getStaticFiles() { return staticFiles.get(); }
 
 	/**
 	 * Gives access to the internal statistics on this context.
@@ -3456,7 +3550,7 @@ public class RestContext extends Context {
 	 * 	The information provider for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public SwaggerProvider getSwaggerProvider() { return swaggerProviderMemo.get(); }
+	public SwaggerProvider getSwaggerProvider() { return swaggerProvider.get(); }
 
 	/**
 	 * Returns the stack trace database associated with this context.
@@ -3465,7 +3559,7 @@ public class RestContext extends Context {
 	 * 	The stack trace database for this resource.
 	 * 	<br>Never <jk>null</jk>.
 	 */
-	public ThrownStore getThrownStore() { return thrownStoreMemo.get(); }
+	public ThrownStore getThrownStore() { return thrownStore.get(); }
 
 	/**
 	 * Returns the authority path of the resource.
@@ -3574,7 +3668,7 @@ public class RestContext extends Context {
 	 *
 	 * @return The var resolver in use by this resource.
 	 */
-	public VarResolver getVarResolver() { return varResolverMemo.get(); }
+	public VarResolver getVarResolver() { return varResolver.get(); }
 
 	/**
 	 * Returns the bootstrap (pre-runtime) variable resolver used during context construction.
@@ -3587,7 +3681,7 @@ public class RestContext extends Context {
 	 *
 	 * @return The bootstrap var resolver in use by this resource.
 	 */
-	public VarResolver getBootstrapVarResolver() { return bootstrapVarResolverMemo.get(); }
+	public VarResolver getBootstrapVarResolver() { return bootstrapVarResolver.get(); }
 
 	/**
 	 * Returns whether it's safe to pass the HTTP content as a <js>"content"</js> GET parameter.
@@ -3674,34 +3768,6 @@ public class RestContext extends Context {
 		return u(s);
 	}
 
-	private LifecycleInvokerPair findDestroyLifecycle() {
-		return buildLifecycleInvokerPair(() -> builder.createDestroyMethods(beanStore, resource));
-	}
-
-	private LifecycleInvokerPair findEndCallLifecycle() {
-		return buildLifecycleInvokerPair(() -> builder.createEndCallMethods(beanStore, resource));
-	}
-
-	private LifecycleInvokerPair findPostInitLifecycle() {
-		return buildLifecycleInvokerPair(() -> builder.createPostInitMethods(beanStore, resource));
-	}
-
-	private LifecycleInvokerPair findPostInitChildFirstLifecycle() {
-		return buildLifecycleInvokerPair(() -> builder.createPostInitChildFirstMethods(beanStore, resource));
-	}
-
-	private LifecycleInvokerPair findStartCallLifecycle() {
-		return buildLifecycleInvokerPair(() -> builder.createStartCallMethods(beanStore, resource));
-	}
-
-	private MethodList findPreCallMethodsList() {
-		return builder.createPreCallMethods(beanStore, resource);
-	}
-
-	private MethodList findPostCallMethodsList() {
-		return builder.createPostCallMethods(beanStore, resource);
-	}
-
 	private LifecycleInvokerPair buildLifecycleInvokerPair(Supplier<MethodList> methods) {
 		var ml = MethodList.of(methods.get());
 		var inv = ml.stream().map(this::toMethodInvoker).toArray(MethodInvoker[]::new);
@@ -3832,14 +3898,14 @@ public class RestContext extends Context {
 	 *
 	 * @return The list of methods to invoke after the actual REST method is called.
 	 */
-	public MethodList getPostCallMethods() { return postCallMethodsMemo.get(); }
+	public MethodList getPostCallMethods() { return postCallMethods.get(); }
 
 	/**
 	 * Returns the list of methods to invoke before the actual REST method is called.
 	 *
 	 * @return The list of methods to invoke before the actual REST method is called.
 	 */
-	public MethodList getPreCallMethods() { return preCallMethodsMemo.get(); }
+	public MethodList getPreCallMethods() { return preCallMethods.get(); }
 
 	/**
 	 * Returns the list of methods to invoke during servlet destruction.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
index 00b4c7b51c..cc91ea11a1 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContextInit.java
@@ -61,7 +61,8 @@ import org.apache.juneau.rest.annotation.Rest;
  *
  * <h5 class='section'>Note on naming:</h5>
  * <p>
- * Decision #27 in {@code TODO-16} originally settled on the {@code XArgs} suffix for ctor-arg-bundle records, but
+ * Decision #27 in the RestContext refactor plan originally settled on the {@code XArgs} suffix for
+ * ctor-arg-bundle records, but
  * {@code RestContextArgs} is already taken by the {@link org.apache.juneau.rest.arg.RestContextArgs} parameter
  * resolver in the {@link org.apache.juneau.rest.arg} package (one of an entire family of {@code *Args}
  * {@link org.apache.juneau.rest.annotation.RestOp @RestOp}-method arg-resolver classes:
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 0eab48fc90..a6e480bf89 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -81,7 +81,9 @@ import jakarta.servlet.http.*;
  * </ul>
  */
 @SuppressWarnings({
-	"java:S115"  // Constants use UPPER_snakeCase convention (e.g., PROP_defaultRequestFormData)
+	"java:S115",  // Constants use UPPER_snakeCase convention (e.g., PROP_defaultRequestFormData)
+	"java:S1200", // High coupling is intentional in this central operation context aggregator.
+	"java:S6539"  // RestOpContext intentionally centralizes op wiring and annotation resolution.
 })
 public class RestOpContext extends Context implements Comparable<RestOpContext> {
 
@@ -155,24 +157,22 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 		return BeanCreator.of(HttpPartSerializer.class).type(c).orElse(defaultSerializer);
 	}
 
-	protected final int hierarchyDepth;
+	private final BasicBeanStore opBeanStore;
 	protected final Map<Class<?>,ResponseBeanMeta> responseBeanMetas = new ConcurrentHashMap<>();
 	protected final Map<Class<?>,ResponsePartMeta> headerPartMetas = new ConcurrentHashMap<>();
 	protected final Method method;
 	protected final MethodInfo mi;
 	protected final RestContext context;
-	protected final RestOpInvoker methodInvoker;
-	protected final RestOpInvoker[] postCallMethods;
-	protected final RestOpInvoker[] preCallMethods;
-	protected final ResponseBeanMeta responseMeta;
 
 	// The annotation work-list produced during construction.
 	private final AnnotationWorkList appliedAnnotations;
 
 	private RestContext restContext() { return context; }
 	private Method method() { return method; }
+	private MethodInfo methodInfo() { return mi; }
 	private AnnotationWorkList appliedAnnotations() { return appliedAnnotations; }
 	private BasicBeanStore beanStore() { return context.getBeanStore(); }
+	private BasicBeanStore opBeanStore() { return opBeanStore; }
 	private Object resource() { return context.getResource(); }
 	private VarResolver varResolver() { return context.getVarResolver(); }
 
@@ -180,115 +180,6 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 	// Memoized fields
 	//-----------------------------------------------------------------------------------------------------------------
 
-	/** The effective default {@link Charset} for this operation, resolved from op annotations, context, or env. */
-	private final Memoizer<Charset> defaultCharset = memoizer(() -> {
-		var v = findOpString(PROPERTY_defaultCharset);
-		if (v.isPresent())
-			return Charset.forName(v.get());
-		if (isInherited(PROPERTY_defaultCharset)) {
-			var rv = restContext().mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
-			if (rv != null && !rv.isEmpty())
-				return Charset.forName(rv);
-		}
-		return envDefaultRestCharset();
-	});
-
-	/** The effective max-input byte limit for this operation. */
-	private final Memoizer<Long> maxInput = memoizer(() -> {
-		var v = findOpString(PROPERTY_maxInput);
-		if (v.isPresent())
-			return parseLongWithSuffix(v.get());
-		if (isInherited(PROPERTY_maxInput)) {
-			var rv = restContext().mergeReplacedStringAttribute(PROPERTY_maxInput, null);
-			if (rv != null && !rv.isEmpty())
-				return parseLongWithSuffix(rv);
-		}
-		return envDefaultRestMaxInput();
-	});
-
-	/** The effective {@link DebugEnablement} for this operation. */
-	private final Memoizer<DebugEnablement> debugEnablement = memoizer(() -> {
-		var v = findOpString(PROPERTY_debug);
-		if (v.isPresent())
-			return DebugEnablement.create(beanStore()).enable(Enablement.fromString(v.get()), "*").build();
-		if (isInherited(PROPERTY_debug))
-			return restContext().getDebugEnablement();
-		return DebugEnablement.create(beanStore()).build();
-	});
-
-	/**
-	 * The supported response accept types for this operation.
-	 *
-	 * <p>
-	 * Walks op-level {@code @RestOp}-group annotations for {@code produces} (and, when
-	 * {@code noInherit} does not block it, the class-level {@code @Rest(produces)} hierarchy).
-	 * Falls back to the supported media types of the operation's {@link SerializerSet}.
-	 */
-	private final Memoizer<List<MediaType>> supportedAcceptTypes = memoizer(() -> {
-		var result = collectAnnotationMediaTypes(PROPERTY_produces);
-		if (result.isEmpty())
-			return u(getSerializers().getSupportedMediaTypes());
-		return u(result);
-	});
-
-	/**
-	 * The supported request content types for this operation.
-	 *
-	 * <p>
-	 * Walks op-level {@code @RestOp}-group annotations for {@code consumes} (and, when
-	 * {@code noInherit} does not block it, the class-level {@code @Rest(consumes)} hierarchy).
-	 * Falls back to the supported content types of the operation's {@link ParserSet}.
-	 */
-	private final Memoizer<List<MediaType>> supportedContentTypes = memoizer(() -> {
-		var result = collectAnnotationMediaTypes(PROPERTY_consumes);
-		if (result.isEmpty())
-			return u(getParsers().getSupportedMediaTypes());
-		return u(result);
-	});
-
-	/**
-	 * The HTTP method string for this operation (e.g. {@code "GET"}, {@code "*"}).
-	 *
-	 * <p>
-	 * Walks {@code @RestOp}-group annotations child-to-parent; verb-specific annotations
-	 * ({@link RestGet}, etc.) imply their fixed verb. Falls back to
-	 * {@link HttpUtils#detectHttpMethod} when no annotation declares a verb.
-	 */
-	@SuppressWarnings("java:S3776")
-	private final Memoizer<String> httpMethod = memoizer(() -> {
-		var vr = varResolver();
-		for (var ai : getRestOpAnnotations()) {
-			var v = httpMethodFromAnnotation(ai.inner(), vr);
-			if (v != null && !v.isEmpty())
-				return normalizeHttpMethod(v);
-		}
-		return normalizeHttpMethod(HttpUtils.detectHttpMethod(method(), true, "GET"));
-	});
-
-	/**
-	 * All {@link RestOp}-group annotations on this method, in child-to-parent order.
-	 *
-	 * <p>
-	 * Uses the same {@link MethodInfo} as the annotation traversal in the {@link Builder}, so
-	 * method-level metadata (e.g. {@code noInherit}) resolves consistently when the implementation
-	 * class differs from the method's declaring class.
-	 */
-	private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations = memoizer(() -> {
-		var methodInfo = MethodInfo.of(restContext().getResourceClass(), method()).accessible();
-		return restContext().getAnnotationProvider().find(methodInfo, SELF, MATCHING_METHODS).stream()
-			.filter(ai -> ai.isInGroup(RestOp.class))
-			.toList();
-	});
-
-	/** Aggregated {@code noInherit} keys from all RestOp-group annotations on this operation. */
-	private final Memoizer<SortedSet<String>> noInheritOp = memoizer(() -> {
-		var l = getRestOpAnnotations().stream()
-			.map(ai -> ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
-			.flatMap(this::resolveCdl)
-			.toList();
-		return u(treeSetCi(l));
-	});
-
 	/** Effective allowed parser session-option keys for this operation. */
 	private final Memoizer<SortedSet<String>> allowedParserOptions = memoizer(() -> {
 		var l = new ArrayList<String>();
@@ -334,136 +225,55 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 	private final Memoizer<CallLogger> callLogger = memoizer(() -> restContext().getCallLogger());
 
 	/**
-	 * The encoder group for this operation.
-	 *
-	 * <p>
-	 * Walks the {@code @RestOp(encoders)} chain (parent-to-child); each non-empty {@code encoders()}
-	 * array REPLACES the inherited set (with {@link Inherit} as a sentinel that re-injects the prior
-	 * set's entries at the specified position — matches the legacy {@code EncoderSet.Builder.set(...)}
-	 * semantics). Falls through to the class-level {@link RestContext#getEncoders()} when no op
-	 * annotation declares encoders. An {@code @RestInject EncoderSet} bean (matching this operation's
-	 * method scope) REPLACES the result entirely.
-	 */
-	private final Memoizer<EncoderSet> encoders = memoizer(() -> {
-		var bs = beanStore();
-		var b = restContext().getEncodersBuilder().copy();
-		getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai -> {
-			var c = ai.getClassArray("encoders", Encoder.class).orElse(null);
-			if (nn(c) && c.length > 0)
-				b.set(c);
-		});
-		var v = Value.of(b.build());
-		new BeanCreateMethodFinder<>(EncoderSet.class, resource(), bs)
-			.find(this::matchesInjectScope)
-			.run(v::set);
-		return v.get();
-	});
-
-	/** The JSON-Schema generator for this operation (op-level annotations applied on top of the parent). */
-	private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = memoizer(() -> {
-		var aa = appliedAnnotations();
-		var parent = restContext().getJsonSchemaGeneratorBuilder();
-		if (!parent.canApply(aa))
-			return restContext().getJsonSchemaGenerator();
-		Value<JsonSchemaGenerator.Builder> v = Value.of(parent.copy());
-		v.get().apply(aa);
-		var bs = BasicBeanStore.of(beanStore())
-			.addBean(Method.class, method())
-			.addBean(JsonSchemaGenerator.Builder.class, v.get());
-		new BeanCreateMethodFinder<>(JsonSchemaGenerator.class, resource(), bs)
-			.find(this::matchesInjectScope)
-			.run(x -> v.get().impl(x));
-		return v.get().build();
-	});
-
-	/**
-	 * The parser group for this operation.
+	 * The response-converter array for this operation.
 	 *
 	 * <p>
-	 * Walks the {@code @RestOp(parsers)} chain (parent-to-child); the most-derived non-empty
-	 * {@code parsers()} array REPLACES the entire inherited set. Falls through to the class-level
-	 * {@link RestContext#getParsers()} when no op annotation declares parsers. An
-	 * {@code @RestInject ParserSet} bean (matching this operation's method scope) REPLACES the result.
+	 * Walks the {@code @Rest(converters)} class chain (parent-to-child) followed by the
+	 * {@code @RestOp(converters)} method chain (parent-to-child). Op-level
+	 * {@code noInherit={"converters"}} cuts off the class-chain contribution. An
+	 * {@code @RestInject RestConverterList} bean (either as a name-anonymous bean in the bean store
+	 * or as a {@code @RestInject} method whose {@code methodScope} matches this operation's method
+	 * name) REPLACES the entire annotation-derived list.
 	 */
-	private final Memoizer<ParserSet> parsers = memoizer(() -> {
-		var aa = appliedAnnotations();
+	private final Memoizer<RestConverter[]> converters = memoizer(() -> {
 		var bs = beanStore();
-		var pb = restContext().getParsersBuilder();
-		var b = pb.copy();
-		if (pb.canApply(aa))
-			b.apply(aa);
-		getRestOpAnnotationsForProperty(PROPERTY_parsers).forEach(ai -> {
-			var c = ai.getClassArray("parsers", java.lang.Object.class).orElse(null);
-			if (nn(c) && c.length > 0)
-				b.set(c);
-		});
-		var result = Value.of(b.build());
-		new BeanCreateMethodFinder<>(ParserSet.class, resource(), bs)
-			.find(this::matchesInjectScope)
-			.run(result::set);
-		return result.get();
-	});
-
-	/** The HTTP part parser for this operation (op-level creator applied on top of the parent). */
-	private final Memoizer<HttpPartParser> partParser = memoizer(() -> {
-		var aa = appliedAnnotations();
-		var parent = restContext().getPartParserCreator();
-		if (!parent.canApply(aa))
-			return restContext().getPartParser();
-		Value<HttpPartParser.Creator> v = Value.of(parent.copy());
-		v.get().apply(aa);
-		var bs = BasicBeanStore.of(beanStore())
-			.addBean(Method.class, method())
-			.addBean(HttpPartParser.Creator.class, v.get());
-		new BeanCreateMethodFinder<>(HttpPartParser.class, resource(), bs)
+		var v = Value.of(RestConverterList.create(bs));
+		if (isInherited(PROPERTY_converters))
+			restContext().getRestAnnotationsForProperty(PROPERTY_converters)
+				.forEach(ai -> v.get().append(ai.inner().converters()));
+		getRestOpAnnotationsForProperty(PROPERTY_converters)
+			.forEach(ai -> ai.getClassArray("converters", RestConverter.class).ifPresent(classes -> {
+				for (var c : classes)
+					v.get().append(classArray(c));
+			}));
+		bs.getBean(RestConverterList.class).ifPresent(x -> v.get().impl(x));
+		new BeanCreateMethodFinder<>(RestConverterList.class, resource(), bs)
 			.find(this::matchesInjectScope)
 			.run(x -> v.get().impl(x));
-		return v.get().create();
+		return v.get().build().asArray();
 	});
 
-	/** The HTTP part serializer for this operation (op-level creator applied on top of the parent). */
-	private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() -> {
-		var aa = appliedAnnotations();
-		var parent = restContext().getPartSerializerCreator();
-		if (!parent.canApply(aa))
-			return restContext().getPartSerializer();
-		Value<HttpPartSerializer.Creator> v = Value.of(parent.copy());
-		v.get().apply(aa);
-		var bs = BasicBeanStore.of(beanStore())
-			.addBean(Method.class, method())
-			.addBean(HttpPartSerializer.Creator.class, v.get());
-		new BeanCreateMethodFinder<>(HttpPartSerializer.class, resource(), bs)
-			.find(this::matchesInjectScope)
-			.run(x -> v.get().impl(x));
-		return v.get().create();
+	/** The effective {@link DebugEnablement} for this operation. */
+	private final Memoizer<DebugEnablement> debugEnablement = memoizer(() -> {
+		var v = findOpString(PROPERTY_debug);
+		if (v.isPresent())
+			return DebugEnablement.create(beanStore()).enable(Enablement.fromString(v.get()), "*").build();
+		if (isInherited(PROPERTY_debug))
+			return restContext().getDebugEnablement();
+		return DebugEnablement.create(beanStore()).build();
 	});
 
-	/**
-	 * The serializer group for this operation.
-	 *
-	 * <p>
-	 * Walks the {@code @RestOp(serializers)} chain (parent-to-child); the most-derived non-empty
-	 * {@code serializers()} array REPLACES the entire inherited set. Falls through to the class-level
-	 * {@link RestContext#getSerializers()} when no op annotation declares serializers. An
-	 * {@code @RestInject SerializerSet} bean (matching this operation's method scope) REPLACES the result.
-	 */
-	private final Memoizer<SerializerSet> serializers = memoizer(() -> {
-		var aa = appliedAnnotations();
-		var bs = beanStore();
-		var sb = restContext().getSerializersBuilder();
-		var b = sb.copy();
-		if (sb.canApply(aa))
-			b.apply(aa);
-		getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
-			var c = ai.getClassArray("serializers", Serializer.class).orElse(null);
-			if (nn(c) && c.length > 0)
-				b.set(c);
-		});
-		var result = Value.of(b.build());
-		new BeanCreateMethodFinder<>(SerializerSet.class, resource(), bs)
-			.find(this::matchesInjectScope)
-			.run(result::set);
-		return result.get();
+	/** The effective default {@link Charset} for this operation, resolved from op annotations, context, or env. */
+	private final Memoizer<Charset> defaultCharset = memoizer(() -> {
+		var v = findOpString(PROPERTY_defaultCharset);
+		if (v.isPresent())
+			return Charset.forName(v.get());
+		if (isInherited(PROPERTY_defaultCharset)) {
+			var rv = restContext().mergeReplacedStringAttribute(PROPERTY_defaultCharset, null);
+			if (rv != null && !rv.isEmpty())
+				return Charset.forName(rv);
+		}
+		return envDefaultRestCharset();
 	});
 
 	/**
@@ -618,96 +428,31 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 	});
 
 	/**
-	 * Iterates over each parameter annotation on the operation method, computing the parameter's
-	 * {@link Schema#default_()}/{@link Schema#df()} string (joined-non-blank-first) and dispatching
-	 * each annotation+default pair to the supplied callback.
-	 */
-	private void processParameterDefaults(java.util.function.BiConsumer<Annotation,String> callback) {
-		for (var aa : method.getParameterAnnotations()) {
-			String def = null;
-			for (var a : aa) {
-				if (a instanceof Schema s)
-					def = joinnlFirstNonEmptyArray(s.default_(), s.df());
-			}
-			if (def == null)
-				continue;
-			for (var a : aa)
-				callback.accept(a, def);
-		}
-	}
-
-	/**
-	 * The response-converter array for this operation.
+	 * The encoder group for this operation.
 	 *
 	 * <p>
-	 * Walks the {@code @Rest(converters)} class chain (parent-to-child) followed by the
-	 * {@code @RestOp(converters)} method chain (parent-to-child). Op-level
-	 * {@code noInherit={"converters"}} cuts off the class-chain contribution. An
-	 * {@code @RestInject RestConverterList} bean (either as a name-anonymous bean in the bean store
-	 * or as a {@code @RestInject} method whose {@code methodScope} matches this operation's method
-	 * name) REPLACES the entire annotation-derived list.
+	 * Walks the {@code @RestOp(encoders)} chain (parent-to-child); each non-empty {@code encoders()}
+	 * array REPLACES the inherited set (with {@link Inherit} as a sentinel that re-injects the prior
+	 * set's entries at the specified position — matches the legacy {@code EncoderSet.Builder.set(...)}
+	 * semantics). Falls through to the class-level {@link RestContext#getEncoders()} when no op
+	 * annotation declares encoders. An {@code @RestInject EncoderSet} bean (matching this operation's
+	 * method scope) REPLACES the result entirely.
 	 */
-	private final Memoizer<RestConverter[]> converters = memoizer(() -> {
+	private final Memoizer<EncoderSet> encoders = memoizer(() -> {
 		var bs = beanStore();
-		var v = Value.of(RestConverterList.create(bs));
-		if (isInherited(PROPERTY_converters))
-			restContext().getRestAnnotationsForProperty(PROPERTY_converters)
-				.forEach(ai -> v.get().append(ai.inner().converters()));
-		getRestOpAnnotationsForProperty(PROPERTY_converters)
-			.forEach(ai -> ai.getClassArray("converters", RestConverter.class).ifPresent(classes -> {
-				for (var c : classes)
-					v.get().append(classArray(c));
-			}));
-		bs.getBean(RestConverterList.class).ifPresent(x -> v.get().impl(x));
-		new BeanCreateMethodFinder<>(RestConverterList.class, resource(), bs)
+		var b = restContext().getEncodersBuilder().copy();
+		getRestOpAnnotationsForProperty(PROPERTY_encoders).forEach(ai -> {
+			var c = ai.getClassArray("encoders", Encoder.class).orElse(null);
+			if (nn(c) && c.length > 0)
+				b.set(c);
+		});
+		var v = Value.of(b.build());
+		new BeanCreateMethodFinder<>(EncoderSet.class, resource(), bs)
 			.find(this::matchesInjectScope)
-			.run(x -> v.get().impl(x));
-		return v.get().build().asArray();
+			.run(v::set);
+		return v.get();
 	});
 
-	/**
-	 * Returns {@code true} if the given method has a {@code @RestInject} annotation whose
-	 * {@code methodScope} includes this operation's method name (or {@code "*"}).
-	 *
-	 * <p>
-	 * Used by op-level memoizers when scanning the resource class for {@code @RestInject}-supplied
-	 * composite-bean overrides ({@code RestConverterList}, {@code RestGuardList}, etc.). This is the
-	 * {@link RestOpContext}-scope peer of {@link Builder#matches(MethodInfo)} — kept in sync with that
-	 * one.
-	 */
-	private boolean matchesInjectScope(MethodInfo annotated) {
-		var a = annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
-		if (a != null) {
-			for (var n : a.methodScope()) {
-				if ("*".equals(n) || method.getName().equals(n))
-					return true;
-			}
-		}
-		return false;
-	}
-
-	/**
-	 * Same as {@link #matchesInjectScope(MethodInfo)} but additionally requires the
-	 * {@link RestInject#name()} attribute to equal {@code beanName}.
-	 *
-	 * <p>
-	 * Used for named composite-bean overrides ({@code defaultRequestHeaders}, {@code defaultResponseHeaders},
-	 * etc.) where the bean type alone is ambiguous (e.g. {@link HeaderList} appears as both request and
-	 * response headers).
-	 */
-	private boolean matchesInjectScope(MethodInfo annotated, String beanName) {
-		var a = annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
-		if (a != null) {
-			if (! a.name().equals(beanName))
-				return false;
-			for (var n : a.methodScope()) {
-				if ("*".equals(n) || method.getName().equals(n))
-					return true;
-			}
-		}
-		return false;
-	}
-
 	/**
 	 * The request-guard array for this operation. <b>Cross-bucket memoizer</b> — folds three
 	 * annotation attributes ({@code guards}, {@code roleGuard}, {@code rolesDeclared}) into a
@@ -761,11 +506,58 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 			}
 		}
 
-		bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
-		new BeanCreateMethodFinder<>(RestGuardList.class, resource(), bs)
+		bs.getBean(RestGuardList.class).ifPresent(x -> v.get().impl(x));
+		new BeanCreateMethodFinder<>(RestGuardList.class, resource(), bs)
+			.find(this::matchesInjectScope)
+			.run(x -> v.get().impl(x));
+		return v.get().build().asArray();
+	});
+
+	/** The depth of the declaring class in the inheritance chain. */
+	private final Memoizer<Integer> hierarchyDepth = memoizer(() -> {
+		int hierarchyDepthTemp = 0;
+		var sc = method().getDeclaringClass().getSuperclass();
+		while (nn(sc)) {
+			hierarchyDepthTemp++;
+			sc = sc.getSuperclass();
+		}
+		return hierarchyDepthTemp;
+	});
+
+	/**
+	 * The HTTP method string for this operation (e.g. {@code "GET"}, {@code "*"}).
+	 *
+	 * <p>
+	 * Walks {@code @RestOp}-group annotations child-to-parent; verb-specific annotations
+	 * ({@link RestGet}, etc.) imply their fixed verb. Falls back to
+	 * {@link HttpUtils#detectHttpMethod} when no annotation declares a verb.
+	 */
+	@SuppressWarnings("java:S3776")
+	private final Memoizer<String> httpMethod = memoizer(() -> {
+		var vr = varResolver();
+		for (var ai : getRestOpAnnotations()) {
+			var v = httpMethodFromAnnotation(ai.inner(), vr);
+			if (v != null && !v.isEmpty())
+				return normalizeHttpMethod(v);
+		}
+		return normalizeHttpMethod(HttpUtils.detectHttpMethod(method(), true, "GET"));
+	});
+
+	/** The JSON-Schema generator for this operation (op-level annotations applied on top of the parent). */
+	private final Memoizer<JsonSchemaGenerator> jsonSchemaGenerator = memoizer(() -> {
+		var aa = appliedAnnotations();
+		var parent = restContext().getJsonSchemaGeneratorBuilder();
+		if (!parent.canApply(aa))
+			return restContext().getJsonSchemaGenerator();
+		Value<JsonSchemaGenerator.Builder> v = Value.of(parent.copy());
+		v.get().apply(aa);
+		var bs = BasicBeanStore.of(beanStore())
+			.addBean(Method.class, method())
+			.addBean(JsonSchemaGenerator.Builder.class, v.get());
+		new BeanCreateMethodFinder<>(JsonSchemaGenerator.class, resource(), bs)
 			.find(this::matchesInjectScope)
 			.run(x -> v.get().impl(x));
-		return v.get().build().asArray();
+		return v.get().build();
 	});
 
 	/**
@@ -804,11 +596,107 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 		return v.get().build();
 	});
 
+	/** The invoker for the operation method itself. */
+	private final Memoizer<RestOpInvoker> methodInvoker = memoizer(() ->
+		new RestOpInvoker(method(), restContext().findRestOperationArgs(method(), opBeanStore()), restContext().getMethodExecStats(method()))
+	);
+
+	/** The effective max-input byte limit for this operation. */
+	private final Memoizer<Long> maxInput = memoizer(() -> {
+		var v = findOpString(PROPERTY_maxInput);
+		if (v.isPresent())
+			return parseLongWithSuffix(v.get());
+		if (isInherited(PROPERTY_maxInput)) {
+			var rv = restContext().mergeReplacedStringAttribute(PROPERTY_maxInput, null);
+			if (rv != null && !rv.isEmpty())
+				return parseLongWithSuffix(rv);
+		}
+		return envDefaultRestMaxInput();
+	});
+
+	/** Aggregated {@code noInherit} keys from all RestOp-group annotations on this operation. */
+	private final Memoizer<SortedSet<String>> noInheritOp = memoizer(() -> {
+		var l = getRestOpAnnotations().stream()
+			.map(ai -> ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
+			.flatMap(this::resolveCdl)
+			.toList();
+		return u(treeSetCi(l));
+	});
+
 	/** The optional (non-required) matchers extracted from {@link #matchersList}. */
 	private final Memoizer<RestMatcher[]> optionalMatchers = memoizer(() -> matchersList.get().getOptionalEntries());
 
-	/** The required matchers extracted from {@link #matchersList}. */
-	private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> matchersList.get().getRequiredEntries());
+	/** The invokers for context-level post-call lifecycle methods. */
+	private final Memoizer<RestOpInvoker[]> postCallMethods = memoizer(() ->
+		restContext().getPostCallMethods().stream().map(x -> new RestOpInvoker(x, restContext().findRestOperationArgs(x, opBeanStore()), restContext().getMethodExecStats(x))).toArray(RestOpInvoker[]::new)
+	);
+
+	/** The invokers for context-level pre-call lifecycle methods. */
+	private final Memoizer<RestOpInvoker[]> preCallMethods = memoizer(() ->
+		restContext().getPreCallMethods().stream().map(x -> new RestOpInvoker(x, restContext().findRestOperationArgs(x, opBeanStore()), restContext().getMethodExecStats(x))).toArray(RestOpInvoker[]::new)
+	);
+
+	/**
+	 * The parser group for this operation.
+	 *
+	 * <p>
+	 * Walks the {@code @RestOp(parsers)} chain (parent-to-child); the most-derived non-empty
+	 * {@code parsers()} array REPLACES the entire inherited set. Falls through to the class-level
+	 * {@link RestContext#getParsers()} when no op annotation declares parsers. An
+	 * {@code @RestInject ParserSet} bean (matching this operation's method scope) REPLACES the result.
+	 */
+	private final Memoizer<ParserSet> parsers = memoizer(() -> {
+		var aa = appliedAnnotations();
+		var bs = beanStore();
+		var pb = restContext().getParsersBuilder();
+		var b = pb.copy();
+		if (pb.canApply(aa))
+			b.apply(aa);
+		getRestOpAnnotationsForProperty(PROPERTY_parsers).forEach(ai -> {
+			var c = ai.getClassArray("parsers", java.lang.Object.class).orElse(null);
+			if (nn(c) && c.length > 0)
+				b.set(c);
+		});
+		var result = Value.of(b.build());
+		new BeanCreateMethodFinder<>(ParserSet.class, resource(), bs)
+			.find(this::matchesInjectScope)
+			.run(result::set);
+		return result.get();
+	});
+
+	/** The HTTP part parser for this operation (op-level creator applied on top of the parent). */
+	private final Memoizer<HttpPartParser> partParser = memoizer(() -> {
+		var aa = appliedAnnotations();
+		var parent = restContext().getPartParserCreator();
+		if (!parent.canApply(aa))
+			return restContext().getPartParser();
+		Value<HttpPartParser.Creator> v = Value.of(parent.copy());
+		v.get().apply(aa);
+		var bs = BasicBeanStore.of(beanStore())
+			.addBean(Method.class, method())
+			.addBean(HttpPartParser.Creator.class, v.get());
+		new BeanCreateMethodFinder<>(HttpPartParser.class, resource(), bs)
+			.find(this::matchesInjectScope)
+			.run(x -> v.get().impl(x));
+		return v.get().create();
+	});
+
+	/** The HTTP part serializer for this operation (op-level creator applied on top of the parent). */
+	private final Memoizer<HttpPartSerializer> partSerializer = memoizer(() -> {
+		var aa = appliedAnnotations();
+		var parent = restContext().getPartSerializerCreator();
+		if (!parent.canApply(aa))
+			return restContext().getPartSerializer();
+		Value<HttpPartSerializer.Creator> v = Value.of(parent.copy());
+		v.get().apply(aa);
+		var bs = BasicBeanStore.of(beanStore())
+			.addBean(Method.class, method())
+			.addBean(HttpPartSerializer.Creator.class, v.get());
+		new BeanCreateMethodFinder<>(HttpPartSerializer.class, resource(), bs)
+			.find(this::matchesInjectScope)
+			.run(x -> v.get().impl(x));
+		return v.get().create();
+	});
 
 	/**
 	 * The URL path matchers for this operation.
@@ -886,6 +774,147 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 		return v.get().asArray();
 	});
 
+	/** The required matchers extracted from {@link #matchersList}. */
+	private final Memoizer<RestMatcher[]> requiredMatchers = memoizer(() -> matchersList.get().getRequiredEntries());
+
+	/** The computed response metadata for this operation method. */
+	private final Memoizer<ResponseBeanMeta> responseMeta = memoizer(() -> ResponseBeanMeta.create(methodInfo(), appliedAnnotations()));
+
+	/**
+	 * All {@link RestOp}-group annotations on this method, in child-to-parent order.
+	 *
+	 * <p>
+	 * Uses the same {@link MethodInfo} as the annotation traversal in the {@link Builder}, so
+	 * method-level metadata (e.g. {@code noInherit}) resolves consistently when the implementation
+	 * class differs from the method's declaring class.
+	 */
+	private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations = memoizer(() -> {
+		var methodInfo = MethodInfo.of(restContext().getResourceClass(), method()).accessible();
+		return restContext().getAnnotationProvider().find(methodInfo, SELF, MATCHING_METHODS).stream()
+			.filter(ai -> ai.isInGroup(RestOp.class))
+			.toList();
+	});
+
+	/**
+	 * The serializer group for this operation.
+	 *
+	 * <p>
+	 * Walks the {@code @RestOp(serializers)} chain (parent-to-child); the most-derived non-empty
+	 * {@code serializers()} array REPLACES the entire inherited set. Falls through to the class-level
+	 * {@link RestContext#getSerializers()} when no op annotation declares serializers. An
+	 * {@code @RestInject SerializerSet} bean (matching this operation's method scope) REPLACES the result.
+	 */
+	private final Memoizer<SerializerSet> serializers = memoizer(() -> {
+		var aa = appliedAnnotations();
+		var bs = beanStore();
+		var sb = restContext().getSerializersBuilder();
+		var b = sb.copy();
+		if (sb.canApply(aa))
+			b.apply(aa);
+		getRestOpAnnotationsForProperty(PROPERTY_serializers).forEach(ai -> {
+			var c = ai.getClassArray("serializers", Serializer.class).orElse(null);
+			if (nn(c) && c.length > 0)
+				b.set(c);
+		});
+		var result = Value.of(b.build());
+		new BeanCreateMethodFinder<>(SerializerSet.class, resource(), bs)
+			.find(this::matchesInjectScope)
+			.run(result::set);
+		return result.get();
+	});
+
+	/**
+	 * The supported response accept types for this operation.
+	 *
+	 * <p>
+	 * Walks op-level {@code @RestOp}-group annotations for {@code produces} (and, when
+	 * {@code noInherit} does not block it, the class-level {@code @Rest(produces)} hierarchy).
+	 * Falls back to the supported media types of the operation's {@link SerializerSet}.
+	 */
+	private final Memoizer<List<MediaType>> supportedAcceptTypes = memoizer(() -> {
+		var result = collectAnnotationMediaTypes(PROPERTY_produces);
+		if (result.isEmpty())
+			return u(getSerializers().getSupportedMediaTypes());
+		return u(result);
+	});
+
+	/**
+	 * The supported request content types for this operation.
+	 *
+	 * <p>
+	 * Walks op-level {@code @RestOp}-group annotations for {@code consumes} (and, when
+	 * {@code noInherit} does not block it, the class-level {@code @Rest(consumes)} hierarchy).
+	 * Falls back to the supported content types of the operation's {@link ParserSet}.
+	 */
+	private final Memoizer<List<MediaType>> supportedContentTypes = memoizer(() -> {
+		var result = collectAnnotationMediaTypes(PROPERTY_consumes);
+		if (result.isEmpty())
+			return u(getParsers().getSupportedMediaTypes());
+		return u(result);
+	});
+
+	/**
+	 * Iterates over each parameter annotation on the operation method, computing the parameter's
+	 * {@link Schema#default_()}/{@link Schema#df()} string (joined-non-blank-first) and dispatching
+	 * each annotation+default pair to the supplied callback.
+	 */
+	private void processParameterDefaults(java.util.function.BiConsumer<Annotation,String> callback) {
+		for (var aa : method.getParameterAnnotations()) {
+			String def = null;
+			for (var a : aa) {
+				if (a instanceof Schema s)
+					def = joinnlFirstNonEmptyArray(s.default_(), s.df());
+			}
+			if (def == null)
+				continue;
+			for (var a : aa)
+				callback.accept(a, def);
+		}
+	}
+
+	/**
+	 * Returns {@code true} if the given method has a {@code @RestInject} annotation whose
+	 * {@code methodScope} includes this operation's method name (or {@code "*"}).
+	 *
+	 * <p>
+	 * Used by op-level memoizers when scanning the resource class for {@code @RestInject}-supplied
+	 * composite-bean overrides ({@code RestConverterList}, {@code RestGuardList}, etc.). This is the
+	 * {@link RestOpContext}-scope peer of {@link Builder#matches(MethodInfo)} — kept in sync with that
+	 * one.
+	 */
+	private boolean matchesInjectScope(MethodInfo annotated) {
+		var a = annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
+		if (a != null) {
+			for (var n : a.methodScope()) {
+				if ("*".equals(n) || method.getName().equals(n))
+					return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * Same as {@link #matchesInjectScope(MethodInfo)} but additionally requires the
+	 * {@link RestInject#name()} attribute to equal {@code beanName}.
+	 *
+	 * <p>
+	 * Used for named composite-bean overrides ({@code defaultRequestHeaders}, {@code defaultResponseHeaders},
+	 * etc.) where the bean type alone is ambiguous (e.g. {@link HeaderList} appears as both request and
+	 * response headers).
+	 */
+	private boolean matchesInjectScope(MethodInfo annotated, String beanName) {
+		var a = annotated.getAnnotations(RestInject.class).findFirst().map(AnnotationInfo::inner).orElse(null);
+		if (a != null) {
+			if (! a.name().equals(beanName))
+				return false;
+			for (var n : a.methodScope()) {
+				if ("*".equals(n) || method.getName().equals(n))
+					return true;
+			}
+		}
+		return false;
+	}
+
 	private Stream<String> resolveCdl(String...values) {
 		if (values == null || values.length == 0)
 			return Stream.empty();
@@ -1080,7 +1109,7 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 			mi = MethodInfo.of(method).accessible();
 
 			// @formatter:off
-			var bs = BasicBeanStore.of(context.getBootstrapBeanStore())
+			var bs = opBeanStore = BasicBeanStore.of(context.getBootstrapBeanStore())
 				.addBean(RestOpContext.class, this)
 				.addBean(Method.class, method)
 				.addBean(AnnotationWorkList.class, appliedAnnotations);
@@ -1105,19 +1134,6 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 			bs.add(UrlPathMatcher[].class, pm);
 			bs.addBean(UrlPathMatcher.class, pm.length > 0 ? pm[0] : null);
 
-			int hierarchyDepthTemp = 0;
-			var sc = method.getDeclaringClass().getSuperclass();
-			while (nn(sc)) {
-				hierarchyDepthTemp++;
-				sc = sc.getSuperclass();
-			}
-			hierarchyDepth = hierarchyDepthTemp;
-
-			responseMeta = ResponseBeanMeta.create(mi, appliedAnnotations);
-
-			preCallMethods = context.getPreCallMethods().stream().map(x -> new RestOpInvoker(x, context.findRestOperationArgs(x, bs), context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
-			postCallMethods = context.getPostCallMethods().stream().map(x -> new RestOpInvoker(x, context.findRestOperationArgs(x, bs), context.getMethodExecStats(x))).toArray(RestOpInvoker[]::new);
-			methodInvoker = new RestOpInvoker(method, context.findRestOperationArgs(method, bs), context.getMethodExecStats(method));
 		} catch (Exception e) {
 			throw new ServletException(e);
 		}
@@ -1139,7 +1155,7 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 				return c;
 		}
 
-		c = cmp(o.hierarchyDepth, hierarchyDepth);
+		c = cmp(o.hierarchyDepth.get(), hierarchyDepth.get());
 		if (c != 0)
 			return c;
 
@@ -1430,7 +1446,7 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 	 *
 	 * @return The response bean meta or <jk>null</jk> if it's not a {@link Response}-annotated bean.
 	 */
-	public ResponseBeanMeta getResponseMeta() { return responseMeta; }
+	public ResponseBeanMeta getResponseMeta() { return responseMeta.get(); }
 
 	/**
 	 * Returns the serializers to use for this method.
@@ -1534,11 +1550,11 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 
 	RestGuard[] getGuards() { return guards.get(); }
 
-	RestOpInvoker getMethodInvoker() { return methodInvoker; }
+	RestOpInvoker getMethodInvoker() { return methodInvoker.get(); }
 
-	RestOpInvoker[] getPostCallMethods() { return postCallMethods; }
+	RestOpInvoker[] getPostCallMethods() { return postCallMethods.get(); }
 
-	RestOpInvoker[] getPreCallMethods() { return preCallMethods; }
+	RestOpInvoker[] getPreCallMethods() { return preCallMethods.get(); }
 	
 	private static String joinnlFirstNonEmptyArray(String[]...s) {
 		for (var ss : s)
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
index d2b9bfc46e..54961a8eca 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOperations.java
@@ -28,7 +28,6 @@ import org.apache.juneau.rest.annotation.*;
 
 /**
  * Encapsulates the set of {@link RestOp}-annotated methods within a single {@link Rest}-annotated object.
- *
  */
 public class RestOperations {