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/06/01 13:19:52 UTC

(juneau) branch master updated: Implement TODO-145 builder-deferred REST class-family architecture

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 40a74b4f94 Implement TODO-145 builder-deferred REST class-family architecture
40a74b4f94 is described below

commit 40a74b4f945238fa6d35946fa63380d0e8ec936d
Author: James Bognar <ja...@apache.org>
AuthorDate: Mon Jun 1 09:19:45 2026 -0400

    Implement TODO-145 builder-deferred REST class-family architecture
    
    - Rename RestObject -> RestResource (+ BasicRestObject[Group] -> BasicRestResource[Group]) and introduce the opt-in RestMixin base.
    - Add per-capability flavor classes (Servlet/Resource/Mixin) for StaticFiles, Version, Admin, Health, the 4 view engines, and Favicon (annotation-configured; builder surface deferred to TODO-143).
    - Split BasicRestOperations into single-responsibility op-mixins (error/favicon/htdoc/stats).
    - Drop the "Basic" prefix from capability classes (kept for convenience bundles); BasicViewServlet -> ViewServlet.
    - Remove RestRequest.getStaticFiles() (repoint FileVar/Widget/StaticFilesMixin; keep RestContext.getStaticFiles()).
    - Planning docs: TODO-145 refinements, new TODO-146 (mixin host-RestContext access), TODO.md/TODO-143 updates.
---
 .../juneau/commons/inject/ValueResolver.java       | 124 +++++++---
 .../rest/springboot/HelloWorldResource.java        |   2 +-
 .../juneau/examples/rest/HelloWorldResource.java   |   2 +-
 .../juneau/examples/rest/HtmlBeansResource.java    |   2 +-
 .../juneau/examples/rest/RequestEchoResource.java  |   2 +-
 .../juneau/examples/rest/SseDemoResource.java      |   2 +-
 .../juneau/examples/rest/UtilityBeansResource.java |   2 +-
 .../jetty/HealthProbeConfiguration.java            |   4 +-
 .../springboot/template/HelloWorldResource.java    |   2 +-
 .../juneau/rest/mock/classic/MockConsole.java      |   2 +-
 .../rest/config/BasicUniversalJenaConfig.java      |   6 +-
 .../rest/springboot/BasicSpringRestServlet.java    |  62 +++--
 .../springboot/BasicSpringRestServletGroup.java    |  25 +-
 .../springboot/JuneauServletRegistrations.java     |   2 +-
 ...reemarkerResource.java => FreemarkerMixin.java} |  44 ++--
 ...emarkerServlet.java => FreemarkerResource.java} |  43 ++--
 ...eemarkerServlet.java => FreemarkerServlet.java} |  26 +-
 .../rest/view/freemarker/FreemarkerView.java       |   8 +-
 .../view/freemarker/FreemarkerViewRenderer.java    |  12 +-
 .../juneau/rest/view/freemarker/package-info.java  |   8 +-
 .../jsp/{BasicJspResource.java => JspMixin.java}   |  36 +--
 .../apache/juneau/rest/view/jsp/JspResource.java   |  68 ++++++
 .../jsp/{BasicJspServlet.java => JspServlet.java}  |  30 +--
 .../org/apache/juneau/rest/view/jsp/JspView.java   |   6 +-
 .../juneau/rest/view/jsp/JspViewRenderer.java      |  18 +-
 .../apache/juneau/rest/view/jsp/package-info.java  |   8 +-
 ...sicMustacheResource.java => MustacheMixin.java} |  40 +--
 ...cMustacheServlet.java => MustacheResource.java} |  43 ++--
 ...icMustacheServlet.java => MustacheServlet.java} |  26 +-
 .../juneau/rest/view/mustache/MustacheView.java    |   6 +-
 .../rest/view/mustache/MustacheViewRenderer.java   |  12 +-
 .../juneau/rest/view/mustache/package-info.java    |   8 +-
 ...cThymeleafResource.java => ThymeleafMixin.java} |  36 +--
 ...hymeleafServlet.java => ThymeleafResource.java} |  43 ++--
 ...ThymeleafServlet.java => ThymeleafServlet.java} |  26 +-
 .../juneau/rest/view/thymeleaf/ThymeleafView.java  |   4 +-
 .../rest/view/thymeleaf/ThymeleafViewRenderer.java |  12 +-
 .../juneau/rest/view/thymeleaf/package-info.java   |   8 +-
 .../java/org/apache/juneau/rest/RestContext.java   |   8 +-
 .../java/org/apache/juneau/rest/RestRequest.java   |  11 -
 .../org/apache/juneau/rest/annotation/Rest.java    |   5 +-
 .../rest/beans/ChildResourceDescriptions.java      |   3 +-
 .../juneau/rest/config/BasicJson5Config.java       |   2 +-
 .../apache/juneau/rest/config/BasicJsonConfig.java |   2 +-
 .../juneau/rest/config/BasicJsonHtmlConfig.java    |   2 +-
 .../juneau/rest/config/BasicOpenApiConfig.java     |   2 +-
 .../juneau/rest/config/BasicUniversalConfig.java   |   6 +-
 ...BasicFaviconResource.java => FaviconMixin.java} |  50 ++--
 .../juneau/rest/convention/FaviconResource.java    |  74 ++++++
 .../juneau/rest/convention/FaviconServlet.java     |  86 +++++++
 .../{BasicSeoResource.java => SeoMixin.java}       |  40 +--
 ...BasicVersionResource.java => VersionMixin.java} |  58 ++---
 ...sicVersionServlet.java => VersionResource.java} |  45 ++--
 ...asicVersionServlet.java => VersionServlet.java} |  22 +-
 ...cWellKnownResource.java => WellKnownMixin.java} |  38 +--
 .../juneau/rest/convention/package-info.java       |  26 +-
 ...BasicOpenApiResource.java => OpenApiMixin.java} |  14 +-
 .../{BasicRedocResource.java => RedocMixin.java}   |  20 +-
 ...BasicSwaggerResource.java => SwaggerMixin.java} |  18 +-
 ...cSwaggerUiResource.java => SwaggerUiMixin.java} |  22 +-
 .../org/apache/juneau/rest/docs/package-info.java  |  26 +-
 .../apache/juneau/rest/guard/RateLimitGuard.java   |   2 +-
 ...icHealthResource.java => HealthAggregator.java} |  88 ++++---
 .../org/apache/juneau/rest/health/HealthMixin.java |  85 +++++++
 .../apache/juneau/rest/health/HealthResource.java  |  87 +++++++
 .../apache/juneau/rest/health/HealthServlet.java   |  93 +++++++
 .../{BasicAdminResource.java => AdminMixin.java}   |  30 +--
 .../{BasicAdminServlet.java => AdminResource.java} |  56 ++---
 .../{BasicAdminServlet.java => AdminServlet.java}  |  22 +-
 .../ops/{BasicEchoResource.java => EchoMixin.java} |  30 +--
 .../org/apache/juneau/rest/ops/ErrorMixin.java     |  56 +++++
 .../org/apache/juneau/rest/ops/HtdocMixin.java     |  67 +++++
 ...outeIndexResource.java => RouteIndexMixin.java} |  16 +-
 .../org/apache/juneau/rest/ops/StatsMixin.java     |  64 +++++
 .../org/apache/juneau/rest/ops/package-info.java   |  20 +-
 .../juneau/rest/servlet/BasicGroupOperations.java  |  66 -----
 .../juneau/rest/servlet/BasicRestOperations.java   | 162 -------------
 ...BasicRestObject.java => BasicRestResource.java} |  59 +++--
 ...bjectGroup.java => BasicRestResourceGroup.java} |  23 +-
 .../juneau/rest/servlet/BasicRestServlet.java      |  65 +++--
 .../juneau/rest/servlet/BasicRestServletGroup.java |  23 +-
 .../org/apache/juneau/rest/servlet/RestMixin.java  |  58 +++++
 .../servlet/{RestObject.java => RestResource.java} |   2 +-
 ...ticFilesResource.java => StaticFilesMixin.java} |  14 +-
 ...cFilesServlet.java => StaticFilesResource.java} |  48 ++--
 ...icFilesServlet.java => StaticFilesServlet.java} |  22 +-
 .../java/org/apache/juneau/rest/vars/FileVar.java  |   4 +-
 .../juneau/rest/view/RawTemplateDispatcher.java    |   8 +-
 .../java/org/apache/juneau/rest/view/View.java     |   2 +-
 .../org/apache/juneau/rest/view/ViewResource.java  |  81 +++++++
 .../{BasicViewServlet.java => ViewServlet.java}    |   2 +-
 .../java/org/apache/juneau/rest/widget/Widget.java |   2 +-
 .../inject/Value_SupplierTypedFieldType_Test.java  | 269 +++++++++++++++++++++
 .../utils/FileUtils_ResolveSafely_Test.java        |   2 +-
 .../http/classic/SerializedHttpEntity_Test.java    |   2 +-
 .../org/apache/juneau/http/remote/Remote_Test.java |   6 +-
 .../org/apache/juneau/rest/NoInherit_Test.java     |  22 +-
 ...RestPathsRuntimeOverride_GetterShapes_Test.java |   2 +-
 .../rest/RestPathsRuntimeOverride_Getter_Test.java |   8 +-
 .../rest/StandaloneServletPathResolution_Test.java |  20 +-
 .../StandaloneServlets_JettyMicroservice_Test.java |  12 +-
 .../classic/BasicHttpRequestRetryHandler_Test.java |   2 +-
 .../client/classic/RestCallException_Test.java     |   2 +-
 .../client/classic/RestClient_BasicCalls_Test.java |   2 +-
 .../rest/client/classic/RestClient_Body_Test.java  |   2 +-
 .../classic/RestClient_Config_Context_Test.java    |   2 +-
 .../RestClient_Config_MarshallingContext_Test.java |   4 +-
 .../classic/RestClient_Config_OpenApi_Test.java    |   2 +-
 .../classic/RestClient_Config_Parser_Test.java     |   2 +-
 .../classic/RestClient_Config_RestClient_Test.java |   4 +-
 .../classic/RestClient_Config_Serializer_Test.java |   2 +-
 .../client/classic/RestClient_FormData_Test.java   |   2 +-
 .../client/classic/RestClient_Headers_Test.java    |   2 +-
 .../RestClient_Logging_StreamClosed_Test.java      |   2 +-
 .../client/classic/RestClient_Logging_Test.java    |   2 +-
 .../client/classic/RestClient_Marshalls_Test.java  |   2 +-
 .../rest/client/classic/RestClient_Paths_Test.java |   2 +-
 .../rest/client/classic/RestClient_Query_Test.java |   2 +-
 .../classic/RestClient_Response_Body_Test.java     |   2 +-
 .../classic/RestClient_Response_Headers_Test.java  |   2 +-
 .../client/classic/RestClient_Response_Test.java   |   6 +-
 .../rest/client/classic/RestClient_Test.java       |   4 +-
 .../convention/BasicConvention_Builders_Test.java  |  82 +++----
 .../BasicConvention_OpenApiHidden_Test.java        |  20 +-
 .../BasicConvention_ParentChain_Test.java          |  30 +--
 ...in_Test.java => FaviconMixin_AsMixin_Test.java} |  28 +--
 ...sMixin_Test.java => SeoMixin_AsMixin_Test.java} |  20 +-
 ...in_Test.java => VersionMixin_AsMixin_Test.java} |  28 +--
 ...va => VersionMixin_JettyMicroservice_Test.java} |  16 +-
 ...Test.java => VersionMixin_Springboot_Test.java} |  20 +-
 ...java => VersionMixin_SvlPathOverride_Test.java} |  18 +-
 ...t.java => VersionMixin_ValueAdoption_Test.java} |  10 +-
 ..._Test.java => WellKnownMixin_AsMixin_Test.java} |  14 +-
 .../docs/BasicApiDocs_JettyMicroservice_Test.java  |   4 +-
 .../rest/docs/BasicApiDocs_ParentChain_Test.java   |  31 ++-
 .../rest/docs/BasicApiDocs_Springboot_Test.java    |   6 +-
 .../docs/BasicApiDocs_TransitiveDedupe_Test.java   |  39 +--
 ...in_Test.java => OpenApiMixin_AsMixin_Test.java} |   6 +-
 ...java => OpenApiMixin_SvlPathOverride_Test.java} |  18 +-
 ...ixin_Test.java => RedocMixin_AsMixin_Test.java} |  12 +-
 ...t.java => RedocMixin_SvlPathOverride_Test.java} |  18 +-
 ...in_Test.java => SwaggerMixin_AsMixin_Test.java} |   6 +-
 ...Test.java => SwaggerMixin_Standalone_Test.java} |  10 +-
 ...java => SwaggerMixin_SvlPathOverride_Test.java} |  18 +-
 ..._Test.java => SwaggerUiMixin_AsMixin_Test.java} |  12 +-
 ...va => SwaggerUiMixin_SvlPathOverride_Test.java} |  18 +-
 ...rce_AsMixin_Test.java => HealthMixin_Test.java} |   4 +-
 ...hResource_Test.java => HealthServlet_Test.java} |   6 +-
 .../rest/openapi/OpenApiSchemaReuse_Test.java      |   2 +-
 .../rest/openapi/OpenApiYamlRoundTrip_Test.java    |   2 +-
 ...ixin_Test.java => AdminMixin_AsMixin_Test.java} |  44 ++--
 ...t.java => AdminMixin_AuthIntegration_Test.java} |   8 +-
 ...java => AdminMixin_RateLimitSnapshot_Test.java} |  16 +-
 ...t.java => AdminMixin_SvlPathOverride_Test.java} |  20 +-
 .../rest/ops/BasicOps_OpenApiHidden_Test.java      |  10 +-
 .../juneau/rest/ops/BasicOps_ParentChain_Test.java |  16 +-
 ...Mixin_Test.java => EchoMixin_AsMixin_Test.java} |  58 ++---
 ....java => EchoMixin_JettyMicroservice_Test.java} |  12 +-
 ...ot_Test.java => EchoMixin_Springboot_Test.java} |  16 +-
 ...st.java => EchoMixin_SvlPathOverride_Test.java} |  24 +-
 ...Test.java => RouteIndexMixin_AsMixin_Test.java} |  12 +-
 ...a => RouteIndexMixin_SvlPathOverride_Test.java} |  18 +-
 ...JuneauRestServletContainerInitializer_Test.java |  10 +-
 .../JuneauRestAutoConfiguration_Test.java          |   8 +-
 .../JuneauServletRegistrations_Test.java           |   2 +-
 ...tPropertySource_SpringbootIntegration_Test.java |   2 +-
 .../SpringEnvironmentPropertySource_Test.java      |   4 +-
 ...est.java => StaticFilesMixin_AsMixin_Test.java} |   8 +-
 ...ava => StaticFilesMixin_CacheControl_Test.java} |   6 +-
 ...=> StaticFilesMixin_ImporterOverride_Test.java} |   6 +-
 ...> StaticFilesMixin_JettyMicroservice_Test.java} |   6 +-
 ...va => StaticFilesMixin_OpenApiHidden_Test.java} |  16 +-
 ...> StaticFilesMixin_SpringbootMetaInf_Test.java} |  10 +-
 ....java => StaticFilesMixin_Springboot_Test.java} |  10 +-
 ....java => StaticFilesMixin_Standalone_Test.java} |  12 +-
 ... => StaticFilesMixin_SvlPathOverride_Test.java} |  20 +-
 ...Test.java => FreemarkerMixin_Builder_Test.java} |  94 +++----
 ...est.java => FreemarkerMixin_MockRest_Test.java} |  12 +-
 ...ava => FreemarkerMixin_PathTraversal_Test.java} |  16 +-
 ...a => FreemarkerMixin_SvlPathOverride_Test.java} |   6 +-
 ...uilder_Test.java => JspMixin_Builder_Test.java} |  26 +-
 ...kRest_Test.java => JspMixin_MockRest_Test.java} |  10 +-
 ..._Test.java => JspMixin_PathTraversal_Test.java} |  10 +-
 ...est.java => JspMixin_SvlPathOverride_Test.java} |  18 +-
 ...est_Test.java => JspServlet_MockRest_Test.java} |   8 +-
 ...r_Test.java => MustacheMixin_Builder_Test.java} |  86 +++----
 ..._Test.java => MustacheMixin_MockRest_Test.java} |  12 +-
 ....java => MustacheMixin_PathTraversal_Test.java} |  14 +-
 ...ava => MustacheMixin_SvlPathOverride_Test.java} |   6 +-
 ..._Test.java => ThymeleafMixin_Builder_Test.java} |  62 ++---
 ...Test.java => ThymeleafMixin_MockRest_Test.java} |  12 +-
 ...java => ThymeleafMixin_PathTraversal_Test.java} |  12 +-
 ...va => ThymeleafMixin_SvlPathOverride_Test.java} |   6 +-
 juneau-utest/test-run-history.tsv                  |   1 +
 194 files changed, 2669 insertions(+), 1756 deletions(-)

diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/ValueResolver.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/ValueResolver.java
index 75dea4ad9a..b9afa41d39 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/ValueResolver.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/inject/ValueResolver.java
@@ -194,9 +194,9 @@ public final class ValueResolver {
 	}
 
 	/**
-	 * Generic-aware {@code @Value} resolution that honors {@link Supplier}{@code <String>}
+	 * Generic-aware {@code @Value} resolution that honors {@link Supplier}{@code <T>}
 	 * field types by returning a re-evaluating, threadsafe Supplier instead of a one-shot
-	 * resolved value (field-type autodetect — bare {@code String} resolves once, {@code Supplier<String>}
+	 * resolved value (field-type autodetect — bare {@code String} resolves once, {@code Supplier<T>}
 	 * re-evaluates per {@code .get()}).
 	 *
 	 * <p>
@@ -205,16 +205,25 @@ public final class ValueResolver {
 	 * 	<li>{@code Supplier<String>} → returns a {@link Supplier} that re-evaluates the
 	 * 		compiled template on every {@link Supplier#get()} call, each time against a
 	 * 		fresh {@link VarResolverSession}. Safe to share across threads.
-	 * 	<li>Anything else → delegates to {@link #resolve(String, Class, String)} with the
-	 * 		raw target class (preserves existing behavior).
+	 * 	<li>{@code Supplier<T>} for {@code T != String} → returns a {@link Supplier} that, on every
+	 * 		{@link Supplier#get()} call, re-evaluates the template <i>and</i> coerces the resolved
+	 * 		string into {@code T} via the same {@code String -> T} converter machinery that plain
+	 * 		{@code @Value T} uses ({@link Settings#toType(String, Class)}). The coerced value is never
+	 * 		memoized (late-binding contract); coercion failures surface as a {@link RuntimeException}
+	 * 		from {@code .get()} naming the {@code @Value} key and target type. {@code T} is taken from
+	 * 		any type the converter registry can build from a {@code String} (e.g. {@code Integer},
+	 * 		{@code Long}, {@code Boolean}, {@code Duration}, {@code URI}, {@code Path}, enums).
+	 * 	<li>Anything else (including raw {@code Supplier} and {@code Optional<Supplier<T>>}) → delegates
+	 * 		to {@link #resolve(String, Class, String)} with the raw target class (preserves existing
+	 * 		behavior; the {@code Optional<Supplier<T>>} nesting is intentionally not specially handled).
 	 * </ul>
 	 *
 	 * @param expression The {@code @Value} expression. May be {@code null}.
 	 * @param targetType The erased target class (e.g. {@code String.class} or {@code Supplier.class}).
-	 * @param genericTargetType The declared generic type (e.g. {@code Supplier<String>}). May be
+	 * @param genericTargetType The declared generic type (e.g. {@code Supplier<Integer>}). May be
 	 * 	{@code null}, in which case Supplier-detection is skipped.
 	 * @param siteDescription Human-readable site description for error messages.
-	 * @return Either a one-shot coerced value, or a {@code Supplier<String>} factory.
+	 * @return Either a one-shot coerced value, or a {@code Supplier<T>} factory.
 	 */
 	public static Object resolve(String expression, Class<?> targetType, Type genericTargetType, String siteDescription) {
 		return resolve(expression, targetType, genericTargetType, siteDescription, null);
@@ -226,7 +235,7 @@ public final class ValueResolver {
 	 *
 	 * <p>
 	 * The {@code beanStore} reference is captured (not snapshotted) by the returned
-	 * {@code Supplier<String>} when the declared field/parameter type is {@code Supplier<String>},
+	 * {@code Supplier<T>} when the declared field/parameter type is {@code Supplier<...>},
 	 * so re-evaluating reads always see the current state of {@code beanStore.getBeansOfType(PropertySource.class)}.
 	 *
 	 * @param expression The {@code @Value} expression. May be {@code null}.
@@ -235,37 +244,65 @@ public final class ValueResolver {
 	 * @param siteDescription Human-readable site description for error messages.
 	 * @param beanStore Caller-scoped {@link BeanStore} consulted for {@link PropertySource} beans.
 	 * 	May be {@code null}.
-	 * @return Either a one-shot coerced value, or a {@code Supplier<String>} factory.
+	 * @return Either a one-shot coerced value, or a {@code Supplier<T>} factory.
 	 */
 	public static Object resolve(String expression, Class<?> targetType, Type genericTargetType, String siteDescription,
 			BeanStore beanStore) {
-		if (isSupplierOfString(targetType, genericTargetType)) {
-			if (expression == null)
-				return (Supplier<String>) () -> null;
-			var template = getCompiledTemplate(expression);
-			// Literal templates fast-path: capture the resolved string once and hand back a
-			// constant Supplier. Saves a per-.get() session allocation when the expression
-			// has no variables (e.g. @Value("literal") Supplier<String>).
-			if (template.isLiteral()) {
-				var literal = template.resolve(VarResolver.DEFAULT.createSession());
-				return (Supplier<String>) () -> literal;
-			}
-			if (beanStore == null)
-				return template.asSupplierWithFreshSessions(VarResolver.DEFAULT);
-			// Capture the BeanStore by-reference so re-evaluating reads see the current state of
-			// its PropertySource-typed beans (matches the "by-reference, not snapshot" contract).
-			final BeanStore scope = beanStore;
-			return (Supplier<String>) () -> {
-				var s = VarResolver.DEFAULT.createSession();
-				var sources = scopedSources(scope);
-				if (sources != null)
-					s.bean(PropertySource[].class, sources);
-				return template.resolve(s);
-			};
+		var elementType = supplierElementType(targetType, genericTargetType);
+		if (elementType != null) {
+			// Build the re-evaluating string Supplier once. This is the same factory the bare
+			// Supplier<String> path has always used.
+			var stringSupplier = buildStringSupplier(expression, beanStore);
+			if (elementType == String.class)
+				return stringSupplier;
+			// Supplier<T> for T != String: coerce per .get() using the same String -> T converter
+			// machinery that plain @Value T uses (resolveCoerce -> Settings.toType). Per the
+			// late-binding contract, the string is re-resolved AND re-coerced on every .get() — the
+			// coerced value is never memoized. Only the converter lookup is cached (Settings.toType
+			// memoizes the discovered String -> T function per type in its toTypeFunctions registry,
+			// so each .get() pays the coercion but not the reflective converter discovery). A coercion
+			// failure surfaces as a BeanCreationException (a RuntimeException) from .get(), naming the
+			// @Value expression and the target type.
+			final Class<?> et = elementType;
+			return (Supplier<Object>) () -> resolveCoerce(stringSupplier.get(), et, expression, siteDescription);
 		}
 		return resolve(expression, targetType, siteDescription, beanStore);
 	}
 
+	/**
+	 * Builds the re-evaluating {@code Supplier<String>} used by the {@code @Value Supplier<...>}
+	 * autodetect path. Each {@link Supplier#get()} re-resolves the compiled template against a fresh
+	 * {@link VarResolverSession}, so live config changes are reflected (the late-binding contract).
+	 *
+	 * @param expression The {@code @Value} expression. May be {@code null}.
+	 * @param beanStore Caller-scoped {@link BeanStore} consulted for {@link PropertySource} beans. May be {@code null}.
+	 * @return A threadsafe, re-evaluating {@code Supplier<String>}.
+	 */
+	private static Supplier<String> buildStringSupplier(String expression, BeanStore beanStore) {
+		if (expression == null)
+			return () -> null;
+		var template = getCompiledTemplate(expression);
+		// Literal templates fast-path: capture the resolved string once and hand back a
+		// constant Supplier. Saves a per-.get() session allocation when the expression
+		// has no variables (e.g. @Value("literal") Supplier<String>).
+		if (template.isLiteral()) {
+			var literal = template.resolve(VarResolver.DEFAULT.createSession());
+			return () -> literal;
+		}
+		if (beanStore == null)
+			return template.asSupplierWithFreshSessions(VarResolver.DEFAULT);
+		// Capture the BeanStore by-reference so re-evaluating reads see the current state of
+		// its PropertySource-typed beans (matches the "by-reference, not snapshot" contract).
+		final BeanStore scope = beanStore;
+		return () -> {
+			var s = VarResolver.DEFAULT.createSession();
+			var sources = scopedSources(scope);
+			if (sources != null)
+				s.bean(PropertySource[].class, sources);
+			return template.resolve(s);
+		};
+	}
+
 	/**
 	 * Returns the caller-scoped {@link PropertySource} array for the supplied {@link BeanStore},
 	 * or {@code null} if the store has no {@code PropertySource}-typed beans (matching the
@@ -286,14 +323,29 @@ public final class ValueResolver {
 		return map.values().toArray(new PropertySource[0]);
 	}
 
-	/** Returns {@code true} if the declared field/parameter type is exactly {@code Supplier<String>}. */
-	private static boolean isSupplierOfString(Class<?> targetType, Type genericTargetType) {
+	/**
+	 * Returns the {@code T} element class of a declared {@code Supplier<T>} injection site, or
+	 * {@code null} if the declared type is not a coercible {@code Supplier<T>}.
+	 *
+	 * <p>
+	 * Returns {@code null} (i.e. "not a typed Supplier site, fall through to one-shot resolution")
+	 * for raw {@code Supplier}, non-{@code Supplier} parameterized types such as
+	 * {@code Optional<Supplier<T>>} (whose raw type is {@code Optional}, not {@code Supplier} — the
+	 * nesting is intentionally not specially handled), and type arguments that are not a concrete
+	 * {@link Class} (wildcards / type variables). {@code Supplier<String>} returns {@code String.class},
+	 * which the caller treats as the pre-existing pass-through behavior.
+	 */
+	private static Class<?> supplierElementType(Class<?> targetType, Type genericTargetType) {
 		if (targetType != Supplier.class)
-			return false;
+			return null;
 		if (!(genericTargetType instanceof ParameterizedType pt))
-			return false;
+			return null;
+		if (pt.getRawType() != Supplier.class)
+			return null;
 		var args = pt.getActualTypeArguments();
-		return args.length == 1 && args[0] == String.class;
+		if (args.length != 1)
+			return null;
+		return args[0] instanceof Class<?> c ? c : null;
 	}
 
 	private static Object resolveCoerce(String resolved, Class<?> targetType, String expression, String siteDescription) {
diff --git a/juneau-examples/juneau-examples-rest-springboot/src/main/java/org/apache/juneau/examples/rest/springboot/HelloWorldResource.java b/juneau-examples/juneau-examples-rest-springboot/src/main/java/org/apache/juneau/examples/rest/springboot/HelloWorldResource.java
index 93b1292063..727a1380c1 100644
--- a/juneau-examples/juneau-examples-rest-springboot/src/main/java/org/apache/juneau/examples/rest/springboot/HelloWorldResource.java
+++ b/juneau-examples/juneau-examples-rest-springboot/src/main/java/org/apache/juneau/examples/rest/springboot/HelloWorldResource.java
@@ -42,7 +42,7 @@ import org.springframework.beans.factory.annotation.*;
 		"</div>"
 	}
 )
-public class HelloWorldResource extends BasicRestObject {
+public class HelloWorldResource extends BasicRestResource {
 
 	@Autowired
 	private HelloWorldMessageProvider messageProvider;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
index 188c3b2ec1..0f84ce6f66 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HelloWorldResource.java
@@ -37,7 +37,7 @@ import org.apache.juneau.rest.servlet.*;
 		"</div>"
 	}
 )
-public class HelloWorldResource extends BasicRestObject {
+public class HelloWorldResource extends BasicRestResource {
 
 	/**
 	 * [HTTP GET /helloworld]
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
index 94f96e67eb..743edab595 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/HtmlBeansResource.java
@@ -56,7 +56,7 @@ import org.apache.juneau.rest.widget.*;
 	},
 	asideFloat="RIGHT"
 )
-public class HtmlBeansResource extends BasicRestObject {
+public class HtmlBeansResource extends BasicRestResource {
 
 	@SuppressWarnings("unused")
 	private static final long serialVersionUID = 1L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
index 6ffb9006c7..21eb349774 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/RequestEchoResource.java
@@ -73,7 +73,7 @@ import org.apache.juneau.commons.bean.*;
 @BeanTypeApply(on="HttpServletRequest",value=@BeanType(interfaceClass=HttpServletRequest.class))
 @BeanTypeApply(on="HttpSession",value=@BeanType(interfaceClass=HttpSession.class))
 @BeanTypeApply(on="ServletContext",value=@BeanType(interfaceClass=ServletContext.class))
-public class RequestEchoResource extends BasicRestObject {
+public class RequestEchoResource extends BasicRestResource {
 
 	/**
 	 * [HTTP GET /echo/*]
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SseDemoResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SseDemoResource.java
index 093211dfe7..00332b10d2 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SseDemoResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/SseDemoResource.java
@@ -54,7 +54,7 @@ import org.apache.juneau.sse.*;
 	title = "SSE demo",
 	description = "Emits a stream of Server-Sent Events with a short delay between events."
 )
-public class SseDemoResource extends BasicRestObject {
+public class SseDemoResource extends BasicRestResource {
 
 	private static final int EVENT_COUNT = 10;
 	private static final long SLEEP_MILLIS = 500L;
diff --git a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UtilityBeansResource.java b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UtilityBeansResource.java
index 067b6dabde..62c2453dd0 100644
--- a/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UtilityBeansResource.java
+++ b/juneau-examples/juneau-examples-rest/src/main/java/org/apache/juneau/examples/rest/UtilityBeansResource.java
@@ -54,7 +54,7 @@ import org.apache.juneau.commons.bean.*;
 	},
 	asideFloat="RIGHT"
 )
-public class UtilityBeansResource extends BasicRestObject {
+public class UtilityBeansResource extends BasicRestResource {
 
 	/**
 	 * Sample address bean used for demonstrating utility bean functionality.
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/HealthProbeConfiguration.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/HealthProbeConfiguration.java
index 101290b38c..612f0c9d51 100644
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/HealthProbeConfiguration.java
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/HealthProbeConfiguration.java
@@ -22,7 +22,7 @@ import org.apache.juneau.rest.health.*;
 import jakarta.servlet.*;
 
 /**
- * Opt-in probe configuration that auto-mounts {@link BasicHealthResource}.
+ * Opt-in probe configuration that auto-mounts {@link HealthServlet}.
  *
  * @since 9.5.0
  */
@@ -48,6 +48,6 @@ public class HealthProbeConfiguration {
 	@Bean(name="healthProbeServlet")
 	@ConditionalOnMissingBean(name="healthProbeServlet")
 	public Servlet healthProbeServlet() {
-		return new BasicHealthResource();
+		return new HealthServlet();
 	}
 }
diff --git a/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/HelloWorldResource.java b/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/HelloWorldResource.java
index 72ffd14264..f0c7c358dc 100644
--- a/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/HelloWorldResource.java
+++ b/juneau-microservice/juneau-my-springboot-microservice/src/main/java/org/apache/juneau/microservice/springboot/template/HelloWorldResource.java
@@ -36,7 +36,7 @@ import org.springframework.beans.factory.annotation.*;
 @Rest(title = "Hello World", description = "An example of the simplest-possible resource", path = "/helloWorld")
 @HtmlDocConfig(aside = { "<div style='max-width:400px' class='text'>", "\t<p>This page shows a resource that simply response with a 'Hello world!' message</p>",
 	"\t<p>The POJO serialized is a simple String.</p>", "</div>" })
-public class HelloWorldResource extends BasicRestObject {
+public class HelloWorldResource extends BasicRestResource {
 
 	private final String message;
 
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/classic/MockConsole.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/classic/MockConsole.java
index ce2a283c14..a3e9e073f1 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/classic/MockConsole.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/classic/MockConsole.java
@@ -35,7 +35,7 @@ import org.apache.juneau.assertions.*;
  * <p class='bjava'>
  * 	<jc>// A simple REST API that echos a posted bean.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyRest <jk>extends</jk> BasicRestObject {
+ * 	<jk>public class</jk> MyRest <jk>extends</jk> BasicRestResource {
  * 		<ja>@RestPost</ja>(<js>"/bean"</js>)
  * 		<jk>public</jk> Bean postBean(<ja>@Content</ja> Bean <jv>bean</jv>) {
  * 			<jk>return</jk> <jv>bean</jv>;
diff --git a/juneau-rest/juneau-rest-server-rdf/src/main/java/org/apache/juneau/rest/config/BasicUniversalJenaConfig.java b/juneau-rest/juneau-rest-server-rdf/src/main/java/org/apache/juneau/rest/config/BasicUniversalJenaConfig.java
index 2259e4638e..ad9bf1c86d 100644
--- a/juneau-rest/juneau-rest-server-rdf/src/main/java/org/apache/juneau/rest/config/BasicUniversalJenaConfig.java
+++ b/juneau-rest/juneau-rest-server-rdf/src/main/java/org/apache/juneau/rest/config/BasicUniversalJenaConfig.java
@@ -129,7 +129,7 @@ import org.apache.juneau.xml.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicUniversalJenaConfig { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicUniversalJenaConfig { ... }
  * </p>
  *
  * <p>
@@ -142,8 +142,8 @@ import org.apache.juneau.xml.*;
  * 	<ul class='javatreec'>
  * 		<li class='jc'>{@link BasicRestServlet}
  * 		<li class='jc'>{@link BasicRestServletGroup}
- * 		<li class='jc'>{@link BasicRestObject}
- * 		<li class='jc'>{@link BasicRestObjectGroup}
+ * 		<li class='jc'>{@link BasicRestResource}
+ * 		<li class='jc'>{@link BasicRestResourceGroup}
  *	</ul>
  * 	<li class='link'>{doc jrs.AnnotatedClasses}
  * </ul>
diff --git a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServlet.java b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServlet.java
index cffb7cd4f8..de48cb3c36 100644
--- a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServlet.java
+++ b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServlet.java
@@ -16,19 +16,15 @@
  */
 package org.apache.juneau.rest.springboot;
 
-import java.util.*;
-
-import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.http.response.*;
-import org.apache.juneau.rest.*;
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.jsonschema.annotation.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.config.*;
-import org.apache.juneau.rest.servlet.*;
-import org.apache.juneau.rest.stats.*;
+import org.apache.juneau.rest.convention.*;
+import org.apache.juneau.rest.ops.*;
 
 /**
- * Subclass of {@link SpringRestServlet} with default settings and standard methods defined.
+ * Subclass of {@link SpringRestServlet} with default settings and standard endpoints defined.
  *
  * <p>
  * Meant as base class for top-level REST resources in Spring Boot environments.
@@ -38,7 +34,8 @@ import org.apache.juneau.rest.stats.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations}.
+ * The residual cross-cutting endpoints are supplied by single-responsibility op-mixins ({@link ErrorMixin},
+ * {@link HtdocMixin}, {@link StatsMixin}, and {@link FaviconMixin}).
  *
  * <h5 class='section'>See Also:</h5><ul>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauRestServerSpringbootBasics">juneau-rest-server-springboot Basics</a>
@@ -47,26 +44,27 @@ import org.apache.juneau.rest.stats.*;
  *
  * @serial exclude
  */
-@Rest
-public abstract class BasicSpringRestServlet extends SpringRestServlet implements BasicRestOperations, BasicUniversalConfig {
-	private static final long serialVersionUID = 1L;
-
-	@Override /* Overridden from BasicRestConfig */
-	public void error() {}
-
-	@Override /* Overridden from BasicRestConfig */
-	public HttpResource getFavIcon() {
-		var favIcon = getContext().getConfig().get("REST/favicon").orElse("images/juneau.png");
-		return getHtdoc(favIcon, null);
+// @formatter:off
+@Rest(mixins={ErrorMixin.class, HtdocMixin.class, StatsMixin.class, FaviconMixin.class})
+@HtmlDocConfig(
+	// Basic page navigation links.
+	navlinks={
+		"up: request:/..",
+		"api: servlet:/api",
+		"stats: servlet:/stats"
 	}
-
-	@Override /* Overridden from BasicRestConfig */
-	public HttpResource getHtdoc(@Path("/*") String path, Locale locale) throws NotFound {
-		return getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
-	}
-
-	@Override /* Overridden from BasicRestConfig */
-	public RestContextStats getStats(RestRequest req) {
-		return req.getContext().getStats();
-	}
-}
\ No newline at end of file
+)
+@JsonSchemaConfig(
+	// Add descriptions to the following types when not specified:
+	addDescriptionsTo="bean,collection,array,map,enum",
+	// Add example to the following types:
+	addExamplesTo="bean,collection,array,map",
+	// Don't generate schema information on the Swagger / OpenApi beans themselves or HTML beans.
+	ignoreTypes="Swagger,OpenApi,org.apache.juneau.bean.html5.*",
+	// Use $ref references for bean definitions to reduce duplication in generated specs.
+	useBeanDefs="true"
+)
+// @formatter:on
+public abstract class BasicSpringRestServlet extends SpringRestServlet implements BasicUniversalConfig {
+	private static final long serialVersionUID = 1L;
+}
diff --git a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
index 50aa13814f..0f05aa98b9 100644
--- a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
+++ b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/BasicSpringRestServletGroup.java
@@ -16,9 +16,10 @@
  */
 package org.apache.juneau.rest.springboot;
 
+import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
-import org.apache.juneau.rest.servlet.*;
 
 /**
  * Specialized subclass of {@link BasicSpringRestServlet} for showing "group" pages.
@@ -31,7 +32,10 @@ import org.apache.juneau.rest.servlet.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations}.
+ * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on top of the residual op-mixins
+ * inherited from {@link BasicSpringRestServlet}. The navigation page is rendered as a method of the host
+ * resource (rather than a sub-context mixin) so it inherits the host's
+ * {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig} page decoration.
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() @Rest(children)} annotation.
@@ -47,6 +51,21 @@ import org.apache.juneau.rest.servlet.*;
 @SuppressWarnings({
 	"java:S110" // Inheritance depth acceptable for BasicSpringRestServletGroup hierarchy
 })
-public abstract class BasicSpringRestServletGroup extends BasicSpringRestServlet implements BasicGroupOperations {
+public abstract class BasicSpringRestServletGroup extends BasicSpringRestServlet {
 	private static final long serialVersionUID = 1L;
+
+	/**
+	 * [GET /] - Get child resources.
+	 *
+	 * <p>
+	 * Returns a bean that lists and allows navigation to child resources. Default implementation
+	 * delegates to {@link ChildResourceDescriptions#of(RestRequest)}; subclasses may override.
+	 *
+	 * @param req The HTTP request.
+	 * @return The bean containing links to the child resources.
+	 */
+	@RestGet(path="/", summary="Navigation page")
+	public ChildResourceDescriptions getChildren(RestRequest req) {
+		return ChildResourceDescriptions.of(req);
+	}
 }
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations.java b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations.java
index 3bbb5d9395..5b4bb37cce 100644
--- a/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations.java
+++ b/juneau-rest/juneau-rest-server-springboot/src/main/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations.java
@@ -34,7 +34,7 @@ import org.springframework.boot.web.servlet.*;
  * <p class='bjava'>
  * 	<ja>@Bean</ja>
  * 	<jk>public</jk> ServletRegistrationBean&lt;?&gt; jspServlet(BeanStore <jv>beanStore</jv>) {
- * 		<jk>return</jk> JuneauServletRegistrations.<jsm>forServlet</jsm>(<jk>new</jk> BasicJspServlet(), <jv>beanStore</jv>);
+ * 		<jk>return</jk> JuneauServletRegistrations.<jsm>forServlet</jsm>(<jk>new</jk> JspServlet(), <jv>beanStore</jv>);
  * 	}
  * </p>
  *
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerResource.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerMixin.java
similarity index 93%
rename from juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerResource.java
rename to juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerMixin.java
index 021ef69916..b458d20c58 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerResource.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerMixin.java
@@ -36,7 +36,7 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicFreemarkerResource.class)}; the host then:
+ * {@link Rest#mixins() @Rest(mixins=FreemarkerMixin.class)}; the host then:
  *
  * <ol class='spaced-list'>
  * 	<li>Gains a default mount at {@code /freemarker/*} that serves raw FreeMarker templates from
@@ -52,11 +52,11 @@ import org.apache.juneau.rest.view.*;
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicFreemarkerResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=FreemarkerMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicFreemarkerResource freemarker() {
- * 			<jk>return</jk> BasicFreemarkerResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> FreemarkerMixin freemarker() {
+ * 			<jk>return</jk> FreemarkerMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.templateSuffix(<js>".ftlh"</js>)
  * 				.build();
@@ -140,9 +140,9 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two
- * {@code BasicFreemarkerResource} beans, each mounted via a subclass declaring its own op-level
+ * {@code FreemarkerMixin} beans, each mounted via a subclass declaring its own op-level
  * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
- * {@code BasicFreemarkerResource_MockRest_Test} test in {@code juneau-utest} for the canonical
+ * {@code FreemarkerMixin_MockRest_Test} test in {@code juneau-utest} for the canonical
  * pattern.
  *
  * <h5 class='section'>OpenAPI surface:</h5>
@@ -164,7 +164,7 @@ import org.apache.juneau.rest.view.*;
 @Rest(
 	responseProcessors={FreemarkerViewRenderer.class}
 )
-public class BasicFreemarkerResource implements RawTemplateDispatcher {
+public class FreemarkerMixin implements RawTemplateDispatcher {
 
 	/** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
 	public static final String DEFAULT_BASE_PATH = "/";
@@ -201,9 +201,9 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 	 * <p>
 	 * The mixin walk falls back to this constructor when the
 	 * {@link org.apache.juneau.commons.inject.BeanStore BeanStore} does not have a registered
-	 * {@code BasicFreemarkerResource} bean.
+	 * {@code FreemarkerMixin} bean.
 	 */
-	public BasicFreemarkerResource() {
+	public FreemarkerMixin() {
 		this(create());
 	}
 
@@ -212,7 +212,7 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 	 *
 	 * @param builder The builder. Must not be {@code null}.
 	 */
-	protected BasicFreemarkerResource(Builder builder) {
+	protected FreemarkerMixin(Builder builder) {
 		basePath = builder.basePath;
 		templateSuffix = builder.templateSuffix;
 		cacheTemplates = builder.cacheTemplates;
@@ -329,7 +329,7 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 	 */
 	protected Configuration buildDefaultConfiguration() {
 		var cfg = new Configuration(Configuration.VERSION_2_3_34);
-		cfg.setClassLoaderForTemplateLoading(BasicFreemarkerResource.class.getClassLoader(), toResourceRoot(basePath));
+		cfg.setClassLoaderForTemplateLoading(FreemarkerMixin.class.getClassLoader(), toResourceRoot(basePath));
 		cfg.setDefaultEncoding("UTF-8");
 		cfg.setOutputFormat(HTMLOutputFormat.INSTANCE);
 		cfg.setTemplateUpdateDelayMilliseconds(cacheTemplates ? Long.MAX_VALUE : 0L);
@@ -476,7 +476,7 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 	}
 
 	/**
-	 * Builder for {@link BasicFreemarkerResource}.
+	 * Builder for {@link FreemarkerMixin}.
 	 */
 	public static class Builder {
 
@@ -484,19 +484,19 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 		private String templateSuffix = DEFAULT_TEMPLATE_SUFFIX;
 		private boolean cacheTemplates = DEFAULT_CACHE_TEMPLATES;
 
-		/** Constructor &mdash; package access for {@link BasicFreemarkerResource#create()}. */
+		/** Constructor &mdash; package access for {@link FreemarkerMixin#create()}. */
 		protected Builder() {}
 
 		/**
 		 * Sets the classpath base path under which template resources are resolved.
 		 *
 		 * <p>
-		 * Defaults to {@link BasicFreemarkerResource#DEFAULT_BASE_PATH "/"}. A typical configured
+		 * Defaults to {@link FreemarkerMixin#DEFAULT_BASE_PATH "/"}. A typical configured
 		 * value is {@code "/templates/"} &mdash; the same Spring-Boot-compatible layout
 		 * convention the sibling JSP / Thymeleaf / Mustache bridges use.
 		 *
 		 * @param value The base path. {@code null} or blank values reset to the default
-		 * 	{@link BasicFreemarkerResource#DEFAULT_BASE_PATH}.
+		 * 	{@link FreemarkerMixin#DEFAULT_BASE_PATH}.
 		 * @return This object.
 		 */
 		public Builder basePath(String value) {
@@ -509,12 +509,12 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 		 * (idempotent).
 		 *
 		 * <p>
-		 * Defaults to {@link BasicFreemarkerResource#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
+		 * Defaults to {@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
 		 * suffix &mdash; literal template names). Typical configured values are {@code ".ftlh"}
 		 * (HTML auto-escape) or {@code ".ftl"} (no auto-escape).
 		 *
 		 * @param value The new suffix. {@code null} resets to
-		 * 	{@link BasicFreemarkerResource#DEFAULT_TEMPLATE_SUFFIX}.
+		 * 	{@link FreemarkerMixin#DEFAULT_TEMPLATE_SUFFIX}.
 		 * @return This object.
 		 */
 		public Builder templateSuffix(String value) {
@@ -526,7 +526,7 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 		 * Sets the template-cache flag for the bridge-default configuration.
 		 *
 		 * <p>
-		 * Defaults to {@link BasicFreemarkerResource#DEFAULT_CACHE_TEMPLATES true} (cache
+		 * Defaults to {@link FreemarkerMixin#DEFAULT_CACHE_TEMPLATES true} (cache
 		 * forever &mdash; production-safe). Set {@code false} to enable dev hot-reload (FreeMarker
 		 * re-checks the underlying resource on every render).
 		 *
@@ -570,14 +570,14 @@ public class BasicFreemarkerResource implements RawTemplateDispatcher {
 		}
 
 		/**
-		 * Builds the {@link BasicFreemarkerResource}.
+		 * Builds the {@link FreemarkerMixin}.
 		 *
-		 * @return A new {@link BasicFreemarkerResource} instance.
+		 * @return A new {@link FreemarkerMixin} instance.
 		 */
-		public BasicFreemarkerResource build() {
+		public FreemarkerMixin build() {
 			if (basePath == null)
 				throw illegalArg("basePath must not be null");
-			return new BasicFreemarkerResource(this);
+			return new FreemarkerMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerResource.java
similarity index 52%
copy from juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java
copy to juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerResource.java
index edab5c2ad3..d8a04858ee 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerResource.java
@@ -20,55 +20,48 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicFreemarkerResource} mixin.
+ * Child-resource flavor of the {@link FreemarkerMixin} mixin.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /freemarker/*} and renders raw FreeMarker
- * templates by delegating to a shared {@link BasicFreemarkerResource} instance &mdash; the same
- * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
- *
- * <p>
- * Whereas the {@link BasicFreemarkerResource} mixin pins its op at {@code /freemarker/*} for
- * composition into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/freemarker/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=FreemarkerResource.class)}
+ * under a parent at the subtree {@code /freemarker} and renders raw FreeMarker templates by delegating to
+ * a shared {@link FreemarkerMixin} instance &mdash; the same {@link RawTemplateDispatcher} implementation
+ * the mixin and {@link FreemarkerServlet servlet} flavors use, so the three forms cannot drift.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFreemarkerResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link FreemarkerMixin}
+ * 	<li class='jc'>{@link FreemarkerServlet}
+ * 	<li class='jc'>{@link ViewResource}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 @Rest(
-	paths="/freemarker/*",
+	path="/freemarker",
 	responseProcessors={FreemarkerViewRenderer.class}
 )
-public class BasicFreemarkerServlet extends BasicViewServlet {
-
-	private static final long serialVersionUID = 1L;
+public class FreemarkerResource extends ViewResource {
 
-	private final transient BasicFreemarkerResource delegate;
+	private final transient FreemarkerMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicFreemarkerResource} delegate. */
-	public BasicFreemarkerServlet() {
-		this(BasicFreemarkerResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link FreemarkerMixin} delegate. */
+	public FreemarkerResource() {
+		this(FreemarkerMixin.create().build());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared FreeMarker renderer/mixin this servlet delegates raw dispatch to.
-	 * 	Must not be {@code null}.
+	 * @param delegate The shared FreeMarker renderer/mixin this child delegates raw dispatch to. Must
+	 * 	not be {@code null}.
 	 */
-	protected BasicFreemarkerServlet(BasicFreemarkerResource delegate) {
+	protected FreemarkerResource(FreemarkerMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewResource */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerServlet.java
similarity index 72%
rename from juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java
rename to juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerServlet.java
index edab5c2ad3..3b5ebcdafe 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/BasicFreemarkerServlet.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerServlet.java
@@ -20,22 +20,22 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicFreemarkerResource} mixin.
+ * Standalone servlet companion of the {@link FreemarkerMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /freemarker/*} and renders raw FreeMarker
- * templates by delegating to a shared {@link BasicFreemarkerResource} instance &mdash; the same
+ * templates by delegating to a shared {@link FreemarkerMixin} instance &mdash; the same
  * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicFreemarkerResource} mixin pins its op at {@code /freemarker/*} for
+ * Whereas the {@link FreemarkerMixin} mixin pins its op at {@code /freemarker/*} for
  * composition into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/freemarker/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * {@link ViewServlet}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFreemarkerResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link FreemarkerMixin}
+ * 	<li class='jc'>{@link ViewServlet}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
@@ -47,15 +47,15 @@ import org.apache.juneau.rest.view.*;
 	paths="/freemarker/*",
 	responseProcessors={FreemarkerViewRenderer.class}
 )
-public class BasicFreemarkerServlet extends BasicViewServlet {
+public class FreemarkerServlet extends ViewServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicFreemarkerResource delegate;
+	private final transient FreemarkerMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicFreemarkerResource} delegate. */
-	public BasicFreemarkerServlet() {
-		this(BasicFreemarkerResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link FreemarkerMixin} delegate. */
+	public FreemarkerServlet() {
+		this(FreemarkerMixin.create().build());
 	}
 
 	/**
@@ -64,11 +64,11 @@ public class BasicFreemarkerServlet extends BasicViewServlet {
 	 * @param delegate The shared FreeMarker renderer/mixin this servlet delegates raw dispatch to.
 	 * 	Must not be {@code null}.
 	 */
-	protected BasicFreemarkerServlet(BasicFreemarkerResource delegate) {
+	protected FreemarkerServlet(FreemarkerMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewServlet */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerView.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerView.java
index 588edefa58..5b5dae0961 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerView.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerView.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.view.*;
  * render an Apache FreeMarker template.
  *
  * <p>
- * Companion to {@link BasicFreemarkerResource} and {@link FreemarkerViewRenderer}: the mixin sets
+ * Companion to {@link FreemarkerMixin} and {@link FreemarkerViewRenderer}: the mixin sets
  * up the {@code /freemarker/*} mount and registers the renderer; the renderer detects
  * {@code FreemarkerView} returns in the response-processor chain and asks the configured
  * {@code freemarker.template.Configuration} to {@code getTemplate(templateName)} and then
@@ -62,7 +62,7 @@ import org.apache.juneau.rest.view.*;
  * <h5 class='section'>Template suffix:</h5>
  *
  * <p>
- * The {@link BasicFreemarkerResource.Builder#templateSuffix(String) templateSuffix(...)} builder
+ * The {@link FreemarkerMixin.Builder#templateSuffix(String) templateSuffix(...)} builder
  * knob lets the bridge append a suffix (e.g. {@code ".ftlh"}) to template names that don't
  * already end with it, so callers can write {@code FreemarkerView.of("hello")} and have the
  * bridge resolve it as {@code "hello.ftlh"} on the classpath (idempotent &mdash; explicit suffix
@@ -77,7 +77,7 @@ import org.apache.juneau.rest.view.*;
  * singleton built once and returned from many handlers).
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFreemarkerResource}
+ * 	<li class='jc'>{@link FreemarkerMixin}
  * 	<li class='jc'>{@link FreemarkerViewRenderer}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a>
  * </ul>
@@ -100,7 +100,7 @@ public final class FreemarkerView implements View {
 	 * {@code "hello.ftlh"} resolves to {@code /templates/hello.ftlh} on the classpath.
 	 *
 	 * @param templateName The template name (configuration-relative, suffix optional when the
-	 * 	mixin's {@link BasicFreemarkerResource.Builder#templateSuffix(String) templateSuffix} is
+	 * 	mixin's {@link FreemarkerMixin.Builder#templateSuffix(String) templateSuffix} is
 	 * 	configured). Must not be {@code null} or blank.
 	 * @return A new {@code FreemarkerView} instance.
 	 * @throws IllegalArgumentException If {@code templateName} is {@code null} or blank.
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerViewRenderer.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerViewRenderer.java
index af2f21f02a..6bfd52b5c1 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerViewRenderer.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/FreemarkerViewRenderer.java
@@ -30,7 +30,7 @@ import org.apache.juneau.rest.view.*;
  * configured {@link Configuration} to render them directly onto the response writer.
  *
  * <p>
- * Auto-registered by {@link BasicFreemarkerResource} via
+ * Auto-registered by {@link FreemarkerMixin} via
  * {@link org.apache.juneau.rest.annotation.Rest#responseProcessors() @Rest(responseProcessors=...)}
  * &mdash; callers who add the mixin don't need to wire up this class explicitly. Callers who want
  * to handle {@code FreemarkerView} returns <i>without</i> adopting the mixin can add this class
@@ -42,7 +42,7 @@ import org.apache.juneau.rest.view.*;
  * <ol class='spaced-list'>
  * 	<li>Inspect the response content. If the value is not a {@link FreemarkerView}, return
  * 		{@link ResponseProcessor#NEXT NEXT} so the rest of the chain runs.
- * 	<li>Read the active {@link BasicFreemarkerResource} from the {@code RestContext} bean store
+ * 	<li>Read the active {@link FreemarkerMixin} from the {@code RestContext} bean store
  * 		to discover the {@link Configuration} (lazy default if no configuration bean is
  * 		registered) and the optional template-suffix knob.
  * 	<li>Apply every entry from {@link FreemarkerView#getResponseHeaders()} via
@@ -59,7 +59,7 @@ import org.apache.juneau.rest.view.*;
  * </ol>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFreemarkerResource}
+ * 	<li class='jc'>{@link FreemarkerMixin}
  * 	<li class='jc'>{@link FreemarkerView}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/FreemarkerViewSupport">FreeMarker View Support</a>
@@ -100,11 +100,11 @@ public class FreemarkerViewRenderer implements ViewRenderer {
 			return NEXT;
 
 		// Resolve the bridge resource (carries Configuration + cached default + templateSuffix).
-		// Fall back to a fresh BasicFreemarkerResource when the renderer is used standalone
+		// Fall back to a fresh FreemarkerMixin when the renderer is used standalone
 		// without the mixin.
 		var bridge = req.getContext().getBeanStore()
-			.getBean(BasicFreemarkerResource.class)
-			.orElseGet(BasicFreemarkerResource::new);
+			.getBean(FreemarkerMixin.class)
+			.orElseGet(FreemarkerMixin::new);
 
 		// Apply caller-supplied response headers first so a caller-provided Content-Type wins
 		// over the bridge's default below.
diff --git a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/package-info.java b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/package-info.java
index 3389612aff..b8bc74d27f 100644
--- a/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/package-info.java
+++ b/juneau-rest/juneau-rest-server-view-freemarker/src/main/java/org/apache/juneau/rest/view/freemarker/package-info.java
@@ -23,7 +23,7 @@
  * <a href="https://freemarker.apache.org/">Apache FreeMarker</a> templates:
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.view.freemarker.BasicFreemarkerResource} &mdash;
+ * 	<li class='jc'>{@link org.apache.juneau.rest.view.freemarker.FreemarkerMixin} &mdash;
  * 		mixin that renders raw FreeMarker templates from the importer's classpath under
  * 		{@code /freemarker/*} and auto-registers
  * 		{@link org.apache.juneau.rest.view.freemarker.FreemarkerViewRenderer}.
@@ -42,11 +42,11 @@
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicFreemarkerResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=FreemarkerMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicFreemarkerResource freemarker() {
- * 			<jk>return</jk> BasicFreemarkerResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> FreemarkerMixin freemarker() {
+ * 			<jk>return</jk> FreemarkerMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.templateSuffix(<js>".ftlh"</js>)
  * 				.build();
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspMixin.java
similarity index 90%
rename from juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
rename to juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspMixin.java
index 6bf88ba0ec..39b5620a4d 100644
--- a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspResource.java
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspMixin.java
@@ -31,7 +31,7 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicJspResource.class)}; the host then:
+ * {@link Rest#mixins() @Rest(mixins=JspMixin.class)}; the host then:
  *
  * <ol class='spaced-list'>
  * 	<li>Gains a default mount at {@code /jsp/*} that serves raw {@code .jsp} resources from the
@@ -46,11 +46,11 @@ import org.apache.juneau.rest.view.*;
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicJspResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=JspMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicJspResource jsp() {
- * 			<jk>return</jk> BasicJspResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> JspMixin jsp() {
+ * 			<jk>return</jk> JspMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/WEB-INF/views/"</js>)
  * 				.build();
  * 		}
@@ -117,9 +117,9 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * A host with {@code /views/} JSPs and {@code /admin/views/} JSPs registers two
- * {@code BasicJspResource} beans, each mounted via a subclass declaring its own op-level
+ * {@code JspMixin} beans, each mounted via a subclass declaring its own op-level
  * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
- * {@code BasicJspResource_MultiBasePath_Test} test in {@code juneau-utest} for the canonical
+ * {@code JspMixin_MultiBasePath_Test} test in {@code juneau-utest} for the canonical
  * pattern.
  *
  * <h5 class='section'>OpenAPI surface:</h5>
@@ -141,7 +141,7 @@ import org.apache.juneau.rest.view.*;
 @Rest(
 	responseProcessors={JspViewRenderer.class}
 )
-public class BasicJspResource implements RawTemplateDispatcher {
+public class JspMixin implements RawTemplateDispatcher {
 
 	/** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
 	public static final String DEFAULT_BASE_PATH = "/";
@@ -162,9 +162,9 @@ public class BasicJspResource implements RawTemplateDispatcher {
 	 *
 	 * <p>
 	 * The mixin walk falls back to this constructor when the {@link org.apache.juneau.commons.inject.BeanStore
-	 * BeanStore} does not have a registered {@code BasicJspResource} bean.
+	 * BeanStore} does not have a registered {@code JspMixin} bean.
 	 */
-	public BasicJspResource() {
+	public JspMixin() {
 		this(create());
 	}
 
@@ -173,7 +173,7 @@ public class BasicJspResource implements RawTemplateDispatcher {
 	 *
 	 * @param builder The builder. Must not be {@code null}.
 	 */
-	protected BasicJspResource(Builder builder) {
+	protected JspMixin(Builder builder) {
 		basePath = builder.basePath;
 	}
 
@@ -247,25 +247,25 @@ public class BasicJspResource implements RawTemplateDispatcher {
 	}
 
 	/**
-	 * Builder for {@link BasicJspResource}.
+	 * Builder for {@link JspMixin}.
 	 */
 	public static class Builder {
 
 		private String basePath = DEFAULT_BASE_PATH;
 
-		/** Constructor &mdash; package access for {@link BasicJspResource#create()}. */
+		/** Constructor &mdash; package access for {@link JspMixin#create()}. */
 		protected Builder() {}
 
 		/**
 		 * Sets the classpath / webapp base path under which {@code .jsp} resources are resolved.
 		 *
 		 * <p>
-		 * Defaults to {@link BasicJspResource#DEFAULT_BASE_PATH "/"}. A typical configured value
+		 * Defaults to {@link JspMixin#DEFAULT_BASE_PATH "/"}. A typical configured value
 		 * is {@code "/WEB-INF/views/"} &mdash; the Servlet-spec convention for hiding template
 		 * files from direct HTTP access.
 		 *
 		 * @param value The base path. {@code null} or blank values reset to the default
-		 * 	{@link BasicJspResource#DEFAULT_BASE_PATH}.
+		 * 	{@link JspMixin#DEFAULT_BASE_PATH}.
 		 * @return This object.
 		 */
 		public Builder basePath(String value) {
@@ -283,14 +283,14 @@ public class BasicJspResource implements RawTemplateDispatcher {
 		}
 
 		/**
-		 * Builds the {@link BasicJspResource}.
+		 * Builds the {@link JspMixin}.
 		 *
-		 * @return A new {@link BasicJspResource} instance.
+		 * @return A new {@link JspMixin} instance.
 		 */
-		public BasicJspResource build() {
+		public JspMixin build() {
 			if (basePath == null)
 				throw illegalArg("basePath must not be null");
-			return new BasicJspResource(this);
+			return new JspMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspResource.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspResource.java
new file mode 100644
index 0000000000..8e5b08fed2
--- /dev/null
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspResource.java
@@ -0,0 +1,68 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.view.jsp;
+
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.view.*;
+
+/**
+ * Child-resource flavor of the {@link JspMixin} mixin.
+ *
+ * <p>
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=JspResource.class)} under a
+ * parent at the subtree {@code /jsp} and serves raw {@code .jsp} resources by delegating to a shared
+ * {@link JspMixin} instance &mdash; the same {@link RawTemplateDispatcher} implementation the mixin and
+ * {@link JspServlet servlet} flavors use, so the three forms cannot drift.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link JspMixin}
+ * 	<li class='jc'>{@link JspServlet}
+ * 	<li class='jc'>{@link ViewResource}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JspViewSupport">JSP View Support</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest(
+	path="/jsp",
+	responseProcessors={JspViewRenderer.class}
+)
+public class JspResource extends ViewResource {
+
+	private final transient JspMixin delegate;
+
+	/** No-arg constructor &mdash; uses a default {@link JspMixin} delegate. */
+	public JspResource() {
+		this(JspMixin.create().build());
+	}
+
+	/**
+	 * Delegate constructor.
+	 *
+	 * @param delegate The shared JSP renderer/mixin this child delegates raw dispatch to. Must not be
+	 * 	{@code null}.
+	 */
+	protected JspResource(JspMixin delegate) {
+		this.delegate = delegate;
+	}
+
+	@Override /* ViewResource */
+	protected RawTemplateDispatcher dispatcher() {
+		return delegate;
+	}
+}
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspServlet.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspServlet.java
similarity index 72%
rename from juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspServlet.java
rename to juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspServlet.java
index c0b0997d77..d4974e23bb 100644
--- a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/BasicJspServlet.java
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspServlet.java
@@ -20,37 +20,37 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicJspResource} mixin.
+ * Standalone servlet companion of the {@link JspMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /jsp/*} (a sibling of the host
  * application's other servlets, e.g. {@code /rest/*}) and serves raw {@code .jsp} resources by
- * delegating to a shared {@link BasicJspResource} instance &mdash; the same
+ * delegating to a shared {@link JspMixin} instance &mdash; the same
  * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicJspResource} mixin pins its op at {@code /jsp/*} for composition into a
+ * Whereas the {@link JspMixin} mixin pins its op at {@code /jsp/*} for composition into a
  * host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/jsp/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet} &mdash; eliminating the path-doubling that a mixin-shaped op would
+ * {@link ViewServlet} &mdash; eliminating the path-doubling that a mixin-shaped op would
  * produce when the servlet itself is mounted at {@code /jsp/*}.
  *
  * <h5 class='figure'>Microservice registration:</h5>
  *
  * <p class='bjava'>
  * 	<ja>@Bean</ja> Servlet jsp() {
- * 		<jk>return new</jk> BasicJspServlet();  <jc>// auto-mounted at /jsp/* by JettyServerComponent</jc>
+ * 		<jk>return new</jk> JspServlet();  <jc>// auto-mounted at /jsp/* by JettyServerComponent</jc>
  * 	}
  * </p>
  *
  * <p>
  * The default mount {@code /jsp/*} can be relocated by overriding {@link #getPaths()} or via the
  * programmatic {@code RestContext.Builder.paths(...)} rung; the underlying base path for resolving
- * {@code .jsp} files is configured on the {@link BasicJspResource} delegate.
+ * {@code .jsp} files is configured on the {@link JspMixin} delegate.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicJspResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link JspMixin}
+ * 	<li class='jc'>{@link ViewServlet}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JspViewSupport">JSP View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
@@ -62,15 +62,15 @@ import org.apache.juneau.rest.view.*;
 	paths="/jsp/*",
 	responseProcessors={JspViewRenderer.class}
 )
-public class BasicJspServlet extends BasicViewServlet {
+public class JspServlet extends ViewServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicJspResource delegate;
+	private final transient JspMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicJspResource} delegate. */
-	public BasicJspServlet() {
-		this(BasicJspResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link JspMixin} delegate. */
+	public JspServlet() {
+		this(JspMixin.create().build());
 	}
 
 	/**
@@ -79,11 +79,11 @@ public class BasicJspServlet extends BasicViewServlet {
 	 * @param delegate The shared JSP renderer/mixin this servlet delegates raw dispatch to. Must
 	 * 	not be {@code null}.
 	 */
-	protected BasicJspServlet(BasicJspResource delegate) {
+	protected JspServlet(JspMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewServlet */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspView.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspView.java
index 0e0d4811ff..a5118a77a7 100644
--- a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspView.java
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspView.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.view.*;
  * render a JSP template.
  *
  * <p>
- * Companion to {@link BasicJspResource} and {@link JspViewRenderer}: the mixin sets up the
+ * Companion to {@link JspMixin} and {@link JspViewRenderer}: the mixin sets up the
  * {@code /jsp/*} mount and registers the renderer; the renderer detects {@code JspView} returns
  * in the response-processor chain and dispatches via
  * {@link jakarta.servlet.RequestDispatcher#forward(jakarta.servlet.ServletRequest,
@@ -53,7 +53,7 @@ import org.apache.juneau.rest.view.*;
  * once and returned from many handlers).
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicJspResource}
+ * 	<li class='jc'>{@link JspMixin}
  * 	<li class='jc'>{@link JspViewRenderer}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JspViewSupport">JSP View Support</a>
  * </ul>
@@ -70,7 +70,7 @@ public final class JspView implements View {
 	 * Creates a new {@code JspView} carrying the given template name and no attributes.
 	 *
 	 * @param templateName The template name (relative to the
-	 * 	{@link BasicJspResource.Builder#basePath base path}). Must not be {@code null} or blank.
+	 * 	{@link JspMixin.Builder#basePath base path}). Must not be {@code null} or blank.
 	 * @return A new {@code JspView} instance.
 	 * @throws IllegalArgumentException If {@code templateName} is {@code null} or blank.
 	 */
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspViewRenderer.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspViewRenderer.java
index e03090d13d..ef0104f76c 100644
--- a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspViewRenderer.java
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/JspViewRenderer.java
@@ -31,7 +31,7 @@ import org.apache.juneau.rest.view.*;
  * jakarta.servlet.ServletResponse) ServletContext.getRequestDispatcher(...).forward(...)}.
  *
  * <p>
- * Auto-registered by {@link BasicJspResource} via
+ * Auto-registered by {@link JspMixin} via
  * {@link org.apache.juneau.rest.annotation.Rest#responseProcessors() @Rest(responseProcessors=...)}
  * &mdash; callers who add the mixin don't need to wire up this class explicitly. Callers who want
  * to handle {@code JspView} returns <i>without</i> adopting the mixin (e.g. mounting their own
@@ -44,7 +44,7 @@ import org.apache.juneau.rest.view.*;
  * 	<li>Inspect the response content. If the value is not a {@link JspView}, return
  * 		{@link ResponseProcessor#NEXT NEXT} so the rest of the chain runs (the standard POJO
  * 		serializer / plain-text fallback handle non-{@code JspView} returns).
- * 	<li>Read the active {@link BasicJspResource} from the {@code RestContext} bean store to
+ * 	<li>Read the active {@link JspMixin} from the {@code RestContext} bean store to
  * 		discover the configured base path; fall back to {@code "/"} when none is registered (a
  * 		caller using the renderer standalone without the mixin).
  * 	<li>Copy every entry from {@link JspView#getAttributes()} onto the request as a request
@@ -70,7 +70,7 @@ import org.apache.juneau.rest.view.*;
  * accident; document the constraint for any user relying on {@code RestPreCall}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicJspResource}
+ * 	<li class='jc'>{@link JspMixin}
  * 	<li class='jc'>{@link JspView}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JspViewSupport">JSP View Support</a>
@@ -103,11 +103,11 @@ public class JspViewRenderer implements ViewRenderer {
 		if (! (content instanceof JspView view))
 			return NEXT;
 
-		// Resolve the base path. Prefer the registered BasicJspResource bean (which the mixin
+		// Resolve the base path. Prefer the registered JspMixin bean (which the mixin
 		// instantiates), fall back to "/" when the renderer is used standalone.
 		var basePath = req.getContext().getBeanStore()
-			.getBean(BasicJspResource.class)
-			.map(BasicJspResource::getBasePath)
+			.getBean(JspMixin.class)
+			.map(JspMixin::getBasePath)
 			.orElse("/");
 
 		// Copy attributes onto the request so the JSP / JSTL EL can resolve them.
@@ -120,7 +120,7 @@ public class JspViewRenderer implements ViewRenderer {
 		// A path that escapes basePath (e.g. template names assembled from user input that
 		// included ../ segments) is rejected with InternalServerError — template names are
 		// caller-controlled, so an escape attempt indicates a server-side bug, not a request-
-		// side attack. (User-input flowing into JSP rendering is handled by BasicJspResource.render
+		// side attack. (User-input flowing into JSP rendering is handled by JspMixin.render
 		// which catches IAE and surfaces it as Forbidden.)
 		String target;
 		try {
@@ -156,7 +156,7 @@ public class JspViewRenderer implements ViewRenderer {
 	 *
 	 * <p>
 	 * Delegates to {@link FileUtils#resolveVirtualPathSafely(String, String)} — the canonical
-	 * shared implementation reused by {@code BasicJspResource.render(...)} for the
+	 * shared implementation reused by {@code JspMixin.render(...)} for the
 	 * raw-{@code .jsp} dispatch path. Handles every {@code (basePath, template)} combination of
 	 * trailing/leading slashes uniformly so callers can pass {@code basePath("/WEB-INF/views")} or
 	 * {@code basePath("/WEB-INF/views/")} and {@code "hello.jsp"} or {@code "/hello.jsp"} without
@@ -164,7 +164,7 @@ public class JspViewRenderer implements ViewRenderer {
 	 *
 	 * <p>
 	 * A {@code null} or empty {@code basePath} is normalized to {@code "/"} (the
-	 * {@link BasicJspResource#DEFAULT_BASE_PATH default}) before the boundary check runs.
+	 * {@link JspMixin#DEFAULT_BASE_PATH default}) before the boundary check runs.
 	 *
 	 * @param basePath The base path (e.g. {@code "/WEB-INF/views/"}).
 	 * @param template The template name (e.g. {@code "hello.jsp"}).
diff --git a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/package-info.java b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/package-info.java
index e3b9466209..ef14305a77 100644
--- a/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/package-info.java
+++ b/juneau-rest/juneau-rest-server-view-jsp/src/main/java/org/apache/juneau/rest/view/jsp/package-info.java
@@ -21,7 +21,7 @@
  * Provides three sibling pieces that together let a Juneau REST resource render JSP templates:
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.view.jsp.BasicJspResource} &mdash; mixin that
+ * 	<li class='jc'>{@link org.apache.juneau.rest.view.jsp.JspMixin} &mdash; mixin that
  * 		serves raw {@code .jsp} resources from the importer's classpath under
  * 		{@code /jsp/*} and auto-registers {@link org.apache.juneau.rest.view.jsp.JspViewRenderer}.
  * 	<li class='jc'>{@link org.apache.juneau.rest.view.jsp.JspView} &mdash; immutable value class
@@ -37,11 +37,11 @@
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicJspResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=JspMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicJspResource jsp() {
- * 			<jk>return</jk> BasicJspResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> JspMixin jsp() {
+ * 			<jk>return</jk> JspMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/WEB-INF/views/"</js>)
  * 				.build();
  * 		}
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheResource.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheMixin.java
similarity index 93%
rename from juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheResource.java
rename to juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheMixin.java
index 1751960503..39125a11d7 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheResource.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheMixin.java
@@ -35,7 +35,7 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicMustacheResource.class)}; the host then:
+ * {@link Rest#mixins() @Rest(mixins=MustacheMixin.class)}; the host then:
  *
  * <ol class='spaced-list'>
  * 	<li>Gains a default mount at {@code /mustache/*} that serves raw Mustache templates from the
@@ -51,11 +51,11 @@ import org.apache.juneau.rest.view.*;
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicMustacheResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=MustacheMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicMustacheResource mustache() {
- * 			<jk>return</jk> BasicMustacheResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> MustacheMixin mustache() {
+ * 			<jk>return</jk> MustacheMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.templateSuffix(<js>".mustache"</js>)
  * 				.build();
@@ -131,9 +131,9 @@ import org.apache.juneau.rest.view.*;
  *
  * <p>
  * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two
- * {@code BasicMustacheResource} beans, each mounted via a subclass declaring its own op-level
+ * {@code MustacheMixin} beans, each mounted via a subclass declaring its own op-level
  * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
- * {@code BasicMustacheResource_MockRest_Test} test in {@code juneau-utest} for the canonical
+ * {@code MustacheMixin_MockRest_Test} test in {@code juneau-utest} for the canonical
  * pattern.
  *
  * <h5 class='section'>OpenAPI surface:</h5>
@@ -155,7 +155,7 @@ import org.apache.juneau.rest.view.*;
 @Rest(
 	responseProcessors={MustacheViewRenderer.class}
 )
-public class BasicMustacheResource implements RawTemplateDispatcher {
+public class MustacheMixin implements RawTemplateDispatcher {
 
 	/** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
 	public static final String DEFAULT_BASE_PATH = "/";
@@ -187,9 +187,9 @@ public class BasicMustacheResource implements RawTemplateDispatcher {
 	 * <p>
 	 * The mixin walk falls back to this constructor when the
 	 * {@link org.apache.juneau.commons.inject.BeanStore BeanStore} does not have a registered
-	 * {@code BasicMustacheResource} bean.
+	 * {@code MustacheMixin} bean.
 	 */
-	public BasicMustacheResource() {
+	public MustacheMixin() {
 		this(create());
 	}
 
@@ -198,7 +198,7 @@ public class BasicMustacheResource implements RawTemplateDispatcher {
 	 *
 	 * @param builder The builder. Must not be {@code null}.
 	 */
-	protected BasicMustacheResource(Builder builder) {
+	protected MustacheMixin(Builder builder) {
 		basePath = builder.basePath;
 		templateSuffix = builder.templateSuffix;
 	}
@@ -428,26 +428,26 @@ public class BasicMustacheResource implements RawTemplateDispatcher {
 	}
 
 	/**
-	 * Builder for {@link BasicMustacheResource}.
+	 * Builder for {@link MustacheMixin}.
 	 */
 	public static class Builder {
 
 		private String basePath = DEFAULT_BASE_PATH;
 		private String templateSuffix = DEFAULT_TEMPLATE_SUFFIX;
 
-		/** Constructor &mdash; package access for {@link BasicMustacheResource#create()}. */
+		/** Constructor &mdash; package access for {@link MustacheMixin#create()}. */
 		protected Builder() {}
 
 		/**
 		 * Sets the classpath base path under which template resources are resolved.
 		 *
 		 * <p>
-		 * Defaults to {@link BasicMustacheResource#DEFAULT_BASE_PATH "/"}. A typical configured
+		 * Defaults to {@link MustacheMixin#DEFAULT_BASE_PATH "/"}. A typical configured
 		 * value is {@code "/templates/"} &mdash; the same Spring-Boot-compatible layout
 		 * convention the sibling JSP / Thymeleaf bridges use.
 		 *
 		 * @param value The base path. {@code null} or blank values reset to the default
-		 * 	{@link BasicMustacheResource#DEFAULT_BASE_PATH}.
+		 * 	{@link MustacheMixin#DEFAULT_BASE_PATH}.
 		 * @return This object.
 		 */
 		public Builder basePath(String value) {
@@ -459,12 +459,12 @@ public class BasicMustacheResource implements RawTemplateDispatcher {
 		 * Sets the suffix to append to template names that don't already end with it (idempotent).
 		 *
 		 * <p>
-		 * Defaults to {@link BasicMustacheResource#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
+		 * Defaults to {@link MustacheMixin#DEFAULT_TEMPLATE_SUFFIX ""} (no implicit
 		 * suffix &mdash; literal template names). Typical configured value is
 		 * {@code ".mustache"}, the mustache.java convention.
 		 *
 		 * @param value The new suffix. {@code null} resets to
-		 * 	{@link BasicMustacheResource#DEFAULT_TEMPLATE_SUFFIX}.
+		 * 	{@link MustacheMixin#DEFAULT_TEMPLATE_SUFFIX}.
 		 * @return This object.
 		 */
 		public Builder templateSuffix(String value) {
@@ -491,14 +491,14 @@ public class BasicMustacheResource implements RawTemplateDispatcher {
 		}
 
 		/**
-		 * Builds the {@link BasicMustacheResource}.
+		 * Builds the {@link MustacheMixin}.
 		 *
-		 * @return A new {@link BasicMustacheResource} instance.
+		 * @return A new {@link MustacheMixin} instance.
 		 */
-		public BasicMustacheResource build() {
+		public MustacheMixin build() {
 			if (basePath == null)
 				throw illegalArg("basePath must not be null");
-			return new BasicMustacheResource(this);
+			return new MustacheMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheResource.java
similarity index 52%
copy from juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java
copy to juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheResource.java
index 30fb29ea55..9e79a47375 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheResource.java
@@ -20,55 +20,48 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicMustacheResource} mixin.
+ * Child-resource flavor of the {@link MustacheMixin} mixin.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /mustache/*} and renders raw Mustache
- * templates by delegating to a shared {@link BasicMustacheResource} instance &mdash; the same
- * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
- *
- * <p>
- * Whereas the {@link BasicMustacheResource} mixin pins its op at {@code /mustache/*} for composition
- * into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/mustache/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=MustacheResource.class)}
+ * under a parent at the subtree {@code /mustache} and renders raw Mustache templates by delegating to a
+ * shared {@link MustacheMixin} instance &mdash; the same {@link RawTemplateDispatcher} implementation the
+ * mixin and {@link MustacheServlet servlet} flavors use, so the three forms cannot drift.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicMustacheResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link MustacheMixin}
+ * 	<li class='jc'>{@link MustacheServlet}
+ * 	<li class='jc'>{@link ViewResource}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MustacheViewSupport">Mustache View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 @Rest(
-	paths="/mustache/*",
+	path="/mustache",
 	responseProcessors={MustacheViewRenderer.class}
 )
-public class BasicMustacheServlet extends BasicViewServlet {
-
-	private static final long serialVersionUID = 1L;
+public class MustacheResource extends ViewResource {
 
-	private final transient BasicMustacheResource delegate;
+	private final transient MustacheMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicMustacheResource} delegate. */
-	public BasicMustacheServlet() {
-		this(BasicMustacheResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link MustacheMixin} delegate. */
+	public MustacheResource() {
+		this(MustacheMixin.create().build());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared Mustache renderer/mixin this servlet delegates raw dispatch to.
-	 * 	Must not be {@code null}.
+	 * @param delegate The shared Mustache renderer/mixin this child delegates raw dispatch to. Must
+	 * 	not be {@code null}.
 	 */
-	protected BasicMustacheServlet(BasicMustacheResource delegate) {
+	protected MustacheResource(MustacheMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewResource */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheServlet.java
similarity index 72%
rename from juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java
rename to juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheServlet.java
index 30fb29ea55..858888c112 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/BasicMustacheServlet.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheServlet.java
@@ -20,22 +20,22 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicMustacheResource} mixin.
+ * Standalone servlet companion of the {@link MustacheMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /mustache/*} and renders raw Mustache
- * templates by delegating to a shared {@link BasicMustacheResource} instance &mdash; the same
+ * templates by delegating to a shared {@link MustacheMixin} instance &mdash; the same
  * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicMustacheResource} mixin pins its op at {@code /mustache/*} for composition
+ * Whereas the {@link MustacheMixin} mixin pins its op at {@code /mustache/*} for composition
  * into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/mustache/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * {@link ViewServlet}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicMustacheResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link MustacheMixin}
+ * 	<li class='jc'>{@link ViewServlet}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MustacheViewSupport">Mustache View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
@@ -47,15 +47,15 @@ import org.apache.juneau.rest.view.*;
 	paths="/mustache/*",
 	responseProcessors={MustacheViewRenderer.class}
 )
-public class BasicMustacheServlet extends BasicViewServlet {
+public class MustacheServlet extends ViewServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicMustacheResource delegate;
+	private final transient MustacheMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicMustacheResource} delegate. */
-	public BasicMustacheServlet() {
-		this(BasicMustacheResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link MustacheMixin} delegate. */
+	public MustacheServlet() {
+		this(MustacheMixin.create().build());
 	}
 
 	/**
@@ -64,11 +64,11 @@ public class BasicMustacheServlet extends BasicViewServlet {
 	 * @param delegate The shared Mustache renderer/mixin this servlet delegates raw dispatch to.
 	 * 	Must not be {@code null}.
 	 */
-	protected BasicMustacheServlet(BasicMustacheResource delegate) {
+	protected MustacheServlet(MustacheMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewServlet */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheView.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheView.java
index 4a431d5e9f..350d007310 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheView.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheView.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.view.*;
  * render a Mustache template.
  *
  * <p>
- * Companion to {@link BasicMustacheResource} and {@link MustacheViewRenderer}: the mixin sets up
+ * Companion to {@link MustacheMixin} and {@link MustacheViewRenderer}: the mixin sets up
  * the {@code /mustache/*} mount and registers the renderer; the renderer detects
  * {@code MustacheView} returns in the response-processor chain and asks the configured
  * {@code com.github.mustachejava.MustacheFactory} to {@code compile(templateName)} and then
@@ -57,7 +57,7 @@ import org.apache.juneau.rest.view.*;
  * Unlike the Thymeleaf bridge (which always appends {@code .html} via the engine resolver's
  * suffix setting), mustache.java does not have a built-in resolver-suffix concept &mdash; the
  * literal template name is what {@code factory.compile(...)} sees. The
- * {@link BasicMustacheResource.Builder#templateSuffix(String) templateSuffix(...)} builder knob
+ * {@link MustacheMixin.Builder#templateSuffix(String) templateSuffix(...)} builder knob
  * fills the gap: when set, the bridge appends the suffix to template names that don't already
  * end with it (idempotent), so the caller can write {@code MustacheView.of("hello")} and the
  * bridge resolves it as {@code "hello.mustache"} on the classpath.
@@ -71,7 +71,7 @@ import org.apache.juneau.rest.view.*;
  * singleton built once and returned from many handlers).
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicMustacheResource}
+ * 	<li class='jc'>{@link MustacheMixin}
  * 	<li class='jc'>{@link MustacheViewRenderer}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MustacheViewSupport">Mustache View Support</a>
  * </ul>
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheViewRenderer.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheViewRenderer.java
index bbe8ea1011..50946589a7 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheViewRenderer.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/MustacheViewRenderer.java
@@ -30,7 +30,7 @@ import org.apache.juneau.rest.view.*;
  * configured {@link MustacheFactory} to render them directly onto the response writer.
  *
  * <p>
- * Auto-registered by {@link BasicMustacheResource} via
+ * Auto-registered by {@link MustacheMixin} via
  * {@link org.apache.juneau.rest.annotation.Rest#responseProcessors() @Rest(responseProcessors=...)}
  * &mdash; callers who add the mixin don't need to wire up this class explicitly. Callers who want
  * to handle {@code MustacheView} returns <i>without</i> adopting the mixin can add this class to
@@ -42,7 +42,7 @@ import org.apache.juneau.rest.view.*;
  * <ol class='spaced-list'>
  * 	<li>Inspect the response content. If the value is not a {@link MustacheView}, return
  * 		{@link ResponseProcessor#NEXT NEXT} so the rest of the chain runs.
- * 	<li>Read the active {@link BasicMustacheResource} from the {@code RestContext} bean store to
+ * 	<li>Read the active {@link MustacheMixin} from the {@code RestContext} bean store to
  * 		discover the {@link MustacheFactory} (lazy default if no factory bean is registered) and
  * 		the optional template-suffix knob.
  * 	<li>Apply every entry from {@link MustacheView#getResponseHeaders()} via
@@ -59,7 +59,7 @@ import org.apache.juneau.rest.view.*;
  * </ol>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicMustacheResource}
+ * 	<li class='jc'>{@link MustacheMixin}
  * 	<li class='jc'>{@link MustacheView}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/MustacheViewSupport">Mustache View Support</a>
@@ -99,11 +99,11 @@ public class MustacheViewRenderer implements ViewRenderer {
 			return NEXT;
 
 		// Resolve the bridge resource (carries MustacheFactory + cached default + templateSuffix).
-		// Fall back to a fresh BasicMustacheResource when the renderer is used standalone without
+		// Fall back to a fresh MustacheMixin when the renderer is used standalone without
 		// the mixin.
 		var bridge = req.getContext().getBeanStore()
-			.getBean(BasicMustacheResource.class)
-			.orElseGet(BasicMustacheResource::new);
+			.getBean(MustacheMixin.class)
+			.orElseGet(MustacheMixin::new);
 
 		// Apply caller-supplied response headers first so a caller-provided Content-Type wins
 		// over the bridge's default below.
diff --git a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/package-info.java b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/package-info.java
index 858131def7..bbddfbe6ac 100644
--- a/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/package-info.java
+++ b/juneau-rest/juneau-rest-server-view-mustache/src/main/java/org/apache/juneau/rest/view/mustache/package-info.java
@@ -22,7 +22,7 @@
  * templates via <a href="https://github.com/spullara/mustache.java">mustache.java</a>:
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.view.mustache.BasicMustacheResource} &mdash;
+ * 	<li class='jc'>{@link org.apache.juneau.rest.view.mustache.MustacheMixin} &mdash;
  * 		mixin that renders raw Mustache templates from the importer's classpath under
  * 		{@code /mustache/*} and auto-registers
  * 		{@link org.apache.juneau.rest.view.mustache.MustacheViewRenderer}.
@@ -40,11 +40,11 @@
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicMustacheResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=MustacheMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicMustacheResource mustache() {
- * 			<jk>return</jk> BasicMustacheResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> MustacheMixin mustache() {
+ * 			<jk>return</jk> MustacheMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.templateSuffix(<js>".mustache"</js>)
  * 				.build();
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafResource.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafMixin.java
similarity index 93%
rename from juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafResource.java
rename to juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafMixin.java
index 2d88060a08..4302b2df3a 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafResource.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafMixin.java
@@ -35,7 +35,7 @@ import org.thymeleaf.templateresolver.*;
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicThymeleafResource.class)}; the host then:
+ * {@link Rest#mixins() @Rest(mixins=ThymeleafMixin.class)}; the host then:
  *
  * <ol class='spaced-list'>
  * 	<li>Gains a default mount at {@code /thymeleaf/*} that serves raw {@code .html} templates from
@@ -51,11 +51,11 @@ import org.thymeleaf.templateresolver.*;
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicThymeleafResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=ThymeleafMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicThymeleafResource thymeleaf() {
- * 			<jk>return</jk> BasicThymeleafResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> ThymeleafMixin thymeleaf() {
+ * 			<jk>return</jk> ThymeleafMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.build();
  * 		}
@@ -118,9 +118,9 @@ import org.thymeleaf.templateresolver.*;
  *
  * <p>
  * A host with {@code /templates/} and {@code /admin/templates/} template trees registers two
- * {@code BasicThymeleafResource} beans, each mounted via a subclass declaring its own op-level
+ * {@code ThymeleafMixin} beans, each mounted via a subclass declaring its own op-level
  * {@code @RestGet(path=...)} override and its own {@code basePath}. See the
- * {@code BasicThymeleafResource_MockRest_Test} test in {@code juneau-utest} for the canonical
+ * {@code ThymeleafMixin_MockRest_Test} test in {@code juneau-utest} for the canonical
  * pattern.
  *
  * <h5 class='section'>OpenAPI surface:</h5>
@@ -142,7 +142,7 @@ import org.thymeleaf.templateresolver.*;
 @Rest(
 	responseProcessors={ThymeleafViewRenderer.class}
 )
-public class BasicThymeleafResource implements RawTemplateDispatcher {
+public class ThymeleafMixin implements RawTemplateDispatcher {
 
 	/** Default base path applied when no {@link Builder#basePath(String)} call has been made. */
 	public static final String DEFAULT_BASE_PATH = "/";
@@ -178,9 +178,9 @@ public class BasicThymeleafResource implements RawTemplateDispatcher {
 	 *
 	 * <p>
 	 * The mixin walk falls back to this constructor when the {@link org.apache.juneau.commons.inject.BeanStore
-	 * BeanStore} does not have a registered {@code BasicThymeleafResource} bean.
+	 * BeanStore} does not have a registered {@code ThymeleafMixin} bean.
 	 */
-	public BasicThymeleafResource() {
+	public ThymeleafMixin() {
 		this(create());
 	}
 
@@ -189,7 +189,7 @@ public class BasicThymeleafResource implements RawTemplateDispatcher {
 	 *
 	 * @param builder The builder. Must not be {@code null}.
 	 */
-	protected BasicThymeleafResource(Builder builder) {
+	protected ThymeleafMixin(Builder builder) {
 		basePath = builder.basePath;
 		cacheTemplates = builder.cacheTemplates;
 		templateMode = builder.templateMode;
@@ -391,7 +391,7 @@ public class BasicThymeleafResource implements RawTemplateDispatcher {
 	}
 
 	/**
-	 * Builder for {@link BasicThymeleafResource}.
+	 * Builder for {@link ThymeleafMixin}.
 	 */
 	public static class Builder {
 
@@ -399,18 +399,18 @@ public class BasicThymeleafResource implements RawTemplateDispatcher {
 		private boolean cacheTemplates = DEFAULT_CACHE_TEMPLATES;
 		private TemplateMode templateMode = DEFAULT_TEMPLATE_MODE;
 
-		/** Constructor &mdash; package access for {@link BasicThymeleafResource#create()}. */
+		/** Constructor &mdash; package access for {@link ThymeleafMixin#create()}. */
 		protected Builder() {}
 
 		/**
 		 * Sets the classpath base path under which template resources are resolved.
 		 *
 		 * <p>
-		 * Defaults to {@link BasicThymeleafResource#DEFAULT_BASE_PATH "/"}. A typical configured
+		 * Defaults to {@link ThymeleafMixin#DEFAULT_BASE_PATH "/"}. A typical configured
 		 * value is {@code "/templates/"} &mdash; the Spring-Boot-compatible layout convention.
 		 *
 		 * @param value The base path. {@code null} or blank values reset to the default
-		 * 	{@link BasicThymeleafResource#DEFAULT_BASE_PATH}.
+		 * 	{@link ThymeleafMixin#DEFAULT_BASE_PATH}.
 		 * @return This object.
 		 */
 		public Builder basePath(String value) {
@@ -480,14 +480,14 @@ public class BasicThymeleafResource implements RawTemplateDispatcher {
 		}
 
 		/**
-		 * Builds the {@link BasicThymeleafResource}.
+		 * Builds the {@link ThymeleafMixin}.
 		 *
-		 * @return A new {@link BasicThymeleafResource} instance.
+		 * @return A new {@link ThymeleafMixin} instance.
 		 */
-		public BasicThymeleafResource build() {
+		public ThymeleafMixin build() {
 			if (basePath == null)
 				throw illegalArg("basePath must not be null");
-			return new BasicThymeleafResource(this);
+			return new ThymeleafMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafResource.java
similarity index 52%
copy from juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java
copy to juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafResource.java
index d2c5ee349c..214f575854 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafResource.java
@@ -20,55 +20,48 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicThymeleafResource} mixin.
+ * Child-resource flavor of the {@link ThymeleafMixin} mixin.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /thymeleaf/*} and renders raw Thymeleaf
- * templates by delegating to a shared {@link BasicThymeleafResource} instance &mdash; the same
- * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
- *
- * <p>
- * Whereas the {@link BasicThymeleafResource} mixin pins its op at {@code /thymeleaf/*} for
- * composition into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/thymeleaf/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=ThymeleafResource.class)}
+ * under a parent at the subtree {@code /thymeleaf} and renders raw Thymeleaf templates by delegating to a
+ * shared {@link ThymeleafMixin} instance &mdash; the same {@link RawTemplateDispatcher} implementation the
+ * mixin and {@link ThymeleafServlet servlet} flavors use, so the three forms cannot drift.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicThymeleafResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link ThymeleafMixin}
+ * 	<li class='jc'>{@link ThymeleafServlet}
+ * 	<li class='jc'>{@link ViewResource}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ThymeleafViewSupport">Thymeleaf View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 @Rest(
-	paths="/thymeleaf/*",
+	path="/thymeleaf",
 	responseProcessors={ThymeleafViewRenderer.class}
 )
-public class BasicThymeleafServlet extends BasicViewServlet {
-
-	private static final long serialVersionUID = 1L;
+public class ThymeleafResource extends ViewResource {
 
-	private final transient BasicThymeleafResource delegate;
+	private final transient ThymeleafMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicThymeleafResource} delegate. */
-	public BasicThymeleafServlet() {
-		this(BasicThymeleafResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link ThymeleafMixin} delegate. */
+	public ThymeleafResource() {
+		this(ThymeleafMixin.create().build());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared Thymeleaf renderer/mixin this servlet delegates raw dispatch to.
-	 * 	Must not be {@code null}.
+	 * @param delegate The shared Thymeleaf renderer/mixin this child delegates raw dispatch to. Must
+	 * 	not be {@code null}.
 	 */
-	protected BasicThymeleafServlet(BasicThymeleafResource delegate) {
+	protected ThymeleafResource(ThymeleafMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewResource */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafServlet.java
similarity index 73%
rename from juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java
rename to juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafServlet.java
index d2c5ee349c..7ce236b9d7 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/BasicThymeleafServlet.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafServlet.java
@@ -20,22 +20,22 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.view.*;
 
 /**
- * Standalone servlet companion of the {@link BasicThymeleafResource} mixin.
+ * Standalone servlet companion of the {@link ThymeleafMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /thymeleaf/*} and renders raw Thymeleaf
- * templates by delegating to a shared {@link BasicThymeleafResource} instance &mdash; the same
+ * templates by delegating to a shared {@link ThymeleafMixin} instance &mdash; the same
  * {@link RawTemplateDispatcher} implementation the mixin uses, so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicThymeleafResource} mixin pins its op at {@code /thymeleaf/*} for
+ * Whereas the {@link ThymeleafMixin} mixin pins its op at {@code /thymeleaf/*} for
  * composition into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/thymeleaf/*")}) and inherits the {@code /*} op from
- * {@link BasicViewServlet}.
+ * {@link ViewServlet}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicThymeleafResource}
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link ThymeleafMixin}
+ * 	<li class='jc'>{@link ViewServlet}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ThymeleafViewSupport">Thymeleaf View Support</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
@@ -47,15 +47,15 @@ import org.apache.juneau.rest.view.*;
 	paths="/thymeleaf/*",
 	responseProcessors={ThymeleafViewRenderer.class}
 )
-public class BasicThymeleafServlet extends BasicViewServlet {
+public class ThymeleafServlet extends ViewServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicThymeleafResource delegate;
+	private final transient ThymeleafMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicThymeleafResource} delegate. */
-	public BasicThymeleafServlet() {
-		this(BasicThymeleafResource.create().build());
+	/** No-arg constructor &mdash; uses a default {@link ThymeleafMixin} delegate. */
+	public ThymeleafServlet() {
+		this(ThymeleafMixin.create().build());
 	}
 
 	/**
@@ -64,11 +64,11 @@ public class BasicThymeleafServlet extends BasicViewServlet {
 	 * @param delegate The shared Thymeleaf renderer/mixin this servlet delegates raw dispatch to.
 	 * 	Must not be {@code null}.
 	 */
-	protected BasicThymeleafServlet(BasicThymeleafResource delegate) {
+	protected ThymeleafServlet(ThymeleafMixin delegate) {
 		this.delegate = delegate;
 	}
 
-	@Override /* BasicViewServlet */
+	@Override /* ViewServlet */
 	protected RawTemplateDispatcher dispatcher() {
 		return delegate;
 	}
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafView.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafView.java
index 1270fae79e..62855ddac2 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafView.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafView.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.view.*;
  * render a Thymeleaf template.
  *
  * <p>
- * Companion to {@link BasicThymeleafResource} and {@link ThymeleafViewRenderer}: the mixin sets up
+ * Companion to {@link ThymeleafMixin} and {@link ThymeleafViewRenderer}: the mixin sets up
  * the {@code /thymeleaf/*} mount and registers the renderer; the renderer detects
  * {@code ThymeleafView} returns in the response-processor chain and asks the configured
  * {@code org.thymeleaf.TemplateEngine} to {@code process(templateName, context, writer)} directly
@@ -60,7 +60,7 @@ import org.apache.juneau.rest.view.*;
  * singleton built once and returned from many handlers).
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicThymeleafResource}
+ * 	<li class='jc'>{@link ThymeleafMixin}
  * 	<li class='jc'>{@link ThymeleafViewRenderer}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ThymeleafViewSupport">Thymeleaf View Support</a>
  * </ul>
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafViewRenderer.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafViewRenderer.java
index 79b0467e59..b4ceaa3e44 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafViewRenderer.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/ThymeleafViewRenderer.java
@@ -30,7 +30,7 @@ import org.thymeleaf.context.*;
  * configured {@link TemplateEngine} to render them directly onto the response writer.
  *
  * <p>
- * Auto-registered by {@link BasicThymeleafResource} via
+ * Auto-registered by {@link ThymeleafMixin} via
  * {@link org.apache.juneau.rest.annotation.Rest#responseProcessors() @Rest(responseProcessors=...)}
  * &mdash; callers who add the mixin don't need to wire up this class explicitly. Callers who want
  * to handle {@code ThymeleafView} returns <i>without</i> adopting the mixin can add this class to
@@ -42,7 +42,7 @@ import org.thymeleaf.context.*;
  * <ol class='spaced-list'>
  * 	<li>Inspect the response content. If the value is not a {@link ThymeleafView}, return
  * 		{@link ResponseProcessor#NEXT NEXT} so the rest of the chain runs.
- * 	<li>Read the active {@link BasicThymeleafResource} from the {@code RestContext} bean store to
+ * 	<li>Read the active {@link ThymeleafMixin} from the {@code RestContext} bean store to
  * 		discover the {@link TemplateEngine} (lazy default if no engine bean is registered).
  * 	<li>Build a Thymeleaf {@link Context} populated with the view's attribute map.
  * 	<li>Apply every entry from {@link ThymeleafView#getResponseHeaders()} via
@@ -58,7 +58,7 @@ import org.thymeleaf.context.*;
  * </ol>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicThymeleafResource}
+ * 	<li class='jc'>{@link ThymeleafMixin}
  * 	<li class='jc'>{@link ThymeleafView}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ResponseProcessors">Response Processors</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/ThymeleafViewSupport">Thymeleaf View Support</a>
@@ -96,10 +96,10 @@ public class ThymeleafViewRenderer implements ViewRenderer {
 			return NEXT;
 
 		// Resolve the bridge resource (carries TemplateEngine + cached default). Fall back to a
-		// fresh BasicThymeleafResource when the renderer is used standalone without the mixin.
+		// fresh ThymeleafMixin when the renderer is used standalone without the mixin.
 		var bridge = req.getContext().getBeanStore()
-			.getBean(BasicThymeleafResource.class)
-			.orElseGet(BasicThymeleafResource::new);
+			.getBean(ThymeleafMixin.class)
+			.orElseGet(ThymeleafMixin::new);
 
 		// Apply caller-supplied response headers first so a caller-provided Content-Type wins
 		// over the bridge's default below.
diff --git a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/package-info.java b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/package-info.java
index c0c196a17f..258f97ff41 100644
--- a/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/package-info.java
+++ b/juneau-rest/juneau-rest-server-view-thymeleaf/src/main/java/org/apache/juneau/rest/view/thymeleaf/package-info.java
@@ -22,7 +22,7 @@
  * templates:
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.view.thymeleaf.BasicThymeleafResource} &mdash;
+ * 	<li class='jc'>{@link org.apache.juneau.rest.view.thymeleaf.ThymeleafMixin} &mdash;
  * 		mixin that renders raw {@code .html} templates from the importer's classpath under
  * 		{@code /thymeleaf/*} and auto-registers
  * 		{@link org.apache.juneau.rest.view.thymeleaf.ThymeleafViewRenderer}.
@@ -40,11 +40,11 @@
  * <h5 class='figure'>Composition example (microservice):</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicThymeleafResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=ThymeleafMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
- * 		<ja>@Bean</ja> BasicThymeleafResource thymeleaf() {
- * 			<jk>return</jk> BasicThymeleafResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> ThymeleafMixin thymeleaf() {
+ * 			<jk>return</jk> ThymeleafMixin.<jsm>create</jsm>()
  * 				.basePath(<js>"/templates/"</js>)
  * 				.build();
  * 		}
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 ac854c12c5..3a4bfcf601 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
@@ -516,7 +516,7 @@ public class RestContext extends Context {
 	 * (which is only meaningful inside the {@link RestContext} constructor):
 	 * <ol>
 	 * 	<li>The resource's {@code getPaths()} virtual getter (defined on {@code RestServlet} /
-	 * 		{@code RestObject}). {@code null} return falls through; non-{@code null} (including empty array) wins.
+	 * 		{@code RestResource}). {@code null} return falls through; non-{@code null} (including empty array) wins.
 	 * 	<li>{@link Rest#paths() @Rest(paths)} annotation default on the most-derived class, SVL-resolved per
 	 * 		element and comma-split.  A {@code VarResolver} is looked up on {@code store} (when present); SVL
 	 * 		variables like {@code $C{key}} resolve against beans visible to the bean store.
@@ -542,7 +542,7 @@ public class RestContext extends Context {
 	}
 
 	private static String[] resolvePathsCore(List<AnnotationInfo<Rest>> annotations, Object resource, VarResolver vr, WritableBeanStore beanStore) {
-		// Rung 2: getPaths() virtual getter on the resource (RestServlet / RestObject / any matching class).
+		// Rung 2: getPaths() virtual getter on the resource (RestServlet / RestResource / any matching class).
 		// The getter return is normalized to a flat List<String> of raw leaves (any String / String[] /
 		// Collection / Iterable / Stream / nested mix of those), then each leaf flows through the same
 		// SVL-resolve + comma-split pipeline used for @Rest(paths=...) annotation elements.
@@ -637,7 +637,7 @@ public class RestContext extends Context {
 	/**
 	 * Reflectively invokes a no-arg {@code getPaths()} method on the resource (defined on
 	 * {@link org.apache.juneau.rest.servlet.RestServlet RestServlet} and
-	 * {@link org.apache.juneau.rest.servlet.RestObject RestObject}). Reflection is used to avoid coupling this
+	 * {@link org.apache.juneau.rest.servlet.RestResource RestResource}). Reflection is used to avoid coupling this
 	 * static utility to either base class &mdash; if a custom {@code @Rest}-annotated POJO declares
 	 * {@code public Object getPaths()} (or any covariant return type such as {@code String[]},
 	 * {@code List<String>}, etc.), it participates uniformly.
@@ -3400,7 +3400,7 @@ public class RestContext extends Context {
 	 * 	<li><b>Programmatic</b> &mdash; {@link Builder#paths(String...)} setter (or {@link Args#paths()} record
 	 * 		field). Highest precedence; an empty array (not {@code null}) explicitly clears the mount list.
 	 * 	<li><b>Getter</b> &mdash; the resource's {@code getPaths()} virtual method (defined on
-	 * 		{@code RestServlet} and {@code RestObject}). {@code null} return inherits; non-{@code null} (including
+	 * 		{@code RestServlet} and {@code RestResource}). {@code null} return inherits; non-{@code null} (including
 	 * 		empty array) wins over the annotation default.
 	 * 	<li><b>Annotation default</b> &mdash; {@link Rest#paths()}, walked top-down across the resource-class
 	 * 		hierarchy with the most-derived non-empty value winning. Each element is
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 82350d3519..7ad92a9255 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -135,7 +135,6 @@ import jakarta.servlet.http.*;
  * 		</ul>
  * 		<li>Methods for accessing static files:
  * 		<ul class='javatreec'>
- * 			<li class='jm'>{@link RestRequest#getStaticFiles() getStaticFiles()}
  * 			<li class='jm'>{@link RestRequest#getVarResolverSession() getVarResolverSession()}
  * 		</ul>
  * 		<li>Methods for assertions:
@@ -1395,16 +1394,6 @@ public class RestRequest extends HttpServletRequestWrapper {
 		return cp == null || ! sp.startsWith(cp) ? sp : sp.substring(cp.length());
 	}
 
-	/**
-	 * Returns the static files registered on the REST resource context object.
-	 *
-	 * <p>
-	 * Used to retrieve localized files to be served up as static files through the REST API.
-	 *
-	 * @return This object.
-	 */
-	public StaticFiles getStaticFiles() { return context.getStaticFiles(); }
-
 	/**
 	 * Returns the localized swagger associated with the resource.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 921a408658..a35b6573de 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -448,7 +448,7 @@ public @interface Rest {
 	 * {@code "debugEnablement"}, {@code "debugDefault"}, {@code "partSerializer"}, {@code "partParser"}, and
 	 * {@code "messages"}.  For example, {@code @Rest(noInherit={"guards"})} on a mixin removes the host's guard
 	 * chain from the mixin's endpoints (typical pattern for deliberately-unguarded probes like
-	 * {@code BasicHealthResource}).
+	 * {@code HealthMixin}).
 	 *
 	 * <h5 class='section'>See Also:</h5><ul>
 	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
@@ -1589,7 +1589,7 @@ public @interface Rest {
 	 * Used to retrieve localized files to be served up as static files through the REST API via the following
 	 * predefined methods:
 	 * <ul class='javatree'>
-	 * 	<li class='jm'>{@link BasicRestObject#getHtdoc(String, Locale)}.
+	 * 	<li class='jm'>{@link BasicRestResource#getHtdoc(String, Locale)}.
 	 * 	<li class='jm'>{@link BasicRestServlet#getHtdoc(String, Locale)}.
 	 * </ul>
 	 *
@@ -1597,7 +1597,6 @@ public @interface Rest {
 	 * The static file finder can be accessed through the following methods:
 	 * <ul class='javatree'>
 	 * 	<li class='jm'>{@link RestContext#getStaticFiles()}
-	 * 	<li class='jm'>{@link RestRequest#getStaticFiles()}
 	 * </ul>
 	 *
 	 * <h5 class='section'>Inheritance Rules</h5>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/beans/ChildResourceDescriptions.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/beans/ChildResourceDescriptions.java
index 6202117368..f76484933c 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/beans/ChildResourceDescriptions.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/beans/ChildResourceDescriptions.java
@@ -31,8 +31,7 @@ import org.apache.juneau.rest.servlet.*;
  *
  * <h5 class='section'>See Also:</h5><ul>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/UtilityBeans">Utility Beans</a>
- * 	<li class='jic'>{@link BasicGroupOperations}
-
+ * 	<li class='jc'>{@link org.apache.juneau.rest.servlet.BasicRestServletGroup}
  * </ul>
  *
  * @serial exclude
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJson5Config.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJson5Config.java
index 87b7112159..030436aaf6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJson5Config.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJson5Config.java
@@ -68,7 +68,7 @@ import org.apache.juneau.serializer.annotation.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicJson5Config { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicJson5Config { ... }
  * </p>
  *
  * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonConfig.java
index fe80458596..6d713f8a21 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonConfig.java
@@ -71,7 +71,7 @@ import org.apache.juneau.serializer.annotation.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicJsonConfig { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicJsonConfig { ... }
  * </p>
  *
  * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonHtmlConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonHtmlConfig.java
index e0ca14386b..341c134eda 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonHtmlConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicJsonHtmlConfig.java
@@ -85,7 +85,7 @@ import org.apache.juneau.serializer.annotation.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicJsonHtmlConfig { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicJsonHtmlConfig { ... }
  * </p>
  *
  * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicOpenApiConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicOpenApiConfig.java
index 1bde4e3aa2..36f69040f0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicOpenApiConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicOpenApiConfig.java
@@ -68,7 +68,7 @@ import org.apache.juneau.serializer.annotation.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicOpenApiConfig { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicOpenApiConfig { ... }
  * </p>
  *
  * <p>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
index acf496d595..03d56cfc91 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
@@ -122,7 +122,7 @@ import org.apache.juneau.xml.*;
  * <p class='bjava'>
  * 	<jc>// Used on a child resource.</jc>
  * 	<ja>@Rest</ja>
- * 	<jk>public class</jk> MyResource <jk>extends</jk> RestObject <jk>implements</jk> BasicUniversalConfig { ... }
+ * 	<jk>public class</jk> MyResource <jk>extends</jk> RestResource <jk>implements</jk> BasicUniversalConfig { ... }
  * </p>
  *
  * <p>
@@ -135,8 +135,8 @@ import org.apache.juneau.xml.*;
  * 	<ul class='javatreec'>
  * 		<li class='jc'>{@link BasicRestServlet}
  * 		<li class='jc'>{@link BasicRestServletGroup}
- * 		<li class='jc'>{@link BasicRestObject}
- * 		<li class='jc'>{@link BasicRestObjectGroup}
+ * 		<li class='jc'>{@link BasicRestResource}
+ * 		<li class='jc'>{@link BasicRestResourceGroup}
  *	</ul>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
  * </ul>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicFaviconResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
similarity index 82%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicFaviconResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
index cce1f5e64f..0e4170c371 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicFaviconResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconMixin.java
@@ -30,21 +30,21 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that serves a {@code favicon.ico} icon at {@code /favicon.ico}.
  *
  * <p>
- * Sibling of {@link BasicSeoResource} (robots.txt / sitemap.xml), {@link BasicVersionResource}
- * ({@code /version} / {@code /info} / {@code /about}), and {@link BasicWellKnownResource}
+ * Sibling of {@link SeoMixin} (robots.txt / sitemap.xml), {@link VersionMixin}
+ * ({@code /version} / {@code /info} / {@code /about}), and {@link WellKnownMixin}
  * ({@code /.well-known/*}). All four classes live in the {@code org.apache.juneau.rest.convention}
  * convention-endpoints mixin pack.
  *
  * <p>
- * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=BasicFaviconResource.class)};
+ * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=FaviconMixin.class)};
  * the {@code /favicon.ico} URL becomes available alongside the host's own endpoints with no further
  * wiring.
  *
  * <h5 class='section'>Hardcoded mount path:</h5>
  *
  * <p>
- * Unlike the sibling api-docs and ops mixins (see {@link BasicSwaggerResource},
- * {@link BasicVersionResource}, etc.) the mount path here is <b>not</b> SVL-configurable
+ * Unlike the sibling api-docs and ops mixins (see {@link SwaggerMixin},
+ * {@link VersionMixin}, etc.) the mount path here is <b>not</b> SVL-configurable
  * &mdash; {@code /favicon.ico} is fixed by browser convention and the {@code link rel="icon"}
  * default the HTML spec inherits from <a href="https://html.spec.whatwg.org/multipage/links.html#rel-icon">WHATWG HTML</a>.
  * Browsers fetch {@code /favicon.ico} from the site root regardless of any application-level
@@ -61,14 +61,14 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicFaviconResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=FaviconMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
  * 		<jc>// Use the default Juneau-branded favicon.</jc>
  * 	}
  *
  * 	<jc>// Or override the icon bytes via a @Bean factory:</jc>
- * 	<ja>@Bean BasicFaviconResource favicon()</ja> {
- * 		<jk>return</jk> BasicFaviconResource.<jsm>create</jsm>().bytes(myLogoBytes).build();
+ * 	<ja>@Bean FaviconMixin favicon()</ja> {
+ * 		<jk>return</jk> FaviconMixin.<jsm>create</jsm>().bytes(myLogoBytes).build();
  * 	}
  * </p>
  *
@@ -80,7 +80,7 @@ import org.apache.juneau.rest.annotation.*;
  * 		(30 days &mdash; favicons rarely change and browsers re-fetch frequently when uncached).
  * 	<li>The default icon ({@code juneau-favicon.ico} on the framework classpath) is a
  * 		16&times;16 Juneau-branded ICO; users replace it by registering an alternate
- * 		{@code @Bean BasicFaviconResource} whose builder supplies different bytes.
+ * 		{@code @Bean FaviconMixin} whose builder supplies different bytes.
  * 	<li>The handler is excluded from generated Swagger / OpenAPI specs via
  * 		{@link OpSwagger#ignore() @OpSwagger(ignore=true)} &mdash; favicons are not
  * 		API-meaningful.
@@ -92,15 +92,15 @@ import org.apache.juneau.rest.annotation.*;
  * 	<li>{@link #create() create()} &mdash; entry point for configuring an instance.
  * 	<li>{@link Builder#bytes(byte[]) bytes(byte[])} &mdash; raw favicon bytes.
  * 	<li>{@link Builder#classpath(String) classpath(String)} &mdash; load icon bytes from a
- * 		classpath resource (resolved against the {@code BasicFaviconResource} classloader).
+ * 		classpath resource (resolved against the {@code FaviconMixin} classloader).
  * 	<li>{@link Builder#cacheControl(String) cacheControl(String)} &mdash; override the default
  * 		30-day {@code Cache-Control} header.
  * </ul>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicSeoResource}
- * 	<li class='jc'>{@link BasicVersionResource}
- * 	<li class='jc'>{@link BasicWellKnownResource}
+ * 	<li class='jc'>{@link SeoMixin}
+ * 	<li class='jc'>{@link VersionMixin}
+ * 	<li class='jc'>{@link WellKnownMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -108,7 +108,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicFaviconResource {
+public class FaviconMixin {
 
 	/** Default {@code Cache-Control} header value: {@code max-age=2592000, public} (30 days). */
 	public static final String DEFAULT_CACHE_CONTROL = "max-age=2592000, public";
@@ -117,7 +117,7 @@ public class BasicFaviconResource {
 	private static final String DEFAULT_FAVICON_RESOURCE = "/juneau-favicon.ico";
 
 	/**
-	 * Creates a new builder for configuring a {@link BasicFaviconResource}.
+	 * Creates a new builder for configuring a {@link FaviconMixin}.
 	 *
 	 * @return A new builder.
 	 */
@@ -130,10 +130,10 @@ public class BasicFaviconResource {
 
 	/**
 	 * No-arg constructor &mdash; used when a host registers the mixin without supplying a
-	 * builder-configured {@code @Bean BasicFaviconResource}. Loads the default Juneau-branded
+	 * builder-configured {@code @Bean FaviconMixin}. Loads the default Juneau-branded
 	 * favicon from the classpath.
 	 */
-	public BasicFaviconResource() {
+	public FaviconMixin() {
 		this(create());
 	}
 
@@ -142,7 +142,7 @@ public class BasicFaviconResource {
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicFaviconResource(Builder builder) {
+	protected FaviconMixin(Builder builder) {
 		bytes = builder.resolveBytes();
 		cacheControl = builder.cacheControl;
 	}
@@ -166,7 +166,7 @@ public class BasicFaviconResource {
 	}
 
 	/**
-	 * Builder for {@link BasicFaviconResource} instances.
+	 * Builder for {@link FaviconMixin} instances.
 	 */
 	public static class Builder {
 
@@ -174,7 +174,7 @@ public class BasicFaviconResource {
 		private String classpath;
 		private String cacheControl = DEFAULT_CACHE_CONTROL;
 
-		/** Constructor &mdash; package access for {@link BasicFaviconResource#create()}. */
+		/** Constructor &mdash; package access for {@link FaviconMixin#create()}. */
 		protected Builder() {}
 
 		/**
@@ -198,7 +198,7 @@ public class BasicFaviconResource {
 		 * Sets the classpath resource path from which to load the favicon bytes.
 		 *
 		 * <p>
-		 * Resolved via {@link Class#getResourceAsStream(String) BasicFaviconResource.class.getResourceAsStream(...)}
+		 * Resolved via {@link Class#getResourceAsStream(String) FaviconMixin.class.getResourceAsStream(...)}
 		 * at {@link #build()} time. A resolved-to-{@code null} stream falls back to the framework's
 		 * default favicon. Mutually exclusive with {@link #bytes(byte[])}.
 		 *
@@ -227,12 +227,12 @@ public class BasicFaviconResource {
 		}
 
 		/**
-		 * Builds a {@link BasicFaviconResource} instance.
+		 * Builds a {@link FaviconMixin} instance.
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicFaviconResource build() {
-			return new BasicFaviconResource(this);
+		public FaviconMixin build() {
+			return new FaviconMixin(this);
 		}
 
 		byte[] resolveBytes() {
@@ -249,7 +249,7 @@ public class BasicFaviconResource {
 		}
 
 		private static byte[] readClasspath(String path) {
-			try (var in = BasicFaviconResource.class.getResourceAsStream(path)) {
+			try (var in = FaviconMixin.class.getResourceAsStream(path)) {
 				if (in == null)
 					return null;
 				return in.readAllBytes();
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconResource.java
new file mode 100644
index 0000000000..a20ccc2cac
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconResource.java
@@ -0,0 +1,74 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.convention;
+
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Child-resource flavor of the {@link FaviconMixin} mixin.
+ *
+ * <p>
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=FaviconResource.class)}
+ * under a parent at the subtree {@code /favicon.ico} and serves the configured favicon bytes by
+ * delegating to a shared {@link FaviconMixin} instance &mdash; so the forms cannot drift.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link FaviconMixin}
+ * 	<li class='jc'>{@link FaviconServlet}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+// @formatter:off
+@Rest(path="/favicon.ico")
+public class FaviconResource extends RestResource {
+
+	private final transient FaviconMixin delegate;
+
+	/** No-arg constructor &mdash; uses a default {@link FaviconMixin} delegate. */
+	public FaviconResource() {
+		this(new FaviconMixin());
+	}
+
+	/**
+	 * Delegate constructor.
+	 *
+	 * @param delegate The shared favicon mixin this child delegates to. Must not be {@code null}.
+	 */
+	protected FaviconResource(FaviconMixin delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * [GET /*] &mdash; serve the configured favicon bytes.
+	 *
+	 * @return The favicon as an {@link HttpResource} with proper headers.
+	 */
+	@RestGet(
+		path="/*",
+		summary="Favorites icon",
+		description="Browser favorites icon (favicon.ico).",
+		swagger=@OpSwagger(ignore=true)
+	)
+	public HttpResource getFavicon() {
+		return delegate.getFavicon();
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconServlet.java
new file mode 100644
index 0000000000..82a1cfc769
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/FaviconServlet.java
@@ -0,0 +1,86 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.convention;
+
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Standalone servlet companion of the {@link FaviconMixin} mixin.
+ *
+ * <p>
+ * Mounts as a <b>sibling top-level servlet</b> at {@code /favicon.ico} and serves the configured favicon
+ * bytes by delegating to a shared {@link FaviconMixin} instance &mdash; the same logic the mixin uses,
+ * so the two forms cannot drift. Browsers fetch {@code /favicon.ico} from the site root, so a standalone
+ * servlet at that fixed path is the natural deployment for sites that do not compose the mixin into an
+ * existing host resource.
+ *
+ * <h5 class='figure'>Microservice registration:</h5>
+ *
+ * <p class='bjava'>
+ * 	<ja>@Bean</ja> Servlet favicon() {
+ * 		<jk>return new</jk> FaviconServlet();  <jc>// auto-mounted at /favicon.ico</jc>
+ * 	}
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link FaviconMixin}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @serial exclude
+ * @since 9.5.0
+ */
+// @formatter:off
+@Rest(paths="/favicon.ico")
+public class FaviconServlet extends RestServlet {
+
+	private static final long serialVersionUID = 1L;
+
+	private final transient FaviconMixin delegate;
+
+	/** No-arg constructor &mdash; uses a default {@link FaviconMixin} delegate. */
+	public FaviconServlet() {
+		this(new FaviconMixin());
+	}
+
+	/**
+	 * Delegate constructor.
+	 *
+	 * @param delegate The shared favicon mixin this servlet delegates to. Must not be {@code null}.
+	 */
+	protected FaviconServlet(FaviconMixin delegate) {
+		this.delegate = delegate;
+	}
+
+	/**
+	 * [GET /*] &mdash; serve the configured favicon bytes.
+	 *
+	 * @return The favicon as an {@link HttpResource} with proper headers.
+	 */
+	@RestGet(
+		path="/*",
+		summary="Favorites icon",
+		description="Browser favorites icon (favicon.ico).",
+		swagger=@OpSwagger(ignore=true)
+	)
+	public HttpResource getFavicon() {
+		return delegate.getFavicon();
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicSeoResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
similarity index 90%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicSeoResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
index 34e43b0c1d..f09d0e6776 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicSeoResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/SeoMixin.java
@@ -28,21 +28,21 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that serves SEO-conventional endpoints {@code /robots.txt} and {@code /sitemap.xml}.
  *
  * <p>
- * Sibling of {@link BasicFaviconResource} ({@code /favicon.ico}),
- * {@link BasicVersionResource} ({@code /version} / {@code /info} / {@code /about}), and
- * {@link BasicWellKnownResource} ({@code /.well-known/*}). All four classes live in the
+ * Sibling of {@link FaviconMixin} ({@code /favicon.ico}),
+ * {@link VersionMixin} ({@code /version} / {@code /info} / {@code /about}), and
+ * {@link WellKnownMixin} ({@code /.well-known/*}). All four classes live in the
  * {@code org.apache.juneau.rest.convention} convention-endpoints mixin pack.
  *
  * <p>
- * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=BasicSeoResource.class)};
+ * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=SeoMixin.class)};
  * the {@code /robots.txt} and {@code /sitemap.xml} URLs become available alongside the host's own
  * endpoints with no further wiring.
  *
  * <h5 class='section'>Hardcoded mount paths:</h5>
  *
  * <p>
- * Unlike the sibling api-docs and ops mixins (see {@link BasicSwaggerResource},
- * {@link BasicVersionResource}, etc.), the two mount paths here are <b>not</b> SVL-configurable.
+ * Unlike the sibling api-docs and ops mixins (see {@link SwaggerMixin},
+ * {@link VersionMixin}, etc.), the two mount paths here are <b>not</b> SVL-configurable.
  * {@code /robots.txt} is fixed by the Robots Exclusion Protocol
  * (<a href="https://www.rfc-editor.org/rfc/rfc9309">RFC 9309</a>) which prescribes that
  * crawlers fetch the policy from {@code /robots.txt} at the site root; {@code /sitemap.xml} is
@@ -62,12 +62,12 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicSeoResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=SeoMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { }
  *
  * 	<jc>// Override defaults via a @Bean factory:</jc>
- * 	<ja>@Bean</ja> BasicSeoResource seo() {
- * 		<jk>return</jk> BasicSeoResource.<jsm>create</jsm>()
+ * 	<ja>@Bean</ja> SeoMixin seo() {
+ * 		<jk>return</jk> SeoMixin.<jsm>create</jsm>()
  * 			.robotsAllow(<js>"*"</js>, <js>"/"</js>)
  * 			.sitemapEntry(<js>"/api/items"</js>)
  * 			.build();
@@ -94,9 +94,9 @@ import org.apache.juneau.rest.annotation.*;
  * </ul>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFaviconResource}
- * 	<li class='jc'>{@link BasicVersionResource}
- * 	<li class='jc'>{@link BasicWellKnownResource}
+ * 	<li class='jc'>{@link FaviconMixin}
+ * 	<li class='jc'>{@link VersionMixin}
+ * 	<li class='jc'>{@link WellKnownMixin}
  * 	<li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9309">RFC 9309 &mdash; Robots Exclusion Protocol</a>
  * 	<li class='link'><a class="doclink" href="https://www.sitemaps.org/protocol.html">sitemaps.org &mdash; Sitemap protocol</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
@@ -106,7 +106,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicSeoResource {
+public class SeoMixin {
 
 	/** Default robots policy: deny everything. */
 	public static final String DEFAULT_ROBOTS = "User-agent: *\nDisallow: /\n";
@@ -124,7 +124,7 @@ public class BasicSeoResource {
 	private final List<SitemapEntry> sitemapEntries;
 
 	/** No-arg constructor used when a host registers the mixin without builder configuration. */
-	public BasicSeoResource() {
+	public SeoMixin() {
 		this(create());
 	}
 
@@ -133,7 +133,7 @@ public class BasicSeoResource {
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicSeoResource(Builder builder) {
+	protected SeoMixin(Builder builder) {
 		robotsTxt = builder.buildRobots();
 		sitemapEntries = List.copyOf(builder.sitemapEntries);
 	}
@@ -214,7 +214,7 @@ public class BasicSeoResource {
 	}
 
 	/**
-	 * Builder for {@link BasicSeoResource} instances.
+	 * Builder for {@link SeoMixin} instances.
 	 */
 	public static class Builder {
 
@@ -222,7 +222,7 @@ public class BasicSeoResource {
 		private final List<SitemapEntry> sitemapEntries = new ArrayList<>();
 		private String customRobotsTxt;
 
-		/** Constructor &mdash; package access for {@link BasicSeoResource#create()}. */
+		/** Constructor &mdash; package access for {@link SeoMixin#create()}. */
 		protected Builder() {}
 
 		/**
@@ -301,12 +301,12 @@ public class BasicSeoResource {
 		}
 
 		/**
-		 * Builds a {@link BasicSeoResource} instance.
+		 * Builds a {@link SeoMixin} instance.
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicSeoResource build() {
-			return new BasicSeoResource(this);
+		public SeoMixin build() {
+			return new SeoMixin(this);
 		}
 
 		String buildRobots() {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
similarity index 89%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
index 4535b93635..c09832447b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionMixin.java
@@ -33,13 +33,13 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that serves deployment-introspection metadata at {@code /version} (configurable).
  *
  * <p>
- * Sibling of {@link BasicFaviconResource}, {@link BasicSeoResource}, and
- * {@link BasicWellKnownResource}. All four classes live in the
+ * Sibling of {@link FaviconMixin}, {@link SeoMixin}, and
+ * {@link WellKnownMixin}. All four classes live in the
  * {@code org.apache.juneau.rest.convention} convention-endpoints mixin pack.
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicVersionResource.class)}; the three URLs become available
+ * {@link Rest#mixins() @Rest(mixins=VersionMixin.class)}; the three URLs become available
  * alongside the host's own endpoints with no further wiring.
  *
  * <h5 class='section'>Configurable mount path:</h5>
@@ -77,13 +77,13 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicVersionResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=VersionMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { }
  *
  * 	<jc>// Default behavior reads MANIFEST.MF + git.properties from the importer's classpath.</jc>
  * 	<jc>// Override programmatically via a @Bean factory:</jc>
- * 	<ja>@Bean</ja> BasicVersionResource version() {
- * 		<jk>return</jk> BasicVersionResource.<jsm>create</jsm>()
+ * 	<ja>@Bean</ja> VersionMixin version() {
+ * 		<jk>return</jk> VersionMixin.<jsm>create</jsm>()
  * 			.entry(<js>"name"</js>, <js>"my-app"</js>)
  * 			.entry(<js>"version"</js>, <js>"1.2.3"</js>)
  * 			.build();
@@ -94,7 +94,7 @@ import org.apache.juneau.rest.annotation.*;
  *
  * <p>
  * The default {@link Builder#fromManifest() fromManifest()} reader walks the
- * {@link BasicVersionResource} classloader for {@code /META-INF/MANIFEST.MF} and reads the standard
+ * {@link VersionMixin} classloader for {@code /META-INF/MANIFEST.MF} and reads the standard
  * {@code Implementation-Title}, {@code Implementation-Version}, {@code Implementation-Vendor}, and
  * {@code Build-Jdk} attributes (lowercased to {@code name}, {@code version}, {@code vendor},
  * {@code javaVersion}). The default {@link Builder#fromGitProperties() fromGitProperties()} reader
@@ -106,7 +106,7 @@ import org.apache.juneau.rest.annotation.*;
  *
  * <p>
  * <b>Spring Boot fat-jar caveat:</b> Spring Boot rewrites {@code MANIFEST.MF} during repackaging.
- * The mixin's no-arg-default reader uses {@code BasicVersionResource.class.getClassLoader()};
+ * The mixin's no-arg-default reader uses {@code VersionMixin.class.getClassLoader()};
  * users who want the importer's app manifest under a Spring Boot executable jar should register an
  * explicit {@link Builder#fromManifest(ClassLoader) fromManifest(importerClassLoader)} call so the
  * lookup starts from the importer's classloader rather than the framework's.
@@ -131,9 +131,9 @@ import org.apache.juneau.rest.annotation.*;
  * {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFaviconResource}
- * 	<li class='jc'>{@link BasicSeoResource}
- * 	<li class='jc'>{@link BasicWellKnownResource}
+ * 	<li class='jc'>{@link FaviconMixin}
+ * 	<li class='jc'>{@link SeoMixin}
+ * 	<li class='jc'>{@link WellKnownMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -141,7 +141,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicVersionResource {
+public class VersionMixin {
 
 	/** Sentinel value returned for entries that the mixin couldn't resolve. */
 	public static final String UNKNOWN = "(unknown)";
@@ -177,7 +177,7 @@ public class BasicVersionResource {
 	 * directly or via the {@link org.apache.juneau.commons.inject.BeanInstantiator BeanInstantiator}
 	 * builder-detection path.
 	 */
-	public BasicVersionResource() {
+	public VersionMixin() {
 		this(create());
 	}
 
@@ -188,13 +188,13 @@ public class BasicVersionResource {
 	 * Applies the default lookup chain ({@link Builder#fromManifest()} +
 	 * {@link Builder#fromGitProperties()} + {@link Builder#fromJavaVersion()}) when no other
 	 * builder method has been called &mdash; ensuring that a freshly-created builder
-	 * (whether instantiated directly via {@code new BasicVersionResource(create())} or via the
+	 * (whether instantiated directly via {@code new VersionMixin(create())} or via the
 	 * {@link org.apache.juneau.commons.inject.BeanInstantiator BeanInstantiator}'s
 	 * builder-detection path) always produces a non-empty payload.
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicVersionResource(Builder builder) {
+	protected VersionMixin(Builder builder) {
 		if (! builder.explicit)
 			builder.fromManifest().fromGitProperties().fromJavaVersion();
 		info = Collections.unmodifiableMap(new LinkedHashMap<>(builder.entries));
@@ -233,7 +233,7 @@ public class BasicVersionResource {
 	}
 
 	/**
-	 * Builder for {@link BasicVersionResource} instances.
+	 * Builder for {@link VersionMixin} instances.
 	 */
 	public static class Builder {
 
@@ -243,7 +243,7 @@ public class BasicVersionResource {
 		/**
 		 * Env-driven default for the {@code javaVersion} entry; resolved from the {@code java.version}
 		 * system property (defaulting to the literal {@code (unknown)}, matching
-		 * {@link BasicVersionResource#UNKNOWN}). Populated by {@link BeanInstantiator} via
+		 * {@link VersionMixin#UNKNOWN}). Populated by {@link BeanInstantiator} via
 		 * {@link Value @Value} field injection.
 		 *
 		 * <p>
@@ -254,7 +254,7 @@ public class BasicVersionResource {
 		@Value("${java.version:(unknown)}")
 		String javaVersionDefault;
 
-		/** Constructor &mdash; protected access for {@link BasicVersionResource#create()}. */
+		/** Constructor &mdash; protected access for {@link VersionMixin#create()}. */
 		protected Builder() {}
 
 		/**
@@ -262,7 +262,7 @@ public class BasicVersionResource {
 		 *
 		 * @param key The entry key. Must not be <jk>null</jk> or blank.
 		 * @param value The entry value (a <jk>null</jk> value is recorded as
-		 * 	{@link BasicVersionResource#UNKNOWN}).
+		 * 	{@link VersionMixin#UNKNOWN}).
 		 * @return This object.
 		 */
 		public Builder entry(String key, String value) {
@@ -275,7 +275,7 @@ public class BasicVersionResource {
 		 * Bulk-set entries from a map.
 		 *
 		 * @param values Entries to add. <jk>null</jk> values are recorded as
-		 * 	{@link BasicVersionResource#UNKNOWN}.
+		 * 	{@link VersionMixin#UNKNOWN}.
 		 * @return This object.
 		 */
 		public Builder entries(Map<String,String> values) {
@@ -288,16 +288,16 @@ public class BasicVersionResource {
 		/**
 		 * Reads {@code Implementation-Title}, {@code Implementation-Version},
 		 * {@code Implementation-Vendor}, and {@code Build-Jdk} from
-		 * {@code /META-INF/MANIFEST.MF} on the {@link BasicVersionResource} classloader.
+		 * {@code /META-INF/MANIFEST.MF} on the {@link VersionMixin} classloader.
 		 *
 		 * <p>
-		 * Missing keys are recorded as {@link BasicVersionResource#UNKNOWN}. A missing
+		 * Missing keys are recorded as {@link VersionMixin#UNKNOWN}. A missing
 		 * {@code MANIFEST.MF} resource is silently skipped.
 		 *
 		 * @return This object.
 		 */
 		public Builder fromManifest() {
-			return fromManifest(BasicVersionResource.class.getClassLoader());
+			return fromManifest(VersionMixin.class.getClassLoader());
 		}
 
 		/**
@@ -306,7 +306,7 @@ public class BasicVersionResource {
 		 * <p>
 		 * Useful under Spring Boot fat jars, where the importer's app manifest is reachable from
 		 * its own classloader but not the framework's. Missing keys are recorded as
-		 * {@link BasicVersionResource#UNKNOWN}; a missing {@code MANIFEST.MF} resource is silently
+		 * {@link VersionMixin#UNKNOWN}; a missing {@code MANIFEST.MF} resource is silently
 		 * skipped.
 		 *
 		 * @param classLoader The classloader to walk.
@@ -350,7 +350,7 @@ public class BasicVersionResource {
 
 		/**
 		 * Reads {@code git.commit.id}, {@code git.branch}, and {@code git.build.time} from
-		 * {@code /git.properties} on the {@link BasicVersionResource} classloader.
+		 * {@code /git.properties} on the {@link VersionMixin} classloader.
 		 *
 		 * <p>
 		 * Output of the
@@ -361,7 +361,7 @@ public class BasicVersionResource {
 		 * @return This object.
 		 */
 		public Builder fromGitProperties() {
-			return fromGitProperties(BasicVersionResource.class.getClassLoader());
+			return fromGitProperties(VersionMixin.class.getClassLoader());
 		}
 
 		/**
@@ -396,7 +396,7 @@ public class BasicVersionResource {
 		}
 
 		/**
-		 * Builds a {@link BasicVersionResource} instance.
+		 * Builds a {@link VersionMixin} instance.
 		 *
 		 * <p>
 		 * The constructor applies the default lookup chain ({@link #fromManifest()} +
@@ -406,8 +406,8 @@ public class BasicVersionResource {
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicVersionResource build() {
-			return new BasicVersionResource(this);
+		public VersionMixin build() {
+			return new VersionMixin(this);
 		}
 
 		private static Map<String,String> readManifestAttributes(ClassLoader cl) {
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionResource.java
similarity index 56%
copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java
copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionResource.java
index 5927252eed..74da413b42 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionResource.java
@@ -24,54 +24,43 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicVersionResource} mixin.
+ * Child-resource flavor of the {@link VersionMixin} version/build-metadata capability.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /version/*} and serves the same
- * deployment-introspection JSON map as the mixin by delegating to a shared
- * {@link BasicVersionResource} instance &mdash; so the two forms cannot drift.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=VersionResource.class)}
+ * under a parent at the subtree {@code /version} and serves the same deployment-introspection JSON map
+ * as the mixin by delegating to a shared {@link VersionMixin} instance &mdash; so the forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicVersionResource} mixin pins its op at {@code /version} for composition
- * into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/version/*")}) and pins the op at {@code /*}.
- *
- * <h5 class='figure'>Microservice registration:</h5>
- *
- * <p class='bjava'>
- * 	<ja>@Bean</ja> Servlet version() {
- * 		<jk>return new</jk> BasicVersionServlet();  <jc>// auto-mounted at /version/* by JettyServerComponent</jc>
- * 	}
- * </p>
+ * Whereas the {@link VersionMixin} mixin pins its op at {@code /version} for composition into a host at
+ * {@code /}, this child declares the subtree at the class level
+ * ({@link Rest#path() @Rest(path="/version")}) and pins the op at {@code /*}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicVersionResource}
+ * 	<li class='jc'>{@link VersionMixin}
+ * 	<li class='jc'>{@link VersionServlet}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 // @formatter:off
-@Rest(paths="/version/*")
-public class BasicVersionServlet extends RestServlet {
-
-	private static final long serialVersionUID = 1L;
+@Rest(path="/version")
+public class VersionResource extends RestResource {
 
-	private final transient BasicVersionResource delegate;
+	private final transient VersionMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicVersionResource} delegate. */
-	public BasicVersionServlet() {
-		this(new BasicVersionResource());
+	/** No-arg constructor &mdash; uses a default {@link VersionMixin} delegate. */
+	public VersionResource() {
+		this(new VersionMixin());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared version mixin this servlet delegates to. Must not be
-	 * 	{@code null}.
+	 * @param delegate The shared version mixin this child delegates to. Must not be {@code null}.
 	 */
-	protected BasicVersionServlet(BasicVersionResource delegate) {
+	protected VersionResource(VersionMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionServlet.java
similarity index 77%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionServlet.java
index 5927252eed..51f9607395 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicVersionServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/VersionServlet.java
@@ -24,15 +24,15 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicVersionResource} mixin.
+ * Standalone servlet companion of the {@link VersionMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /version/*} and serves the same
  * deployment-introspection JSON map as the mixin by delegating to a shared
- * {@link BasicVersionResource} instance &mdash; so the two forms cannot drift.
+ * {@link VersionMixin} instance &mdash; so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicVersionResource} mixin pins its op at {@code /version} for composition
+ * Whereas the {@link VersionMixin} mixin pins its op at {@code /version} for composition
  * into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/version/*")}) and pins the op at {@code /*}.
  *
@@ -40,12 +40,12 @@ import org.apache.juneau.rest.servlet.*;
  *
  * <p class='bjava'>
  * 	<ja>@Bean</ja> Servlet version() {
- * 		<jk>return new</jk> BasicVersionServlet();  <jc>// auto-mounted at /version/* by JettyServerComponent</jc>
+ * 		<jk>return new</jk> VersionServlet();  <jc>// auto-mounted at /version/* by JettyServerComponent</jc>
  * 	}
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicVersionResource}
+ * 	<li class='jc'>{@link VersionMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -54,15 +54,15 @@ import org.apache.juneau.rest.servlet.*;
  */
 // @formatter:off
 @Rest(paths="/version/*")
-public class BasicVersionServlet extends RestServlet {
+public class VersionServlet extends RestServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicVersionResource delegate;
+	private final transient VersionMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicVersionResource} delegate. */
-	public BasicVersionServlet() {
-		this(new BasicVersionResource());
+	/** No-arg constructor &mdash; uses a default {@link VersionMixin} delegate. */
+	public VersionServlet() {
+		this(new VersionMixin());
 	}
 
 	/**
@@ -71,7 +71,7 @@ public class BasicVersionServlet extends RestServlet {
 	 * @param delegate The shared version mixin this servlet delegates to. Must not be
 	 * 	{@code null}.
 	 */
-	protected BasicVersionServlet(BasicVersionResource delegate) {
+	protected VersionServlet(VersionMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicWellKnownResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
similarity index 84%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicWellKnownResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
index ae339127af..13412ce23e 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/BasicWellKnownResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/WellKnownMixin.java
@@ -27,8 +27,8 @@ import org.apache.juneau.rest.annotation.*;
  * {@code /.well-known/*} discovery endpoints.
  *
  * <p>
- * Sibling of {@link BasicFaviconResource}, {@link BasicSeoResource}, and
- * {@link BasicVersionResource}. All four classes live in the
+ * Sibling of {@link FaviconMixin}, {@link SeoMixin}, and
+ * {@link VersionMixin}. All four classes live in the
  * {@code org.apache.juneau.rest.convention} convention-endpoints mixin pack.
  *
  * <p>
@@ -40,15 +40,15 @@ import org.apache.juneau.rest.annotation.*;
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicWellKnownResource.class)}; the
+ * {@link Rest#mixins() @Rest(mixins=WellKnownMixin.class)}; the
  * {@code /.well-known/security.txt} URL becomes available alongside the host's own endpoints with
  * no further wiring.
  *
  * <h5 class='section'>Hardcoded mount path:</h5>
  *
  * <p>
- * Unlike the sibling api-docs and ops mixins (see {@link BasicSwaggerResource},
- * {@link BasicVersionResource}, etc.), the mount path here is <b>not</b> SVL-configurable
+ * Unlike the sibling api-docs and ops mixins (see {@link SwaggerMixin},
+ * {@link VersionMixin}, etc.), the mount path here is <b>not</b> SVL-configurable
  * &mdash; {@code /.well-known/security.txt} is fixed by
  * <a href="https://www.rfc-editor.org/rfc/rfc8615">RFC 8615</a> (Well-Known URIs) and
  * <a href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116</a> (security.txt). Both RFCs are
@@ -67,12 +67,12 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicWellKnownResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=WellKnownMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { }
  *
  * 	<jc>// Configure the security.txt body via a @Bean factory:</jc>
- * 	<ja>@Bean</ja> BasicWellKnownResource wellKnown() {
- * 		<jk>return</jk> BasicWellKnownResource.<jsm>create</jsm>()
+ * 	<ja>@Bean</ja> WellKnownMixin wellKnown() {
+ * 		<jk>return</jk> WellKnownMixin.<jsm>create</jsm>()
  * 			.securityTxt(<js>"Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n"</js>)
  * 			.build();
  * 	}
@@ -91,9 +91,9 @@ import org.apache.juneau.rest.annotation.*;
  * </ul>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicFaviconResource}
- * 	<li class='jc'>{@link BasicSeoResource}
- * 	<li class='jc'>{@link BasicVersionResource}
+ * 	<li class='jc'>{@link FaviconMixin}
+ * 	<li class='jc'>{@link SeoMixin}
+ * 	<li class='jc'>{@link VersionMixin}
  * 	<li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc8615">RFC 8615 &mdash; Well-Known URIs</a>
  * 	<li class='link'><a class="doclink" href="https://www.rfc-editor.org/rfc/rfc9116">RFC 9116 &mdash; security.txt</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
@@ -103,7 +103,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicWellKnownResource {
+public class WellKnownMixin {
 
 	/**
 	 * Creates a new builder.
@@ -117,7 +117,7 @@ public class BasicWellKnownResource {
 	private final String securityTxt;
 
 	/** No-arg constructor &mdash; results in a 404 on {@code /.well-known/security.txt}. */
-	public BasicWellKnownResource() {
+	public WellKnownMixin() {
 		this(create());
 	}
 
@@ -126,7 +126,7 @@ public class BasicWellKnownResource {
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicWellKnownResource(Builder builder) {
+	protected WellKnownMixin(Builder builder) {
 		securityTxt = builder.securityTxt;
 	}
 
@@ -162,13 +162,13 @@ public class BasicWellKnownResource {
 	}
 
 	/**
-	 * Builder for {@link BasicWellKnownResource} instances.
+	 * Builder for {@link WellKnownMixin} instances.
 	 */
 	public static class Builder {
 
 		private String securityTxt;
 
-		/** Constructor &mdash; package access for {@link BasicWellKnownResource#create()}. */
+		/** Constructor &mdash; package access for {@link WellKnownMixin#create()}. */
 		protected Builder() {}
 
 		/**
@@ -189,12 +189,12 @@ public class BasicWellKnownResource {
 		}
 
 		/**
-		 * Builds a {@link BasicWellKnownResource} instance.
+		 * Builds a {@link WellKnownMixin} instance.
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicWellKnownResource build() {
-			return new BasicWellKnownResource(this);
+		public WellKnownMixin build() {
+			return new WellKnownMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/package-info.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/package-info.java
index 81ccdac39a..5f8973ace6 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/package-info.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/convention/package-info.java
@@ -22,20 +22,20 @@
  * <p>
  * Four sibling mixins compose into the host {@code @Rest}-annotated resource. Each mixin owns its
  * default mount paths and is independently mountable; the four together drop in as a pack via
- * {@code @Rest(mixins={BasicFaviconResource.class, BasicSeoResource.class, BasicVersionResource.class, BasicWellKnownResource.class})}.
+ * {@code @Rest(mixins={FaviconMixin.class, SeoMixin.class, VersionMixin.class, WellKnownMixin.class})}.
  * </p>
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.convention.BasicFaviconResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.convention.FaviconMixin} —
  * 		{@code /favicon.ico} with a 30-day {@code Cache-Control} and a default Juneau-branded icon
  * 		on the framework classpath.
- * 	<li class='jc'>{@link org.apache.juneau.rest.convention.BasicSeoResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.convention.SeoMixin} —
  * 		{@code /robots.txt} (deny-all by default) and {@code /sitemap.xml} (empty
  * 		{@code <urlset>} by default); both builder-driven.
- * 	<li class='jc'>{@link org.apache.juneau.rest.convention.BasicVersionResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.convention.VersionMixin} —
  * 		{@code /version}, {@code /info}, and {@code /about} returning the same JSON metadata
  * 		map; defaults read {@code MANIFEST.MF} + {@code git.properties} from the classpath.
- * 	<li class='jc'>{@link org.apache.juneau.rest.convention.BasicWellKnownResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.convention.WellKnownMixin} —
  * 		{@code /.well-known/security.txt} per RFC 9116; 404 unless explicitly configured.
  * </ul>
  *
@@ -45,18 +45,18 @@
  * 	<ja>@Rest</ja>(
  * 		path=<js>"/api"</js>,
  * 		mixins={
- * 			BasicFaviconResource.<jk>class</jk>,
- * 			BasicSeoResource.<jk>class</jk>,
- * 			BasicVersionResource.<jk>class</jk>,
- * 			BasicWellKnownResource.<jk>class</jk>
+ * 			FaviconMixin.<jk>class</jk>,
+ * 			SeoMixin.<jk>class</jk>,
+ * 			VersionMixin.<jk>class</jk>,
+ * 			WellKnownMixin.<jk>class</jk>
  * 		}
  * 	)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
- * 		<ja>@Bean</ja> BasicSeoResource seo() {
- * 			<jk>return</jk> BasicSeoResource.<jsm>create</jsm>().robotsAllow(<js>"*"</js>, <js>"/"</js>).build();
+ * 		<ja>@Bean</ja> SeoMixin seo() {
+ * 			<jk>return</jk> SeoMixin.<jsm>create</jsm>().robotsAllow(<js>"*"</js>, <js>"/"</js>).build();
  * 		}
- * 		<ja>@Bean</ja> BasicWellKnownResource wellKnown() {
- * 			<jk>return</jk> BasicWellKnownResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> WellKnownMixin wellKnown() {
+ * 			<jk>return</jk> WellKnownMixin.<jsm>create</jsm>()
  * 				.securityTxt(<js>"Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n"</js>)
  * 				.build();
  * 		}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/OpenApiMixin.java
similarity index 94%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/OpenApiMixin.java
index 6d79f9a04a..91da7a30f8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicOpenApiResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/OpenApiMixin.java
@@ -46,8 +46,8 @@ import org.apache.juneau.yaml.*;
  * </ul>
  *
  * <p>
- * Sibling of {@link BasicSwaggerResource} (Swagger v2 spec at {@code /api}),
- * {@link BasicSwaggerUiResource} (HTML-first mount at {@code /swagger}), and {@link BasicRedocResource}
+ * Sibling of {@link SwaggerMixin} (Swagger v2 spec at {@code /api}),
+ * {@link SwaggerUiMixin} (HTML-first mount at {@code /swagger}), and {@link RedocMixin}
  * (HTML-first mount at {@code /redoc}). All four classes live in the
  * {@code org.apache.juneau.rest.docs} api-docs mixin pack.
  *
@@ -86,16 +86,16 @@ import org.apache.juneau.yaml.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=BasicOpenApiResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=OpenApiMixin.<jk>class</jk>)
  * 	<jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
  * 		<jc>// Now also serves /myresource/openapi, /myresource/openapi.json, /myresource/openapi.yaml.</jc>
  * 	}
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicSwaggerResource}
- * 	<li class='jc'>{@link BasicSwaggerUiResource}
- * 	<li class='jc'>{@link BasicRedocResource}
+ * 	<li class='jc'>{@link SwaggerMixin}
+ * 	<li class='jc'>{@link SwaggerUiMixin}
+ * 	<li class='jc'>{@link RedocMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -111,7 +111,7 @@ import org.apache.juneau.yaml.*;
 	ignoreTypes="OpenApi,org.apache.juneau.bean.html5.*",
 	useBeanDefs="true"
 )
-public class BasicOpenApiResource {
+public class OpenApiMixin {
 
 	/**
 	 * [GET /openapi] &mdash; emit the OpenAPI 3.1 document for this resource.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/RedocMixin.java
similarity index 86%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/RedocMixin.java
index 801dabbf91..33f913b8bf 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicRedocResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/RedocMixin.java
@@ -29,14 +29,14 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that publishes the Redoc HTML view at {@code /redoc} for the OpenAPI 3.1 spec.
  *
  * <p>
- * Sibling of {@link BasicSwaggerResource} (Swagger v2 spec at {@code /api}),
- * {@link BasicSwaggerUiResource} (HTML-first mount at {@code /swagger}), and {@link BasicOpenApiResource}
+ * Sibling of {@link SwaggerMixin} (Swagger v2 spec at {@code /api}),
+ * {@link SwaggerUiMixin} (HTML-first mount at {@code /swagger}), and {@link OpenApiMixin}
  * (OpenAPI 3.1 spec at {@code /openapi*}). All four classes live in the
  * {@code org.apache.juneau.rest.docs} api-docs mixin pack.
  *
  * <p>
- * Declares {@link Rest#mixins() @Rest(mixins={BasicOpenApiResource.class})} so transitive resolution
- * pulls in {@code BasicOpenApiResource} (and its {@code /openapi*} mounts) automatically when this
+ * Declares {@link Rest#mixins() @Rest(mixins={OpenApiMixin.class})} so transitive resolution
+ * pulls in {@code OpenApiMixin} (and its {@code /openapi*} mounts) automatically when this
  * mixin is composed into a host.
  *
  * <h5 class='section'>Configurable mount path:</h5>
@@ -65,7 +65,7 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=BasicRedocResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=RedocMixin.<jk>class</jk>)
  * 	<jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
  * 		<jc>// Serves /myresource/openapi, /openapi.json, /openapi.yaml (transitively) and /myresource/redoc (this mixin).</jc>
  * 	}
@@ -80,9 +80,9 @@ import org.apache.juneau.rest.annotation.*;
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicSwaggerResource}
- * 	<li class='jc'>{@link BasicSwaggerUiResource}
- * 	<li class='jc'>{@link BasicOpenApiResource}
+ * 	<li class='jc'>{@link SwaggerMixin}
+ * 	<li class='jc'>{@link SwaggerUiMixin}
+ * 	<li class='jc'>{@link OpenApiMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -90,7 +90,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest(
-	mixins={BasicOpenApiResource.class},
+	mixins={OpenApiMixin.class},
 	defaultAccept="text/html"
 )
 @JsonSchemaConfig(
@@ -99,7 +99,7 @@ import org.apache.juneau.rest.annotation.*;
 	ignoreTypes="OpenApi,org.apache.juneau.bean.html5.*",
 	useBeanDefs="true"
 )
-public class BasicRedocResource {
+public class RedocMixin {
 
 	/**
 	 * [GET /redoc] &mdash; render the Redoc HTML view (or the spec, depending on {@code Accept}).
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerMixin.java
similarity index 88%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerMixin.java
index 41e39f6812..d3f52fe373 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerMixin.java
@@ -29,13 +29,13 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that publishes the Swagger v2 spec endpoint at {@code /api/*}.
  *
  * <p>
- * Sibling of {@link BasicSwaggerUiResource} (HTML-first mount at {@code /swagger}),
- * {@link BasicOpenApiResource} (OpenAPI 3.1 spec at {@code /openapi*}), and
- * {@link BasicRedocResource} (HTML-first mount at {@code /redoc}). All four classes live in the
+ * Sibling of {@link SwaggerUiMixin} (HTML-first mount at {@code /swagger}),
+ * {@link OpenApiMixin} (OpenAPI 3.1 spec at {@code /openapi*}), and
+ * {@link RedocMixin} (HTML-first mount at {@code /redoc}). All four classes live in the
  * {@code org.apache.juneau.rest.docs} api-docs mixin pack.
  *
  * <p>
- * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=BasicSwaggerResource.class)};
+ * Compose into a host resource via {@link Rest#mixins() @Rest(mixins=SwaggerMixin.class)};
  * the {@code /api} URL becomes available alongside the host's own endpoints with no further wiring.
  *
  * <h5 class='section'>Configurable mount path:</h5>
@@ -64,7 +64,7 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=BasicSwaggerResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=SwaggerMixin.<jk>class</jk>)
  * 	<jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
  * 		<ja>@RestGet</ja>(<js>"/items"</js>) <jk>public</jk> List&lt;Item&gt; items() { ... }
  * 		<jc>// Now also serves /myresource/api with the Swagger v2 spec for this resource.</jc>
@@ -80,9 +80,9 @@ import org.apache.juneau.rest.annotation.*;
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicSwaggerUiResource}
- * 	<li class='jc'>{@link BasicOpenApiResource}
- * 	<li class='jc'>{@link BasicRedocResource}
+ * 	<li class='jc'>{@link SwaggerUiMixin}
+ * 	<li class='jc'>{@link OpenApiMixin}
+ * 	<li class='jc'>{@link RedocMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Composition (mixins, paths)</a>
  * </ul>
  *
@@ -96,7 +96,7 @@ import org.apache.juneau.rest.annotation.*;
 	ignoreTypes="Swagger,org.apache.juneau.bean.html5.*",
 	useBeanDefs="true"
 )
-public class BasicSwaggerResource {
+public class SwaggerMixin {
 
 	/**
 	 * [GET /api] — emit the Swagger v2 spec for this resource.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerUiMixin.java
similarity index 86%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerUiMixin.java
index e6e616d5ee..327231dbd5 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/SwaggerUiMixin.java
@@ -29,14 +29,14 @@ import org.apache.juneau.rest.annotation.*;
  * Mixin that publishes the Swagger-UI HTML view at {@code /swagger}.
  *
  * <p>
- * Sibling of {@link BasicSwaggerResource} (Swagger v2 spec at {@code /api}),
- * {@link BasicOpenApiResource} (OpenAPI 3.1 spec at {@code /openapi*}), and
- * {@link BasicRedocResource} (HTML-first mount at {@code /redoc}). All four classes live in the
+ * Sibling of {@link SwaggerMixin} (Swagger v2 spec at {@code /api}),
+ * {@link OpenApiMixin} (OpenAPI 3.1 spec at {@code /openapi*}), and
+ * {@link RedocMixin} (HTML-first mount at {@code /redoc}). All four classes live in the
  * {@code org.apache.juneau.rest.docs} api-docs mixin pack.
  *
  * <p>
- * Declares {@link Rest#mixins() @Rest(mixins={BasicSwaggerResource.class})} so transitive resolution
- * pulls in {@code BasicSwaggerResource} (and its {@code /api} mount) automatically when this mixin
+ * Declares {@link Rest#mixins() @Rest(mixins={SwaggerMixin.class})} so transitive resolution
+ * pulls in {@code SwaggerMixin} (and its {@code /api} mount) automatically when this mixin
  * is composed into a host. The two endpoints share the same Swagger document; they differ only in
  * URL surface and content-negotiation defaults.
  *
@@ -66,7 +66,7 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=BasicSwaggerUiResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/myresource"</js>, mixins=SwaggerUiMixin.<jk>class</jk>)
  * 	<jk>public class</jk> MyResource <jk>extends</jk> RestServlet {
  * 		<jc>// Serves /myresource/api  (transitively) and /myresource/swagger (this mixin).</jc>
  * 	}
@@ -86,9 +86,9 @@ import org.apache.juneau.rest.annotation.*;
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicSwaggerResource}
- * 	<li class='jc'>{@link BasicOpenApiResource}
- * 	<li class='jc'>{@link BasicRedocResource}
+ * 	<li class='jc'>{@link SwaggerMixin}
+ * 	<li class='jc'>{@link OpenApiMixin}
+ * 	<li class='jc'>{@link RedocMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server — Composition (mixins, paths)</a>
  * </ul>
  *
@@ -96,7 +96,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest(
-	mixins={BasicSwaggerResource.class},
+	mixins={SwaggerMixin.class},
 	defaultAccept="text/html"
 )
 @JsonSchemaConfig(
@@ -105,7 +105,7 @@ import org.apache.juneau.rest.annotation.*;
 	ignoreTypes="Swagger,org.apache.juneau.bean.html5.*",
 	useBeanDefs="true"
 )
-public class BasicSwaggerUiResource {
+public class SwaggerUiMixin {
 
 	/**
 	 * [GET /swagger] — render the Swagger-UI HTML view (or the spec, depending on {@code Accept}).
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/package-info.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/package-info.java
index fd243a41b8..f30d93c6e4 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/package-info.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/docs/package-info.java
@@ -25,38 +25,38 @@
  * </p>
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.docs.BasicSwaggerResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.docs.SwaggerMixin} —
  * 		spec endpoint at {@code /api}, Swagger v2 + Swagger-UI swap.
- * 	<li class='jc'>{@link org.apache.juneau.rest.docs.BasicSwaggerUiResource} —
- * 		HTML-first mount at {@code /swagger}, transitively pulls in {@code BasicSwaggerResource}.
- * 	<li class='jc'>{@link org.apache.juneau.rest.docs.BasicOpenApiResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.docs.SwaggerUiMixin} —
+ * 		HTML-first mount at {@code /swagger}, transitively pulls in {@code SwaggerMixin}.
+ * 	<li class='jc'>{@link org.apache.juneau.rest.docs.OpenApiMixin} —
  * 		spec endpoints at {@code /openapi}, {@code /openapi.json}, and {@code /openapi.yaml};
  * 		OpenAPI 3.1 + Redoc swap on the content-negotiated {@code /openapi} mount.
- * 	<li class='jc'>{@link org.apache.juneau.rest.docs.BasicRedocResource} —
- * 		HTML-first mount at {@code /redoc}, transitively pulls in {@code BasicOpenApiResource}.
+ * 	<li class='jc'>{@link org.apache.juneau.rest.docs.RedocMixin} —
+ * 		HTML-first mount at {@code /redoc}, transitively pulls in {@code OpenApiMixin}.
  * </ul>
  *
  * <h5 class='section'>Composition examples:</h5>
  *
  * <p class='bjava'>
  * 	<jc>// OpenAPI 3.1 spec only — /openapi, /openapi.json, /openapi.yaml.</jc>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicOpenApiResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=OpenApiMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { ... }
  *
  * 	<jc>// OpenAPI 3.1 + Redoc — /openapi, /openapi.json, /openapi.yaml, /redoc.</jc>
- * 	<jc>// Transitive mixin resolution pulls BasicOpenApiResource in for free.</jc>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicRedocResource.<jk>class</jk>)
+ * 	<jc>// Transitive mixin resolution pulls OpenApiMixin in for free.</jc>
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=RedocMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { ... }
  *
  * 	<jc>// Everything — /api, /swagger, /openapi, /openapi.json, /openapi.yaml, /redoc.</jc>
- * 	<jc>// This is the post-9.5.0 default for BasicRestServlet / BasicRestObject subclasses.</jc>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins={BasicSwaggerUiResource.<jk>class</jk>, BasicRedocResource.<jk>class</jk>})
+ * 	<jc>// This is the post-9.5.0 default for BasicRestServlet / BasicRestResource subclasses.</jc>
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins={SwaggerUiMixin.<jk>class</jk>, RedocMixin.<jk>class</jk>})
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { ... }
  * </p>
  *
  * <p>
- * The two UI mixins ({@link org.apache.juneau.rest.docs.BasicSwaggerUiResource} and
- * {@link org.apache.juneau.rest.docs.BasicRedocResource}) declare {@code @Rest(defaultAccept="text/html")}
+ * The two UI mixins ({@link org.apache.juneau.rest.docs.SwaggerUiMixin} and
+ * {@link org.apache.juneau.rest.docs.RedocMixin}) declare {@code @Rest(defaultAccept="text/html")}
  * so that bare browser requests (no {@code Accept} header, or {@code Accept: *}{@code /*}) render the
  * HTML view rather than the serializer-default media type. The two spec mixins use Juneau's standard
  * content negotiation unchanged.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/guard/RateLimitGuard.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/guard/RateLimitGuard.java
index 896011fdc9..01cbbfe32b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/guard/RateLimitGuard.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/guard/RateLimitGuard.java
@@ -382,7 +382,7 @@ public class RateLimitGuard extends RestGuard {
 	 * Point-in-time view of a single token bucket as exposed by {@link Storage#snapshot()}.
 	 *
 	 * <p>
-	 * Used by operator-facing tooling (e.g. {@code BasicAdminResource}'s {@code /admin/ratelimit} endpoint) to
+	 * Used by operator-facing tooling (e.g. {@code AdminMixin}'s {@code /admin/ratelimit} endpoint) to
 	 * surface live per-key bucket state alongside the static configuration.  Token-bucket vocabulary
 	 * intentionally — there is no discrete refill window because the bucket refills continuously at
 	 * {@code permitsPerSecond}, so this record reports the current fill level ({@link #tokens()}), the integer
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/BasicHealthResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthAggregator.java
similarity index 66%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/BasicHealthResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthAggregator.java
index f95c1c3c45..fd7ba0db52 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/BasicHealthResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthAggregator.java
@@ -21,67 +21,53 @@ import static org.apache.juneau.commons.utils.StringUtils.*;
 
 import java.time.*;
 import java.util.*;
-import java.util.Map.*;
 import java.util.concurrent.*;
 
 import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.servlet.*;
 
 /**
- * Built-in health/readiness/liveness probe resource.
+ * Shared health-probe aggregation worker bean.
+ *
+ * <p>
+ * Holds the capability logic for evaluating {@link HealthIndicator} beans and summarizing them into a
+ * probe response. The {@link HealthServlet} (servlet flavor) and {@link HealthMixin} (mixin flavor) are
+ * independent {@code @Rest} classes that each delegate to a {@code HealthAggregator}, so the two
+ * deployment forms cannot drift.
  *
  * @since 9.5.0
  */
-@Rest(paths={"/healthz","/readyz","/livez"})
-public class BasicHealthResource extends BasicRestServlet {
-	private static final long serialVersionUID = 1L;
-
-	/**
-	 * Health probe endpoint.
-	 *
-	 * @param res The response.
-	 * @return Aggregated health payload.
-	 */
-	@RestGet(path="/healthz")
-	public HealthResponse healthz(RestResponse res) {
-		return aggregate(null, res);
-	}
+public class HealthAggregator {
 
 	/**
-	 * Readiness probe endpoint.
+	 * Resolves the {@link HealthIndicator} beans registered in the given context's bean store.
 	 *
-	 * @param res The response.
-	 * @return Aggregated health payload.
+	 * @param context The REST context whose bean store supplies the indicators.
+	 * @return The indicator beans keyed by bean name.
 	 */
-	@RestGet(path="/readyz")
-	public HealthResponse readyz(RestResponse res) {
-		return aggregate(HealthProbe.READY, res);
+	public Map<String,HealthIndicator> indicators(RestContext context) {
+		return context.getBeanStore().getBeansOfType(HealthIndicator.class);
 	}
 
 	/**
-	 * Liveness probe endpoint.
+	 * Evaluates the supplied indicators, summarizes their statuses, and sets the response status.
 	 *
-	 * @param res The response.
-	 * @return Aggregated health payload.
+	 * @param context The REST context (supplies the {@link HealthProbeSettings} timeout).
+	 * @param indicators The indicators to evaluate.
+	 * @param probe The probe filter, or {@code null} to evaluate all indicators.
+	 * @param res The response whose status code is set (200 healthy, 503 down).
+	 * @return The aggregated health payload.
 	 */
-	@RestGet(path="/livez")
-	public HealthResponse livez(RestResponse res) {
-		return aggregate(HealthProbe.LIVE, res);
-	}
-
-	private HealthResponse aggregate(HealthProbe probe, RestResponse res) {
+	public HealthResponse aggregate(RestContext context, Map<String,HealthIndicator> indicators, HealthProbe probe, RestResponse res) {
 		var out = new LinkedHashMap<String,ComponentHealth>();
-		var timeout = getContext().getBeanStore().getBean(HealthProbeSettings.class).map(HealthProbeSettings::getTimeout).orElse(Duration.ofSeconds(1));
+		var timeout = context.getBeanStore().getBean(HealthProbeSettings.class).map(HealthProbeSettings::getTimeout).orElse(Duration.ofSeconds(1));
 		var timeoutMillis = Math.max(1L, timeout.toMillis());
 
-		for (var e : indicators().entrySet()) {
+		for (var e : indicators.entrySet()) {
 			var indicator = e.getValue();
 			if (probe != null && !indicator.probes().contains(probe))
 				continue;
 			var componentName = firstNonEmpty(e.getKey(), indicator.getClass().getSimpleName());
-			var component = runIndicator(componentName, indicator, timeoutMillis);
-			out.put(componentName, component);
+			out.put(componentName, runIndicator(componentName, indicator, timeoutMillis));
 		}
 
 		var status = summarize(out.values());
@@ -89,15 +75,6 @@ public class BasicHealthResource extends BasicRestServlet {
 		return new HealthResponse(status, out);
 	}
 
-	/**
-	 * Returns indicators to evaluate for this request.
-	 *
-	 * @return Indicator map keyed by bean name.
-	 */
-	protected Map<String,HealthIndicator> indicators() {
-		return getContext().getBeanStore().getBeansOfType(HealthIndicator.class);
-	}
-
 	private static ComponentHealth runIndicator(String name, HealthIndicator indicator, long timeoutMillis) {
 		var future = CompletableFuture.supplyAsync(indicator::check);
 		try {
@@ -130,11 +107,20 @@ public class BasicHealthResource extends BasicRestServlet {
 	public static class HealthResponse {
 		private final HealthStatus status;
 		private final Map<String,ComponentHealth> components;
+
 		HealthResponse(HealthStatus status, Map<String,ComponentHealth> components) {
 			this.status = status;
 			this.components = components;
 		}
+
+		/**
+		 * @return The aggregated probe status.
+		 */
 		public HealthStatus getStatus() { return status; }
+
+		/**
+		 * @return The per-component health entries.
+		 */
 		public Map<String,ComponentHealth> getComponents() { return components; }
 	}
 
@@ -144,17 +130,27 @@ public class BasicHealthResource extends BasicRestServlet {
 	public static class ComponentHealth {
 		private final HealthStatus status;
 		private final Map<String,Object> details;
+
 		ComponentHealth(HealthStatus status, Map<String,Object> details) {
 			this.status = status;
 			this.details = details;
 		}
+
 		static ComponentHealth from(Health h) {
 			var details = new LinkedHashMap<>(h.getDetails());
 			if (h.getError() != null)
 				details.putIfAbsent("error", h.getError().toString());
 			return new ComponentHealth(h.getStatus(), details);
 		}
+
+		/**
+		 * @return The component status.
+		 */
 		public HealthStatus getStatus() { return status; }
+
+		/**
+		 * @return The component detail map.
+		 */
 		public Map<String,Object> getDetails() { return details; }
 	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthMixin.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthMixin.java
new file mode 100644
index 0000000000..9a45ace7f2
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthMixin.java
@@ -0,0 +1,85 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.health;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.health.HealthAggregator.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Mixin flavor of the built-in health/readiness/liveness probe capability.
+ *
+ * <p>
+ * Composes the {@code /healthz}, {@code /readyz}, and {@code /livez} probe endpoints into a host
+ * resource via {@link Rest#mixins() @Rest(mixins=HealthMixin.class)}. The ops self-pin their absolute
+ * paths so they merge into the host's namespace. Delegates to a shared {@link HealthAggregator} worker
+ * bean &mdash; the same logic the {@link HealthServlet} servlet flavor uses.
+ *
+ * <p>
+ * Indicators are resolved from the host's bean store, so a host registers its own
+ * {@link HealthIndicator} beans (e.g. via Spring {@code @Bean} or a Juneau {@code BeanStore}).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link HealthServlet}
+ * 	<li class='jc'>{@link HealthAggregator}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest
+public class HealthMixin extends RestMixin {
+
+	private final HealthAggregator aggregator = new HealthAggregator();
+
+	/**
+	 * Health probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/healthz")
+	public HealthResponse healthz(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), null, res);
+	}
+
+	/**
+	 * Readiness probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/readyz")
+	public HealthResponse readyz(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), HealthProbe.READY, res);
+	}
+
+	/**
+	 * Liveness probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/livez")
+	public HealthResponse livez(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), HealthProbe.LIVE, res);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthResource.java
new file mode 100644
index 0000000000..27b5d8e8d4
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthResource.java
@@ -0,0 +1,87 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.health;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.health.HealthAggregator.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Child-resource flavor of the built-in health/readiness/liveness probe capability.
+ *
+ * <p>
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=HealthResource.class)}
+ * under a parent at the subtree {@code /health} and serves the {@code /healthz}, {@code /readyz}, and
+ * {@code /livez} probe endpoints by delegating to a shared {@link HealthAggregator} worker bean &mdash;
+ * the same logic the {@link HealthServlet servlet} and {@link HealthMixin mixin} flavors use, so the
+ * three forms cannot drift.
+ *
+ * <p>
+ * Indicators are resolved from the host's bean store, so a host registers its own
+ * {@link HealthIndicator} beans (e.g. via Spring {@code @Bean} or a Juneau {@code BeanStore}).
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link HealthServlet}
+ * 	<li class='jc'>{@link HealthMixin}
+ * 	<li class='jc'>{@link HealthAggregator}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest(path="/health")
+public class HealthResource extends RestResource {
+
+	private final transient HealthAggregator aggregator = new HealthAggregator();
+
+	/**
+	 * Health probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/healthz")
+	public HealthResponse healthz(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), null, res);
+	}
+
+	/**
+	 * Readiness probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/readyz")
+	public HealthResponse readyz(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), HealthProbe.READY, res);
+	}
+
+	/**
+	 * Liveness probe endpoint.
+	 *
+	 * @param req The request.
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/livez")
+	public HealthResponse livez(RestRequest req, RestResponse res) {
+		return aggregator.aggregate(req.getContext(), aggregator.indicators(req.getContext()), HealthProbe.LIVE, res);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthServlet.java
new file mode 100644
index 0000000000..c40114c460
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/health/HealthServlet.java
@@ -0,0 +1,93 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.health;
+
+import java.util.*;
+
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.health.HealthAggregator.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Servlet flavor of the built-in health/readiness/liveness probe capability.
+ *
+ * <p>
+ * Mounts as a standalone top-level servlet at {@code /healthz}, {@code /readyz}, and {@code /livez},
+ * delegating to a shared {@link HealthAggregator} worker bean. The {@link HealthMixin} mixin flavor
+ * delegates to the same bean, so the two forms cannot drift.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link HealthMixin}
+ * 	<li class='jc'>{@link HealthAggregator}
+ * </ul>
+ *
+ * @serial exclude
+ * @since 9.5.0
+ */
+@Rest(paths={"/healthz","/readyz","/livez"})
+public class HealthServlet extends BasicRestServlet {
+	private static final long serialVersionUID = 1L;
+
+	private final transient HealthAggregator aggregator = new HealthAggregator();
+
+	/**
+	 * Health probe endpoint.
+	 *
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/healthz")
+	public HealthResponse healthz(RestResponse res) {
+		return aggregator.aggregate(getContext(), indicators(), null, res);
+	}
+
+	/**
+	 * Readiness probe endpoint.
+	 *
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/readyz")
+	public HealthResponse readyz(RestResponse res) {
+		return aggregator.aggregate(getContext(), indicators(), HealthProbe.READY, res);
+	}
+
+	/**
+	 * Liveness probe endpoint.
+	 *
+	 * @param res The response.
+	 * @return Aggregated health payload.
+	 */
+	@RestGet(path="/livez")
+	public HealthResponse livez(RestResponse res) {
+		return aggregator.aggregate(getContext(), indicators(), HealthProbe.LIVE, res);
+	}
+
+	/**
+	 * Returns the indicators to evaluate for this request.
+	 *
+	 * <p>
+	 * Defaults to every {@link HealthIndicator} bean registered in this resource's bean store.
+	 * Subclasses may override to supply a fixed indicator set.
+	 *
+	 * @return Indicator map keyed by bean name.
+	 */
+	protected Map<String,HealthIndicator> indicators() {
+		return aggregator.indicators(getContext());
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminMixin.java
similarity index 95%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminMixin.java
index 39bb9743d8..562fac3866 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminMixin.java
@@ -35,8 +35,8 @@ import org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
  * heap statistics, configurable cache-flush hooks, and rate-limit-bucket inspection.
  *
  * <p>
- * Sibling of {@link BasicEchoResource} ({@code /echo/*} / {@code /debug/echo/*}) and
- * {@link BasicRouteIndexResource} ({@code /options} / {@code /routes}). All three classes live in
+ * Sibling of {@link EchoMixin} ({@code /echo/*} / {@code /debug/echo/*}) and
+ * {@link RouteIndexMixin} ({@code /options} / {@code /routes}). All three classes live in
  * the {@code org.apache.juneau.rest.ops} ops/introspection mixin pack.
  *
  * <h5 class='section'>Default-deny security posture:</h5>
@@ -86,7 +86,7 @@ import org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicAdminResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=AdminMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
  *
  * 		<jc>// Required: register an auth guard chain to unlock the admin paths.</jc>
@@ -97,8 +97,8 @@ import org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
  * 				.build();
  * 		}
  *
- * 		<ja>@Bean</ja> BasicAdminResource admin() {
- * 			<jk>return</jk> BasicAdminResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> AdminMixin admin() {
+ * 			<jk>return</jk> AdminMixin.<jsm>create</jsm>()
  * 				.cacheFlush(<js>"primary"</js>, () -&gt; primaryCache.invalidateAll())
  * 				.build();
  * 		}
@@ -136,8 +136,8 @@ import org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
  * stays out of any generated Swagger / OpenAPI spec.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicEchoResource}
- * 	<li class='jc'>{@link BasicRouteIndexResource}
+ * 	<li class='jc'>{@link EchoMixin}
+ * 	<li class='jc'>{@link RouteIndexMixin}
  * 	<li class='jc'>{@link DenyAllGuard}
  * 	<li class='jc'>{@link RestGuardList}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
@@ -149,7 +149,7 @@ import org.apache.juneau.rest.guard.RateLimitGuard.BucketState;
 @Rest(
 	guards=DenyAllGuard.class
 )
-public class BasicAdminResource {
+public class AdminMixin {
 
 	/**
 	 * Default thread-name-prefix exclude list: filter out JVM internals, servlet container, and
@@ -175,7 +175,7 @@ public class BasicAdminResource {
 	private final List<String> threadNamePrefixExclude;
 
 	/** No-arg constructor &mdash; uses the default thread filter and no cache-flush hooks. */
-	public BasicAdminResource() {
+	public AdminMixin() {
 		this(create());
 	}
 
@@ -184,7 +184,7 @@ public class BasicAdminResource {
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicAdminResource(Builder builder) {
+	protected AdminMixin(Builder builder) {
 		cacheFlushHooks = Collections.unmodifiableMap(new LinkedHashMap<>(builder.cacheFlushHooks));
 		threadNamePrefixExclude = List.copyOf(builder.threadNamePrefixExclude);
 	}
@@ -391,14 +391,14 @@ public class BasicAdminResource {
 	}
 
 	/**
-	 * Builder for {@link BasicAdminResource} instances.
+	 * Builder for {@link AdminMixin} instances.
 	 */
 	public static class Builder {
 
 		private final Map<String,Runnable> cacheFlushHooks = new LinkedHashMap<>();
 		private final List<String> threadNamePrefixExclude = new ArrayList<>(DEFAULT_THREAD_NAME_PREFIX_EXCLUDE);
 
-		/** Constructor &mdash; package access for {@link BasicAdminResource#create()}. */
+		/** Constructor &mdash; package access for {@link AdminMixin#create()}. */
 		protected Builder() {}
 
 		/**
@@ -454,12 +454,12 @@ public class BasicAdminResource {
 		}
 
 		/**
-		 * Builds a {@link BasicAdminResource} instance.
+		 * Builds a {@link AdminMixin} instance.
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicAdminResource build() {
-			return new BasicAdminResource(this);
+		public AdminMixin build() {
+			return new AdminMixin(this);
 		}
 
 		/**
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminResource.java
similarity index 67%
copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java
copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminResource.java
index 1ee5c9a64b..b7a8198b4b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminResource.java
@@ -26,69 +26,57 @@ import org.apache.juneau.rest.guard.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicAdminResource} mixin.
+ * Child-resource flavor of the {@link AdminMixin} mixin.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /admin/*} and serves the same operational
- * endpoints ({@code /threads}, {@code /heap}, {@code /cache/flush}, {@code /ratelimit}) as the
- * mixin by delegating to a shared {@link BasicAdminResource} instance &mdash; so the two forms
- * cannot drift.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=AdminResource.class)} under
+ * a parent at the subtree {@code /admin} and serves the same operational endpoints ({@code /threads},
+ * {@code /heap}, {@code /cache/flush}, {@code /ratelimit}) as the mixin by delegating to a shared
+ * {@link AdminMixin} instance &mdash; so the forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicAdminResource} mixin pins its ops at {@code /admin/<endpoint>} for
- * composition into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/admin/*")}) and pins the ops at the bare endpoint names
- * (e.g. {@code /threads}) &mdash; eliminating the path-doubling that mixin-shaped ops would produce
- * when the servlet itself is mounted at {@code /admin/*}.
+ * Whereas the {@link AdminMixin} mixin pins its ops at {@code /admin/<endpoint>} for composition into a
+ * host at {@code /}, this child declares the subtree at the class level
+ * ({@link Rest#path() @Rest(path="/admin")}) and pins the ops at the bare endpoint names
+ * (e.g. {@code /threads}).
  *
  * <h5 class='section'>Default-deny security posture:</h5>
  *
  * <p>
- * Like the mixin, this servlet is annotated {@link Rest#guards() @Rest(guards=DenyAllGuard.class)},
- * so every admin path returns {@code 403 Forbidden} until the importer registers a
- * {@link RestGuardList} factory. Pair the servlet with whatever authentication / authorization
- * story your service uses.
- *
- * <h5 class='figure'>Microservice registration:</h5>
- *
- * <p class='bjava'>
- * 	<ja>@Bean</ja> Servlet admin() {
- * 		<jk>return new</jk> BasicAdminServlet();  <jc>// auto-mounted at /admin/* by JettyServerComponent</jc>
- * 	}
- * </p>
+ * Like the mixin and servlet flavors, this child is annotated
+ * {@link Rest#guards() @Rest(guards=DenyAllGuard.class)}, so every admin path returns
+ * {@code 403 Forbidden} until the importer registers a {@link RestGuardList} factory.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicAdminResource}
+ * 	<li class='jc'>{@link AdminMixin}
+ * 	<li class='jc'>{@link AdminServlet}
  * 	<li class='jc'>{@link DenyAllGuard}
  * 	<li class='jc'>{@link RestGuardList}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 // @formatter:off
 @Rest(
-	paths="/admin/*",
+	path="/admin",
 	guards=DenyAllGuard.class
 )
-public class BasicAdminServlet extends RestServlet {
-
-	private static final long serialVersionUID = 1L;
+public class AdminResource extends RestResource {
 
-	private final transient BasicAdminResource delegate;
+	private final transient AdminMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicAdminResource} delegate. */
-	public BasicAdminServlet() {
-		this(new BasicAdminResource());
+	/** No-arg constructor &mdash; uses a default {@link AdminMixin} delegate. */
+	public AdminResource() {
+		this(new AdminMixin());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared admin mixin this servlet delegates to. Must not be {@code null}.
+	 * @param delegate The shared admin mixin this child delegates to. Must not be {@code null}.
 	 */
-	protected BasicAdminServlet(BasicAdminResource delegate) {
+	protected AdminResource(AdminMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminServlet.java
similarity index 87%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminServlet.java
index 1ee5c9a64b..dd46807e7f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicAdminServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/AdminServlet.java
@@ -26,16 +26,16 @@ import org.apache.juneau.rest.guard.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicAdminResource} mixin.
+ * Standalone servlet companion of the {@link AdminMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /admin/*} and serves the same operational
  * endpoints ({@code /threads}, {@code /heap}, {@code /cache/flush}, {@code /ratelimit}) as the
- * mixin by delegating to a shared {@link BasicAdminResource} instance &mdash; so the two forms
+ * mixin by delegating to a shared {@link AdminMixin} instance &mdash; so the two forms
  * cannot drift.
  *
  * <p>
- * Whereas the {@link BasicAdminResource} mixin pins its ops at {@code /admin/<endpoint>} for
+ * Whereas the {@link AdminMixin} mixin pins its ops at {@code /admin/<endpoint>} for
  * composition into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/admin/*")}) and pins the ops at the bare endpoint names
  * (e.g. {@code /threads}) &mdash; eliminating the path-doubling that mixin-shaped ops would produce
@@ -53,12 +53,12 @@ import org.apache.juneau.rest.servlet.*;
  *
  * <p class='bjava'>
  * 	<ja>@Bean</ja> Servlet admin() {
- * 		<jk>return new</jk> BasicAdminServlet();  <jc>// auto-mounted at /admin/* by JettyServerComponent</jc>
+ * 		<jk>return new</jk> AdminServlet();  <jc>// auto-mounted at /admin/* by JettyServerComponent</jc>
  * 	}
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicAdminResource}
+ * 	<li class='jc'>{@link AdminMixin}
  * 	<li class='jc'>{@link DenyAllGuard}
  * 	<li class='jc'>{@link RestGuardList}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
@@ -72,15 +72,15 @@ import org.apache.juneau.rest.servlet.*;
 	paths="/admin/*",
 	guards=DenyAllGuard.class
 )
-public class BasicAdminServlet extends RestServlet {
+public class AdminServlet extends RestServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicAdminResource delegate;
+	private final transient AdminMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicAdminResource} delegate. */
-	public BasicAdminServlet() {
-		this(new BasicAdminResource());
+	/** No-arg constructor &mdash; uses a default {@link AdminMixin} delegate. */
+	public AdminServlet() {
+		this(new AdminMixin());
 	}
 
 	/**
@@ -88,7 +88,7 @@ public class BasicAdminServlet extends RestServlet {
 	 *
 	 * @param delegate The shared admin mixin this servlet delegates to. Must not be {@code null}.
 	 */
-	protected BasicAdminServlet(BasicAdminResource delegate) {
+	protected AdminServlet(AdminMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/EchoMixin.java
similarity index 94%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/EchoMixin.java
index 0c3cb1eeb4..ef09659c41 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicEchoResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/EchoMixin.java
@@ -31,13 +31,13 @@ import org.apache.juneau.rest.annotation.*;
  * (configurable).
  *
  * <p>
- * Sibling of {@link BasicAdminResource} ({@code /admin/*}) and {@link BasicRouteIndexResource}
+ * Sibling of {@link AdminMixin} ({@code /admin/*}) and {@link RouteIndexMixin}
  * ({@code /options}, {@code /routes}). All three classes live in the
  * {@code org.apache.juneau.rest.ops} ops/introspection mixin pack.
  *
  * <p>
  * Compose into a host resource via
- * {@link Rest#mixins() @Rest(mixins=BasicEchoResource.class)}; the {@code /echo/*} URL becomes
+ * {@link Rest#mixins() @Rest(mixins=EchoMixin.class)}; the {@code /echo/*} URL becomes
  * available alongside the host's own endpoints with no further wiring.
  *
  * <h5 class='section'>Configurable mount path:</h5>
@@ -76,14 +76,14 @@ import org.apache.juneau.rest.annotation.*;
  * <p class='bjava'>
  * 	<ja>@Rest</ja>(
  * 		path=<js>"/api"</js>,
- * 		mixins=BasicEchoResource.<jk>class</jk>,
+ * 		mixins=EchoMixin.<jk>class</jk>,
  * 		debug=<js>"conditional"</js>          <jc>// gates the echo endpoint per request</jc>
  * 	)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet { }
  *
  * 	<jc>// Optional: tighten the body cap or adjust redacted headers via a @Bean factory:</jc>
- * 	<ja>@Bean</ja> BasicEchoResource echo() {
- * 		<jk>return</jk> BasicEchoResource.<jsm>create</jsm>()
+ * 	<ja>@Bean</ja> EchoMixin echo() {
+ * 		<jk>return</jk> EchoMixin.<jsm>create</jsm>()
  * 			.bodyLimit(64 * 1024L)
  * 			.redactHeader(<js>"X-Internal-Trace"</js>)
  * 			.build();
@@ -144,8 +144,8 @@ import org.apache.juneau.rest.annotation.*;
  * {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicAdminResource}
- * 	<li class='jc'>{@link BasicRouteIndexResource}
+ * 	<li class='jc'>{@link AdminMixin}
+ * 	<li class='jc'>{@link RouteIndexMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -153,7 +153,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicEchoResource {
+public class EchoMixin {
 
 	/** Sentinel value emitted in place of redacted header values. */
 	public static final String REDACTED = "[REDACTED]";
@@ -181,7 +181,7 @@ public class BasicEchoResource {
 	private final Set<String> redactedHeadersLower;
 
 	/** No-arg constructor &mdash; uses {@link #DEFAULT_BODY_LIMIT} and {@link #DEFAULT_REDACTED_HEADERS}. */
-	public BasicEchoResource() {
+	public EchoMixin() {
 		this(create());
 	}
 
@@ -190,7 +190,7 @@ public class BasicEchoResource {
 	 *
 	 * @param builder The builder.
 	 */
-	protected BasicEchoResource(Builder builder) {
+	protected EchoMixin(Builder builder) {
 		bodyLimit = builder.bodyLimit;
 		var s = new LinkedHashSet<String>();
 		for (var h : builder.redactedHeaders)
@@ -316,14 +316,14 @@ public class BasicEchoResource {
 	}
 
 	/**
-	 * Builder for {@link BasicEchoResource} instances.
+	 * Builder for {@link EchoMixin} instances.
 	 */
 	public static class Builder {
 
 		private long bodyLimit = DEFAULT_BODY_LIMIT;
 		private final Set<String> redactedHeaders;
 
-		/** Constructor &mdash; package access for {@link BasicEchoResource#create()}. */
+		/** Constructor &mdash; package access for {@link EchoMixin#create()}. */
 		protected Builder() {
 			redactedHeaders = new LinkedHashSet<>(DEFAULT_REDACTED_HEADERS);
 		}
@@ -379,12 +379,12 @@ public class BasicEchoResource {
 		}
 
 		/**
-		 * Builds a {@link BasicEchoResource} instance.
+		 * Builds a {@link EchoMixin} instance.
 		 *
 		 * @return A configured instance.
 		 */
-		public BasicEchoResource build() {
-			return new BasicEchoResource(this);
+		public EchoMixin build() {
+			return new EchoMixin(this);
 		}
 	}
 }
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/ErrorMixin.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/ErrorMixin.java
new file mode 100644
index 0000000000..eb659e126a
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/ErrorMixin.java
@@ -0,0 +1,56 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.ops;
+
+import static org.apache.juneau.http.HttpMethod.*;
+
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Mixin providing the residual {@code [* /error]} endpoint.
+ *
+ * <p>
+ * Single-responsibility op-mixin carved out of the former {@code BasicRestOperations} interface. Compose
+ * into a host via {@link Rest#mixins() @Rest(mixins=ErrorMixin.class)}; the {@link BasicRestServlet} family
+ * pulls it in by default.
+ *
+ * <p>
+ * Servlet chains often redirect to {@code /error} when an error condition occurs (such as failed
+ * authentication) and set appropriate response parameters (such as a {@code WWW-Authenticate} header).
+ *
+ * @since 9.5.0
+ */
+@Rest
+public class ErrorMixin extends RestMixin {
+
+	/**
+	 * [* /error] - Error occurred.
+	 */
+	@RestOp(
+		method=ANY,
+		path="/error",
+		summary="Error occurred",
+		description={
+			"An error occurred during handling of the request.  ",
+			"Servlet chains will often automatically redirect to '/error' when any sort of error condition occurs ",
+			"(such as failed authentication) and will set appropriate response parameters ",
+			"(such as an WWW-Authenticate response header)."
+		}
+	)
+	public void error() {}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/HtdocMixin.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/HtdocMixin.java
new file mode 100644
index 0000000000..538760ce8e
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/HtdocMixin.java
@@ -0,0 +1,67 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.ops;
+
+import java.util.*;
+
+import org.apache.juneau.http.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.response.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Mixin providing the residual {@code [GET /htdocs/*]} static-file endpoint.
+ *
+ * <p>
+ * Single-responsibility op-mixin carved out of the former {@code BasicRestOperations} interface. Resolves
+ * the requested path against the active {@link org.apache.juneau.rest.staticfile.StaticFiles} bean via
+ * {@link RestContext#getStaticFiles()}.
+ *
+ * <p>
+ * The greedy {@code /htdocs/*} blob handler is not API-meaningful and is excluded from the published
+ * Swagger/OpenAPI specification via {@link OpSwagger#ignore() @OpSwagger(ignore=true)}.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link org.apache.juneau.rest.staticfile.StaticFilesMixin}
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest
+public class HtdocMixin extends RestMixin {
+
+	/**
+	 * [GET /htdocs/*] - Retrieve static file.
+	 *
+	 * @param req The HTTP request.
+	 * @param path The path to retrieve.
+	 * @param locale The locale of the HTTP request.
+	 * @return An HTTP resource representing the static file.
+	 * @throws NotFound If no resource matches the requested path.
+	 */
+	@RestGet(
+		path="/htdocs/*",
+		summary="Static files",
+		description="Static file retrieval.",
+		swagger=@OpSwagger(ignore=true)
+	)
+	public HttpResource getHtdoc(RestRequest req, @Path("/*") String path, Locale locale) throws NotFound {
+		return req.getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/RouteIndexMixin.java
similarity index 95%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/RouteIndexMixin.java
index a94828a6d7..136daf8f4c 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/BasicRouteIndexResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/RouteIndexMixin.java
@@ -30,7 +30,7 @@ import org.apache.juneau.rest.annotation.*;
  * resource (and any other mixins on it) at {@code /options} (configurable).
  *
  * <p>
- * Sibling of {@link BasicEchoResource} ({@code /echo/*}) and {@link BasicAdminResource}
+ * Sibling of {@link EchoMixin} ({@code /echo/*}) and {@link AdminMixin}
  * ({@code /admin/*}). All three classes live in the {@code org.apache.juneau.rest.ops}
  * ops/introspection mixin pack.
  *
@@ -70,7 +70,7 @@ import org.apache.juneau.rest.annotation.*;
  * <h5 class='figure'>Composition example:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicRouteIndexResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=RouteIndexMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
  * 		<ja>@RestGet</ja>(path=<js>"/items"</js>, summary=<js>"List items"</js>) <jk>public</jk> List&lt;Item&gt; items() { ... }
  * 	}
@@ -115,8 +115,8 @@ import org.apache.juneau.rest.annotation.*;
  * route-index endpoint is excluded from the OpenAPI spec.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicEchoResource}
- * 	<li class='jc'>{@link BasicAdminResource}
+ * 	<li class='jc'>{@link EchoMixin}
+ * 	<li class='jc'>{@link AdminMixin}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
@@ -124,14 +124,14 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicRouteIndexResource {
+public class RouteIndexMixin {
 
 	private static final List<Class<? extends Annotation>> REST_OP_ANNOTATIONS = List.of(
 		RestGet.class, RestPost.class, RestPut.class, RestDelete.class,
 		RestPatch.class, RestOptions.class, RestOp.class);
 
 	/** No-arg constructor &mdash; route-index has no configurable state. */
-	public BasicRouteIndexResource() {}
+	public RouteIndexMixin() {}
 
 	/**
 	 * [GET /options] &mdash; emit the route index as a JSON list.
@@ -176,7 +176,7 @@ public class BasicRouteIndexResource {
 		for (var mixinCtx : hostCtx.getMixinContexts().values())
 			for (var oc : mixinCtx.getRestOperations().getOpContexts())
 				addEntry(entries, seen, oc);
-		entries.sort(BasicRouteIndexResource::compareByPathThenMethod);
+		entries.sort(RouteIndexMixin::compareByPathThenMethod);
 		return entries;
 	}
 
@@ -218,7 +218,7 @@ public class BasicRouteIndexResource {
 	}
 
 	private static boolean isSelfHandler(Method m) {
-		return m.getDeclaringClass() == BasicRouteIndexResource.class;
+		return m.getDeclaringClass() == RouteIndexMixin.class;
 	}
 
 	@SuppressWarnings("java:S3776") // Same as isHiddenFromIndex — short loop, single concern.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/StatsMixin.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/StatsMixin.java
new file mode 100644
index 0000000000..344cadc72a
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/StatsMixin.java
@@ -0,0 +1,64 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.ops;
+
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+import org.apache.juneau.rest.stats.*;
+
+/**
+ * Mixin providing the residual {@code [GET /stats]} timing-statistics endpoint.
+ *
+ * <p>
+ * Single-responsibility op-mixin carved out of the former {@code BasicRestOperations} interface. Carries
+ * the per-method {@link HtmlDocConfig @HtmlDocConfig} that customizes the {@code /stats} HTML page (per
+ * OQ-8 &mdash; the per-method config travels with the op, while the host-wide navlinks/JSON-schema config
+ * lives at the class level on {@link BasicRestServlet}).
+ *
+ * @since 9.5.0
+ */
+@Rest
+public class StatsMixin extends RestMixin {
+
+	/**
+	 * [GET /stats] - Timing statistics.
+	 *
+	 * <p>
+	 * Timing statistics for method invocations on this resource.
+	 *
+	 * @param req The HTTP request.
+	 * @return A collection of timing statistics for each annotated method on this resource.
+	 */
+	@RestGet(
+		path="/stats",
+		summary="Timing statistics",
+		description="Timing statistics for method invocations on this resource."
+	)
+	@HtmlDocConfig(
+		// Should override config annotations defined on class.
+		rank=10,
+		// Override the nav links for the swagger page.
+		navlinks = { "back: servlet:/", "json: servlet:/stats?Accept=text/json&plainText=true" },
+		// Never show aside contents of page inherited from class.
+		aside="NONE"
+	)
+	public RestContextStats getStats(RestRequest req) {
+		return req.getContext().getStats();
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/package-info.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/package-info.java
index f28f2a722b..1438c3dfa9 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/package-info.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/ops/package-info.java
@@ -23,20 +23,20 @@
  * Three sibling mixins compose into the host {@code @Rest}-annotated resource. Each mixin owns
  * its default mount paths, ships secure-by-default (debug-gated for echo, deny-all-guard for
  * admin), and is independently mountable; the three together drop in as a pack via
- * {@code @Rest(mixins={BasicEchoResource.class, BasicAdminResource.class, BasicRouteIndexResource.class})}.
+ * {@code @Rest(mixins={EchoMixin.class, AdminMixin.class, RouteIndexMixin.class})}.
  * </p>
  *
  * <ul class='javatreec'>
- * 	<li class='jc'>{@link org.apache.juneau.rest.ops.BasicEchoResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.ops.EchoMixin} —
  * 		{@code /echo/*} and {@code /debug/echo/*} request echo, gated behind the host's
  * 		{@link org.apache.juneau.rest.debug.DebugEnablement DebugEnablement}; sensitive headers
  * 		({@code Authorization}, {@code Cookie}, etc.) are redacted by default.
- * 	<li class='jc'>{@link org.apache.juneau.rest.ops.BasicAdminResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.ops.AdminMixin} —
  * 		{@code /admin/threads}, {@code /admin/heap}, {@code /admin/cache/flush} (POST), and
  * 		{@code /admin/ratelimit}; default-deny via
  * 		{@link org.apache.juneau.rest.guard.DenyAllGuard} until the importer registers an
  * 		{@code @Bean RestGuardList} factory.
- * 	<li class='jc'>{@link org.apache.juneau.rest.ops.BasicRouteIndexResource} —
+ * 	<li class='jc'>{@link org.apache.juneau.rest.ops.RouteIndexMixin} —
  * 		{@code /options} and {@code /routes} returning a JSON list of every
  * 		{@code @RestOp}-annotated method on the host (plus mixins), excluding
  * 		{@code @OpSwagger(ignore=true)} ops.
@@ -48,11 +48,11 @@
  * 	<ja>@Rest</ja>(
  * 		path=<js>"/api"</js>,
  * 		mixins={
- * 			BasicEchoResource.<jk>class</jk>,
- * 			BasicAdminResource.<jk>class</jk>,
- * 			BasicRouteIndexResource.<jk>class</jk>
+ * 			EchoMixin.<jk>class</jk>,
+ * 			AdminMixin.<jk>class</jk>,
+ * 			RouteIndexMixin.<jk>class</jk>
  * 		},
- * 		debug=<js>"conditional"</js>            <jc>// gates BasicEchoResource per-request</jc>
+ * 		debug=<js>"conditional"</js>            <jc>// gates EchoMixin per-request</jc>
  * 	)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
  *
@@ -64,8 +64,8 @@
  * 				.build();
  * 		}
  *
- * 		<ja>@Bean</ja> BasicAdminResource admin() {
- * 			<jk>return</jk> BasicAdminResource.<jsm>create</jsm>()
+ * 		<ja>@Bean</ja> AdminMixin admin() {
+ * 			<jk>return</jk> AdminMixin.<jsm>create</jsm>()
  * 				.cacheFlush(<js>"primary"</js>, () -&gt; primaryCache.invalidateAll())
  * 				.build();
  * 		}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicGroupOperations.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicGroupOperations.java
deleted file mode 100644
index 488cc769d4..0000000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicGroupOperations.java
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.rest.servlet;
-
-import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.beans.*;
-
-/**
- * Basic REST group operation methods.
- *
- * <p>
- * 	Defines the residual {@code GET /} navigation endpoint for group resources. The legacy
- * 	{@code ?Swagger} / {@code ?OpenApi} query-mirror overloads were removed in 9.5.0; clients should
- * 	now hit {@code /api}, {@code /swagger}, {@code /openapi}, or {@code /redoc} directly (always
- * 	mounted via the api-docs mixin pack on {@code BasicRestServlet} / {@code BasicRestObject}).
- * </p>
- *
- * <p class='bjava'>
- * 	<ja>@RestGet</ja>(path=<js>"/"</js>)
- * 	<jk>public</jk> {@link ChildResourceDescriptions} {@link #getChildren(RestRequest) getChildren}({@link RestRequest} <jv>req</jv>);
- * </p>
- *
- * <p>
- * 	Implementations provided by the following classes:
- * </p>
- * <ul class='javatreec'>
- * 	<li class='jac'>{@link BasicRestServletGroup}
- * 	<li class='jac'>{@link BasicRestObjectGroup}
- * </ul>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
- * </ul>
- */
-public interface BasicGroupOperations {
-
-	/**
-	 * [GET /] - Get child resources.
-	 *
-	 * <p>
-	 * Returns a bean that lists and allows navigation to child resources. Default implementation
-	 * delegates to {@link ChildResourceDescriptions#of(RestRequest)}; subclasses may override.
-	 *
-	 * @param req The HTTP request.
-	 * @return The bean containing links to the child resources.
-	 */
-	@RestGet(path = "/", summary = "Navigation page")
-	default ChildResourceDescriptions getChildren(RestRequest req) {
-		return ChildResourceDescriptions.of(req);
-	}
-}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestOperations.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestOperations.java
deleted file mode 100644
index cc5d20e405..0000000000
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestOperations.java
+++ /dev/null
@@ -1,162 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one or more
- * contributor license agreements.  See the NOTICE file distributed with
- * this work for additional information regarding copyright ownership.
- * The ASF licenses this file to You under the Apache License, Version 2.0
- * (the "License"); you may not use this file except in compliance with
- * the License.  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-package org.apache.juneau.rest.servlet;
-
-import static org.apache.juneau.http.HttpMethod.*;
-
-import java.util.*;
-
-import org.apache.juneau.html.annotation.*;
-import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.http.response.*;
-import org.apache.juneau.jsonschema.annotation.*;
-import org.apache.juneau.rest.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.docs.*;
-import org.apache.juneau.rest.stats.*;
-
-/**
- * Basic REST operation methods.
- *
- * <p>
- * 	Defines the residual cross-cutting endpoints that are still part of the {@code BasicRestServlet}
- * 	chain after the API-docs surface ({@code /api}, {@code /openapi}, etc.) was lifted into the
- * 	{@link BasicSwaggerResource} / {@link BasicSwaggerUiResource} / {@link BasicOpenApiResource} /
- * 	{@link BasicRedocResource} mixin pack:
- * </p>
- *
- * <p class='bjava'>
- * 	<ja>@RestGet</ja>(path=<js>"/htdocs/*"</js>)
- * 	<jk>public</jk> {@link HttpResource} {@link #getHtdoc(String,Locale) getHtdoc}(<ja>@Path</ja> String <jv>path</jv>, Locale <jv>locale</jv>);
- *
- * 	<ja>@RestGet</ja>(path=<js>"favicon.ico"</js>)
- * 	<jk>public</jk> {@link HttpResource} {@link #getFavIcon() getFavIcon}();
- *
- * 	<ja>@RestGet</ja>(path=<js>"/stats"</js>)
- * 	<jk>public</jk> {@link RestContextStats} {@link #getStats(RestRequest) getStats}({@link RestRequest} <jv>req</jv>);
- *
- * 	<ja>@RestOp</ja>(method=<jsf>ANY</jsf>, path=<js>"/error"</js>)
- * 	<jk>public void</jk> {@link #error() error}();
- * </p>
- *
- * <p>
- * 	Implementations provided by the following classes:
- * </p>
- * <ul class='javatreec'>
- * 	<li class='jac'>{@link BasicRestServlet}
- * 	<li class='jac'>{@link BasicRestServletGroup}
- * 	<li class='jac'>{@link BasicRestObject}
- * 	<li class='jac'>{@link BasicRestObjectGroup}
- * </ul>
- *
- * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
- * </ul>
- */
-// @formatter:off
-@HtmlDocConfig(
-	// Basic page navigation links.
-	navlinks={
-		"up: request:/..",
-		"api: servlet:/api",
-		"stats: servlet:/stats"
-	}
-)
-@JsonSchemaConfig(
-	// Add descriptions to the following types when not specified:
-	addDescriptionsTo="bean,collection,array,map,enum",
-	// Add example to the following types:
-	addExamplesTo="bean,collection,array,map",
-	// Don't generate schema information on the Swagger / OpenApi beans themselves or HTML beans.
-	ignoreTypes="Swagger,OpenApi,org.apache.juneau.bean.html5.*",
-	// Use $ref references for bean definitions to reduce duplication in generated specs.
-	useBeanDefs="true"
-)
-public interface BasicRestOperations {
-
-	/**
-	 * [* /error] - Error occurred.
-	 */
-	@RestOp(
-		method=ANY,
-		path="/error",
-		summary="Error occurred",
-		description={
-			"An error occurred during handling of the request.  ",
-			"Servlet chains will often automatically redirect to '/error' when any sort of error condition occurs ",
-			"(such as failed authentication) and will set appropriate response parameters ",
-			"(such as an WWW-Authenticate response header)."
-		}
-	) void error();
-
-	/**
-	 * [GET favicon.ico] - Retrieve favorites icon image.
-	 *
-	 * @return A bean containing the contents for the OPTIONS page.
-	 */
-	@RestGet(
-		path="favicon.ico",
-		summary="Favorites icon.",
-		description="Favorites icon."
-	) HttpResource getFavIcon();
-
-	/**
-	 * [GET /htdocs/*] - Retrieve static file.
-	 *
-	 * <p>
-	 * Excluded from the published Swagger/OpenAPI specification via
-	 * {@link OpSwagger#ignore() @OpSwagger(ignore=true)} &mdash; a greedy {@code /htdocs/*} blob
-	 * handler is not API-meaningful, and emitting it as an operation just produces noise. This
-	 * mirrors the exclusion strategy used by the {@code BasicStaticFilesResource} mixin and keeps
-	 * legacy {@code BasicRestServlet}-hosted apps consistent with mixin-based static-file mounts.
-	 *
-	 * @param path The path to retrieve.
-	 * @param locale The locale of the HTTP request.
-	 * @return An HTTP resource representing the static file.
-	 */
-	@RestGet(
-		path="/htdocs/*",
-		summary="Static files",
-		description="Static file retrieval.",
-		swagger=@OpSwagger(ignore=true)
-	) HttpResource getHtdoc(@Path String path, Locale locale);
-
-	/**
-	 * [GET /stats] - Timing statistics.
-	 *
-	 * <p>
-	 * Timing statistics for method invocations on this resource.
-	 *
-	 * @param req The HTTP request.
-	 * @return A collection of timing statistics for each annotated method on this resource.
-	 */
-	@RestGet(
-		path="/stats",
-		summary="Timing statistics",
-		description="Timing statistics for method invocations on this resource."
-	)
-	@HtmlDocConfig(
-		// Should override config annotations defined on class.
-		rank=10,
-		// Override the nav links for the swagger page.
-		navlinks = { "back: servlet:/", "json: servlet:/stats?Accept=text/json&plainText=true" },
-		// Never show aside contents of page inherited from class.
-		aside="NONE"
-	)
-	RestContextStats getStats(RestRequest req);
-}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObject.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResource.java
similarity index 55%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObject.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResource.java
index 711e55bc6e..31cd776ec8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObject.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResource.java
@@ -16,16 +16,13 @@
  */
 package org.apache.juneau.rest.servlet;
 
-import java.util.*;
-
-import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.http.response.*;
-import org.apache.juneau.rest.*;
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.jsonschema.annotation.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.convention.*;
 import org.apache.juneau.rest.docs.*;
-import org.apache.juneau.rest.stats.*;
+import org.apache.juneau.rest.ops.*;
 
 import jakarta.servlet.http.*;
 
@@ -40,9 +37,10 @@ import jakarta.servlet.http.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations}, plus the api-docs
- * mixin pack ({@link BasicSwaggerUiResource} and {@link BasicRedocResource}, which transitively
- * pull in {@link BasicSwaggerResource} and {@link BasicOpenApiResource}). Resulting endpoints:
+ * The residual cross-cutting endpoints are supplied by single-responsibility op-mixins ({@link ErrorMixin},
+ * {@link HtdocMixin}, {@link StatsMixin}, and {@link FaviconMixin}), and the api-docs surface by the
+ * api-docs mixin pack ({@link SwaggerUiMixin} and {@link RedocMixin}, which transitively pull in
+ * {@link SwaggerMixin} and {@link OpenApiMixin}). Resulting endpoints:
  * {@code /api}, {@code /swagger}, {@code /openapi}, {@code /openapi.json}, {@code /openapi.yaml},
  * {@code /redoc}, {@code /htdocs/*}, {@code /favicon.ico}, {@code /stats}, {@code /error}.
  *
@@ -50,24 +48,25 @@ import jakarta.servlet.http.*;
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
  * </ul>
  */
-@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})
-public abstract class BasicRestObject extends RestObject implements BasicRestOperations, BasicUniversalConfig {
-	@Override /* Overridden from BasicRestOperations */
-	public void error() {}
-
-	@Override /* Overridden from BasicRestOperations */
-	public HttpResource getFavIcon() {
-		String favIcon = getContext().getConfig().get("REST/favicon").orElse("images/juneau.png");
-		return getHtdoc(favIcon, null);
-	}
-
-	@Override /* Overridden from BasicRestOperations */
-	public HttpResource getHtdoc(@Path("/*") String path, Locale locale) throws NotFound {
-		return getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
-	}
-
-	@Override /* Overridden from BasicRestOperations */
-	public RestContextStats getStats(RestRequest req) {
-		return req.getContext().getStats();
+// @formatter:off
+@Rest(mixins={SwaggerUiMixin.class, RedocMixin.class, ErrorMixin.class, HtdocMixin.class, StatsMixin.class, FaviconMixin.class})
+@HtmlDocConfig(
+	// Basic page navigation links.
+	navlinks={
+		"up: request:/..",
+		"api: servlet:/api",
+		"stats: servlet:/stats"
 	}
-}
\ No newline at end of file
+)
+@JsonSchemaConfig(
+	// Add descriptions to the following types when not specified:
+	addDescriptionsTo="bean,collection,array,map,enum",
+	// Add example to the following types:
+	addExamplesTo="bean,collection,array,map",
+	// Don't generate schema information on the Swagger / OpenApi beans themselves or HTML beans.
+	ignoreTypes="Swagger,OpenApi,org.apache.juneau.bean.html5.*",
+	// Use $ref references for bean definitions to reduce duplication in generated specs.
+	useBeanDefs="true"
+)
+// @formatter:on
+public abstract class BasicRestResource extends RestResource implements BasicUniversalConfig {}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObjectGroup.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
similarity index 84%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObjectGroup.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
index 4ee3801363..2060d99a69 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestObjectGroup.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestResourceGroup.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.servlet;
 
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
 
 import jakarta.servlet.*;
@@ -34,7 +35,10 @@ import jakarta.servlet.http.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations} and {@link BasicGroupOperations}.
+ * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on top of the residual op-mixins
+ * inherited from {@link BasicRestResource}. The navigation page is rendered as a method of the host resource
+ * (rather than a sub-context mixin) so it inherits the host's {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig}
+ * page decoration.
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() @Rest(children)} annotation.
@@ -52,7 +56,22 @@ import jakarta.servlet.http.*;
  * </ul>
  */
 @Rest
-public abstract class BasicRestObjectGroup extends BasicRestObject implements BasicGroupOperations {
+public abstract class BasicRestResourceGroup extends BasicRestResource {
+
+	/**
+	 * [GET /] - Get child resources.
+	 *
+	 * <p>
+	 * Returns a bean that lists and allows navigation to child resources. Default implementation
+	 * delegates to {@link ChildResourceDescriptions#of(RestRequest)}; subclasses may override.
+	 *
+	 * @param req The HTTP request.
+	 * @return The bean containing links to the child resources.
+	 */
+	@RestGet(path="/", summary="Navigation page")
+	public ChildResourceDescriptions getChildren(RestRequest req) {
+		return ChildResourceDescriptions.of(req);
+	}
 
 	/**
 	 * Returns the {@link RestChildren} registry backing this group resource.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServlet.java
index 492d8a0f88..ef42cc857a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServlet.java
@@ -16,19 +16,16 @@
  */
 package org.apache.juneau.rest.servlet;
 
-import java.util.*;
-
-import org.apache.juneau.http.annotation.*;
-import org.apache.juneau.http.*;
-import org.apache.juneau.http.response.*;
-import org.apache.juneau.rest.*;
+import org.apache.juneau.html.annotation.*;
+import org.apache.juneau.jsonschema.annotation.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.config.*;
+import org.apache.juneau.rest.convention.*;
 import org.apache.juneau.rest.docs.*;
-import org.apache.juneau.rest.stats.*;
+import org.apache.juneau.rest.ops.*;
 
 /**
- * Subclass of {@link RestServlet} with default settings and standard methods defined.
+ * Subclass of {@link RestServlet} with default settings and standard endpoints defined.
  *
  * <p>
  * Meant as a base class for top-level REST resources in servlet containers.
@@ -38,9 +35,10 @@ import org.apache.juneau.rest.stats.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations}, plus the api-docs
- * mixin pack ({@link BasicSwaggerUiResource} and {@link BasicRedocResource}, which transitively
- * pull in {@link BasicSwaggerResource} and {@link BasicOpenApiResource}). Resulting endpoints:
+ * The residual cross-cutting endpoints are supplied by single-responsibility op-mixins ({@link ErrorMixin},
+ * {@link HtdocMixin}, {@link StatsMixin}, and {@link FaviconMixin}), and the api-docs surface by the
+ * api-docs mixin pack ({@link SwaggerUiMixin} and {@link RedocMixin}, which transitively pull in
+ * {@link SwaggerMixin} and {@link OpenApiMixin}). Resulting endpoints:
  * {@code /api}, {@code /swagger}, {@code /openapi}, {@code /openapi.json}, {@code /openapi.yaml},
  * {@code /redoc}, {@code /htdocs/*}, {@code /favicon.ico}, {@code /stats}, {@code /error}.
  *
@@ -50,26 +48,27 @@ import org.apache.juneau.rest.stats.*;
  *
  * @serial exclude
  */
-@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})
-public abstract class BasicRestServlet extends RestServlet implements BasicRestOperations, BasicUniversalConfig {
-	private static final long serialVersionUID = 1L;
-
-	@Override /* Overridden from BasicRestOperations */
-	public void error() {}
-
-	@Override /* Overridden from BasicRestOperations */
-	public HttpResource getFavIcon() {
-		String favIcon = getContext().getConfig().get("REST/favicon").orElse("images/juneau.png");
-		return getHtdoc(favIcon, null);
+// @formatter:off
+@Rest(mixins={SwaggerUiMixin.class, RedocMixin.class, ErrorMixin.class, HtdocMixin.class, StatsMixin.class, FaviconMixin.class})
+@HtmlDocConfig(
+	// Basic page navigation links.
+	navlinks={
+		"up: request:/..",
+		"api: servlet:/api",
+		"stats: servlet:/stats"
 	}
-
-	@Override /* Overridden from BasicRestOperations */
-	public HttpResource getHtdoc(@Path("/*") String path, Locale locale) throws NotFound {
-		return getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
-	}
-
-	@Override /* Overridden from BasicRestOperations */
-	public RestContextStats getStats(RestRequest req) {
-		return req.getContext().getStats();
-	}
-}
\ No newline at end of file
+)
+@JsonSchemaConfig(
+	// Add descriptions to the following types when not specified:
+	addDescriptionsTo="bean,collection,array,map,enum",
+	// Add example to the following types:
+	addExamplesTo="bean,collection,array,map",
+	// Don't generate schema information on the Swagger / OpenApi beans themselves or HTML beans.
+	ignoreTypes="Swagger,OpenApi,org.apache.juneau.bean.html5.*",
+	// Use $ref references for bean definitions to reduce duplication in generated specs.
+	useBeanDefs="true"
+)
+// @formatter:on
+public abstract class BasicRestServlet extends RestServlet implements BasicUniversalConfig {
+	private static final long serialVersionUID = 1L;
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
index 46316db13a..a7163e13a9 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/BasicRestServletGroup.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.servlet;
 
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.beans.*;
 import org.apache.juneau.rest.config.*;
 
 import jakarta.servlet.*;
@@ -33,7 +34,10 @@ import jakarta.servlet.*;
  * for details.
  *
  * <p>
- * Implements the basic REST endpoints defined in {@link BasicRestOperations} and {@link BasicGroupOperations}.
+ * Adds the group-navigation endpoint ({@code GET /}) as a concrete method on top of the residual op-mixins
+ * inherited from {@link BasicRestServlet}. The navigation page is rendered as a method of the host resource
+ * (rather than a sub-context mixin) so it inherits the host's {@link org.apache.juneau.html.annotation.HtmlDocConfig @HtmlDocConfig}
+ * page decoration.
  *
  * <p>
  * Children are attached to this resource using the {@link Rest#children() @Rest(children)} annotation.
@@ -53,9 +57,24 @@ import jakarta.servlet.*;
  * @serial exclude
  */
 @Rest
-public abstract class BasicRestServletGroup extends BasicRestServlet implements BasicGroupOperations {
+public abstract class BasicRestServletGroup extends BasicRestServlet {
 	private static final long serialVersionUID = 1L;
 
+	/**
+	 * [GET /] - Get child resources.
+	 *
+	 * <p>
+	 * Returns a bean that lists and allows navigation to child resources. Default implementation
+	 * delegates to {@link ChildResourceDescriptions#of(RestRequest)}; subclasses may override.
+	 *
+	 * @param req The HTTP request.
+	 * @return The bean containing links to the child resources.
+	 */
+	@RestGet(path="/", summary="Navigation page")
+	public ChildResourceDescriptions getChildren(RestRequest req) {
+		return ChildResourceDescriptions.of(req);
+	}
+
 	/**
 	 * Returns the {@link RestChildren} registry backing this group resource.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
new file mode 100644
index 0000000000..d97cd3eb16
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestMixin.java
@@ -0,0 +1,58 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.servlet;
+
+import org.apache.juneau.rest.annotation.*;
+
+/**
+ * Optional base class for <c>@Rest</c> mixin classes &mdash; the third sibling of {@link RestServlet}
+ * (servlet flavor) and {@link RestResource} (child-resource flavor), completing the
+ * servlet / resource / mixin naming triad.
+ *
+ * <p>
+ * A <i>mixin</i> is a plain {@link Rest @Rest}-annotated POJO whose {@code @RestOp} methods are pulled
+ * into a host servlet/resource context when the host declares
+ * {@link Rest#mixins() @Rest(mixins=ThatMixin.class)}. Mixins compose through the existing sub-context
+ * model; {@code RestMixin} does <b>not</b> change that composition mechanism.
+ *
+ * <h5 class='section'>Opt-in, not mandatory:</h5>
+ *
+ * <p>
+ * Extending {@code RestMixin} is <b>optional</b>. A bare {@code @Rest} POJO with no base class remains a
+ * fully-supported mixin (annotation-only configuration), exactly as before &mdash; the framework reads
+ * such mixins reflectively. Capability mixins that ship without state (e.g. the api-docs pack) stay
+ * base-less; the new single-responsibility op-mixins extend this base to make the triad membership
+ * explicit.
+ *
+ * <h5 class='section'>Builder support (deferred):</h5>
+ *
+ * <p>
+ * The fluent programmatic-builder surface ({@code RestBuilder}/{@code RestMixin.Builder}) that would let
+ * a mixin be configured programmatically rather than by annotation is <b>not</b> part of this base yet
+ * &mdash; it is deferred along with the resource/servlet builder work. Until then, {@code RestMixin}
+ * carries no builder or stashed-builder state and mixins are configured purely by their {@code @Rest} /
+ * {@code @RestOp} annotations.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link RestServlet}
+ * 	<li class='jc'>{@link RestResource}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+public abstract class RestMixin {}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
similarity index 99%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
index bb277b13fc..69c72d48a3 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestObject.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/servlet/RestResource.java
@@ -41,7 +41,7 @@ import jakarta.servlet.http.*;
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestAnnotatedClassBasics">@Rest-Annotated Class Basics</a>
  * </ul>
  */
-public abstract class RestObject {
+public abstract class RestResource {
 
 	private AtomicReference<RestContext> context = new AtomicReference<>();
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesMixin.java
similarity index 95%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesMixin.java
index 28a7162aa0..afcff8157c 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesResource.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesMixin.java
@@ -31,7 +31,7 @@ import org.apache.juneau.rest.annotation.*;
  * <p>
  * Wraps the existing {@link BasicStaticFiles} plumbing (a {@link StaticFiles} implementation, not a
  * servlet) in a servlet-level mixin so any Juneau resource can opt into static-file serving via
- * {@link Rest#mixins() @Rest(mixins=BasicStaticFilesResource.class)}. The mixin reads the active
+ * {@link Rest#mixins() @Rest(mixins=StaticFilesMixin.class)}. The mixin reads the active
  * {@link StaticFiles} from the bean store at request time &mdash; importer's
  * {@code @Bean StaticFiles} declarations + classpath defaults are picked up automatically.
  *
@@ -74,7 +74,7 @@ import org.apache.juneau.rest.annotation.*;
  *
  * <p class='bjava'>
  * 	<jc>// Vanilla resource gains static-file serving at /static/*.</jc>
- * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=BasicStaticFilesResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/api"</js>, mixins=StaticFilesMixin.<jk>class</jk>)
  * 	<jk>public class</jk> ApiResource <jk>extends</jk> RestServlet {
  * 		<ja>@RestGet</ja>(<js>"/items"</js>) <jk>public</jk> List&lt;Item&gt; items() { ... }
  * 	}
@@ -84,7 +84,7 @@ import org.apache.juneau.rest.annotation.*;
  *
  * <ul class='spaced-list'>
  * 	<li>GET hits the active {@link StaticFiles} bean (resolved via
- * 		{@link RestRequest#getStaticFiles()}); missing paths surface as
+ * 		{@link RestContext#getStaticFiles()}); missing paths surface as
  * 		{@link NotFound} thrown from the handler.
  * 	<li>{@code HEAD} requests are accepted via the standard servlet
  * 		{@code HEAD}-via-{@code GET} contract: the response carries identical headers to the
@@ -129,7 +129,7 @@ import org.apache.juneau.rest.annotation.*;
  */
 // @formatter:off
 @Rest
-public class BasicStaticFilesResource {
+public class StaticFilesMixin {
 
 	/**
 	 * [GET /static/*] &mdash; serve a static file from the active {@link StaticFiles} bean.
@@ -140,7 +140,7 @@ public class BasicStaticFilesResource {
 	 * {@link StaticFiles#resolve(String,Locale) StaticFiles.resolve(...)} so localized lookups
 	 * (e.g. {@code styles_fr.css}) work out of the box.
 	 *
-	 * @param req The current REST request &mdash; supplies {@link RestRequest#getStaticFiles()}.
+	 * @param req The current REST request &mdash; supplies {@link RestContext#getStaticFiles()}.
 	 * @param path The trailing remainder after the mount prefix (the file path within the
 	 * 	{@code static/} or {@code htdocs/} classpath / filesystem search roots).
 	 * @param locale The request locale (used for localized resource lookups).
@@ -154,7 +154,7 @@ public class BasicStaticFilesResource {
 		swagger=@OpSwagger(ignore=true)
 	)
 	public HttpResource getStaticFile(RestRequest req, @Path("/*") String path, Locale locale) {
-		return req.getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
+		return req.getContext().getStaticFiles().resolve(path, locale).orElseThrow(NotFound::new);
 	}
 
 	/**
@@ -167,7 +167,7 @@ public class BasicStaticFilesResource {
 	 * at the response-processor layer based on {@link RestRequest#getMethod()}, so this handler can
 	 * delegate to {@link #getStaticFile} verbatim.
 	 *
-	 * @param req The current REST request &mdash; supplies {@link RestRequest#getStaticFiles()}.
+	 * @param req The current REST request &mdash; supplies {@link RestContext#getStaticFiles()}.
 	 * @param path The trailing remainder after the mount prefix.
 	 * @param locale The request locale (used for localized resource lookups).
 	 * @return The matching {@link HttpResource} (with headers; body suppressed by the processor).
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesResource.java
similarity index 65%
copy from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java
copy to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesResource.java
index c97d1173f9..a597bc4873 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesResource.java
@@ -26,58 +26,48 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicStaticFilesResource} mixin.
+ * Child-resource flavor of the static-files capability.
  *
  * <p>
- * Mounts as a <b>sibling top-level servlet</b> at {@code /static/*} and serves static files from the
- * active {@link StaticFiles} bean by delegating to a shared {@link BasicStaticFilesResource}
- * instance &mdash; the same logic the mixin uses, so the two forms cannot drift.
+ * Mounts as a <b>routed child</b> via {@link Rest#children() @Rest(children=StaticFilesResource.class)}
+ * under a parent at the subtree {@code /static} and serves static files from the active
+ * {@link StaticFiles} bean by delegating to a shared {@link StaticFilesMixin} instance &mdash; the same
+ * logic the mixin and {@link StaticFilesServlet servlet} flavors use, so the three forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicStaticFilesResource} mixin pins its op at {@code /static/*} for composition
- * into a host at {@code /}, this companion declares the mount at the class level
- * ({@link Rest#paths() @Rest(paths="/static/*")}) and pins the ops at {@code /*} &mdash; eliminating
- * the path-doubling that a mixin-shaped op would produce when the servlet itself is mounted at
- * {@code /static/*}.
- *
- * <h5 class='figure'>Microservice registration:</h5>
- *
- * <p class='bjava'>
- * 	<ja>@Bean</ja> Servlet staticFiles() {
- * 		<jk>return new</jk> BasicStaticFilesServlet();  <jc>// auto-mounted at /static/* by JettyServerComponent</jc>
- * 	}
- * </p>
+ * Whereas the {@link StaticFilesMixin} mixin pins its op at {@code /static/*} to merge into a host's
+ * namespace, this child declares the subtree at the class level ({@link Rest#path() @Rest(path="/static")})
+ * and pins the ops at {@code /*} &mdash; the op must <b>not</b> re-pin {@code /static/*} or it would
+ * double-path to {@code /static/static/*} under the routed-subtree wiring.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicStaticFilesResource}
+ * 	<li class='jc'>{@link StaticFilesMixin}
+ * 	<li class='jc'>{@link StaticFilesServlet}
  * 	<li class='jc'>{@link StaticFiles}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/StaticFiles">Static files</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
  *
- * @serial exclude
  * @since 9.5.0
  */
 // @formatter:off
-@Rest(paths="/static/*")
-public class BasicStaticFilesServlet extends RestServlet {
-
-	private static final long serialVersionUID = 1L;
+@Rest(path="/static")
+public class StaticFilesResource extends RestResource {
 
-	private final transient BasicStaticFilesResource delegate;
+	private final transient StaticFilesMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicStaticFilesResource} delegate. */
-	public BasicStaticFilesServlet() {
-		this(new BasicStaticFilesResource());
+	/** No-arg constructor &mdash; uses a default {@link StaticFilesMixin} delegate. */
+	public StaticFilesResource() {
+		this(new StaticFilesMixin());
 	}
 
 	/**
 	 * Delegate constructor.
 	 *
-	 * @param delegate The shared static-files mixin this servlet delegates to. Must not be
+	 * @param delegate The shared static-files mixin this child delegates to. Must not be
 	 * 	{@code null}.
 	 */
-	protected BasicStaticFilesServlet(BasicStaticFilesResource delegate) {
+	protected StaticFilesResource(StaticFilesMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesServlet.java
similarity index 84%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesServlet.java
index c97d1173f9..0a2269d6af 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/BasicStaticFilesServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/staticfile/StaticFilesServlet.java
@@ -26,15 +26,15 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
 
 /**
- * Standalone servlet companion of the {@link BasicStaticFilesResource} mixin.
+ * Standalone servlet companion of the {@link StaticFilesMixin} mixin.
  *
  * <p>
  * Mounts as a <b>sibling top-level servlet</b> at {@code /static/*} and serves static files from the
- * active {@link StaticFiles} bean by delegating to a shared {@link BasicStaticFilesResource}
+ * active {@link StaticFiles} bean by delegating to a shared {@link StaticFilesMixin}
  * instance &mdash; the same logic the mixin uses, so the two forms cannot drift.
  *
  * <p>
- * Whereas the {@link BasicStaticFilesResource} mixin pins its op at {@code /static/*} for composition
+ * Whereas the {@link StaticFilesMixin} mixin pins its op at {@code /static/*} for composition
  * into a host at {@code /}, this companion declares the mount at the class level
  * ({@link Rest#paths() @Rest(paths="/static/*")}) and pins the ops at {@code /*} &mdash; eliminating
  * the path-doubling that a mixin-shaped op would produce when the servlet itself is mounted at
@@ -44,12 +44,12 @@ import org.apache.juneau.rest.servlet.*;
  *
  * <p class='bjava'>
  * 	<ja>@Bean</ja> Servlet staticFiles() {
- * 		<jk>return new</jk> BasicStaticFilesServlet();  <jc>// auto-mounted at /static/* by JettyServerComponent</jc>
+ * 		<jk>return new</jk> StaticFilesServlet();  <jc>// auto-mounted at /static/* by JettyServerComponent</jc>
  * 	}
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicStaticFilesResource}
+ * 	<li class='jc'>{@link StaticFilesMixin}
  * 	<li class='jc'>{@link StaticFiles}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/StaticFiles">Static files</a>
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
@@ -60,15 +60,15 @@ import org.apache.juneau.rest.servlet.*;
  */
 // @formatter:off
 @Rest(paths="/static/*")
-public class BasicStaticFilesServlet extends RestServlet {
+public class StaticFilesServlet extends RestServlet {
 
 	private static final long serialVersionUID = 1L;
 
-	private final transient BasicStaticFilesResource delegate;
+	private final transient StaticFilesMixin delegate;
 
-	/** No-arg constructor &mdash; uses a default {@link BasicStaticFilesResource} delegate. */
-	public BasicStaticFilesServlet() {
-		this(new BasicStaticFilesResource());
+	/** No-arg constructor &mdash; uses a default {@link StaticFilesMixin} delegate. */
+	public StaticFilesServlet() {
+		this(new StaticFilesMixin());
 	}
 
 	/**
@@ -77,7 +77,7 @@ public class BasicStaticFilesServlet extends RestServlet {
 	 * @param delegate The shared static-files mixin this servlet delegates to. Must not be
 	 * 	{@code null}.
 	 */
-	protected BasicStaticFilesServlet(BasicStaticFilesResource delegate) {
+	protected StaticFilesServlet(StaticFilesMixin delegate) {
 		this.delegate = delegate;
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
index f18ec3bc42..f9d1c3f236 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/vars/FileVar.java
@@ -29,7 +29,7 @@ import org.apache.juneau.commons.svl.*;
  * The format for this var is <js>"$F{path[,defaultValue]}"</js>.
  *
  * <p>
- * Contents of files are retrieved from the request using {@link RestRequest#getStaticFiles()}.
+ * Contents of files are retrieved from the resource context using {@link RestContext#getStaticFiles()}.
 
  * <p>
  * Localized resources (based on the locale of the HTTP request) are supported.
@@ -84,7 +84,7 @@ public class FileVar extends DefaultingVar {
 
 		RestRequest req = session.getBean(RestRequest.class).orElseThrow(InternalServerError::new);
 
-		String s = req.getStaticFiles().getString(key, null).orElse(null);
+		String s = req.getContext().getStaticFiles().getString(key, null).orElse(null);
 		if (s == null)
 			return null;
 		String subType = getFileExtension(key);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/RawTemplateDispatcher.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/RawTemplateDispatcher.java
index f81f5f71c6..e4bf103b0a 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/RawTemplateDispatcher.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/RawTemplateDispatcher.java
@@ -31,10 +31,10 @@ import org.apache.juneau.rest.*;
  * {@code .html} template directly by its trailing request path.
  *
  * <p>
- * The per-engine mixin resources ({@code BasicJspResource}, {@code BasicFreemarkerResource},
- * {@code BasicMustacheResource}, {@code BasicThymeleafResource}) implement this interface so the
+ * The per-engine mixin resources ({@code JspMixin}, {@code FreemarkerMixin},
+ * {@code MustacheMixin}, {@code ThymeleafMixin}) implement this interface so the
  * raw dispatch they already perform can be reused verbatim by their {@code Basic*Servlet} standalone
- * companions through {@link BasicViewServlet}. Sharing a single implementation means the standalone
+ * companions through {@link ViewServlet}. Sharing a single implementation means the standalone
  * and mixin forms cannot drift in their file-serving behavior.
  *
  * <p>
@@ -44,7 +44,7 @@ import org.apache.juneau.rest.*;
  * output directly onto the response.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='jc'>{@link BasicViewServlet}
+ * 	<li class='jc'>{@link ViewServlet}
  * 	<li class='jic'>{@link ViewRenderer}
  * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
  * </ul>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/View.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/View.java
index 4114c9b27f..1cd58cc2a5 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/View.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/View.java
@@ -41,7 +41,7 @@ import org.apache.juneau.rest.processor.*;
  * <h5 class='figure'>End-state usage:</h5>
  *
  * <p class='bjava'>
- * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=BasicJspResource.<jk>class</jk>)
+ * 	<ja>@Rest</ja>(path=<js>"/app"</js>, mixins=JspMixin.<jk>class</jk>)
  * 	<jk>public class</jk> AppResource <jk>extends</jk> RestServlet {
  *
  * 		<ja>@RestGet</ja>(<js>"/hello/{name}"</js>)
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewResource.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewResource.java
new file mode 100644
index 0000000000..beed2793e3
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewResource.java
@@ -0,0 +1,81 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest.view;
+
+import java.io.*;
+
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.rest.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+
+/**
+ * Abstract base for the child-resource flavors of the view mixin resources.
+ *
+ * <p>
+ * Mirror of {@link ViewServlet} for routed-child deployment: each per-engine bridge module ships a thin
+ * {@code <Engine>Resource} subclass that declares its own subtree mount via
+ * {@link Rest#path() @Rest(path)} (e.g. {@code "/jsp"}) and supplies the per-engine
+ * {@link RawTemplateDispatcher} (the matching {@code <Engine>Mixin} instance) via {@link #dispatcher()}.
+ *
+ * <p>
+ * The single greedy op declared here ({@code @RestGet(path="/*")}) captures the trailing remainder under
+ * the child's subtree mount and delegates straight to the shared {@link RawTemplateDispatcher}, so the
+ * mixin, servlet, and child forms cannot drift.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jic'>{@link RawTemplateDispatcher}
+ * 	<li class='jc'>{@link ViewServlet}
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/RestServerComposition">REST Server &mdash; Composition (mixins, paths)</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Rest
+public abstract class ViewResource extends RestResource {
+
+	/**
+	 * Returns the per-engine {@link RawTemplateDispatcher} this child delegates raw template dispatch
+	 * to.
+	 *
+	 * <p>
+	 * Typically the matching {@code <Engine>Mixin} instance (which implements
+	 * {@link RawTemplateDispatcher}).  Never {@code null}.
+	 *
+	 * @return The raw-template dispatcher. Never {@code null}.
+	 */
+	protected abstract RawTemplateDispatcher dispatcher();
+
+	/**
+	 * [GET /*] &mdash; render the raw template identified by the trailing path under this child's
+	 * subtree mount.
+	 *
+	 * @param path The trailing path segment after the mount prefix.
+	 * @param req The current REST request.
+	 * @param res The current REST response.
+	 * @throws IOException If the underlying response writer fails.
+	 */
+	@RestGet(
+		path="/*",
+		summary="View",
+		description="Render a raw template under this child's subtree mount.",
+		swagger=@OpSwagger(ignore=true)
+	)
+	public void render(@Path("/*") String path, RestRequest req, RestResponse res) throws IOException {
+		dispatcher().render(path, req, res);
+	}
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/BasicViewServlet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewServlet.java
similarity index 98%
rename from juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/BasicViewServlet.java
rename to juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewServlet.java
index 99d9b1797e..3b7d505f9f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/BasicViewServlet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/view/ViewServlet.java
@@ -63,7 +63,7 @@ import org.apache.juneau.rest.servlet.*;
  * @since 9.5.0
  */
 @Rest
-public abstract class BasicViewServlet extends RestServlet {
+public abstract class ViewServlet extends RestServlet {
 
 	private static final long serialVersionUID = 1L;
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/Widget.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/Widget.java
index c07085f905..7cd01f5d47 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/Widget.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/widget/Widget.java
@@ -136,7 +136,7 @@ public abstract class Widget implements HtmlWidget {
 	 * @return The file finder to used for finding files on the file system.
 	 */
 	protected FileFinder getFileFinder(RestRequest req) {
-		return req.getStaticFiles();
+		return req.getContext().getStaticFiles();
 	}
 
 	/**
diff --git a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/Value_SupplierTypedFieldType_Test.java b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/Value_SupplierTypedFieldType_Test.java
new file mode 100644
index 0000000000..f02e138a1c
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/Value_SupplierTypedFieldType_Test.java
@@ -0,0 +1,269 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.commons.inject;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.net.*;
+import java.nio.file.*;
+import java.time.*;
+import java.util.function.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.settings.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Verifies the typed {@code @Value Supplier<T>} autodetect contract — the extension of the bare
+ * {@code Supplier<String>} path so that {@code T != String} is coerced from the resolved string
+ * using the same converter machinery plain {@code @Value T} uses.
+ *
+ * <ul>
+ * 	<li>{@code Supplier<T>} for the common converter targets (Integer, Long, Boolean, Duration, URI,
+ * 		enum) resolve and coerce correctly.
+ * 	<li>Each {@code .get()} re-resolves the string <i>and</i> re-coerces — live config changes are
+ * 		reflected (the coerced value is never memoized).
+ * 	<li>A coercion failure surfaces as a {@link RuntimeException} from {@code .get()}.
+ * 	<li>{@code Supplier<String>} and raw {@code Supplier} behavior is unchanged.
+ * 	<li>{@code Optional<Supplier<T>>} nesting is not specially handled.
+ * </ul>
+ */
+class Value_SupplierTypedFieldType_Test extends TestBase {
+
+	private static final String P_KEY = "Value_SupplierTypedFieldType_Test.key";
+
+	private BasicBeanStore beanStore;
+
+	@BeforeEach
+	void setUp() {
+		beanStore = new BasicBeanStore(null);
+	}
+
+	@AfterEach
+	void cleanup() {
+		Settings.get().unsetGlobal(P_KEY);
+		ValueResolver.clearTemplateCache();
+	}
+
+	enum A07_Color { RED, GREEN, BLUE }
+
+	public static class IntegerSupplierBean {
+		@Value("${" + P_KEY + ":42}")
+		Supplier<Integer> value;
+	}
+
+	public static class LongSupplierBean {
+		@Value("${" + P_KEY + ":42}")
+		Supplier<Long> value;
+	}
+
+	public static class BooleanSupplierBean {
+		@Value("${" + P_KEY + ":true}")
+		Supplier<Boolean> value;
+	}
+
+	public static class DurationSupplierBean {
+		@Value("${" + P_KEY + ":PT30S}")
+		Supplier<Duration> value;
+	}
+
+	public static class UriSupplierBean {
+		@Value("${" + P_KEY + ":https://example.org/}")
+		Supplier<URI> value;
+	}
+
+	public static class EnumSupplierBean {
+		@Value("${" + P_KEY + ":RED}")
+		Supplier<A07_Color> value;
+	}
+
+	public static class PathSupplierBean {
+		@Value("${" + P_KEY + ":/tmp/x}")
+		Supplier<Path> value;
+	}
+
+	public static class StringSupplierBean {
+		@Value("${" + P_KEY + ":default}")
+		Supplier<String> value;
+	}
+
+	@SuppressWarnings("rawtypes")
+	public static class RawSupplierBean {
+		@Value("${" + P_KEY + ":default}")
+		Supplier value;
+	}
+
+	public static class OptionalSupplierBean {
+		@Value("${" + P_KEY + ":42}")
+		java.util.Optional<Supplier<Integer>> value;
+	}
+
+	public static class IntegerSupplierCtorBean {
+		final Supplier<Integer> value;
+
+		@Inject
+		public IntegerSupplierCtorBean(@Value("${" + P_KEY + ":42}") Supplier<Integer> value) {
+			this.value = value;
+		}
+	}
+
+	//====================================================================================================
+	// a — one typed-supplier test per converter category.
+	//====================================================================================================
+
+	@Test void a01_integer() {
+		Settings.get().setGlobal(P_KEY, "1234");
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		assertEquals(Integer.valueOf(1234), bean.value.get());
+	}
+
+	@Test void a02_long() {
+		Settings.get().setGlobal(P_KEY, "9999999999");
+		var bean = BeanInstantiator.of(LongSupplierBean.class, beanStore).run();
+		assertEquals(Long.valueOf(9999999999L), bean.value.get());
+	}
+
+	@Test void a03_boolean() {
+		Settings.get().setGlobal(P_KEY, "true");
+		var bean = BeanInstantiator.of(BooleanSupplierBean.class, beanStore).run();
+		assertEquals(Boolean.TRUE, bean.value.get());
+	}
+
+	@Test void a04_duration() {
+		Settings.get().setGlobal(P_KEY, "PT1M");
+		var bean = BeanInstantiator.of(DurationSupplierBean.class, beanStore).run();
+		assertEquals(Duration.ofMinutes(1), bean.value.get());
+	}
+
+	@Test void a05_uri() {
+		Settings.get().setGlobal(P_KEY, "https://juneau.apache.org/");
+		var bean = BeanInstantiator.of(UriSupplierBean.class, beanStore).run();
+		assertEquals(URI.create("https://juneau.apache.org/"), bean.value.get());
+	}
+
+	@Test void a06_defaultExpression_coerced() {
+		// No Settings entry — the ${key:42} default flows through coercion the same as a resolved value.
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		assertEquals(Integer.valueOf(42), bean.value.get());
+	}
+
+	@Test void a07_enum() {
+		Settings.get().setGlobal(P_KEY, "GREEN");
+		var bean = BeanInstantiator.of(EnumSupplierBean.class, beanStore).run();
+		assertEquals(A07_Color.GREEN, bean.value.get());
+	}
+
+	@Test void a08_constructorParam() {
+		Settings.get().setGlobal(P_KEY, "777");
+		var bean = BeanInstantiator.of(IntegerSupplierCtorBean.class, beanStore).run();
+		assertEquals(Integer.valueOf(777), bean.value.get());
+	}
+
+	@Test void a09_path_registryBoundary_throwsFromGet() {
+		// Path is NOT in the String->T converter registry (it is an interface with no (String)
+		// constructor or single-arg String factory — StringSetting.asPath() special-cases Paths.get()
+		// outside of Settings.toType). The typed-supplier path deliberately reuses ONLY the registry
+		// (no hardcoded subset), so Supplier<Path> behaves identically to plain @Value Path: the
+		// coercion fails as a RuntimeException at .get() time.
+		Settings.get().setGlobal(P_KEY, "/tmp/x");
+		var bean = BeanInstantiator.of(PathSupplierBean.class, beanStore).run();
+		assertThrows(RuntimeException.class, () -> bean.value.get());
+	}
+
+	//====================================================================================================
+	// b — late binding: each .get() re-resolves AND re-coerces (no value memoization).
+	//====================================================================================================
+
+	@Test void b01_lateBinding_reCoercesPerGet() {
+		Settings.get().setGlobal(P_KEY, "1");
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		assertEquals(Integer.valueOf(1), bean.value.get());
+
+		Settings.get().setGlobal(P_KEY, "2");
+		assertEquals(Integer.valueOf(2), bean.value.get(), "Supplier<Integer> re-resolves and re-coerces per .get()");
+
+		Settings.get().setGlobal(P_KEY, "3");
+		assertEquals(Integer.valueOf(3), bean.value.get());
+	}
+
+	@Test void b02_lateBinding_notMemoizedAcrossInstances() {
+		Settings.get().setGlobal(P_KEY, "10");
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		var first = bean.value.get();
+		var second = bean.value.get();
+		// Distinct boxed instances (outside the Integer cache range) prove no value memoization.
+		Settings.get().setGlobal(P_KEY, "1000");
+		assertEquals(Integer.valueOf(10), first);
+		assertEquals(Integer.valueOf(10), second);
+		assertEquals(Integer.valueOf(1000), bean.value.get());
+	}
+
+	//====================================================================================================
+	// c — coercion failure surfaces as a RuntimeException from .get().
+	//====================================================================================================
+
+	@Test void c01_coercionFailure_throwsFromGet() {
+		Settings.get().setGlobal(P_KEY, "notAnInt");
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		assertThrows(RuntimeException.class, () -> bean.value.get());
+	}
+
+	@Test void c02_coercionFailure_messageNamesKeyAndType() {
+		Settings.get().setGlobal(P_KEY, "notAnInt");
+		var bean = BeanInstantiator.of(IntegerSupplierBean.class, beanStore).run();
+		var e = assertThrows(RuntimeException.class, () -> bean.value.get());
+		assertTrue(e.getMessage().contains("Integer"), "Message should name the target type: " + e.getMessage());
+		assertTrue(e.getMessage().contains(P_KEY), "Message should name the @Value expression: " + e.getMessage());
+	}
+
+	@Test void c03_enumCoercionFailure_throwsFromGet() {
+		Settings.get().setGlobal(P_KEY, "PURPLE");
+		var bean = BeanInstantiator.of(EnumSupplierBean.class, beanStore).run();
+		assertThrows(RuntimeException.class, () -> bean.value.get());
+	}
+
+	//====================================================================================================
+	// d — regression: Supplier<String> and raw Supplier behavior unchanged.
+	//====================================================================================================
+
+	@Test void d01_supplierString_unchanged() {
+		Settings.get().setGlobal(P_KEY, "first");
+		var bean = BeanInstantiator.of(StringSupplierBean.class, beanStore).run();
+		assertEquals("first", bean.value.get());
+
+		Settings.get().setGlobal(P_KEY, "second");
+		assertEquals("second", bean.value.get(), "Supplier<String> still re-evaluates per .get()");
+	}
+
+	@Test void d02_rawSupplier_unchanged() {
+		// Raw Supplier (no type argument) is not a coercible typed-supplier site — it falls through to
+		// the one-shot path, where Supplier.class is not a registry-known type, so injection fails.
+		Settings.get().setGlobal(P_KEY, "anything");
+		assertThrows(RuntimeException.class, () -> BeanInstantiator.of(RawSupplierBean.class, beanStore).run());
+	}
+
+	//====================================================================================================
+	// e — Optional<Supplier<T>> is not specially handled.
+	//====================================================================================================
+
+	@Test void e01_optionalOfSupplier_notSpeciallyHandled() {
+		// Optional<Supplier<Integer>> unwraps to Supplier.class but the generic type's raw is Optional,
+		// so the typed-supplier path is not taken. It is not specially supported.
+		Settings.get().setGlobal(P_KEY, "42");
+		assertThrows(RuntimeException.class, () -> BeanInstantiator.of(OptionalSupplierBean.class, beanStore).run());
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/FileUtils_ResolveSafely_Test.java b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/FileUtils_ResolveSafely_Test.java
index 449caeda50..97705570a4 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/FileUtils_ResolveSafely_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/FileUtils_ResolveSafely_Test.java
@@ -32,7 +32,7 @@ import org.junit.jupiter.api.io.*;
  *
  * <p>
  * The two helpers are the shared canonical implementations behind the path-traversal hardening
- * in {@code DirectoryResource}, {@code LogsResource}, and {@code BasicJspResource}; the tests
+ * in {@code DirectoryResource}, {@code LogsResource}, and {@code JspMixin}; the tests
  * here exercise the shared semantic invariants directly so the per-resource regression suites
  * can focus on caller-layer behavior (HTTP status codes, response shape).
  */
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/classic/SerializedHttpEntity_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/classic/SerializedHttpEntity_Test.java
index 4512d6011e..e639c61ba8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/classic/SerializedHttpEntity_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/classic/SerializedHttpEntity_Test.java
@@ -41,7 +41,7 @@ import org.junit.jupiter.api.*;
 class SerializedHttpEntity_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public String[] checkHeader(org.apache.juneau.rest.RestRequest req) {
 			return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
index 749dfd99bc..d5e7bcad44 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_Test.java
@@ -530,7 +530,7 @@ class Remote_Test extends TestBase {
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class F extends BasicRestObject {
+	public static class F extends BasicRestResource {
 		@RestGet
 		public String[] headers(org.apache.juneau.rest.RestRequest req) {
 			return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
@@ -579,7 +579,7 @@ class Remote_Test extends TestBase {
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class G extends BasicRestObject {}
+	public static class G extends BasicRestResource {}
 
 	@Remote
 	public interface G1 {
@@ -596,7 +596,7 @@ class Remote_Test extends TestBase {
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class H extends BasicRestObject {
+	public static class H extends BasicRestResource {
 		@RestOp(method="*", path="/*")
 		public String echoMethod(@Method String method, @Path("/*") String path) {
 			return method + " " + path;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
index 1057ee527a..95ef56df1f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
@@ -26,20 +26,20 @@ import java.nio.charset.StandardCharsets;
 
 import org.apache.juneau.TestBase;
 import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.servlet.BasicRestObject;
+import org.apache.juneau.rest.servlet.BasicRestResource;
 import org.junit.jupiter.api.*;
 
 class NoInherit_Test extends TestBase {
 
-	private static RestContext restContext(Class<? extends BasicRestObject> c) throws Exception {
+	private static RestContext restContext(Class<? extends BasicRestResource> c) throws Exception {
 		var o = c.getDeclaredConstructor().newInstance();
 		
-		// Check if class has a parent REST resource (not BasicRestObject)
+		// Check if class has a parent REST resource (not BasicRestResource)
 		RestContext parentContext = null;
 		var superClass = c.getSuperclass();
-		if (superClass != null && superClass != BasicRestObject.class && BasicRestObject.class.isAssignableFrom(superClass)) {
+		if (superClass != null && superClass != BasicRestResource.class && BasicRestResource.class.isAssignableFrom(superClass)) {
 			@SuppressWarnings("unchecked")
-			var parentClass = (Class<? extends BasicRestObject>) superClass;
+			var parentClass = (Class<? extends BasicRestResource>) superClass;
 			parentContext = restContext(parentClass);
 		}
 		
@@ -47,7 +47,7 @@ class NoInherit_Test extends TestBase {
 	}
 
 	@Rest(allowedSerializerOptions = "parentSer")
-	public static class ParentSer extends BasicRestObject {}
+	public static class ParentSer extends BasicRestResource {}
 
 	@Rest(allowedSerializerOptions = "childSer", noInherit = "allowedSerializerOptions")
 	public static class ChildSer extends ParentSer {}
@@ -62,7 +62,7 @@ class NoInherit_Test extends TestBase {
 	}
 
 	@Rest(allowedSerializerOptions = "pBoth")
-	public static class ParentBoth extends BasicRestObject {}
+	public static class ParentBoth extends BasicRestResource {}
 
 	@Rest(allowedSerializerOptions = "cBoth")
 	public static class ChildBoth extends ParentBoth {}
@@ -76,7 +76,7 @@ class NoInherit_Test extends TestBase {
 		assertTrue(keys.contains("cBoth"));
 	}
 
-	public static class ParentM extends BasicRestObject {
+	public static class ParentM extends BasicRestResource {
 		@RestGet(allowedSerializerOptions = "parentM")
 		public void get() {
 			// Intentionally empty - method only used for annotation metadata testing
@@ -109,7 +109,7 @@ class NoInherit_Test extends TestBase {
 	// Test case for aggregated noInherit: parent has noInherit="prop1", child has noInherit="prop2"
 	// The aggregated noInherit should be {"prop1", "prop2"}, but allowedSerializerOptions should
 	// include values from both parent and child since neither blocks allowedSerializerOptions
-	public static class ParentAggregated extends BasicRestObject {
+	public static class ParentAggregated extends BasicRestResource {
 		@RestGet(allowedSerializerOptions = "parentOpt", noInherit = "prop1")
 		public void get() {
 			// Intentionally empty - method only used for annotation metadata testing
@@ -140,7 +140,7 @@ class NoInherit_Test extends TestBase {
 
 	// Test that noInherit blocks REST class context inheritance but NOT parent method inheritance
 	@Rest(allowedSerializerOptions = "classLevel")
-	public static class ParentWithClassLevel extends BasicRestObject {
+	public static class ParentWithClassLevel extends BasicRestResource {
 		@RestGet(allowedSerializerOptions = "parentMethod")
 		public void get() {
 			// Intentionally empty - method only used for annotation metadata testing
@@ -173,7 +173,7 @@ class NoInherit_Test extends TestBase {
 	}
 
 	/**
-	 * Plain {@code @Rest} POJO (not {@link BasicRestObject}) so {@code BasicUniversalConfig} does not pre-populate
+	 * Plain {@code @Rest} POJO (not {@link BasicRestResource}) so {@code BasicUniversalConfig} does not pre-populate
 	 * charset / maxInput on the op builder — needed to exercise {@code @Rest}-hierarchy fallbacks and {@code noInherit}.
 	 */
 	@Rest(defaultCharset = "ISO-8859-1")
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_GetterShapes_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_GetterShapes_Test.java
index 694386791b..a42271bfd6 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_GetterShapes_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_GetterShapes_Test.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the permissive {@link Object}-typed return contract for the {@code getPaths()} virtual
- * getter on {@link RestServlet} / {@link RestObject}.
+ * getter on {@link RestServlet} / {@link RestResource}.
  *
  * <p>
  * Since 9.5.0 the {@code getPaths()} signature returns {@link Object}, accepting any of:
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_Getter_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_Getter_Test.java
index 0e205fd624..c075cdda01 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_Getter_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/RestPathsRuntimeOverride_Getter_Test.java
@@ -26,7 +26,7 @@ import org.junit.jupiter.api.*;
 /**
  * Validates the {@code getPaths()} virtual-getter rung of the runtime-override resolution chain on
  * {@link RestContext#getPaths()}: a {@link RestServlet#getPaths() RestServlet.getPaths()} (or
- * {@link RestObject#getPaths() RestObject.getPaths()}) override beats the annotation default; a
+ * {@link RestResource#getPaths() RestResource.getPaths()}) override beats the annotation default; a
  * {@code null} return falls through to the annotation; an explicit {@code new String[0]} return clears.
  *
  * <p>
@@ -98,16 +98,16 @@ class RestPathsRuntimeOverride_Getter_Test extends TestBase {
 	}
 
 	@Rest(paths={"/from-annotation"})
-	public static class E_RestObjectGetter extends RestObject {
+	public static class E_RestResourceGetter extends RestResource {
 		@Override public String[] getPaths() { return new String[]{"/from-restobject-getter"}; }
 	}
 
 	@Test
 	void e01_restObjectGetter_overrideAnnotation() throws Exception {
-		var args = new RestContext.Args(E_RestObjectGetter.class, null, null, E_RestObjectGetter::new, "", null, null, null, false);
+		var args = new RestContext.Args(E_RestResourceGetter.class, null, null, E_RestResourceGetter::new, "", null, null, null, false);
 		var ctx = new RestContext(args).postInit().postInitChildFirst();
 
 		assertArrayEquals(new String[]{"/from-restobject-getter"}, ctx.getPaths(),
-			"RestObject.getPaths() override should also beat the annotation default");
+			"RestResource.getPaths() override should also beat the annotation default");
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServletPathResolution_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServletPathResolution_Test.java
index 64e52c576d..9d216165b8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServletPathResolution_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServletPathResolution_Test.java
@@ -49,31 +49,31 @@ class StandaloneServletPathResolution_Test extends TestBase {
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Test void a01_jspServlet() {
-		assertArrayEquals(new String[]{"/jsp/*"}, paths(BasicJspServlet.class, new BasicJspServlet()));
+		assertArrayEquals(new String[]{"/jsp/*"}, paths(JspServlet.class, new JspServlet()));
 	}
 
 	@Test void a02_freemarkerServlet() {
-		assertArrayEquals(new String[]{"/freemarker/*"}, paths(BasicFreemarkerServlet.class, new BasicFreemarkerServlet()));
+		assertArrayEquals(new String[]{"/freemarker/*"}, paths(FreemarkerServlet.class, new FreemarkerServlet()));
 	}
 
 	@Test void a03_mustacheServlet() {
-		assertArrayEquals(new String[]{"/mustache/*"}, paths(BasicMustacheServlet.class, new BasicMustacheServlet()));
+		assertArrayEquals(new String[]{"/mustache/*"}, paths(MustacheServlet.class, new MustacheServlet()));
 	}
 
 	@Test void a04_thymeleafServlet() {
-		assertArrayEquals(new String[]{"/thymeleaf/*"}, paths(BasicThymeleafServlet.class, new BasicThymeleafServlet()));
+		assertArrayEquals(new String[]{"/thymeleaf/*"}, paths(ThymeleafServlet.class, new ThymeleafServlet()));
 	}
 
 	@Test void a05_staticFilesServlet() {
-		assertArrayEquals(new String[]{"/static/*"}, paths(BasicStaticFilesServlet.class, new BasicStaticFilesServlet()));
+		assertArrayEquals(new String[]{"/static/*"}, paths(StaticFilesServlet.class, new StaticFilesServlet()));
 	}
 
 	@Test void a06_versionServlet() {
-		assertArrayEquals(new String[]{"/version/*"}, paths(BasicVersionServlet.class, new BasicVersionServlet()));
+		assertArrayEquals(new String[]{"/version/*"}, paths(VersionServlet.class, new VersionServlet()));
 	}
 
 	@Test void a07_adminServlet() {
-		assertArrayEquals(new String[]{"/admin/*"}, paths(BasicAdminServlet.class, new BasicAdminServlet()));
+		assertArrayEquals(new String[]{"/admin/*"}, paths(AdminServlet.class, new AdminServlet()));
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
@@ -81,14 +81,14 @@ class StandaloneServletPathResolution_Test extends TestBase {
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Test void b01_versionMixin_noTopLevelPaths() {
-		assertEquals(0, paths(BasicVersionResource.class, new BasicVersionResource()).length);
+		assertEquals(0, paths(VersionMixin.class, new VersionMixin()).length);
 	}
 
 	@Test void b02_adminMixin_noTopLevelPaths() {
-		assertEquals(0, paths(BasicAdminResource.class, new BasicAdminResource()).length);
+		assertEquals(0, paths(AdminMixin.class, new AdminMixin()).length);
 	}
 
 	@Test void b03_staticFilesMixin_noTopLevelPaths() {
-		assertEquals(0, paths(BasicStaticFilesResource.class, new BasicStaticFilesResource()).length);
+		assertEquals(0, paths(StaticFilesMixin.class, new StaticFilesMixin()).length);
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServlets_JettyMicroservice_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServlets_JettyMicroservice_Test.java
index 8f700c067e..127236dfbd 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServlets_JettyMicroservice_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/StandaloneServlets_JettyMicroservice_Test.java
@@ -39,9 +39,9 @@ import jakarta.servlet.*;
  *
  * <p>
  * Boots a {@link org.apache.juneau.microservice.Microservice Microservice} on an ephemeral port and
- * mounts three standalone servlets as siblings &mdash; {@link BasicVersionServlet} at
- * {@code /version/*}, {@link BasicStaticFilesServlet} at {@code /static/*}, and
- * {@link BasicAdminServlet} at {@code /admin/*} &mdash; relying on the {@code JettyServerComponent}
+ * mounts three standalone servlets as siblings &mdash; {@link VersionServlet} at
+ * {@code /version/*}, {@link StaticFilesServlet} at {@code /static/*}, and
+ * {@link AdminServlet} at {@code /admin/*} &mdash; relying on the {@code JettyServerComponent}
  * auto-discovery loop to read each servlet's {@code @Rest(paths)} via
  * {@code RestContext.resolveTopLevelPaths(...)}. This is the cross-runtime parity check that the
  * sibling top-level mount + op-at-{@code /*} shape resolves correctly through a real servlet
@@ -54,9 +54,9 @@ class StandaloneServlets_JettyMicroservice_Test extends TestBase {
 
 	@Configuration
 	public static class Config {
-		@Bean(name="versionServlet") public Servlet versionServlet() { return new BasicVersionServlet(); }
-		@Bean(name="staticFilesServlet") public Servlet staticFilesServlet() { return new BasicStaticFilesServlet(); }
-		@Bean(name="adminServlet") public Servlet adminServlet() { return new BasicAdminServlet(); }
+		@Bean(name="versionServlet") public Servlet versionServlet() { return new VersionServlet(); }
+		@Bean(name="staticFilesServlet") public Servlet staticFilesServlet() { return new StaticFilesServlet(); }
+		@Bean(name="adminServlet") public Servlet adminServlet() { return new AdminServlet(); }
 	}
 
 	@RegisterExtension
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/BasicHttpRequestRetryHandler_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/BasicHttpRequestRetryHandler_Test.java
index 57776043c5..ec8c76a550 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/BasicHttpRequestRetryHandler_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/BasicHttpRequestRetryHandler_Test.java
@@ -33,7 +33,7 @@ import org.junit.jupiter.api.*;
 class BasicHttpRequestRetryHandler_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public Ok get() {
 			return OK;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestCallException_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestCallException_Test.java
index 545733d006..c3425b78f3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestCallException_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestCallException_Test.java
@@ -46,7 +46,7 @@ class RestCallException_Test extends TestBase {
 	}
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public InputStream echo(InputStream is) {
 			return is;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_BasicCalls_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_BasicCalls_Test.java
index ae5d399674..e4a23dd170 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_BasicCalls_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_BasicCalls_Test.java
@@ -58,7 +58,7 @@ class RestClient_BasicCalls_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestOp(path="/bean") public ABean getBean() { return bean; }
 		@RestOp(path="/bean") public ABean postBean(@Content ABean b) { return b; }
 		@RestOp(path="/bean") public ABean putBean(@Content ABean b) { return b; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Body_Test.java
index 7a7e07c4bd..ed51639e42 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Body_Test.java
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Body_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader post(org.apache.juneau.rest.RestRequest req, org.apache.juneau.rest.RestResponse res) throws IOException {
 			for (var e : req.getHeaders())
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Context_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Context_Test.java
index b85044c2bd..9a20673ac8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Context_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Context_Test.java
@@ -36,7 +36,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Config_Context_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader echoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
 			return req.getContent().getReader();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_MarshallingContext_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_MarshallingContext_Test.java
index d23ed3b244..c5f65acb86 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_MarshallingContext_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_MarshallingContext_Test.java
@@ -40,7 +40,7 @@ import org.apache.juneau.commons.bean.*;
 class RestClient_Config_MarshallingContext_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader echoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
 			return req.getContent().getReader();
@@ -91,7 +91,7 @@ class RestClient_Config_MarshallingContext_Test extends TestBase {
 	}
 
 	@Rest
-	public static class A2b extends BasicRestObject {
+	public static class A2b extends BasicRestResource {
 		@RestPost
 		public Reader test(org.apache.juneau.rest.RestRequest req,org.apache.juneau.rest.RestResponse res) throws IOException {
 			res.setHeader("X",req.getHeaderParam("X").orElse(null));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_OpenApi_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_OpenApi_Test.java
index 9903efc702..ff444cbe5e 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_OpenApi_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_OpenApi_Test.java
@@ -35,7 +35,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Config_OpenApi_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader echoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
 			return req.getContent().getReader();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Parser_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Parser_Test.java
index 8b518773de..69ff5d6d12 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Parser_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Parser_Test.java
@@ -30,7 +30,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Config_Parser_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader echoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
 			return req.getContent().getReader();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_RestClient_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_RestClient_Test.java
index b5cc302195..420be61cb9 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_RestClient_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_RestClient_Test.java
@@ -70,7 +70,7 @@ class RestClient_Config_RestClient_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestOp(path="/bean")
 		public ABean getBean() {
 			return bean;
@@ -410,7 +410,7 @@ class RestClient_Config_RestClient_Test extends TestBase {
 	}
 
 	@Rest(partSerializer=A12a.class,partParser=A12b.class)
-	public static class A12 extends BasicRestObject {
+	public static class A12 extends BasicRestResource {
 		@RestGet(path="/")
 		public Ok get(@Header(name="Foo") @Schema(cf="multi") ABean[] foo,org.apache.juneau.rest.RestRequest req,org.apache.juneau.rest.RestResponse res) throws Exception {
 			assertEquals(2,foo.length);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Serializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Serializer_Test.java
index e77103c427..915c6274b9 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Serializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Config_Serializer_Test.java
@@ -44,7 +44,7 @@ class RestClient_Config_Serializer_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader echoBody(org.apache.juneau.rest.RestRequest req) throws IOException {
 			return req.getContent().getReader();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_FormData_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_FormData_Test.java
index a438684939..78c2e3525c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_FormData_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_FormData_Test.java
@@ -40,7 +40,7 @@ import org.junit.jupiter.api.*;
 class RestClient_FormData_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Reader formData(org.apache.juneau.rest.RestRequest req) {
 			return reader(req.getFormParams().asQueryString());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Headers_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Headers_Test.java
index 6d3acd1a87..efbeeba52f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Headers_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Headers_Test.java
@@ -70,7 +70,7 @@ public class RestClient_Headers_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest(callLogger=CaptureLogger.class)
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet(allowedSerializerOptions="simple", allowedParserOptions="addBeanTypes")
 		public String[] headers(org.apache.juneau.rest.RestRequest req) {
 			return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_StreamClosed_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_StreamClosed_Test.java
index 00093ec7ec..2815953011 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_StreamClosed_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_StreamClosed_Test.java
@@ -56,7 +56,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Logging_StreamClosed_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public String hello() {
 			return "hello world";
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_Test.java
index a2214841fb..7cd9c3f037 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Logging_Test.java
@@ -43,7 +43,7 @@ class RestClient_Logging_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public ABean bean(@Content ABean b) {
 			return b;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Marshalls_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Marshalls_Test.java
index 386e3a8041..63f36db633 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Marshalls_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Marshalls_Test.java
@@ -52,7 +52,7 @@ public class RestClient_Marshalls_Test extends TestBase {
 	public static Bean bean = Bean.create();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public Bean a01(@Content Bean b, @Header("Accept") String accept, @Header("Content-Type") String ct, @Header("X-Accept") String xaccept, @Header("X-Content-Type") String xct) {
 			assertEquals(nn(xaccept), nn(accept), "Accept doesn't match");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Paths_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Paths_Test.java
index 90f5dbc1e1..b349922508 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Paths_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Paths_Test.java
@@ -32,7 +32,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Paths_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet(path="/echo/*")
 		public String getEcho(org.apache.juneau.rest.RestRequest req) {
 			return req.toString();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Query_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Query_Test.java
index 54b062c0dc..b3b51e9577 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Query_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Query_Test.java
@@ -40,7 +40,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Query_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public Reader query(org.apache.juneau.rest.RestRequest req) {
 			return reader(req.getQueryParams().asQueryString());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Body_Test.java
index 67c0bdacb9..84027dc716 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Body_Test.java
@@ -62,7 +62,7 @@ class RestClient_Response_Body_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestPost
 		public InputStream echo(InputStream is) {
 			return is;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Headers_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Headers_Test.java
index a68d2dabda..f2aa8a3060 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Headers_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Headers_Test.java
@@ -44,7 +44,7 @@ import org.junit.jupiter.api.*;
 class RestClient_Response_Headers_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public String echo(org.apache.juneau.rest.RestRequest req, org.apache.juneau.rest.RestResponse res) {
 			var c = req.getHeaderParam("Check").orElse(null);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Test.java
index 0854df5ff1..c43d9fc556 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Response_Test.java
@@ -58,7 +58,7 @@ class RestClient_Response_Test extends TestBase {
 	private static final ZonedDateTime ZONEDDATETIME = ZonedDateTime.from(RFC_1123_DATE_TIME.parse("Sat, 29 Oct 1994 19:43:31 GMT")).truncatedTo(SECONDS);
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public ABean bean() {
 			return bean;
@@ -123,7 +123,7 @@ class RestClient_Response_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class C extends BasicRestObject {
+	public static class C extends BasicRestResource {
 		@RestGet(path="/")
 		public String getHeader(org.apache.juneau.rest.RestRequest req, org.apache.juneau.rest.RestResponse res) {
 			var n = req.getHeaderParam("Check").orElse(null);
@@ -197,7 +197,7 @@ class RestClient_Response_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class D extends BasicRestObject {
+	public static class D extends BasicRestResource {
 		@RestPost
 		public ABean bean(@Content ABean bean) {
 			return bean;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Test.java
index 9027050f3b..3ccec6d512 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/classic/RestClient_Test.java
@@ -63,7 +63,7 @@ class RestClient_Test extends TestBase {
 	private static ABean bean = ABean.get();
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet
 		public ABean bean() {
 			return bean;
@@ -290,7 +290,7 @@ class RestClient_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	@Rest
-	public static class D extends BasicRestObject {
+	public static class D extends BasicRestResource {
 		@RestGet
 		public String echo(@org.apache.juneau.http.annotation.Header("Authorization") String auth, org.apache.juneau.rest.RestResponse res) {
 			if (auth == null) {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_Builders_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_Builders_Test.java
index f34cd8e346..524aeabfa2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_Builders_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_Builders_Test.java
@@ -43,22 +43,22 @@ import org.junit.jupiter.api.io.*;
  */
 class BasicConvention_Builders_Test extends TestBase {
 
-	// -------- BasicFaviconResource --------
+	// -------- FaviconMixin --------
 
 	@Test void favicon_noArgConstructorLoadsDefault() {
-		var f = new BasicFaviconResource();
+		var f = new FaviconMixin();
 		assertNotNull(f);
 	}
 
 	@Test void favicon_classpathResolvesExistingResource() {
-		var f = BasicFaviconResource.create()
+		var f = FaviconMixin.create()
 			.classpath("/juneau-favicon.ico")
 			.build();
 		assertNotNull(f);
 	}
 
 	@Test void favicon_classpathFallsBackOnMissingResource() {
-		var f = BasicFaviconResource.create()
+		var f = FaviconMixin.create()
 			.classpath("/no-such-resource.ico")
 			.build();
 		assertNotNull(f, "Missing classpath path must fall back to default favicon");
@@ -66,7 +66,7 @@ class BasicConvention_Builders_Test extends TestBase {
 
 	@Test void favicon_bytesAndClasspathAreMutuallyExclusive() {
 		var bytes = new byte[]{1,2,3};
-		var f = BasicFaviconResource.create()
+		var f = FaviconMixin.create()
 			.classpath("/anything.ico")
 			.bytes(bytes)
 			.build();
@@ -75,22 +75,22 @@ class BasicConvention_Builders_Test extends TestBase {
 	}
 
 	@Test void favicon_customCacheControl() {
-		var f = BasicFaviconResource.create()
+		var f = FaviconMixin.create()
 			.bytes(new byte[]{1,2})
 			.cacheControl("no-store")
 			.build();
 		assertNotNull(f);
 	}
 
-	// -------- BasicSeoResource --------
+	// -------- SeoMixin --------
 
 	@Test void seo_noArgConstructorYieldsDenyAll() {
-		var s = new BasicSeoResource();
+		var s = new SeoMixin();
 		assertNotNull(s);
 	}
 
 	@Test void seo_robotsAllowAndDisallowMix() {
-		var s = BasicSeoResource.create()
+		var s = SeoMixin.create()
 			.robotsAllow("Googlebot", "/")
 			.robotsDisallow("BadBot", "/private", "/admin")
 			.build();
@@ -98,7 +98,7 @@ class BasicConvention_Builders_Test extends TestBase {
 	}
 
 	@Test void seo_sitemapEntryWithFullMetadata() {
-		var s = BasicSeoResource.create()
+		var s = SeoMixin.create()
 			.sitemapEntry("https://example.com/page",
 				ZonedDateTime.parse("2026-05-24T18:00:00Z"),
 				"weekly",
@@ -108,7 +108,7 @@ class BasicConvention_Builders_Test extends TestBase {
 	}
 
 	@Test void seo_sitemapEntryDirectInstance() {
-		var e = new BasicSeoResource.SitemapEntry(
+		var e = new SeoMixin.SitemapEntry(
 			"https://x.example.com/<unsafe&\"chars'>",
 			ZonedDateTime.parse("2026-05-24T18:00:00+02:00"),
 			"daily",
@@ -132,45 +132,45 @@ class BasicConvention_Builders_Test extends TestBase {
 
 	@Test void seo_emptyPathArrayInRobotsRuleIsTolerated() {
 		// Internal RobotsRule's path-array handling — null/empty paths is just an empty rule.
-		var s = BasicSeoResource.create().robotsAllow("MyBot").build();
+		var s = SeoMixin.create().robotsAllow("MyBot").build();
 		assertNotNull(s);
 	}
 
-	// -------- BasicVersionResource --------
+	// -------- VersionMixin --------
 
 	@Test void version_noArgConstructorPopulatesDefaults() {
-		var v = new BasicVersionResource();
+		var v = new VersionMixin();
 		assertNotNull(v.getInfoMap().get("javaVersion"), "no-arg ctor must surface javaVersion");
 	}
 
 	@Test void version_entryAcceptsNullValue() {
-		var v = BasicVersionResource.create()
+		var v = VersionMixin.create()
 			.entry("missing", null)
 			.build();
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("missing"));
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("missing"));
 	}
 
 	@Test void version_entriesMapSink() {
 		var values = new LinkedHashMap<String,String>();
 		values.put("a", "1");
 		values.put("b", null);
-		var v = BasicVersionResource.create().entries(values).build();
+		var v = VersionMixin.create().entries(values).build();
 		assertEquals("1", v.getInfoMap().get("a"));
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("b"));
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("b"));
 	}
 
 	@Test void version_entriesNullMapIsTolerated() {
-		var v = BasicVersionResource.create().entries(null).build();
+		var v = VersionMixin.create().entries(null).build();
 		assertNotNull(v);
 	}
 
 	@Test void version_fromManifestFallbackUnknownNameAndVersion() {
 		// URLClassLoader with no manifest available: name+version fall back to (unknown).
-		var v = BasicVersionResource.create()
+		var v = VersionMixin.create()
 			.fromManifest(new URLClassLoader(new URL[0], null))
 			.build();
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("name"));
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("version"));
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("name"));
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("version"));
 	}
 
 	@Test void version_fromManifestObjectFullAttrs() {
@@ -181,7 +181,7 @@ class BasicConvention_Builders_Test extends TestBase {
 		attrs.putValue("Implementation-Version", "5.0.0");
 		attrs.putValue("Implementation-Vendor", "Co");
 		attrs.putValue("Build-Jdk", "21");
-		var v = BasicVersionResource.create().fromManifest(m).build();
+		var v = VersionMixin.create().fromManifest(m).build();
 		assertEquals("obj-app", v.getInfoMap().get("name"));
 		assertEquals("5.0.0", v.getInfoMap().get("version"));
 		assertEquals("Co", v.getInfoMap().get("vendor"));
@@ -196,7 +196,7 @@ class BasicConvention_Builders_Test extends TestBase {
 			+ "git.build.time=2026-05-24T17:00:00Z\n";
 		Files.writeString(tempDir.resolve("git.properties"), props, StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(new URL[]{ tempDir.toUri().toURL() }, null)) {
-			var v = BasicVersionResource.create()
+			var v = VersionMixin.create()
 				.fromGitProperties(cl)
 				.build();
 			assertEquals("abcdef0123456789", v.getInfoMap().get("gitCommit"));
@@ -210,7 +210,7 @@ class BasicConvention_Builders_Test extends TestBase {
 			+ "git.branch=main\n";
 		Files.writeString(tempDir.resolve("git.properties"), props, StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(new URL[]{ tempDir.toUri().toURL() }, null)) {
-			var v = BasicVersionResource.create()
+			var v = VersionMixin.create()
 				.fromGitProperties(cl)
 				.build();
 			assertEquals("abcdef0", v.getInfoMap().get("gitCommit"),
@@ -220,7 +220,7 @@ class BasicConvention_Builders_Test extends TestBase {
 
 	@Test void version_fromGitPropertiesMissingFileIsSilent() throws Exception {
 		try (var cl = new URLClassLoader(new URL[0], null)) {
-			var v = BasicVersionResource.create()
+			var v = VersionMixin.create()
 				.fromGitProperties(cl)
 				.build();
 			assertNull(v.getInfoMap().get("gitCommit"),
@@ -236,17 +236,17 @@ class BasicConvention_Builders_Test extends TestBase {
 			"Manifest-Version: 1.0\nBuild-Jdk: 22\n\n",
 			StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(new URL[]{ tempDir.toUri().toURL() }, null)) {
-			var v = BasicVersionResource.create()
+			var v = VersionMixin.create()
 				.fromManifest(cl)
 				.build();
 			assertEquals("22", v.getInfoMap().get("javaVersion"));
-			assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("name"),
+			assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("name"),
 				"name unset → (unknown)");
 		}
 	}
 
 	@Test void version_fromJavaVersionPutsIfAbsent() {
-		var v = BasicVersionResource.create()
+		var v = VersionMixin.create()
 			.entry("javaVersion", "preset")
 			.fromJavaVersion()
 			.build();
@@ -262,7 +262,7 @@ class BasicConvention_Builders_Test extends TestBase {
 		attrs.putValue("Implementation-Title", "x");
 		attrs.putValue("Implementation-Version", "1");
 		attrs.putValue("Implementation-Vendor", "");
-		var v = BasicVersionResource.create().fromManifest(m).build();
+		var v = VersionMixin.create().fromManifest(m).build();
 		assertNull(v.getInfoMap().get("vendor"), "empty string Implementation-Vendor must be skipped");
 	}
 
@@ -272,7 +272,7 @@ class BasicConvention_Builders_Test extends TestBase {
 			+ "git.build.time=\n";
 		Files.writeString(tempDir.resolve("git.properties"), props, StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(new URL[]{ tempDir.toUri().toURL() }, null)) {
-			var v = BasicVersionResource.create().fromGitProperties(cl).build();
+			var v = VersionMixin.create().fromGitProperties(cl).build();
 			assertNull(v.getInfoMap().get("gitCommit"), "empty git.commit.id skipped");
 			assertNull(v.getInfoMap().get("gitBranch"), "empty git.branch skipped");
 			assertNull(v.getInfoMap().get("buildTime"), "empty git.build.time skipped");
@@ -288,9 +288,9 @@ class BasicConvention_Builders_Test extends TestBase {
 				throw new IOException("boom");
 			}
 		};
-		var v = BasicVersionResource.create().fromManifest(brokenCl).build();
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("name"));
-		assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("version"));
+		var v = VersionMixin.create().fromManifest(brokenCl).build();
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("name"));
+		assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("version"));
 	}
 
 	@Test void version_fromGitPropertiesClassLoaderThrowsIOException() {
@@ -307,7 +307,7 @@ class BasicConvention_Builders_Test extends TestBase {
 				};
 			}
 		};
-		var v = BasicVersionResource.create().fromGitProperties(brokenCl).build();
+		var v = VersionMixin.create().fromGitProperties(brokenCl).build();
 		assertNull(v.getInfoMap().get("gitCommit"), "broken git.properties read produces no entry");
 	}
 
@@ -321,8 +321,8 @@ class BasicConvention_Builders_Test extends TestBase {
 			"Manifest-Version: 1.0\nImplementation-Title: gone\n\n", StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(new URL[]{ tempDir.toUri().toURL() }, null)) {
 			Files.delete(manifest);
-			var v = BasicVersionResource.create().fromManifest(cl).build();
-			assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("name"));
+			var v = VersionMixin.create().fromManifest(cl).build();
+			assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("name"));
 		}
 	}
 
@@ -340,22 +340,22 @@ class BasicConvention_Builders_Test extends TestBase {
 			StandardCharsets.UTF_8);
 		try (var cl = new URLClassLoader(
 				new URL[]{ dirA.toUri().toURL(), dirB.toUri().toURL() }, null)) {
-			var v = BasicVersionResource.create().fromManifest(cl).build();
+			var v = VersionMixin.create().fromManifest(cl).build();
 			assertEquals("titled", v.getInfoMap().get("name"),
 				"Locator must prefer Implementation-Title-bearing manifest");
 		}
 	}
 
-	// -------- BasicWellKnownResource --------
+	// -------- WellKnownMixin --------
 
 	@Test void wellKnown_noArgConstructorYieldsNullBody() {
-		var w = new BasicWellKnownResource();
+		var w = new WellKnownMixin();
 		assertNull(w.getSecurityTxtBody());
 	}
 
 	@Test void wellKnown_securityTxtBodyAccessor() {
 		var body = "Contact: x@example.com\nExpires: 2027-01-01T00:00:00Z\n";
-		var w = BasicWellKnownResource.create().securityTxt(body).build();
+		var w = WellKnownMixin.create().securityTxt(body).build();
 		assertEquals(body, w.getSecurityTxtBody());
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_OpenApiHidden_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_OpenApiHidden_Test.java
index 3903409be2..6db045a2af 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_OpenApiHidden_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_OpenApiHidden_Test.java
@@ -31,7 +31,7 @@ import org.junit.jupiter.api.*;
  *
  * <p>
  * The host extends vanilla {@link RestServlet} (not {@link BasicRestServlet}) and mounts the four
- * convention mixins plus {@link BasicOpenApiResource} (the OpenAPI generator). The generated spec
+ * convention mixins plus {@link OpenApiMixin} (the OpenAPI generator). The generated spec
  * must list the host's own {@code /items} endpoint but NOT the convention paths.
  *
  * @since 9.5.0
@@ -40,11 +40,11 @@ class BasicConvention_OpenApiHidden_Test extends TestBase {
 
 	@Rest(
 		mixins={
-			BasicFaviconResource.class,
-			BasicSeoResource.class,
-			BasicVersionResource.class,
-			BasicWellKnownResource.class,
-			BasicOpenApiResource.class
+			FaviconMixin.class,
+			SeoMixin.class,
+			VersionMixin.class,
+			WellKnownMixin.class,
+			OpenApiMixin.class
 		},
 		swaggerProvider=BasicSwaggerProvider.class
 	)
@@ -53,12 +53,12 @@ class BasicConvention_OpenApiHidden_Test extends TestBase {
 
 		@RestGet(path="/items") public String items() { return "items"; }
 
-		@Bean public BasicVersionResource version() {
-			return BasicVersionResource.create().entry("name", "convention-openapi-test").build();
+		@Bean public VersionMixin version() {
+			return VersionMixin.create().entry("name", "convention-openapi-test").build();
 		}
 
-		@Bean public BasicWellKnownResource wellKnown() {
-			return BasicWellKnownResource.create().securityTxt("Contact: x@example.com\n").build();
+		@Bean public WellKnownMixin wellKnown() {
+			return WellKnownMixin.create().securityTxt("Contact: x@example.com\n").build();
 		}
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_ParentChain_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_ParentChain_Test.java
index 05d1e01873..96ab42cdd7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_ParentChain_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicConvention_ParentChain_Test.java
@@ -49,33 +49,33 @@ import org.junit.jupiter.api.*;
 class BasicConvention_ParentChain_Test extends TestBase {
 
 	@Rest(mixins={
-		BasicFaviconResource.class,
-		BasicSeoResource.class,
-		BasicVersionResource.class,
-		BasicWellKnownResource.class
+		FaviconMixin.class,
+		SeoMixin.class,
+		VersionMixin.class,
+		WellKnownMixin.class
 	})
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
 		@RestGet(path="/items") public String items() { return "items"; }
 
-		@Bean public BasicSeoResource seo() {
-			return BasicSeoResource.create()
+		@Bean public SeoMixin seo() {
+			return SeoMixin.create()
 				.robotsAllow("*", "/")
 				.sitemapEntry("https://example.com/items")
 				.build();
 		}
 
-		@Bean public BasicVersionResource version() {
-			return BasicVersionResource.create()
+		@Bean public VersionMixin version() {
+			return VersionMixin.create()
 				.entry("name", "convention-pack")
 				.entry("version", "9.5.0")
 				.fromJavaVersion()
 				.build();
 		}
 
-		@Bean public BasicWellKnownResource wellKnown() {
-			return BasicWellKnownResource.create()
+		@Bean public WellKnownMixin wellKnown() {
+			return WellKnownMixin.create()
 				.securityTxt("Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n")
 				.build();
 		}
@@ -88,10 +88,10 @@ class BasicConvention_ParentChain_Test extends TestBase {
 		var hostCtx = RestContext.getGlobalRegistry().get(A.class);
 		var ctxs = hostCtx.getMixinContexts();
 
-		assertNotNull(ctxs.get(BasicFaviconResource.class), "Favicon mixin context registered");
-		assertNotNull(ctxs.get(BasicSeoResource.class), "SEO mixin context registered");
-		assertNotNull(ctxs.get(BasicVersionResource.class), "Version mixin context registered");
-		assertNotNull(ctxs.get(BasicWellKnownResource.class), "Well-known mixin context registered");
+		assertNotNull(ctxs.get(FaviconMixin.class), "Favicon mixin context registered");
+		assertNotNull(ctxs.get(SeoMixin.class), "SEO mixin context registered");
+		assertNotNull(ctxs.get(VersionMixin.class), "Version mixin context registered");
+		assertNotNull(ctxs.get(WellKnownMixin.class), "Well-known mixin context registered");
 		assertEquals(4, ctxs.size(),
 			"Expected exactly four mixin contexts; got: " + ctxs.keySet());
 	}
@@ -118,7 +118,7 @@ class BasicConvention_ParentChain_Test extends TestBase {
 
 	@Test void a06_infoLegacyAliasNotMountedByDefault() throws Exception {
 		// FINISHED-101: /info is no longer a multi-path default. Migration to that secondary
-		// alias is covered by BasicVersionResource_SvlPathOverride_Test#a02.
+		// alias is covered by VersionMixin_SvlPathOverride_Test#a02.
 		c.get("/info").run().assertStatus(404);
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicFaviconResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/FaviconMixin_AsMixin_Test.java
similarity index 84%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicFaviconResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/FaviconMixin_AsMixin_Test.java
index f99ee392fd..d6b8126aeb 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicFaviconResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/FaviconMixin_AsMixin_Test.java
@@ -24,26 +24,26 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicFaviconResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link FaviconMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
  * Cases:
  * <ul>
  * 	<li>Default favicon (framework-shipped {@code /juneau-favicon.ico}) is served when no
- * 		{@code @Bean BasicFaviconResource} is registered on the host.
+ * 		{@code @Bean FaviconMixin} is registered on the host.
  * 	<li>{@code Content-Type: image/x-icon} and 30-day {@code Cache-Control} headers flow through.
- * 	<li>Importer's {@code @Bean BasicFaviconResource} factory overrides the default bytes.
- * 	<li>{@link BasicFaviconResource.Builder#classpath(String) classpath(...)} loads icon bytes
+ * 	<li>Importer's {@code @Bean FaviconMixin} factory overrides the default bytes.
+ * 	<li>{@link FaviconMixin.Builder#classpath(String) classpath(...)} loads icon bytes
  * 		from a classpath resource path.
  * 	<li>The host's own endpoints are unaffected by the mixin.
  * </ul>
  *
  * @since 9.5.0
  */
-class BasicFaviconResource_AsMixin_Test extends TestBase {
+class FaviconMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicFaviconResource.class)
+	@Rest(mixins=FaviconMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -77,12 +77,12 @@ class BasicFaviconResource_AsMixin_Test extends TestBase {
 			.assertContent().asString().isContains("items");
 	}
 
-	/** Host with an importer-supplied {@code @Bean BasicFaviconResource} carrying custom bytes. */
-	@Rest(mixins=BasicFaviconResource.class)
+	/** Host with an importer-supplied {@code @Bean FaviconMixin} carrying custom bytes. */
+	@Rest(mixins=FaviconMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicFaviconResource favicon() {
-			return BasicFaviconResource.create()
+		@Bean public FaviconMixin favicon() {
+			return FaviconMixin.create()
 				.bytes(new byte[]{(byte)0xCA, (byte)0xFE, (byte)0xBA, (byte)0xBE})
 				.build();
 		}
@@ -104,13 +104,13 @@ class BasicFaviconResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with an importer-supplied factory that loads bytes via classpath resource path. */
-	@Rest(mixins=BasicFaviconResource.class)
+	@Rest(mixins=FaviconMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicFaviconResource favicon() {
+		@Bean public FaviconMixin favicon() {
 			// /juneau-favicon.ico is the framework's default-shipping classpath resource;
 			// loading it explicitly via classpath(...) verifies the classpath path works.
-			return BasicFaviconResource.create()
+			return FaviconMixin.create()
 				.classpath("/juneau-favicon.ico")
 				.cacheControl("max-age=300, public")
 				.build();
@@ -129,7 +129,7 @@ class BasicFaviconResource_AsMixin_Test extends TestBase {
 
 	@Test void c02_classpathMissingFallsBackToDefault() throws Exception {
 		// A builder pointed at a missing classpath path falls back to the framework default.
-		var fav = BasicFaviconResource.create()
+		var fav = FaviconMixin.create()
 			.classpath("/no-such-favicon.ico")
 			.build();
 		// Even with a missing classpath override, the default favicon resource provides bytes.
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicSeoResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/SeoMixin_AsMixin_Test.java
similarity index 90%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicSeoResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/SeoMixin_AsMixin_Test.java
index 2771706a02..0dbbe18709 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicSeoResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/SeoMixin_AsMixin_Test.java
@@ -24,7 +24,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicSeoResource} mounted as a mixin via {@code @Rest(mixins=...)} on a vanilla
+ * Validates {@link SeoMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a vanilla
  * {@link RestServlet}.
  *
  * <p>
@@ -32,7 +32,7 @@ import org.junit.jupiter.api.*;
  * <ul>
  * 	<li>Default {@code /robots.txt} returns deny-all when no override is registered.
  * 	<li>Default {@code /sitemap.xml} returns an empty {@code <urlset>}.
- * 	<li>Importer's {@code @Bean BasicSeoResource} factory drives custom robots policy and sitemap
+ * 	<li>Importer's {@code @Bean SeoMixin} factory drives custom robots policy and sitemap
  * 		entries.
  * 	<li>Custom {@code robotsTxt(...)} body overrides any builder rules.
  * 	<li>Content-Type pinning works for both endpoints.
@@ -40,10 +40,10 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicSeoResource_AsMixin_Test extends TestBase {
+class SeoMixin_AsMixin_Test extends TestBase {
 
 	/** Default-host mounting the mixin with no @Bean override. */
-	@Rest(mixins=BasicSeoResource.class)
+	@Rest(mixins=SeoMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -76,11 +76,11 @@ class BasicSeoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with builder-driven robots policy via @Bean factory. */
-	@Rest(mixins=BasicSeoResource.class)
+	@Rest(mixins=SeoMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicSeoResource seo() {
-			return BasicSeoResource.create()
+		@Bean public SeoMixin seo() {
+			return SeoMixin.create()
 				.robotsAllow("*", "/")
 				.robotsDisallow("BadBot", "/private")
 				.sitemapEntry("https://example.com/")
@@ -106,11 +106,11 @@ class BasicSeoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with a custom robots body via robotsTxt(...). */
-	@Rest(mixins=BasicSeoResource.class)
+	@Rest(mixins=SeoMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicSeoResource seo() {
-			return BasicSeoResource.create()
+		@Bean public SeoMixin seo() {
+			return SeoMixin.create()
 				.robotsTxt("User-agent: *\nCrawl-delay: 10\nSitemap: https://example.com/sitemap.xml\n")
 				.build();
 		}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_AsMixin_Test.java
similarity index 86%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_AsMixin_Test.java
index 51a786d344..0d1a4501d3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_AsMixin_Test.java
@@ -28,7 +28,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicVersionResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link VersionMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -36,7 +36,7 @@ import org.junit.jupiter.api.*;
  * <ul>
  * 	<li>{@code /version} returns the JSON map (single SVL-configurable default mount).
  * 	<li>{@code Content-Type: application/json}.
- * 	<li>Importer's {@code @Bean BasicVersionResource} factory drives the entries map (manifest
+ * 	<li>Importer's {@code @Bean VersionMixin} factory drives the entries map (manifest
  * 		read, programmatic entries, custom Manifest).
  * 	<li>Missing manifest gracefully resolves to {@code (unknown)}.
  * </ul>
@@ -45,13 +45,13 @@ import org.junit.jupiter.api.*;
  * Per the FINISHED-101 multi-path collapse, the historical {@code /info} and {@code /about}
  * mount aliases (formerly a triple default on a single op) are now reached via
  * {@code -Djuneau.version.path=info} (or {@code about}); that behavior is covered by
- * {@code BasicVersionResource_SvlPathOverride_Test}.
+ * {@code VersionMixin_SvlPathOverride_Test}.
  *
  * @since 9.5.0
  */
-class BasicVersionResource_AsMixin_Test extends TestBase {
+class VersionMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -84,11 +84,11 @@ class BasicVersionResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host providing a programmatic version map via @Bean factory. */
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicVersionResource version() {
-			return BasicVersionResource.create()
+		@Bean public VersionMixin version() {
+			return VersionMixin.create()
 				.entry("name", "my-app")
 				.entry("version", "1.2.3")
 				.entry("gitCommit", "abc123")
@@ -113,10 +113,10 @@ class BasicVersionResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host providing a synthetic Manifest via fromManifest(Manifest). */
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicVersionResource version() {
+		@Bean public VersionMixin version() {
 			var manifest = new Manifest();
 			var attrs = manifest.getMainAttributes();
 			attrs.putValue("Manifest-Version", "1.0");
@@ -124,7 +124,7 @@ class BasicVersionResource_AsMixin_Test extends TestBase {
 			attrs.putValue("Implementation-Version", "9.9.9");
 			attrs.putValue("Implementation-Vendor", "Acme");
 			attrs.putValue("Build-Jdk", "21.0.0");
-			return BasicVersionResource.create().fromManifest(manifest).build();
+			return VersionMixin.create().fromManifest(manifest).build();
 		}
 	}
 
@@ -140,12 +140,12 @@ class BasicVersionResource_AsMixin_Test extends TestBase {
 	}
 
 	@Test void d01_missingManifestGracefulFallback() {
-		var v = BasicVersionResource.create()
+		var v = VersionMixin.create()
 			.fromManifest(new java.net.URLClassLoader(new java.net.URL[0], null))
 			.build();
-		Assertions.assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("name"),
+		Assertions.assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("name"),
 			"missing manifest must yield (unknown) name");
-		Assertions.assertEquals(BasicVersionResource.UNKNOWN, v.getInfoMap().get("version"),
+		Assertions.assertEquals(VersionMixin.UNKNOWN, v.getInfoMap().get("version"),
 			"missing manifest must yield (unknown) version");
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_JettyMicroservice_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_JettyMicroservice_Test.java
similarity index 91%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_JettyMicroservice_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_JettyMicroservice_Test.java
index 7721db0b06..bbcf501630 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_JettyMicroservice_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_JettyMicroservice_Test.java
@@ -34,13 +34,13 @@ import org.junit.jupiter.api.extension.*;
 import jakarta.servlet.*;
 
 /**
- * Real-Jetty deployment-parity assertion for {@link BasicVersionResource}.
+ * Real-Jetty deployment-parity assertion for {@link VersionMixin}.
  *
  * <p>
  * Boots a {@link org.apache.juneau.microservice.Microservice Microservice} backed by
  * {@link org.apache.juneau.microservice.jetty.JettyConfiguration JettyConfiguration} on an
  * ephemeral port via {@link MicroserviceTestFixture}, mounts a vanilla {@link RestServlet} host
- * with the version mixin and a {@code @Bean BasicVersionResource} factory configuring known
+ * with the version mixin and a {@code @Bean VersionMixin} factory configuring known
  * entries, and hits {@code /version}, {@code /info}, and {@code /about} over real HTTP.
  *
  * <p>
@@ -50,7 +50,7 @@ import jakarta.servlet.*;
  * 	<li>JSON serialization through {@link org.apache.juneau.rest.RestResponse#getDirectWriter
  * 		getDirectWriter("application/json")} on a vanilla {@link RestServlet} host (no JSON
  * 		serializer wired up explicitly).
- * 	<li>Mixin-walk + bean-store override flow when {@code @Bean BasicVersionResource} is
+ * 	<li>Mixin-walk + bean-store override flow when {@code @Bean VersionMixin} is
  * 		registered on the host: the mixin serves the host-supplied configuration end-to-end through
  * 		the network stack.
  * </ul>
@@ -58,13 +58,13 @@ import jakarta.servlet.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.JettyMicroserviceTest
-class BasicVersionResource_JettyMicroservice_Test extends TestBase {
+class VersionMixin_JettyMicroservice_Test extends TestBase {
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class Host extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicVersionResource version() {
-			return BasicVersionResource.create()
+		@Bean public VersionMixin version() {
+			return VersionMixin.create()
 				.entry("name", "jetty-test")
 				.entry("version", "0.0.1")
 				.entry("gitCommit", "deadbeef")
@@ -112,7 +112,7 @@ class BasicVersionResource_JettyMicroservice_Test extends TestBase {
 
 	@Test void a02_infoLegacyAliasNotMountedByDefault() throws Exception {
 		// FINISHED-101: /info is no longer a multi-path default. Migration covered by
-		// BasicVersionResource_SvlPathOverride_Test#a02.
+		// VersionMixin_SvlPathOverride_Test#a02.
 		var resp = get("/info");
 		assertTrue(resp.statusCode() == 404 || resp.statusCode() == 500,
 			"expected 404 or 500 (not mounted); got " + resp.statusCode() + ": " + resp.body());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_Springboot_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_Springboot_Test.java
similarity index 89%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_Springboot_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_Springboot_Test.java
index 6c8ba80b44..81238fe76f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_Springboot_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_Springboot_Test.java
@@ -36,20 +36,20 @@ import org.springframework.context.annotation.*;
 import org.springframework.test.annotation.*;
 
 /**
- * Real-Spring-Boot deployment-parity assertion for {@link BasicVersionResource}.
+ * Real-Spring-Boot deployment-parity assertion for {@link VersionMixin}.
  *
  * <p>
  * Boots a full Spring Boot context with embedded Tomcat on a random port, registers a
  * {@link BasicSpringRestServlet}-based host with the version mixin via
- * {@link ServletRegistrationBean}, supplies a Spring {@code @Bean BasicVersionResource}, and hits
+ * {@link ServletRegistrationBean}, supplies a Spring {@code @Bean VersionMixin}, and hits
  * {@code /version}, {@code /info}, and {@code /about} over real HTTP.
  *
  * <p>
  * Catches things {@code MockRest} and the
- * {@link BasicVersionResource_JettyMicroservice_Test Jetty parity test} cannot:
+ * {@link VersionMixin_JettyMicroservice_Test Jetty parity test} cannot:
  * <ul>
  * 	<li>Spring's bean store adapter ({@code SpringBeanStore}) resolving the host's
- * 		{@code @Bean BasicVersionResource} during the FINISHED-72 mixin walk through
+ * 		{@code @Bean VersionMixin} during the FINISHED-72 mixin walk through
  * 		{@link org.springframework.context.ApplicationContext#getBean(Class)
  * 		ApplicationContext.getBean(...)}.
  * 	<li>End-to-end format-pinned JSON ({@link org.apache.juneau.rest.RestResponse#getDirectWriter
@@ -59,10 +59,10 @@ import org.springframework.test.annotation.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.SpringbootTest
-@SpringBootTest(classes = BasicVersionResource_Springboot_Test.TestApp.class,
+@SpringBootTest(classes = VersionMixin_Springboot_Test.TestApp.class,
 	webEnvironment = WebEnvironment.RANDOM_PORT)
 @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
-class BasicVersionResource_Springboot_Test {
+class VersionMixin_Springboot_Test {
 
 	@SpringBootConfiguration
 	@EnableAutoConfiguration
@@ -79,8 +79,8 @@ class BasicVersionResource_Springboot_Test {
 		}
 
 		@Bean
-		public BasicVersionResource versionResource() {
-			return BasicVersionResource.create()
+		public VersionMixin versionResource() {
+			return VersionMixin.create()
 				.entry("name", "spring-test")
 				.entry("version", "0.0.2")
 				.entry("gitBranch", "release")
@@ -89,7 +89,7 @@ class BasicVersionResource_Springboot_Test {
 		}
 	}
 
-	@Rest(mixins = BasicVersionResource.class)
+	@Rest(mixins = VersionMixin.class)
 	public static class Host extends BasicSpringRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -132,7 +132,7 @@ class BasicVersionResource_Springboot_Test {
 
 	@Test void a03_aboutLegacyAliasNotMountedByDefault() throws Exception {
 		// FINISHED-101: /about is no longer a multi-path default. Migration covered by
-		// BasicVersionResource_SvlPathOverride_Test#a02.
+		// VersionMixin_SvlPathOverride_Test#a02.
 		var resp = get("/about");
 		assertTrue(resp.statusCode() == 404 || resp.statusCode() == 500,
 			"expected 404 or 500 (not mounted); got " + resp.statusCode() + ": " + resp.body());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_SvlPathOverride_Test.java
index b4844aff39..baa14a0e2f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_SvlPathOverride_Test.java
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the {@code ${juneau.version.path:version}} SVL override on
- * {@link BasicVersionResource}.
+ * {@link VersionMixin}.
  *
  * <p>
  * Per the FINISHED-101 multi-path collapse, the historical {@code /info} and {@code /about}
@@ -39,9 +39,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicVersionResource_SvlPathOverride_Test extends TestBase {
+class VersionMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -67,7 +67,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A02_InfoAliasMigration extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -93,7 +93,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A03_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -111,7 +111,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A04_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -129,7 +129,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A05_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -147,7 +147,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A06_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -165,7 +165,7 @@ class BasicVersionResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicVersionResource.class)
+	@Rest(mixins=VersionMixin.class)
 	public static class A07_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_ValueAdoption_Test.java
similarity index 86%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_ValueAdoption_Test.java
index dcb0f0c049..8329e82e6c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicVersionResource_ValueAdoption_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/VersionMixin_ValueAdoption_Test.java
@@ -23,7 +23,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * TODO-92 acceptance tests for the {@code @Value}-driven {@code java.version} default on
- * {@link BasicVersionResource.Builder}.
+ * {@link VersionMixin.Builder}.
  *
  * <p>
  * 3-test triad per migrated field per OQA #4 — system property set, unset (default = JVM-resolved),
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.*;
  * is set by the JVM itself; the assertion simply verifies that the JVM-supplied value is what flows
  * through {@code @Value} resolution.
  */
-class BasicVersionResource_ValueAdoption_Test {
+class VersionMixin_ValueAdoption_Test {
 
 	private static final String SP = "java.version";
 
@@ -57,20 +57,20 @@ class BasicVersionResource_ValueAdoption_Test {
 	@Test
 	void a01_javaVersion_set() {
 		System.setProperty(SP, "todo92-set");
-		var v = BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+		var v = VersionMixin.create().fromJavaVersion().build().getInfoMap();
 		assertEquals("todo92-set", v.get("javaVersion"));
 	}
 
 	@Test
 	void a02_javaVersion_unset() {
-		var v = BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+		var v = VersionMixin.create().fromJavaVersion().build().getInfoMap();
 		assertEquals(saved, v.get("javaVersion"));
 	}
 
 	@Test
 	void a03_javaVersion_setGlobal() {
 		Settings.get().setGlobal(SP, "todo92-global");
-		var v = BasicVersionResource.create().fromJavaVersion().build().getInfoMap();
+		var v = VersionMixin.create().fromJavaVersion().build().getInfoMap();
 		assertEquals("todo92-global", v.get("javaVersion"));
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicWellKnownResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/WellKnownMixin_AsMixin_Test.java
similarity index 85%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicWellKnownResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/convention/WellKnownMixin_AsMixin_Test.java
index 5f7fb1f597..493ed727c4 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/convention/BasicWellKnownResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/convention/WellKnownMixin_AsMixin_Test.java
@@ -24,7 +24,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicWellKnownResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link WellKnownMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -32,15 +32,15 @@ import org.junit.jupiter.api.*;
  * <ul>
  * 	<li>{@code /.well-known/security.txt} returns {@code 404 Not Found} when no body is configured
  * 		(per RFC 9116 default — no placeholder).
- * 	<li>Importer's {@code @Bean BasicWellKnownResource} factory drives the body content.
+ * 	<li>Importer's {@code @Bean WellKnownMixin} factory drives the body content.
  * 	<li>{@code Content-Type: text/plain; charset=UTF-8} when a body is configured.
  * </ul>
  *
  * @since 9.5.0
  */
-class BasicWellKnownResource_AsMixin_Test extends TestBase {
+class WellKnownMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicWellKnownResource.class)
+	@Rest(mixins=WellKnownMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -57,11 +57,11 @@ class BasicWellKnownResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host that configures a security.txt body via @Bean factory. */
-	@Rest(mixins=BasicWellKnownResource.class)
+	@Rest(mixins=WellKnownMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicWellKnownResource wellKnown() {
-			return BasicWellKnownResource.create()
+		@Bean public WellKnownMixin wellKnown() {
+			return WellKnownMixin.create()
 				.securityTxt("Contact: security@example.com\nExpires: 2027-01-01T00:00:00Z\n")
 				.build();
 		}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_JettyMicroservice_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_JettyMicroservice_Test.java
index 075156bb78..8125ac1725 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_JettyMicroservice_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_JettyMicroservice_Test.java
@@ -38,8 +38,8 @@ import jakarta.servlet.*;
  * {@link org.apache.juneau.microservice.jetty.JettyConfiguration JettyConfiguration} on an
  * ephemeral port via {@link MicroserviceTestFixture}, mounts a {@link BasicRestServlet} host that
  * inherits the four-mixin pack
- * ({@link BasicSwaggerUiResource} + {@link BasicRedocResource} &mdash; transitively pulling in
- * {@link BasicSwaggerResource} + {@link BasicOpenApiResource}), and then hits the six canonical
+ * ({@link SwaggerUiMixin} + {@link RedocMixin} &mdash; transitively pulling in
+ * {@link SwaggerMixin} + {@link OpenApiMixin}), and then hits the six canonical
  * api-docs URLs over real HTTP.
  *
  * <p>
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_ParentChain_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_ParentChain_Test.java
index c79e9ab109..c4f9a8e2e0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_ParentChain_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_ParentChain_Test.java
@@ -21,7 +21,9 @@ import static org.junit.jupiter.api.Assertions.*;
 import org.apache.juneau.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.convention.*;
 import org.apache.juneau.rest.mock.classic.*;
+import org.apache.juneau.rest.ops.*;
 import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
@@ -32,27 +34,27 @@ import org.junit.jupiter.api.*;
  * <p>
  * Setup:
  * <ul>
- * 	<li>{@code Parent} declares {@code @Rest(mixins=BasicSwaggerUiResource.class)}.
- * 	<li>{@code Child} extends {@code Parent} and declares {@code @Rest(mixins=BasicRedocResource.class)}.
+ * 	<li>{@code Parent} declares {@code @Rest(mixins=SwaggerUiMixin.class)}.
+ * 	<li>{@code Child} extends {@code Parent} and declares {@code @Rest(mixins=RedocMixin.class)}.
  * </ul>
  *
  * <p>
  * Acceptance:
  * <ul>
  * 	<li>{@code Child}'s {@code mixinContexts} map contains all four mixin classes
- * 		({@code BasicSwaggerUiResource}, {@code BasicSwaggerResource} via transitive,
- * 		{@code BasicRedocResource}, {@code BasicOpenApiResource} via transitive).
+ * 		({@code SwaggerUiMixin}, {@code SwaggerMixin} via transitive,
+ * 		{@code RedocMixin}, {@code OpenApiMixin} via transitive).
  * 	<li>All four URL paths resolve correctly.
  * </ul>
  */
 class BasicApiDocs_ParentChain_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class Parent extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class Child extends Parent {
 		private static final long serialVersionUID = 1L;
 	}
@@ -64,13 +66,18 @@ class BasicApiDocs_ParentChain_Test extends TestBase {
 		var hostCtx = RestContext.getGlobalRegistry().get(Child.class);
 		var contexts = hostCtx.getMixinContexts();
 
-		assertNotNull(contexts.get(BasicSwaggerUiResource.class), "Parent's mixin must propagate to child");
-		assertNotNull(contexts.get(BasicSwaggerResource.class), "Parent's transitive mixin must propagate to child");
-		assertNotNull(contexts.get(BasicRedocResource.class), "Child's mixin must be present");
-		assertNotNull(contexts.get(BasicOpenApiResource.class), "Child's transitive mixin must be present");
+		assertNotNull(contexts.get(SwaggerUiMixin.class), "Parent's mixin must propagate to child");
+		assertNotNull(contexts.get(SwaggerMixin.class), "Parent's transitive mixin must propagate to child");
+		assertNotNull(contexts.get(RedocMixin.class), "Child's mixin must be present");
+		assertNotNull(contexts.get(OpenApiMixin.class), "Child's transitive mixin must be present");
+		// Residual op-mixins contributed by the BasicRestServlet base of the parent chain.
+		assertNotNull(contexts.get(ErrorMixin.class));
+		assertNotNull(contexts.get(HtdocMixin.class));
+		assertNotNull(contexts.get(StatsMixin.class));
+		assertNotNull(contexts.get(FaviconMixin.class));
 
-		assertEquals(4, contexts.size(),
-			"Expected exactly four mixin contexts; got: " + contexts.keySet());
+		assertEquals(8, contexts.size(),
+			"Expected exactly eight mixin contexts; got: " + contexts.keySet());
 	}
 
 	@Test void a02_apiFromParentChain() throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_Springboot_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_Springboot_Test.java
index 0e13ab00e8..e3c2deb59c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_Springboot_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_Springboot_Test.java
@@ -39,8 +39,8 @@ import org.springframework.test.annotation.*;
  * <p>
  * Boots a full Spring Boot context with embedded Tomcat on a random port, registers a
  * {@link BasicSpringRestServlet}-based host wired up with the four-mixin pack
- * ({@link BasicSwaggerUiResource} + {@link BasicRedocResource} &mdash; transitively pulling in
- * {@link BasicSwaggerResource} + {@link BasicOpenApiResource}) via {@link ServletRegistrationBean},
+ * ({@link SwaggerUiMixin} + {@link RedocMixin} &mdash; transitively pulling in
+ * {@link SwaggerMixin} + {@link OpenApiMixin}) via {@link ServletRegistrationBean},
  * and hits the six canonical api-docs URLs over real HTTP via {@link HttpClient}.
  *
  * <p>
@@ -109,7 +109,7 @@ class BasicApiDocs_Springboot_Test {
 	 * Inherited}, so this child annotation aggregates with the parent's via the mixin walk
 	 * (parent-first, child-appended; {@code LinkedHashSet} dedupes by class identity).
 	 */
-	@Rest(mixins = {BasicSwaggerUiResource.class, BasicRedocResource.class})
+	@Rest(mixins = {SwaggerUiMixin.class, RedocMixin.class})
 	public static class Host extends BasicSpringRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_TransitiveDedupe_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_TransitiveDedupe_Test.java
index d0438b80d3..3810488f76 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_TransitiveDedupe_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicApiDocs_TransitiveDedupe_Test.java
@@ -21,7 +21,9 @@ import static org.junit.jupiter.api.Assertions.*;
 import org.apache.juneau.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.convention.*;
 import org.apache.juneau.rest.mock.classic.*;
+import org.apache.juneau.rest.ops.*;
 import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
@@ -30,40 +32,49 @@ import org.junit.jupiter.api.*;
  * together.
  *
  * <p>
- * {@code @Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})} should pull in
- * {@code BasicSwaggerResource} (via {@code BasicSwaggerUiResource}) and
- * {@code BasicOpenApiResource} (via {@code BasicRedocResource}) exactly once each &mdash; not twice,
+ * {@code @Rest(mixins={SwaggerUiMixin.class, RedocMixin.class})} should pull in
+ * {@code SwaggerMixin} (via {@code SwaggerUiMixin}) and
+ * {@code OpenApiMixin} (via {@code RedocMixin}) exactly once each &mdash; not twice,
  * even though the user could equivalently have listed all four mixins explicitly.
  *
  * <p>
  * Acceptance:
  * <ul>
- * 	<li>All four mixin classes are present in the host's {@code mixinContexts} map.
- * 	<li>Each mixin appears exactly once (no duplicates).
- * 	<li>All six URL paths (across the four mixins) resolve correctly.
+ * 	<li>The four api-docs mixin classes are present in the host's {@code mixinContexts} map, alongside the
+ * 		four residual op-mixins ({@code ErrorMixin}, {@code HtdocMixin}, {@code StatsMixin},
+ * 		{@code FaviconMixin}) contributed by {@code BasicRestServlet}.
+ * 	<li>Each mixin appears exactly once (no duplicates) &mdash; eight in total.
+ * 	<li>All six api-docs URL paths resolve correctly.
  * </ul>
  */
 class BasicApiDocs_TransitiveDedupe_Test extends TestBase {
 
-	@Rest(mixins={BasicSwaggerUiResource.class, BasicRedocResource.class})
+	@Rest(mixins={SwaggerUiMixin.class, RedocMixin.class})
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
 
 	private static final MockRestClient c = MockRestClient.buildLax(A.class);
 
-	@Test void a01_allFourMixinContextsPresent() throws Exception {
+	@Test void a01_allMixinContextsPresent() throws Exception {
 		MockRestClient.buildLax(A.class);
 		var hostCtx = RestContext.getGlobalRegistry().get(A.class);
 		var contexts = hostCtx.getMixinContexts();
 
-		assertNotNull(contexts.get(BasicSwaggerUiResource.class));
-		assertNotNull(contexts.get(BasicSwaggerResource.class));
-		assertNotNull(contexts.get(BasicRedocResource.class));
-		assertNotNull(contexts.get(BasicOpenApiResource.class));
+		// api-docs pack — deduped from A's explicit SwaggerUiMixin/RedocMixin + BasicRestServlet's own list
+		// plus the transitive SwaggerMixin/OpenApiMixin pulls (each present exactly once).
+		assertNotNull(contexts.get(SwaggerUiMixin.class));
+		assertNotNull(contexts.get(SwaggerMixin.class));
+		assertNotNull(contexts.get(RedocMixin.class));
+		assertNotNull(contexts.get(OpenApiMixin.class));
+		// Residual op-mixins contributed by BasicRestServlet.
+		assertNotNull(contexts.get(ErrorMixin.class));
+		assertNotNull(contexts.get(HtdocMixin.class));
+		assertNotNull(contexts.get(StatsMixin.class));
+		assertNotNull(contexts.get(FaviconMixin.class));
 
-		assertEquals(4, contexts.size(),
-			"Expected exactly four mixin contexts (no duplicates); got: " + contexts.keySet());
+		assertEquals(8, contexts.size(),
+			"Expected exactly eight mixin contexts (no duplicates); got: " + contexts.keySet());
 	}
 
 	@Test void a02_apiPathReachable() throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_AsMixin_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_AsMixin_Test.java
index 0098b39e34..f7e92158d0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_AsMixin_Test.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicOpenApiResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link OpenApiMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -35,9 +35,9 @@ import org.junit.jupiter.api.*;
  * 	<li>The host's own endpoints are unaffected.
  * </ul>
  */
-class BasicOpenApiResource_AsMixin_Test extends TestBase {
+class OpenApiMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_SvlPathOverride_Test.java
index 71e061ffc7..b63abd5a05 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicOpenApiResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/OpenApiMixin_SvlPathOverride_Test.java
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the {@code ${juneau.openapi.path:openapi}} SVL override on
- * {@link BasicOpenApiResource}.
+ * {@link OpenApiMixin}.
  *
  * <p>
  * The same variable is reused across all three op-paths on this mixin
@@ -39,9 +39,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
+class OpenApiMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -77,7 +77,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A02_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -95,7 +95,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A03_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -113,7 +113,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A04_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -131,7 +131,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A05_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -149,7 +149,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A06_WildcardSuffix extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -167,7 +167,7 @@ class BasicOpenApiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicOpenApiResource.class)
+	@Rest(mixins=OpenApiMixin.class)
 	public static class A07_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_AsMixin_Test.java
similarity index 85%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_AsMixin_Test.java
index 57285b4300..504963d9fa 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_AsMixin_Test.java
@@ -26,12 +26,12 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicRedocResource} mounted as a mixin.
+ * Validates {@link RedocMixin} mounted as a mixin.
  *
  * <p>
  * Acceptance:
  * <ul>
- * 	<li>Transitive resolution brings in {@link BasicOpenApiResource}: {@code /openapi}, {@code /openapi.json},
+ * 	<li>Transitive resolution brings in {@link OpenApiMixin}: {@code /openapi}, {@code /openapi.json},
  * 		and {@code /openapi.yaml} are all mounted alongside {@code /redoc}.
  * 	<li>{@code GET /redoc} with no {@code Accept} header renders HTML (via {@code defaultAccept}).
  * 	<li>{@code GET /redoc} with {@code Accept: application/json} returns the OpenAPI spec as JSON
@@ -39,9 +39,9 @@ import org.junit.jupiter.api.*;
  * 	<li>Both endpoints are backed by separate sub-{@code RestContext}s.
  * </ul>
  */
-class BasicRedocResource_AsMixin_Test extends TestBase {
+class RedocMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -90,7 +90,7 @@ class BasicRedocResource_AsMixin_Test extends TestBase {
 		MockRestClient.buildLax(A.class);
 		var hostCtx = RestContext.getGlobalRegistry().get(A.class);
 		var contexts = hostCtx.getMixinContexts();
-		assertNotNull(contexts.get(BasicRedocResource.class), "BasicRedocResource sub-context must be present");
-		assertNotNull(contexts.get(BasicOpenApiResource.class), "BasicOpenApiResource sub-context must be transitively present");
+		assertNotNull(contexts.get(RedocMixin.class), "RedocMixin sub-context must be present");
+		assertNotNull(contexts.get(OpenApiMixin.class), "OpenApiMixin sub-context must be transitively present");
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_SvlPathOverride_Test.java
index 3ecd29a9f4..27829bfe76 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicRedocResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/RedocMixin_SvlPathOverride_Test.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates the {@code ${juneau.redoc.path:redoc}} SVL override on {@link BasicRedocResource}.
+ * Validates the {@code ${juneau.redoc.path:redoc}} SVL override on {@link RedocMixin}.
  *
  * <p>
  * Uses a fresh inner-class resource because {@link MockRestClient} caches {@link RestContext} per
@@ -31,9 +31,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicRedocResource_SvlPathOverride_Test extends TestBase {
+class RedocMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -61,7 +61,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A02_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -79,7 +79,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A03_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -97,7 +97,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A04_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -115,7 +115,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A05_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -133,7 +133,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A06_WildcardSuffix extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -151,7 +151,7 @@ class BasicRedocResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRedocResource.class)
+	@Rest(mixins=RedocMixin.class)
 	public static class A07_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_AsMixin_Test.java
similarity index 92%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_AsMixin_Test.java
index 20490d4258..9101a36b5a 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_AsMixin_Test.java
@@ -24,7 +24,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicSwaggerResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link SwaggerMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -37,9 +37,9 @@ import org.junit.jupiter.api.*;
  * 	<li>The host's own endpoints are unaffected.
  * </ul>
  */
-class BasicSwaggerResource_AsMixin_Test extends TestBase {
+class SwaggerMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_Standalone_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_Standalone_Test.java
similarity index 83%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_Standalone_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_Standalone_Test.java
index c10f02140c..28ac1d89d0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_Standalone_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_Standalone_Test.java
@@ -25,20 +25,20 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicSwaggerResource} composed onto a vanilla {@link RestServlet} — i.e. used
+ * Validates {@link SwaggerMixin} composed onto a vanilla {@link RestServlet} — i.e. used
  * outside of the {@code BasicRestServlet} chain.
  *
  * <p>
  * The mixin's {@code @RestOp} methods are pure POJOs that inherit serializers/parsers from the
  * host's {@code RestContext} via the FINISHED-81 sub-context model. This test demonstrates that a
- * minimal host (vanilla {@link RestObject} + {@link BasicUniversalConfig}) is sufficient to mount
+ * minimal host (vanilla {@link RestResource} + {@link BasicUniversalConfig}) is sufficient to mount
  * the {@code /api} endpoint without dragging in the rest of the {@code BasicRestOperations}
  * surface.
  */
-class BasicSwaggerResource_Standalone_Test extends TestBase {
+class SwaggerMixin_Standalone_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerResource.class)
-	public static class A extends RestObject implements BasicUniversalConfig {}
+	@Rest(mixins=SwaggerMixin.class)
+	public static class A extends RestResource implements BasicUniversalConfig {}
 
 	private static final MockRestClient c = MockRestClient.buildLax(A.class);
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_SvlPathOverride_Test.java
index 94658e0cc8..4059dcfb76 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerMixin_SvlPathOverride_Test.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates the {@code ${juneau.swagger.path:api}} SVL override on {@link BasicSwaggerResource}.
+ * Validates the {@code ${juneau.swagger.path:api}} SVL override on {@link SwaggerMixin}.
  *
  * <p>
  * Uses a fresh inner-class resource because {@link MockRestClient} caches {@link RestContext} per
@@ -33,9 +33,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
+class SwaggerMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -65,7 +65,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A02_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -83,7 +83,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A03_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -101,7 +101,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A04_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -119,7 +119,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A05_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -137,7 +137,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A06_WildcardSuffix extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -155,7 +155,7 @@ class BasicSwaggerResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerResource.class)
+	@Rest(mixins=SwaggerMixin.class)
 	public static class A07_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_AsMixin_Test.java
similarity index 84%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_AsMixin_Test.java
index 88ce6ceea8..3bc8f42260 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_AsMixin_Test.java
@@ -27,12 +27,12 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicSwaggerUiResource} mounted as a mixin.
+ * Validates {@link SwaggerUiMixin} mounted as a mixin.
  *
  * <p>
  * Acceptance:
  * <ul>
- * 	<li>Transitive resolution brings in {@link BasicSwaggerResource}: {@code /api} is mounted alongside
+ * 	<li>Transitive resolution brings in {@link SwaggerMixin}: {@code /api} is mounted alongside
  * 		{@code /swagger}.
  * 	<li>{@code GET /swagger} with no {@code Accept} header renders HTML (via the {@code defaultAccept})
  * 		— this is the no-Accept-defaults-to-HTML behavior unique to UI mixins.
@@ -42,9 +42,9 @@ import org.junit.jupiter.api.*;
  * 	<li>Both endpoints are backed by separate sub-{@code RestContext}s per FINISHED-81.
  * </ul>
  */
-class BasicSwaggerUiResource_AsMixin_Test extends TestBase {
+class SwaggerUiMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -79,7 +79,7 @@ class BasicSwaggerUiResource_AsMixin_Test extends TestBase {
 		MockRestClient.buildLax(A.class);
 		var hostCtx = RestContext.getGlobalRegistry().get(A.class);
 		var contexts = hostCtx.getMixinContexts();
-		assertNotNull(contexts.get(BasicSwaggerUiResource.class), "BasicSwaggerUiResource sub-context must be present");
-		assertNotNull(contexts.get(BasicSwaggerResource.class), "BasicSwaggerResource sub-context must be transitively present");
+		assertNotNull(contexts.get(SwaggerUiMixin.class), "SwaggerUiMixin sub-context must be present");
+		assertNotNull(contexts.get(SwaggerMixin.class), "SwaggerMixin sub-context must be transitively present");
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_SvlPathOverride_Test.java
index be0cd7fd06..740842a3df 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/docs/BasicSwaggerUiResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/docs/SwaggerUiMixin_SvlPathOverride_Test.java
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the {@code ${juneau.swaggerui.path:swagger}} SVL override on
- * {@link BasicSwaggerUiResource}.
+ * {@link SwaggerUiMixin}.
  *
  * <p>
  * Uses a fresh inner-class resource because {@link MockRestClient} caches {@link RestContext} per
@@ -32,9 +32,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
+class SwaggerUiMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -62,7 +62,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A02_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -80,7 +80,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A03_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -98,7 +98,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A04_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -116,7 +116,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A05_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -134,7 +134,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A06_WildcardSuffix extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -152,7 +152,7 @@ class BasicSwaggerUiResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicSwaggerUiResource.class)
+	@Rest(mixins=SwaggerUiMixin.class)
 	public static class A07_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthMixin_Test.java
similarity index 95%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthMixin_Test.java
index 3418163d72..3857d6f017 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthMixin_Test.java
@@ -29,9 +29,9 @@ import org.apache.juneau.rest.mock.classic.*;
 import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
-class BasicHealthResource_AsMixin_Test extends TestBase {
+class HealthMixin_Test extends TestBase {
 
-	@Rest(mixins={BasicHealthResource.class})
+	@Rest(mixins={HealthMixin.class})
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthServlet_Test.java
similarity index 94%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthServlet_Test.java
index 2e0063f82e..48c0b126f7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/health/BasicHealthResource_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/health/HealthServlet_Test.java
@@ -25,10 +25,10 @@ import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.mock.classic.*;
 import org.junit.jupiter.api.*;
 
-class BasicHealthResource_Test extends TestBase {
+class HealthServlet_Test extends TestBase {
 
 	@Rest
-	public static class A extends BasicHealthResource {
+	public static class A extends HealthServlet {
 		@Override
 		protected Map<String,HealthIndicator> indicators() {
 			return Map.of(
@@ -39,7 +39,7 @@ class BasicHealthResource_Test extends TestBase {
 	}
 
 	@Rest
-	public static class B extends BasicHealthResource {
+	public static class B extends HealthServlet {
 		@Override
 		protected Map<String,HealthIndicator> indicators() {
 			return Map.of(
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiSchemaReuse_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiSchemaReuse_Test.java
index 872f431bd3..78c92ff5c7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiSchemaReuse_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiSchemaReuse_Test.java
@@ -63,7 +63,7 @@ class OpenApiSchemaReuse_Test extends TestBase {
 	}
 
 	@Rest
-	public static class A extends BasicRestObject {
+	public static class A extends BasicRestResource {
 		@RestGet(path="/pet") public Pet getPet() { return new Pet(); }
 		@RestPost(path="/pet") public Pet createPet(@Content Pet pet) { return pet; }
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiYamlRoundTrip_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiYamlRoundTrip_Test.java
index fe2097a955..d356da0057 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiYamlRoundTrip_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/openapi/OpenApiYamlRoundTrip_Test.java
@@ -89,7 +89,7 @@ class OpenApiYamlRoundTrip_Test extends TestBase {
 
 	// Inherits the FULL BasicRestServlet api-docs mixin surface — six api-docs URLs, full schema
 	// set, the works. Earlier this resource shipped with noInherit={"mixins"} + mixins={
-	// BasicOpenApiResource} because round-tripping the full doc through the YAML parser threw
+	// OpenApiMixin} because round-tripping the full doc through the YAML parser threw
 	// IOException: Buffer underflow; that latent ParserReader limitation was fixed by widening the
 	// reader's no-mark unread-lookback window (see ParserReader.UNMARKED_LOOKBACK_CHARS), so c01
 	// now asserts against the FULL mixin-pack mount surface.
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AsMixin_Test.java
similarity index 89%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AsMixin_Test.java
index c1a360b0a2..15382f0ced 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AsMixin_Test.java
@@ -30,7 +30,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicAdminResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link AdminMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -49,13 +49,13 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicAdminResource_AsMixin_Test extends TestBase {
+class AdminMixin_AsMixin_Test extends TestBase {
 
 	// -----------------------------------------------------------------------------------------
 	// Default-deny posture (no host-supplied @Bean RestGuardList).
 	// -----------------------------------------------------------------------------------------
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -88,7 +88,7 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 	// -----------------------------------------------------------------------------------------
 
 	/** Allow-all guard chain — replaces the mixin's annotation-derived deny-all. */
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -97,8 +97,8 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 			return RestGuardList.create(bs).build();
 		}
 
-		@Bean public BasicAdminResource admin() {
-			return BasicAdminResource.create()
+		@Bean public AdminMixin admin() {
+			return AdminMixin.create()
 				.cacheFlush("primary", FLUSH_PRIMARY)
 				.cacheFlush("secondary", FLUSH_SECONDARY)
 				.threadNamePrefixExclude()  // disable filtering to make /admin/threads deterministic
@@ -188,7 +188,7 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 	// Allow-all guard list + a registered RateLimitGuard bean.
 	// -----------------------------------------------------------------------------------------
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -221,14 +221,14 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 
 	@Test void d01_builderRejectsBlankCacheFlushName() {
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicAdminResource.create().cacheFlush("", () -> {}));
+			() -> AdminMixin.create().cacheFlush("", () -> {}));
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicAdminResource.create().cacheFlush(null, () -> {}));
+			() -> AdminMixin.create().cacheFlush(null, () -> {}));
 	}
 
 	@Test void d02_builderRejectsNullHook() {
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicAdminResource.create().cacheFlush("foo", null));
+			() -> AdminMixin.create().cacheFlush("foo", null));
 	}
 
 	@Test void d03_cacheFlushAllRoundTrips() {
@@ -237,26 +237,26 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 		var r2 = (Runnable) () -> {};
 		hooks.put("a", r1);
 		hooks.put("b", r2);
-		var admin = BasicAdminResource.create().cacheFlushAll(hooks).build();
+		var admin = AdminMixin.create().cacheFlushAll(hooks).build();
 		Assertions.assertEquals(2, admin.getCacheFlushHooks().size());
 		Assertions.assertSame(r1, admin.getCacheFlushHooks().get("a"));
 		Assertions.assertSame(r2, admin.getCacheFlushHooks().get("b"));
 	}
 
 	@Test void d04_threadNameExcludeReplaceList() {
-		var admin = BasicAdminResource.create().threadNamePrefixExclude("foo-", "bar-").build();
+		var admin = AdminMixin.create().threadNamePrefixExclude("foo-", "bar-").build();
 		Assertions.assertEquals(List.of("foo-", "bar-"), admin.getThreadNamePrefixExclude());
 	}
 
 	@Test void d05_defaultExcludeListContainsKnownNoise() {
-		Assertions.assertTrue(BasicAdminResource.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE.contains("Reference Handler"));
-		Assertions.assertTrue(BasicAdminResource.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE.contains("jetty-"));
+		Assertions.assertTrue(AdminMixin.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE.contains("Reference Handler"));
+		Assertions.assertTrue(AdminMixin.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE.contains("jetty-"));
 	}
 
 	@Test void d06_noArgConstructorMatchesEmptyHooks() {
-		var r = new BasicAdminResource();
+		var r = new AdminMixin();
 		Assertions.assertTrue(r.getCacheFlushHooks().isEmpty());
-		Assertions.assertEquals(BasicAdminResource.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE,
+		Assertions.assertEquals(AdminMixin.DEFAULT_THREAD_NAME_PREFIX_EXCLUDE,
 			r.getThreadNamePrefixExclude());
 	}
 
@@ -267,18 +267,18 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 	}
 
 	@Test void d08_cacheFlushAllNullIsNoOp() {
-		var admin = BasicAdminResource.create().cacheFlushAll(null).build();
+		var admin = AdminMixin.create().cacheFlushAll(null).build();
 		Assertions.assertTrue(admin.getCacheFlushHooks().isEmpty());
 	}
 
 	@Test void d09_threadNamePrefixExcludeNullClearsList() {
-		var admin = BasicAdminResource.create().threadNamePrefixExclude((String[]) null).build();
+		var admin = AdminMixin.create().threadNamePrefixExclude((String[]) null).build();
 		Assertions.assertTrue(admin.getThreadNamePrefixExclude().isEmpty(),
 			"null varargs should clear the list");
 	}
 
 	@Test void d10_threadNamePrefixExcludeFiltersNullAndEmpty() {
-		var admin = BasicAdminResource.create().threadNamePrefixExclude("good-", null, "", "also-").build();
+		var admin = AdminMixin.create().threadNamePrefixExclude("good-", null, "", "also-").build();
 		Assertions.assertEquals(List.of("good-", "also-"), admin.getThreadNamePrefixExclude());
 	}
 
@@ -287,15 +287,15 @@ class BasicAdminResource_AsMixin_Test extends TestBase {
 	// isExcludedThread when the configured filter list catches a real thread).
 	// -----------------------------------------------------------------------------------------
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class F extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
 
 		// Prefix that will match the JUnit launcher / test thread on every supported JVM.
-		@Bean public BasicAdminResource admin() {
-			return BasicAdminResource.create()
+		@Bean public AdminMixin admin() {
+			return AdminMixin.create()
 				.threadNamePrefixExclude("ForkJoinPool", "main", "junit-")
 				.build();
 		}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AuthIntegration_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AuthIntegration_Test.java
similarity index 94%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AuthIntegration_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AuthIntegration_Test.java
index ebf7415180..5f498c2256 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_AuthIntegration_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_AuthIntegration_Test.java
@@ -29,20 +29,20 @@ import org.junit.jupiter.api.*;
 
 /**
  * End-to-end integration smoke test for FINISHED-77's
- * {@link BasicAdminResource}-with-DenyAllGuard-default ↔ TODO-69's {@link BearerTokenGuard}
+ * {@link AdminMixin}-with-DenyAllGuard-default ↔ TODO-69's {@link BearerTokenGuard}
  * cross-cut. Confirms the migration claim from FINISHED-77's Related-work cross-link: swapping
  * {@link org.apache.juneau.rest.guard.DenyAllGuard DenyAllGuard} for an AuthN guard is a
  * zero-mixin-source-change drop-in.
  *
  * <p>
- * Each case sits on a host servlet that mounts {@link BasicAdminResource} as a mixin and supplies
+ * Each case sits on a host servlet that mounts {@link AdminMixin} as a mixin and supplies
  * its own {@code @Bean RestGuardList} containing a {@link BearerTokenGuard} backed by a
  * fixed-token validator. The framework's {@code @Bean RestGuardList} override REPLACES the
  * annotation-derived deny-all on the mixin, so the admin paths gate on the bearer token instead.
  *
  * @since 9.5.0
  */
-class BasicAdminResource_AuthIntegration_Test extends TestBase {
+class AdminMixin_AuthIntegration_Test extends TestBase {
 
 	private static final Principal ADMIN = () -> "admin";
 
@@ -53,7 +53,7 @@ class BasicAdminResource_AuthIntegration_Test extends TestBase {
 		throw new AuthenticationException("Invalid token");
 	};
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_RateLimitSnapshot_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_RateLimitSnapshot_Test.java
similarity index 95%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_RateLimitSnapshot_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_RateLimitSnapshot_Test.java
index a3038b17cf..d6fcd802d7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_RateLimitSnapshot_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_RateLimitSnapshot_Test.java
@@ -30,24 +30,24 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates that {@link BasicAdminResource#getRateLimit} on the {@code /admin/ratelimit} endpoint
+ * Validates that {@link AdminMixin#getRateLimit} on the {@code /admin/ratelimit} endpoint
  * surfaces both static {@code config} and live {@code snapshot} bucket state when a
  * {@link RateLimitGuard} is registered.
  *
  * <p>
- * Builds on {@code BasicAdminResource_AsMixin_Test}'s {@code c01_rateLimitListsRegisteredGuard}
+ * Builds on {@code AdminMixin_AsMixin_Test}'s {@code c01_rateLimitListsRegisteredGuard}
  * pattern; the snapshot coverage is the v1 follow-up that closes the {@code "buckets": []}
  * placeholder noted in the FINISHED-77 archive.
  *
  * @since 9.5.0
  */
-class BasicAdminResource_RateLimitSnapshot_Test extends TestBase {
+class AdminMixin_RateLimitSnapshot_Test extends TestBase {
 
 	// -----------------------------------------------------------------------------------------
 	// Single RateLimitGuard bean — snapshot reports per-key bucket state after activity.
 	//
 	// Stash the guard in a static field so the instance the GuardList enforces on /items is the
-	// same instance BasicAdminResource resolves from the bean store. The @Bean RateLimitGuard
+	// same instance AdminMixin resolves from the bean store. The @Bean RateLimitGuard
 	// factory method below is invoked once by the bean store; both the guard chain (via
 	// bs.getBean(...)) and the admin endpoint pick up that single cached instance.
 	// -----------------------------------------------------------------------------------------
@@ -59,7 +59,7 @@ class BasicAdminResource_RateLimitSnapshot_Test extends TestBase {
 		.exemptPaths("/admin/ratelimit", "/admin/threads", "/admin/heap", "/admin/cache/flush")
 		.build();
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -126,7 +126,7 @@ class BasicAdminResource_RateLimitSnapshot_Test extends TestBase {
 		.exemptPaths("/admin/ratelimit", "/admin/threads", "/admin/heap", "/admin/cache/flush")
 		.build();
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -167,7 +167,7 @@ class BasicAdminResource_RateLimitSnapshot_Test extends TestBase {
 		.exemptPaths("/admin/ratelimit", "/admin/threads", "/admin/heap", "/admin/cache/flush")
 		.build();
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -232,7 +232,7 @@ class BasicAdminResource_RateLimitSnapshot_Test extends TestBase {
 		.storage(new E01_NoSnapshotStorage())
 		.build();
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class E extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_SvlPathOverride_Test.java
index fe0489ac25..94ab1b8ece 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicAdminResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/AdminMixin_SvlPathOverride_Test.java
@@ -27,10 +27,10 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the {@code ${juneau.admin.path:admin}} SVL prefix override on
- * {@link BasicAdminResource}.
+ * {@link AdminMixin}.
  *
  * <p>
- * Unlike the single-op mixins, {@link BasicAdminResource} declares <i>four</i> distinct ops
+ * Unlike the single-op mixins, {@link AdminMixin} declares <i>four</i> distinct ops
  * (threads / heap / cache/flush / ratelimit) sharing a common prefix variable. A single
  * override relocates the whole admin surface; the test exercises {@code GET .../threads} as
  * the canonical end-to-end check since the other three ops share the same SVL substitution
@@ -44,9 +44,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicAdminResource_SvlPathOverride_Test extends TestBase {
+class AdminMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A01_OverridePath extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -79,7 +79,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A02_BareToken extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
@@ -98,7 +98,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A03_LeadingSlash extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
@@ -117,7 +117,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A04_TrailingSlash extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
@@ -136,7 +136,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A05_BothSlashes extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
@@ -155,7 +155,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A06_WildcardSuffix extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
@@ -174,7 +174,7 @@ class BasicAdminResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicAdminResource.class)
+	@Rest(mixins=AdminMixin.class)
 	public static class A07_MultiSegment extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
index e15affe921..7234658090 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_OpenApiHidden_Test.java
@@ -32,7 +32,7 @@ import org.junit.jupiter.api.*;
  *
  * <p>
  * The host extends vanilla {@link RestServlet} and mounts the three ops mixins plus
- * {@link BasicOpenApiResource} (the OpenAPI generator). The generated spec must list the host's
+ * {@link OpenApiMixin} (the OpenAPI generator). The generated spec must list the host's
  * own {@code /items} endpoint but NOT the ops-pack paths.
  *
  * @since 9.5.0
@@ -41,10 +41,10 @@ class BasicOps_OpenApiHidden_Test extends TestBase {
 
 	@Rest(
 		mixins={
-			BasicEchoResource.class,
-			BasicAdminResource.class,
-			BasicRouteIndexResource.class,
-			BasicOpenApiResource.class
+			EchoMixin.class,
+			AdminMixin.class,
+			RouteIndexMixin.class,
+			OpenApiMixin.class
 		},
 		debug=@Debug("always"),
 		swaggerProvider=BasicSwaggerProvider.class
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
index 9537995850..5cf042e5d0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicOps_ParentChain_Test.java
@@ -35,7 +35,7 @@ import org.junit.jupiter.api.*;
  *
  * <p>
  * Setup: a single {@link RestServlet} host mounts all three ops mixins
- * ({@link BasicEchoResource}, {@link BasicAdminResource}, {@link BasicRouteIndexResource}) plus a
+ * ({@link EchoMixin}, {@link AdminMixin}, {@link RouteIndexMixin}) plus a
  * vanilla {@code /items} op of its own. {@code @Rest(debug=@Debug("always"))} unlocks the echo endpoint;
  * an empty {@code @Bean RestGuardList} factory replaces the {@link DenyAllGuard} default to
  * unlock the admin endpoints.
@@ -55,13 +55,13 @@ import org.junit.jupiter.api.*;
 class BasicOps_ParentChain_Test extends TestBase {
 
 	@Rest(
-		mixins={BasicEchoResource.class, BasicAdminResource.class, BasicRouteIndexResource.class},
+		mixins={EchoMixin.class, AdminMixin.class, RouteIndexMixin.class},
 		debug=@Debug("always"))
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items", summary="List items") public String items() { return "items"; }
 
-		// Allow-all guard chain — replaces the BasicAdminResource deny-all default.
+		// Allow-all guard chain — replaces the AdminMixin deny-all default.
 		@Bean public RestGuardList guards(BeanStore bs) { return RestGuardList.create(bs).build(); }
 	}
 
@@ -71,16 +71,16 @@ class BasicOps_ParentChain_Test extends TestBase {
 		MockRestClient.buildLax(A.class);
 		var hostCtx = RestContext.getGlobalRegistry().get(A.class);
 		var ctxs = hostCtx.getMixinContexts();
-		assertNotNull(ctxs.get(BasicEchoResource.class), "Echo mixin context registered");
-		assertNotNull(ctxs.get(BasicAdminResource.class), "Admin mixin context registered");
-		assertNotNull(ctxs.get(BasicRouteIndexResource.class), "RouteIndex mixin context registered");
+		assertNotNull(ctxs.get(EchoMixin.class), "Echo mixin context registered");
+		assertNotNull(ctxs.get(AdminMixin.class), "Admin mixin context registered");
+		assertNotNull(ctxs.get(RouteIndexMixin.class), "RouteIndex mixin context registered");
 		assertEquals(3, ctxs.size(),
 			"Expected exactly three mixin contexts; got: " + ctxs.keySet());
 	}
 
 	@Test void a02_echoServesAtPrimaryMount() throws Exception {
 		// FINISHED-101: /debug/echo/* is no longer a multi-path default. Migration to that
-		// secondary alias is covered by BasicEchoResource_SvlPathOverride_Test#a02.
+		// secondary alias is covered by EchoMixin_SvlPathOverride_Test#a02.
 		c.get("/echo/x").run().assertStatus(200);
 		c.get("/debug/echo/x").run().assertStatus(404);
 	}
@@ -114,7 +114,7 @@ class BasicOps_ParentChain_Test extends TestBase {
 
 	@Test void a06_legacyRoutesAliasNotMountedByDefault() throws Exception {
 		// FINISHED-101: /routes is no longer a multi-path default. Migration covered by
-		// BasicRouteIndexResource_SvlPathOverride_Test#a02.
+		// RouteIndexMixin_SvlPathOverride_Test#a02.
 		c.get("/routes").run().assertStatus(404);
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_AsMixin_Test.java
similarity index 86%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_AsMixin_Test.java
index a824b48c6a..006814b777 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_AsMixin_Test.java
@@ -27,7 +27,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicEchoResource} mounted as a mixin via {@code @Rest(mixins=...)} on a vanilla
+ * Validates {@link EchoMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a vanilla
  * {@link RestServlet}.
  *
  * <p>
@@ -38,7 +38,7 @@ import org.junit.jupiter.api.*;
  * 	<li>{@code @Rest(debug=@Debug("always"))} unlocks the endpoint and returns the full echo payload.
  * 	<li>{@code @Rest(debug=@Debug("conditional"))} requires the {@code Debug: true} request header.
  * 	<li>Sensitive headers ({@code Authorization}, {@code Cookie}) are redacted by default.
- * 	<li>Importer's {@code @Bean BasicEchoResource} factory drives the body cap and redact list.
+ * 	<li>Importer's {@code @Bean EchoMixin} factory drives the body cap and redact list.
  * 	<li>Body capture truncates correctly when the inbound body exceeds the configured cap.
  * 	<li>Path remainder, query string, and query params are surfaced.
  * 	<li>The handler dispatches on {@code @RestOp(method="*")} &mdash; POST and PUT both work.
@@ -47,10 +47,10 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicEchoResource_AsMixin_Test extends TestBase {
+class EchoMixin_AsMixin_Test extends TestBase {
 
 	/** Default-host mounting the mixin without {@code @Rest(debug)}. */
-	@Rest(mixins=BasicEchoResource.class)
+	@Rest(mixins=EchoMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -73,7 +73,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with debug always-on so the echo endpoint serves. */
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -112,7 +112,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 			.getContent().asString();
 		Assertions.assertFalse(body.contains("Bearer secret-token"),
 			"Authorization value must NEVER be reflected back; body was: " + body);
-		Assertions.assertTrue(body.contains(BasicEchoResource.REDACTED),
+		Assertions.assertTrue(body.contains(EchoMixin.REDACTED),
 			"Redaction sentinel should appear; body was: " + body);
 	}
 
@@ -146,7 +146,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with conditional debug — requires {@code Debug: true} request header to unlock echo. */
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("conditional"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("conditional"))
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -166,11 +166,11 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with a custom redact list and a tight body cap via @Bean factory. */
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class D extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicEchoResource echo() {
-			return BasicEchoResource.create()
+		@Bean public EchoMixin echo() {
+			return EchoMixin.create()
 				.bodyLimit(8L)
 				.redactHeader("X-Internal-Trace")
 				.build();
@@ -187,7 +187,7 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 			.getContent().asString();
 		Assertions.assertFalse(body.contains("abc-trace-id"),
 			"Custom-redacted header value must NOT be reflected back; body was: " + body);
-		Assertions.assertTrue(body.contains(BasicEchoResource.REDACTED));
+		Assertions.assertTrue(body.contains(EchoMixin.REDACTED));
 	}
 
 	@Test void d02_authorizationStillRedactedAfterAddingCustom() throws Exception {
@@ -214,11 +214,11 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with a zero body cap — every non-empty body truncates immediately. */
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class G extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicEchoResource echo() {
-			return BasicEchoResource.create().bodyLimit(0L).build();
+		@Bean public EchoMixin echo() {
+			return EchoMixin.create().bodyLimit(0L).build();
 		}
 	}
 
@@ -236,11 +236,11 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	/** Host with a redactedHeaders(...) replace-list that disables built-in defaults. */
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class E extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicEchoResource echo() {
-			return BasicEchoResource.create()
+		@Bean public EchoMixin echo() {
+			return EchoMixin.create()
 				.redactedHeaders("X-Custom-Only")
 				.build();
 		}
@@ -274,36 +274,36 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 
 	@Test void f01_builderRejectsNegativeBodyLimit() {
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicEchoResource.create().bodyLimit(-1L));
+			() -> EchoMixin.create().bodyLimit(-1L));
 	}
 
 	@Test void f02_builderRejectsBlankRedactHeader() {
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicEchoResource.create().redactHeader(""));
+			() -> EchoMixin.create().redactHeader(""));
 		Assertions.assertThrows(IllegalArgumentException.class,
-			() -> BasicEchoResource.create().redactHeader(null));
+			() -> EchoMixin.create().redactHeader(null));
 	}
 
 	@Test void f03_redactedHeadersLowerIsImmutable() {
-		var r = BasicEchoResource.create().build();
+		var r = EchoMixin.create().build();
 		Assertions.assertThrows(UnsupportedOperationException.class,
 			() -> r.getRedactedHeadersLower().add("X-Test"));
 	}
 
 	@Test void f04_defaultsExposedAsConstants() {
-		Assertions.assertEquals(1_048_576L, BasicEchoResource.DEFAULT_BODY_LIMIT);
-		Assertions.assertTrue(BasicEchoResource.DEFAULT_REDACTED_HEADERS.contains("Authorization"));
-		Assertions.assertTrue(BasicEchoResource.DEFAULT_REDACTED_HEADERS.contains("Cookie"));
+		Assertions.assertEquals(1_048_576L, EchoMixin.DEFAULT_BODY_LIMIT);
+		Assertions.assertTrue(EchoMixin.DEFAULT_REDACTED_HEADERS.contains("Authorization"));
+		Assertions.assertTrue(EchoMixin.DEFAULT_REDACTED_HEADERS.contains("Cookie"));
 	}
 
 	@Test void f05_noArgConstructorMatchesDefaultBuilder() {
-		var r = new BasicEchoResource();
-		Assertions.assertEquals(BasicEchoResource.DEFAULT_BODY_LIMIT, r.getBodyLimit());
+		var r = new EchoMixin();
+		Assertions.assertEquals(EchoMixin.DEFAULT_BODY_LIMIT, r.getBodyLimit());
 		Assertions.assertTrue(r.getRedactedHeadersLower().contains("authorization"));
 	}
 
 	@Test void f06_redactedHeadersFiltersNullAndBlank() {
-		var r = BasicEchoResource.create()
+		var r = EchoMixin.create()
 			.redactedHeaders("X-Keep", null, "", "  ", "X-Also")
 			.build();
 		var s = r.getRedactedHeadersLower();
@@ -313,14 +313,14 @@ class BasicEchoResource_AsMixin_Test extends TestBase {
 	}
 
 	@Test void f07_redactedHeadersNullVarargsClears() {
-		var r = BasicEchoResource.create().redactedHeaders((String[]) null).build();
+		var r = EchoMixin.create().redactedHeaders((String[]) null).build();
 		Assertions.assertTrue(r.getRedactedHeadersLower().isEmpty(),
 			"null varargs should clear the redact list");
 	}
 
 	@Test void f08_zeroBodyLimitTruncatesEverything() throws Exception {
 		// Builder accepts 0; the handler then truncates any non-empty body.
-		var b = BasicEchoResource.create().bodyLimit(0L).build();
+		var b = EchoMixin.create().bodyLimit(0L).build();
 		Assertions.assertEquals(0L, b.getBodyLimit());
 	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_JettyMicroservice_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_JettyMicroservice_Test.java
index f106120c43..68db6388c2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_JettyMicroservice_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_JettyMicroservice_Test.java
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.extension.*;
 import jakarta.servlet.*;
 
 /**
- * Real-Jetty deployment-parity assertion for {@link BasicEchoResource}.
+ * Real-Jetty deployment-parity assertion for {@link EchoMixin}.
  *
  * <p>
  * Boots a {@link org.apache.juneau.microservice.Microservice Microservice} backed by
@@ -55,13 +55,13 @@ import jakarta.servlet.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.JettyMicroserviceTest
-class BasicEchoResource_JettyMicroservice_Test extends TestBase {
+class EchoMixin_JettyMicroservice_Test extends TestBase {
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class Host extends RestServlet {
 		private static final long serialVersionUID = 1L;
-		@Bean public BasicEchoResource echo() {
-			return BasicEchoResource.create()
+		@Bean public EchoMixin echo() {
+			return EchoMixin.create()
 				.bodyLimit(1024L)
 				.build();
 		}
@@ -116,7 +116,7 @@ class BasicEchoResource_JettyMicroservice_Test extends TestBase {
 		assertEquals(200, resp.statusCode());
 		assertFalse(resp.body().contains("real-network-secret"),
 			"Authorization secret must NEVER cross back; body: " + resp.body());
-		assertTrue(resp.body().contains(BasicEchoResource.REDACTED));
+		assertTrue(resp.body().contains(EchoMixin.REDACTED));
 	}
 
 	@Test void a03_debugEchoLegacyAliasNotMountedByDefault() throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_Springboot_Test.java
similarity index 89%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_Springboot_Test.java
index 65ef6a7138..73453acf70 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_Springboot_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_Springboot_Test.java
@@ -36,19 +36,19 @@ import org.springframework.context.annotation.*;
 import org.springframework.test.annotation.*;
 
 /**
- * Real-Spring-Boot deployment-parity assertion for {@link BasicEchoResource}.
+ * Real-Spring-Boot deployment-parity assertion for {@link EchoMixin}.
  *
  * <p>
  * Boots a full Spring Boot context with embedded Tomcat on a random port, registers a
  * {@link BasicSpringRestServlet}-based host with the echo mixin via
- * {@link ServletRegistrationBean}, supplies a Spring {@code @Bean BasicEchoResource}, and hits
+ * {@link ServletRegistrationBean}, supplies a Spring {@code @Bean EchoMixin}, and hits
  * {@code /echo/*} over real HTTP.
  *
  * <p>
  * Catches things {@code MockRest} and the Jetty parity test cannot:
  * <ul>
  * 	<li>Spring's bean store adapter ({@code SpringBeanStore}) resolving the host's
- * 		{@code @Bean BasicEchoResource} during the FINISHED-72 mixin walk through
+ * 		{@code @Bean EchoMixin} during the FINISHED-72 mixin walk through
  * 		{@link org.springframework.context.ApplicationContext#getBean(Class)
  * 		ApplicationContext.getBean(...)}.
  * 	<li>End-to-end format-pinned JSON ({@link org.apache.juneau.rest.RestResponse#getDirectWriter
@@ -60,10 +60,10 @@ import org.springframework.test.annotation.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.SpringbootTest
-@SpringBootTest(classes = BasicEchoResource_Springboot_Test.TestApp.class,
+@SpringBootTest(classes = EchoMixin_Springboot_Test.TestApp.class,
 	webEnvironment = WebEnvironment.RANDOM_PORT)
 @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
-class BasicEchoResource_Springboot_Test {
+class EchoMixin_Springboot_Test {
 
 	@SpringBootConfiguration
 	@EnableAutoConfiguration
@@ -75,12 +75,12 @@ class BasicEchoResource_Springboot_Test {
 			return new ServletRegistrationBean<>(servlet, "/*");
 		}
 
-		@Bean public BasicEchoResource echoResource() {
-			return BasicEchoResource.create().bodyLimit(2048L).build();
+		@Bean public EchoMixin echoResource() {
+			return EchoMixin.create().bodyLimit(2048L).build();
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class Host extends BasicSpringRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_SvlPathOverride_Test.java
similarity index 89%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_SvlPathOverride_Test.java
index 10ab7dacd9..a9ab4b5699 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicEchoResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/EchoMixin_SvlPathOverride_Test.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates the {@code ${juneau.echo.path:echo}} SVL override on {@link BasicEchoResource}.
+ * Validates the {@code ${juneau.echo.path:echo}} SVL override on {@link EchoMixin}.
  *
  * <p>
  * Per the FINISHED-101 multi-path collapse, the historical {@code /debug/echo/*} alias (formerly
@@ -32,7 +32,7 @@ import org.junit.jupiter.api.*;
  *
  * <p>
  * Uses {@code @Debug("always")} so the debug guard does not return 404 &mdash; the
- * default-deny behavior of {@link BasicEchoResource} is exercised separately in the AsMixin
+ * default-deny behavior of {@link EchoMixin} is exercised separately in the AsMixin
  * test suite.
  *
  * <p>
@@ -42,9 +42,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicEchoResource_SvlPathOverride_Test extends TestBase {
+class EchoMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A01_OverridePath extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -68,7 +68,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A02_DebugEchoAliasMigration extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -92,7 +92,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A03_BareToken extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -110,7 +110,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A04_LeadingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -128,7 +128,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A05_TrailingSlash extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -146,7 +146,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A06_BothSlashes extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -164,7 +164,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A07_WildcardSuffix extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -182,7 +182,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicEchoResource.class, debug=@Debug("always"))
+	@Rest(mixins=EchoMixin.class, debug=@Debug("always"))
 	public static class A08_MultiSegment extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
@@ -193,7 +193,7 @@ class BasicEchoResource_SvlPathOverride_Test extends TestBase {
 		// Use debug/echo2 (2-segment) to test multi-segment path normalization while staying
 		// under the 2-segment SVL path depth that Juneau's UrlPathMatcher reliably supports
 		// for mixin op-paths. Paths deeper than 2 segments are not tested here to avoid
-		// collision with BasicSwaggerResource's /api/* default and potential routing edge cases
+		// collision with SwaggerMixin's /api/* default and potential routing edge cases
 		// with 3+ segment base paths in SVL-resolved @RestOp patterns.
 		System.setProperty(key, "/debug/echo2/*");
 		try {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_AsMixin_Test.java
similarity index 96%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_AsMixin_Test.java
index 86bfff2ace..550ca2015d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_AsMixin_Test.java
@@ -26,7 +26,7 @@ import org.apache.juneau.rest.servlet.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicRouteIndexResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link RouteIndexMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -46,13 +46,13 @@ import org.junit.jupiter.api.*;
  * Per the FINISHED-101 multi-path collapse, the historical {@code /routes} mount alias
  * (formerly a dual default on a single op) is now reached via
  * {@code -Djuneau.routeindex.path=routes}; that behavior is covered by
- * {@code BasicRouteIndexResource_SvlPathOverride_Test}.
+ * {@code RouteIndexMixin_SvlPathOverride_Test}.
  *
  * @since 9.5.0
  */
-class BasicRouteIndexResource_AsMixin_Test extends TestBase {
+class RouteIndexMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A extends RestServlet {
 		private static final long serialVersionUID = 1L;
 
@@ -150,7 +150,7 @@ class BasicRouteIndexResource_AsMixin_Test extends TestBase {
 	// -----------------------------------------------------------------------------------------
 
 	@Deprecated
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class C extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/c-item", summary="C item",
@@ -182,7 +182,7 @@ class BasicRouteIndexResource_AsMixin_Test extends TestBase {
 	// ops, e.g. /items) and never expose the route-index endpoint itself.
 	// -----------------------------------------------------------------------------------------
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class B extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/only") public String only() { return "only"; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_SvlPathOverride_Test.java
index 98f76b5bde..339af9d1d9 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/ops/BasicRouteIndexResource_SvlPathOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/ops/RouteIndexMixin_SvlPathOverride_Test.java
@@ -24,7 +24,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates the {@code ${juneau.routeindex.path:options}} SVL override on
- * {@link BasicRouteIndexResource}.
+ * {@link RouteIndexMixin}.
  *
  * <p>
  * Per the FINISHED-101 multi-path collapse, the historical {@code /routes} alias (formerly a
@@ -38,9 +38,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
+class RouteIndexMixin_SvlPathOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A01_OverridePath extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -66,7 +66,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A02_RoutesAliasMigration extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -91,7 +91,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A03_BareToken extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -110,7 +110,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A04_LeadingSlash extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -129,7 +129,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A05_TrailingSlash extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -148,7 +148,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A06_BothSlashes extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
@@ -167,7 +167,7 @@ class BasicRouteIndexResource_SvlPathOverride_Test extends TestBase {
 		}
 	}
 
-	@Rest(mixins=BasicRouteIndexResource.class)
+	@Rest(mixins=RouteIndexMixin.class)
 	public static class A07_MultiSegment extends RestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/JuneauRestServletContainerInitializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/JuneauRestServletContainerInitializer_Test.java
index 819a89b551..51ca713071 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/JuneauRestServletContainerInitializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/servlet/JuneauRestServletContainerInitializer_Test.java
@@ -48,9 +48,9 @@ class JuneauRestServletContainerInitializer_Test extends TestBase {
 		var ctx = mockContext("true", dyn);
 
 		new JuneauRestServletContainerInitializer()
-			.onStartup(Set.of(BasicVersionServlet.class), ctx);
+			.onStartup(Set.of(VersionServlet.class), ctx);
 
-		verify(ctx).addServlet(eq(BasicVersionServlet.class.getName()), any(BasicVersionServlet.class));
+		verify(ctx).addServlet(eq(VersionServlet.class.getName()), any(VersionServlet.class));
 		verify(dyn).addMapping("/version/*");
 	}
 
@@ -59,7 +59,7 @@ class JuneauRestServletContainerInitializer_Test extends TestBase {
 		var ctx = mockContext(null, dyn);
 
 		new JuneauRestServletContainerInitializer()
-			.onStartup(Set.of(BasicVersionServlet.class), ctx);
+			.onStartup(Set.of(VersionServlet.class), ctx);
 
 		verify(ctx, never()).addServlet(anyString(), any(Servlet.class));
 	}
@@ -68,10 +68,10 @@ class JuneauRestServletContainerInitializer_Test extends TestBase {
 		var dyn = mock(ServletRegistration.Dynamic.class);
 		var ctx = mockContext("true", dyn);
 
-		// BasicVersionResource is a plain mixin (no top-level paths, not even a RestServlet) — it must
+		// VersionMixin is a plain mixin (no top-level paths, not even a RestServlet) — it must
 		// be filtered out and never mounted.
 		new JuneauRestServletContainerInitializer()
-			.onStartup(Set.of(BasicVersionResource.class), ctx);
+			.onStartup(Set.of(VersionMixin.class), ctx);
 
 		verify(ctx, never()).addServlet(anyString(), any(Servlet.class));
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauRestAutoConfiguration_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauRestAutoConfiguration_Test.java
index 9bab08d88e..6300566a07 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauRestAutoConfiguration_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauRestAutoConfiguration_Test.java
@@ -71,7 +71,7 @@ class JuneauRestAutoConfiguration_Test extends TestBase {
 	@Configuration
 	static class ServletOnlyConfig {
 		@org.springframework.context.annotation.Bean
-		Servlet versionServlet() { return new BasicVersionServlet(); }
+		Servlet versionServlet() { return new VersionServlet(); }
 	}
 
 	@Test void b01_mountsRestServletAtSelfDeclaredPath() throws Exception {
@@ -82,18 +82,18 @@ class JuneauRestAutoConfiguration_Test extends TestBase {
 
 			new JuneauRestAutoConfiguration().juneauRestAutoRegistrar(appContext).onStartup(servletContext);
 
-			verify(servletContext).addServlet(anyString(), any(BasicVersionServlet.class));
+			verify(servletContext).addServlet(anyString(), any(VersionServlet.class));
 			verify(dyn).addMapping("/version/*");
 		}
 	}
 
 	@Configuration
 	static class ManuallyRegisteredConfig {
-		private final BasicVersionServlet servlet = new BasicVersionServlet();
+		private final VersionServlet servlet = new VersionServlet();
 		@org.springframework.context.annotation.Bean
 		Servlet versionServlet() { return servlet; }
 		@org.springframework.context.annotation.Bean
-		ServletRegistrationBean<BasicVersionServlet> versionRegistration() {
+		ServletRegistrationBean<VersionServlet> versionRegistration() {
 			return new ServletRegistrationBean<>(servlet, "/custom/*");
 		}
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations_Test.java
index b36337b49c..d01e9bc626 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/JuneauServletRegistrations_Test.java
@@ -35,7 +35,7 @@ import org.junit.jupiter.api.*;
 class JuneauServletRegistrations_Test extends TestBase {
 
 	@Test void a01_derivesMappingFromAnnotation() {
-		var bean = JuneauServletRegistrations.forServlet(new BasicVersionServlet(), null);
+		var bean = JuneauServletRegistrations.forServlet(new VersionServlet(), null);
 		assertTrue(bean.getUrlMappings().contains("/version/*"),
 			"Mapping should be derived from @Rest(paths); was: " + bean.getUrlMappings());
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_SpringbootIntegration_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_SpringbootIntegration_Test.java
index 49d622341d..f023683920 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_SpringbootIntegration_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_SpringbootIntegration_Test.java
@@ -50,7 +50,7 @@ import org.springframework.test.context.*;
  * </ul>
  *
  * <p>
- * Mirrors the pattern used by {@code BasicStaticFilesResource_Springboot_Test} and friends:
+ * Mirrors the pattern used by {@code StaticFilesMixin_Springboot_Test} and friends:
  * minimal {@code @SpringBootConfiguration} class, no web environment, dirty context after the
  * class so subsequent tests don't see the bridge.
  */
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_Test.java
index ce00bfbc35..a7b9621cdd 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/springboot/SpringEnvironmentPropertySource_Test.java
@@ -44,8 +44,8 @@ import org.springframework.mock.env.*;
  *
  * <p>
  * A full {@code @SpringBootTest} bootstrap (with {@code application.yaml} on the classpath) is
- * exercised indirectly through the existing {@code BasicStaticFilesResource_Springboot_Test} and
- * {@code BasicEchoResource_Springboot_Test} smoke tests — those already build a real Spring
+ * exercised indirectly through the existing {@code StaticFilesMixin_Springboot_Test} and
+ * {@code EchoMixin_Springboot_Test} smoke tests — those already build a real Spring
  * context which now installs the bridge automatically via {@link SpringBeanStore}'s constructor.
  * This test keeps the focus on the bridge itself.
  */
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_AsMixin_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_AsMixin_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_AsMixin_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_AsMixin_Test.java
index 9ae12e5a28..aaeb2001eb 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_AsMixin_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_AsMixin_Test.java
@@ -25,7 +25,7 @@ import org.apache.juneau.rest.staticfile.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicStaticFilesResource} mounted as a mixin via {@code @Rest(mixins=...)} on a
+ * Validates {@link StaticFilesMixin} mounted as a mixin via {@code @Rest(mixins=...)} on a
  * vanilla {@link RestServlet}.
  *
  * <p>
@@ -45,13 +45,13 @@ import org.junit.jupiter.api.*;
  * and {@code htdocs/} directories &mdash; only the URL-side mount is now single. Per the
  * FINISHED-101 multi-path collapse, the {@code /htdocs/*} mount alias (formerly a dual default)
  * is now reached via {@code -Djuneau.staticfiles.path=htdocs}; that behavior is covered by
- * {@code BasicStaticFilesResource_SvlPathOverride_Test}.
+ * {@code StaticFilesMixin_SvlPathOverride_Test}.
  *
  * @since 9.5.0
  */
-class BasicStaticFilesResource_AsMixin_Test extends TestBase {
+class StaticFilesMixin_AsMixin_Test extends TestBase {
 
-	@Rest(mixins=BasicStaticFilesResource.class)
+	@Rest(mixins=StaticFilesMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 		@RestGet(path="/items") public String items() { return "items"; }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_CacheControl_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_CacheControl_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_CacheControl_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_CacheControl_Test.java
index a25d01695e..1561f69bee 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_CacheControl_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_CacheControl_Test.java
@@ -24,7 +24,7 @@ import org.apache.juneau.rest.staticfile.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates that {@link BasicStaticFilesResource} preserves the default
+ * Validates that {@link StaticFilesMixin} preserves the default
  * {@code Cache-Control: max-age=86400, public} header end-to-end (the default supplied by
  * {@link BasicStaticFiles}).
  *
@@ -40,9 +40,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicStaticFilesResource_CacheControl_Test extends TestBase {
+class StaticFilesMixin_CacheControl_Test extends TestBase {
 
-	@Rest(mixins=BasicStaticFilesResource.class)
+	@Rest(mixins=StaticFilesMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_ImporterOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_ImporterOverride_Test.java
similarity index 94%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_ImporterOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_ImporterOverride_Test.java
index 0a865e0bc1..7490db652c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_ImporterOverride_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_ImporterOverride_Test.java
@@ -27,7 +27,7 @@ import org.junit.jupiter.api.*;
 
 /**
  * Validates that an importer's {@code @Bean StaticFiles} factory method overrides the default
- * {@link BasicStaticFiles} configuration when mounted alongside {@link BasicStaticFilesResource}.
+ * {@link BasicStaticFiles} configuration when mounted alongside {@link StaticFilesMixin}.
  *
  * <p>
  * The importer below declares a custom {@code StaticFiles} bean with a non-default
@@ -38,9 +38,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicStaticFilesResource_ImporterOverride_Test extends TestBase {
+class StaticFilesMixin_ImporterOverride_Test extends TestBase {
 
-	@Rest(mixins=BasicStaticFilesResource.class)
+	@Rest(mixins=StaticFilesMixin.class)
 	public static class A extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_JettyMicroservice_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_JettyMicroservice_Test.java
similarity index 96%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_JettyMicroservice_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_JettyMicroservice_Test.java
index 21149081a3..51189b08f5 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_JettyMicroservice_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_JettyMicroservice_Test.java
@@ -35,7 +35,7 @@ import org.junit.jupiter.api.extension.*;
 import jakarta.servlet.*;
 
 /**
- * Real-Jetty deployment-parity assertion for {@link BasicStaticFilesResource}.
+ * Real-Jetty deployment-parity assertion for {@link StaticFilesMixin}.
  *
  * <p>
  * Boots a {@link org.apache.juneau.microservice.Microservice Microservice} backed by
@@ -62,10 +62,10 @@ import jakarta.servlet.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.JettyMicroserviceTest
-class BasicStaticFilesResource_JettyMicroservice_Test extends TestBase {
+class StaticFilesMixin_JettyMicroservice_Test extends TestBase {
 
 	/** Test host: vanilla {@link BasicRestServlet} subclass with the static-files mixin attached. */
-	@Rest(mixins=BasicStaticFilesResource.class)
+	@Rest(mixins=StaticFilesMixin.class)
 	public static class Host extends BasicRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_OpenApiHidden_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_OpenApiHidden_Test.java
similarity index 89%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_OpenApiHidden_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_OpenApiHidden_Test.java
index 7f58ad546f..fed532400f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_OpenApiHidden_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_OpenApiHidden_Test.java
@@ -28,11 +28,11 @@ import org.junit.jupiter.api.*;
 /**
  * Validates that the static-files mixin's greedy {@code /static/*} handler is excluded from the
  * generated Swagger/OpenAPI spec, per {@link OpSwagger#ignore() @OpSwagger(ignore=true)} on
- * {@link BasicStaticFilesResource#getStaticFile} and the matching HEAD handler.
+ * {@link StaticFilesMixin#getStaticFile} and the matching HEAD handler.
  *
  * <p>
- * The host below mounts both {@link BasicStaticFilesResource} (static-file serving) and
- * {@link BasicOpenApiResource} (OpenAPI generator) as mixins on a vanilla {@link RestServlet}
+ * The host below mounts both {@link StaticFilesMixin} (static-file serving) and
+ * {@link OpenApiMixin} (OpenAPI generator) as mixins on a vanilla {@link RestServlet}
  * (NOT {@link BasicRestServlet}) so the legacy
  * {@code BasicRestOperations.getHtdoc(...)} method does not pollute the spec independently
  * of what the mixin emits. The generated spec must list the host's own {@code /items} endpoint
@@ -40,7 +40,7 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicStaticFilesResource_OpenApiHidden_Test extends TestBase {
+class StaticFilesMixin_OpenApiHidden_Test extends TestBase {
 
 	/**
 	 * Host extends vanilla {@link RestServlet} (NOT {@link BasicRestServlet}) so the legacy
@@ -49,7 +49,7 @@ class BasicStaticFilesResource_OpenApiHidden_Test extends TestBase {
 	 * generate; we wire it explicitly via {@link Rest#swaggerProvider}.
 	 */
 	@Rest(
-		mixins={BasicStaticFilesResource.class, BasicOpenApiResource.class},
+		mixins={StaticFilesMixin.class, OpenApiMixin.class},
 		swaggerProvider=BasicSwaggerProvider.class
 	)
 	public static class A extends RestServlet {
@@ -60,7 +60,7 @@ class BasicStaticFilesResource_OpenApiHidden_Test extends TestBase {
 	private static final MockRestClient c = MockRestClient.buildLax(A.class);
 
 	@Test void a01_openapiSpecExcludesStaticPaths() throws Exception {
-		// /openapi.json is format-pinned to JSON regardless of Accept (per BasicOpenApiResource);
+		// /openapi.json is format-pinned to JSON regardless of Accept (per OpenApiMixin);
 		// avoids the need to register JSON serializers on the vanilla RestServlet host.
 		var spec = c.get("/openapi.json")
 			.run()
@@ -79,7 +79,7 @@ class BasicStaticFilesResource_OpenApiHidden_Test extends TestBase {
 			.run()
 			.assertStatus(200);
 		// FINISHED-101: /htdocs/* is no longer a multi-path default; migration covered by
-		// BasicStaticFilesResource_SvlPathOverride_Test#a02.
+		// StaticFilesMixin_SvlPathOverride_Test#a02.
 		c.get("/htdocs/javadoc.css")
 			.run()
 			.assertStatus(404);
@@ -87,7 +87,7 @@ class BasicStaticFilesResource_OpenApiHidden_Test extends TestBase {
 
 	/**
 	 * Host that exercises the LEGACY static-file path: a {@link BasicRestServlet} subclass with
-	 * the api-docs mixin pack inherited and no explicit {@link BasicStaticFilesResource}. The
+	 * the api-docs mixin pack inherited and no explicit {@link StaticFilesMixin}. The
 	 * legacy {@code getHtdoc(...)} method on {@code BasicRestOperations} now carries
 	 * {@code @OpSwagger(ignore=true)} so {@code /htdocs/*} must NOT appear in the generated spec
 	 * even when the new mixin is not mounted.
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SpringbootMetaInf_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_SpringbootMetaInf_Test.java
similarity index 94%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SpringbootMetaInf_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_SpringbootMetaInf_Test.java
index 566de728e5..745e3ca86e 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SpringbootMetaInf_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_SpringbootMetaInf_Test.java
@@ -38,7 +38,7 @@ import org.springframework.context.annotation.*;
 import org.springframework.test.annotation.*;
 
 /**
- * Spring Boot {@code META-INF/resources/} bridge test for {@link BasicStaticFilesResource}.
+ * Spring Boot {@code META-INF/resources/} bridge test for {@link StaticFilesMixin}.
  *
  * <p>
  * Spring Boot's embedded Tomcat / Jetty auto-serves files placed under the conventional
@@ -56,16 +56,16 @@ import org.springframework.test.annotation.*;
  * {@code juneau-utest/src/test/resources/META-INF/resources/spring-fixture.txt}.
  *
  * <p>
- * Companion to {@link BasicStaticFilesResource_Springboot_Test} which exercises the default
+ * Companion to {@link StaticFilesMixin_Springboot_Test} which exercises the default
  * classpath ({@code static/} + {@code htdocs/}) under the same Spring Boot wiring.
  *
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.SpringbootTest
-@SpringBootTest(classes = BasicStaticFilesResource_SpringbootMetaInf_Test.TestApp.class,
+@SpringBootTest(classes = StaticFilesMixin_SpringbootMetaInf_Test.TestApp.class,
 	webEnvironment = WebEnvironment.RANDOM_PORT)
 @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
-class BasicStaticFilesResource_SpringbootMetaInf_Test {
+class StaticFilesMixin_SpringbootMetaInf_Test {
 
 	@SpringBootConfiguration
 	@EnableAutoConfiguration
@@ -87,7 +87,7 @@ class BasicStaticFilesResource_SpringbootMetaInf_Test {
 	 * an importer-supplied {@code StaticFiles} factory that adds {@code /META-INF/resources/} to
 	 * the classpath search list.
 	 */
-	@Rest(mixins = BasicStaticFilesResource.class)
+	@Rest(mixins = StaticFilesMixin.class)
 	public static class Host extends BasicSpringRestServlet {
 		private static final long serialVersionUID = 1L;
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Springboot_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Springboot_Test.java
similarity index 94%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Springboot_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Springboot_Test.java
index edf0055907..2d524c5617 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Springboot_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Springboot_Test.java
@@ -37,7 +37,7 @@ import org.springframework.context.annotation.*;
 import org.springframework.test.annotation.*;
 
 /**
- * Real-Spring-Boot deployment-parity assertion for {@link BasicStaticFilesResource}.
+ * Real-Spring-Boot deployment-parity assertion for {@link StaticFilesMixin}.
  *
  * <p>
  * Boots a full Spring Boot context with embedded Tomcat on a random port, registers a
@@ -46,7 +46,7 @@ import org.springframework.test.annotation.*;
  * real HTTP.
  *
  * <p>
- * Catches things {@code MockRest} and the {@link BasicStaticFilesResource_JettyMicroservice_Test
+ * Catches things {@code MockRest} and the {@link StaticFilesMixin_JettyMicroservice_Test
  * Jetty parity test} cannot:
  * <ul>
  * 	<li>Spring's bean store adapter ({@code SpringBeanStore}) resolving Juneau {@code StaticFiles}
@@ -63,10 +63,10 @@ import org.springframework.test.annotation.*;
  * @since 9.5.0
  */
 @org.apache.juneau.testing.annotations.SpringbootTest
-@SpringBootTest(classes = BasicStaticFilesResource_Springboot_Test.TestApp.class,
+@SpringBootTest(classes = StaticFilesMixin_Springboot_Test.TestApp.class,
 	webEnvironment = WebEnvironment.RANDOM_PORT)
 @DirtiesContext(classMode = DirtiesContext.ClassMode.AFTER_CLASS)
-class BasicStaticFilesResource_Springboot_Test {
+class StaticFilesMixin_Springboot_Test {
 
 	/**
 	 * Minimal Spring Boot application that registers a single {@link Host} servlet at {@code /*}.
@@ -89,7 +89,7 @@ class BasicStaticFilesResource_Springboot_Test {
 	/**
 	 * Test host: subclass of {@link BasicSpringRestServlet} carrying the static-files mixin.
 	 */
-	@Rest(mixins = BasicStaticFilesResource.class)
+	@Rest(mixins = StaticFilesMixin.class)
 	public static class Host extends BasicSpringRestServlet {
 		private static final long serialVersionUID = 1L;
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Standalone_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Standalone_Test.java
similarity index 87%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Standalone_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Standalone_Test.java
index c85625170c..ff61f1a2b3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_Standalone_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_Standalone_Test.java
@@ -23,8 +23,8 @@ import org.apache.juneau.rest.staticfile.*;
 import org.junit.jupiter.api.*;
 
 /**
- * Validates {@link BasicStaticFilesResource} deployed as a <em>standalone</em> resource (Path B
- * in TODO-75) &mdash; the user extends {@code BasicStaticFilesResource} directly and the
+ * Validates {@link StaticFilesMixin} deployed as a <em>standalone</em> resource (Path B
+ * in TODO-75) &mdash; the user extends {@code StaticFilesMixin} directly and the
  * inherited op-level {@code @RestGet(path="/${juneau.staticfiles.path:static}/*")} declares the
  * mount point without any additional configuration.
  *
@@ -34,7 +34,7 @@ import org.junit.jupiter.api.*;
  * per op" principle the historical dual default ({@code "/static/*"} + {@code "/htdocs/*"}) was
  * collapsed to a single SVL-configurable path; the {@code /htdocs/*} alias is reached via
  * {@code -Djuneau.staticfiles.path=htdocs} and is covered by
- * {@code BasicStaticFilesResource_SvlPathOverride_Test#a02}.
+ * {@code StaticFilesMixin_SvlPathOverride_Test#a02}.
  *
  * <p>
  * {@code MockRest} dispatches directly to the inner matcher and does NOT model the
@@ -44,9 +44,9 @@ import org.junit.jupiter.api.*;
  *
  * @since 9.5.0
  */
-class BasicStaticFilesResource_Standalone_Test extends TestBase {
+class StaticFilesMixin_Standalone_Test extends TestBase {
 
-	public static class CdnResource extends BasicStaticFilesResource { }
+	public static class CdnResource extends StaticFilesMixin { }
 
 	private static final MockRestClient c = MockRestClient.buildLax(CdnResource.class);
 
@@ -59,7 +59,7 @@ class BasicStaticFilesResource_Standalone_Test extends TestBase {
 
 	@Test void a02_legacyHtdocsAliasNotMountedByDefault() throws Exception {
 		// FINISHED-101: /htdocs/* is no longer a multi-path default. Migration covered by
-		// BasicStaticFilesResource_SvlPathOverride_Test#a02.
+		// StaticFilesMixin_SvlPathOverride_Test#a02.
 		c.get("/htdocs/javadoc.css")
 			.run()
 			.assertStatus(404);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_SvlPathOverride_Test.java
similarity index 93%
rename from juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/StaticFilesMixin_SvlPathOverride_Test.java
index b474c9c509..40bdaf5f86 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/staticfiles/BasicStaticFilesResource_SvlPathOverride_Test.java
... 1380 lines suppressed ...