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

(juneau) branch master updated: refactor microservice wiring and normalize marshalled collection factories

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 5dd8947e27 refactor microservice wiring and normalize marshalled collection factories
5dd8947e27 is described below

commit 5dd8947e273723bb2986963b30249dc998df4162
Author: James Bognar <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
AuthorDate: Mon May 18 17:03:48 2026 -0400

    refactor microservice wiring and normalize marshalled collection factories
---
 README.md                                          |   2 +-
 .../apache/juneau/commons/bean/BeanSession.java    |   2 +-
 .../main/java/org/apache/juneau/BeanMapLoader.java |   7 +-
 .../juneau/MarshalledBeanMetaInitializer.java      |   7 -
 .../java/org/apache/juneau/MarshallingContext.java |  27 +-
 .../java/org/apache/juneau/MarshallingSession.java |  48 +-
 .../main/java/org/apache/juneau/UriContext.java    |   2 +-
 .../org/apache/juneau/collections/JsonList.java    |  14 +-
 .../org/apache/juneau/collections/JsonMap.java     |  14 +-
 .../apache/juneau/collections/package-info.java    |   4 +-
 .../org/apache/juneau/html/HtmlSerializer.java     |  12 +-
 .../java/org/apache/juneau/json5/Json5List.java    |   6 +-
 .../java/org/apache/juneau/json5/Json5Map.java     |  12 +-
 .../org/apache/juneau/jsonschema/SchemaUtils.java  |   4 +-
 .../org/apache/juneau/objecttools/ObjectRest.java  |   4 +-
 .../org/apache/juneau/uon/UonParserSession.java    |   6 +-
 .../java/org/apache/juneau/uon/UonSerializer.java  |   2 +-
 .../juneau/urlencoding/UrlEncodingSerializer.java  |   2 +-
 juneau-distrib/pom.xml                             |  14 +-
 .../juneau/examples/rest/SamplesMicroservice.java  |  28 +-
 .../org/apache/juneau/examples/rest/jetty/App.java |  37 +-
 .../build-overlay/pom.xml                          |   2 +-
 .../juneau-microservice-jetty/pom.xml              |   4 +-
 .../jetty/BasicJettyMicroserviceListener.java      |  54 --
 .../microservice/jetty/JettyConfiguration.java     | 113 +++
 .../microservice/jetty/JettyMicroservice.java      | 907 ---------------------
 .../jetty/JettyMicroserviceListener.java           |  64 --
 .../microservice/jetty/JettyServerComponent.java   | 398 +++++++++
 .../juneau/microservice/jetty/JettySettings.java   | 177 ++++
 .../jetty/resources/DebugResource.java             |  13 +-
 ....properties => JettyServerComponent.properties} |   0
 .../.gitignore                                     |   0
 .../Dockerfile                                     |   0
 .../pom.xml                                        |   8 +-
 .../microservice/BasicMicroserviceListener.java    |   2 +-
 .../org/apache/juneau/microservice/LogConfig.java  |   0
 .../apache/juneau/microservice/Microservice.java   |  49 +-
 .../juneau/microservice/MicroserviceListener.java  |   2 +-
 .../juneau/microservice/console/ConfigCommand.java |   2 +-
 .../microservice/console/ConsoleCommand.java       |   4 +-
 .../juneau/microservice/console/ExitCommand.java   |   2 +-
 .../juneau/microservice/console/HelpCommand.java   |   2 +-
 .../microservice/console/RestartCommand.java       |   2 +-
 .../juneau/microservice/console/package-info.java  |   0
 .../apache/juneau/microservice/package-info.java   |   0
 .../juneau/microservice/resources/ConfigEdit.html  |   0
 .../microservice/resources/ConfigResource.java     |   2 +-
 .../microservice/resources/DirectoryResource.java  |   2 +-
 .../microservice/resources/LogEntryFormatter.java  |   0
 .../juneau/microservice/resources/LogParser.java   |   0
 .../microservice/resources/LogsResource.java       |   0
 .../microservice/resources/SampleRootResource.java |   2 +-
 .../microservice/resources/ShutdownResource.java   |   2 +-
 .../microservice/resources/package-info.java       |   0
 .../juneau/microservice/Microservice.properties    |   0
 .../microservice/console/Messages.properties       |   0
 .../juneau-my-jetty-microservice/pom.xml           |   2 +-
 .../juneau/microservice/jetty/template/App.java    |  30 +-
 .../build-overlay/pom.xml.2                        |   2 +-
 .../juneau-my-springboot-microservice/pom.xml      |   4 +-
 juneau-microservice/pom.xml                        |   4 +-
 .../org/apache/juneau/rest/client/RestClient.java  |   2 +-
 .../java/org/apache/juneau/rest/RestContext.java   |   5 -
 .../rest/annotation/RestDeleteAnnotation.java      |   2 +-
 .../juneau/rest/annotation/RestGetAnnotation.java  |   2 +-
 .../apache/juneau/rest/annotation/RestInit.java    |   2 +-
 .../juneau/rest/annotation/RestOpAnnotation.java   |   2 +-
 .../rest/annotation/RestOptionsAnnotation.java     |   2 +-
 .../rest/annotation/RestPatchAnnotation.java       |   2 +-
 .../juneau/rest/annotation/RestPostAnnotation.java |   2 +-
 .../juneau/rest/annotation/RestPutAnnotation.java  |   2 +-
 .../rest/processor/PlainTextPojoProcessor.java     |   4 -
 .../rest/swagger/BasicSwaggerProviderSession.java  |   4 +-
 .../java/org/apache/juneau/server/config/App.java  |  40 +-
 juneau-shaded/juneau-shaded-all/pom.xml            |   2 +-
 juneau-utest/pom.xml                               |   2 +-
 .../test/java/org/apache/juneau/BeanMap_Test.java  |  88 +-
 .../org/apache/juneau/DataConversion_Test.java     |   4 +-
 .../test/java/org/apache/juneau/JsonList_Test.java |   2 +-
 .../test/java/org/apache/juneau/JsonMap_Test.java  | 224 ++---
 .../org/apache/juneau/MarshalledConfig_Test.java   |   4 +-
 .../java/org/apache/juneau/ObjectSwap_Test.java    |   2 +-
 .../juneau/a/rttests/JsonMaps_RoundTripTest.java   |   6 +-
 .../a/rttests/ParentProperty_RoundTripTest.java    |   3 +-
 .../a/rttests/SimpleObjects_RoundTripTest.java     |   2 +-
 .../a/rttests/TrimStrings_RoundTripTest.java       |  12 +-
 .../jsonschema/JsonSchemaBeanGenerator_Test.java   |   2 +-
 .../apache/juneau/cbor/CborCompliance_Test.java    |   2 +-
 .../apache/juneau/cbor/CborOutputStream_Test.java  |   4 +-
 .../org/apache/juneau/cbor/CborRoundTrip_Test.java |   2 +-
 .../apache/juneau/cbor/CborSerializer_Test.java    |   6 +-
 .../juneau/commons/inject/BasicBeanStore_Test.java |   4 -
 .../httppart/OpenApiPartSerializer_Test.java       |  68 +-
 .../java/org/apache/juneau/json/Json_Test.java     |   4 +-
 .../org/apache/juneau/json5/Json5List_Test.java    |  10 +-
 .../org/apache/juneau/json5/Json5Map_Test.java     |  12 +-
 .../juneau/jsonschema/JsonSchemaGeneratorTest.java |   4 -
 .../annotation/SchemaAnnotation_Test.java          |   4 -
 .../microservice/MicroserviceTestFixture.java      |  54 +-
 .../Microservice_Listener_Fanout_Test.java         | 141 ++++
 .../jetty/BasicJettyMicroserviceListener_Test.java |  52 --
 ...ject_Test.java => JettyConfiguration_Test.java} | 120 +--
 .../jetty/JettyMicroservice_Builder_Test.java      | 181 ----
 .../juneau/msgpack/MsgPackSerializerTest.java      |   8 +-
 .../apache/juneau/objecttools/ObjectRest_Test.java |  16 +-
 .../juneau/rest/annotation/RestInit_Test.java      |  10 -
 .../annotation/Rest_BeanCreatorOverrides_Test.java |   4 -
 .../org/apache/juneau/uon/UonSerializer_Test.java  |  58 +-
 .../urlencoding/UrlEncodingSerializer_Test.java    |  58 +-
 .../org/apache/juneau/utils/FilteredMapTest.java   |   2 +-
 scripts/apply-eclipse-prefs.py                     |   2 +-
 ...tty-as-bean.md => FINISHED-36-jetty-as-bean.md} |   0
 todo/TODO.md                                       |   2 -
 113 files changed, 1498 insertions(+), 1863 deletions(-)

diff --git a/README.md b/README.md
index 7f09a8ac94..028ef98549 100644
--- a/README.md
+++ b/README.md
@@ -285,7 +285,7 @@ Apache Juneau™ is a single cohesive Java ecosystem consisting of the following
 * **juneau-rest-client** - A universal REST client API for interacting with Juneau or 3rd-party REST interfaces using POJOs and proxy interfaces.
 * **juneau-rest-server-springboot** - Spring boot integration for juneau-rest-servlet.
 * **juneau-rest-mock** - REST testing API.
-* **juneau-microservice-core** - Core microservice API.
+* **juneau-microservice** - Microservice API.
 * **juneau-microservice-jetty** - Jetty microservice API.
 * **juneau-examples-core** - Core code examples.
 * **juneau-examples-rest** - REST code examples.
diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
index ed6d98f3ba..359c4595bb 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanSession.java
@@ -84,7 +84,7 @@ public interface BeanSession {
 	 * directly, so the parse is delegated to the session via this SPI.
 	 *
 	 * <p>
-	 * Implementations typically route through {@code JsonMap.ofJson(value).session(this)}.
+	 * Implementations typically route through {@code JsonMap.ofText(value).session(this)}.
 	 *
 	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
 	 * @return The parsed map.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java
index 075d45ce37..27824e84b7 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java
@@ -26,11 +26,6 @@ import org.apache.juneau.commons.bean.BeanMap;
 /**
  * Marshalling-side helper for loading {@link BeanMap} contents from JSON text or a reader/parser pair.
  *
- * <p>
- * These entry points used to live as {@code BeanMap.load(Reader, ReaderParser)} and {@code BeanMap.load(String)}.
- * They were lifted out as part of TODO-5 Step 8b-ii because they reach into the marshalling-side
- * {@link JsonMap} parser and {@link ReaderParser} types — neither belongs in the bean-modeling layer.
- *
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
  * 	<jc>// Populate a bean map from a JSON string.</jc>
@@ -55,7 +50,7 @@ public final class BeanMapLoader {
 	 * @throws ParseException Malformed input encountered.
 	 */
 	public static <T> BeanMap<T> load(BeanMap<T> m, String input) throws ParseException {
-		m.putAll(Json5Map.ofJson5(input));
+		m.putAll(Json5Map.ofText(input));
 		return m;
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
index 6027d2fa86..a868b0534a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
@@ -30,13 +30,6 @@ import org.apache.juneau.commons.utils.*;
  * Marshalling-side bridge for {@link BeanMeta} construction.
  *
  * <p>
- * Encapsulates marshalling-aware operations that were lifted out of {@link BeanMeta} as part of TODO-5
- * Step 8b-ii Phase C Task 3, so that {@link BeanMeta} can physically move into the
- * {@code org.apache.juneau.commons.bean} package without directly referencing
- * {@link BeanRegistry}, {@link MarshallingContext}, or marshalling-side annotations like
- * {@link Marshalled @Marshalled} from inside its constructor body.
- *
- * <p>
  * Each helper takes the marshalling-side {@link MarshallingContext} as an {@link Object} so the
  * call sites inside {@link BeanMeta} do not need to import {@link MarshallingContext}.  The helper
  * casts back internally.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
index e30144db2e..712839a4f4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingContext.java
@@ -16,7 +16,10 @@
  */
 package org.apache.juneau;
 
+import org.apache.juneau.commons.annotation.Schema;
 import org.apache.juneau.commons.http.MediaType;
+import org.apache.juneau.commons.httppart.SchemaValidationException;
+import org.apache.juneau.parser.ParseException;
 import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
 import static org.apache.juneau.commons.reflect.Visibility.*;
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
@@ -174,7 +177,7 @@ import org.apache.juneau.commons.bean.*;
 	"java:S1452", // Wildcard required - ClassMeta<?> for parameter resolution and type variables
 	"java:S1612"  // Lambdas used instead of method references for readability in complex chained expressions
 })
-public class MarshallingContext extends Context implements ConversionFinder, org.apache.juneau.commons.bean.BeanTypeResolver {
+public class MarshallingContext extends Context implements ConversionFinder, BeanTypeResolver {
 
 	// Property name constants
 	private static final String PROP_beanClassVisibility = "beanClassVisibility";
@@ -1768,7 +1771,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 		}
 
 		@Override /* Overridden from Builder */
-		public Builder cache(Cache<HashKey,? extends org.apache.juneau.Context> value) {
+		public Builder cache(Cache<HashKey,? extends Context> value) {
 			super.cache(value);
 			return this;
 		}
@@ -2451,15 +2454,15 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 		 *
 		 * <p>
 		 * When enabled, bean property values are validated against the constraints declared by
-		 * {@link org.apache.juneau.commons.annotation.Schema @Schema} annotations on those properties.  Validation runs
+		 * {@link Schema @Schema} annotations on those properties.  Validation runs
 		 * during both <b>parsing</b> (value set on the bean) and <b>serialization</b> (value read from the bean).
 		 *
 		 * <p>
 		 * Backed by the typed {@code JsonSchema} bean in {@code juneau-bean-jsonschema} and JSON Schema Draft 2020-12
-		 * semantics.  Validation failures throw {@link org.apache.juneau.commons.httppart.SchemaValidationException}
+		 * semantics.  Validation failures throw {@link SchemaValidationException}
 		 * wrapped in {@code BeanRuntimeException}; the parser surfaces them as
-		 * {@link org.apache.juneau.parser.ParseException} and the serializer surfaces them as
-		 * {@link org.apache.juneau.serializer.SerializeException}.
+		 * {@link ParseException} and the serializer surfaces them as
+		 * {@link SerializeException}.
 		 *
 		 * <p>
 		 * If {@code juneau-bean-jsonschema} is not on the classpath, this flag becomes a silent no-op.
@@ -3391,7 +3394,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 		}
 
 		@Override /* Overridden from Builder */
-		public Builder type(Class<? extends org.apache.juneau.Context> value) {
+		public Builder type(Class<? extends Context> value) {
 			assertArgNotNull(ARG_value, value);
 			super.type(value);
 			return this;
@@ -4115,7 +4118,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 					var str = in.toString();
 					Json5List list;
 					if (startsWith(str, '['))
-						list = Json5List.ofJson5(str).setBeanSession(bs);
+						list = Json5List.ofText(str).setBeanSession(bs);
 					else
 						list = new Json5List((Object[]) splita(str)).setBeanSession(bs);
 					return bs.convertToType(list, outType);
@@ -4130,7 +4133,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 			return (in, memberOf, session, args) -> {
 				try {
 					var bs = beanSessionOrDefault(session);
-					var m = Json5Map.ofJson5(in.toString());
+					var m = Json5Map.ofText(in.toString());
 					m.setBeanSession(bs);
 					return bs.convertToType(m, outType);
 				} catch (Exception e) {
@@ -4194,7 +4197,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 					if (!isProbablyJsonArray(str, false))
 						throw rex("Cannot convert string to {0}: {1}", outType.getName(), str);
 					var elemType = args.length > 0 ? args[0] : null;
-					var l2 = Json5List.ofJson5(str).setBeanSession(bs);
+					var l2 = Json5List.ofText(str).setBeanSession(bs);
 					var result = newCollection(outType);
 					l2.forEach(x -> result.add(elemType != null && x != null ? converter.to(x, elemType) : x));
 					return result;
@@ -4951,7 +4954,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 	//-----------------------------------------------------------------------------------------------------------------
 
 	@Override /* Overridden from BeanTypeResolver */
-	public org.apache.juneau.commons.bean.BeanInfo<?> resolveType(AnnotationInfo<org.apache.juneau.commons.bean.BeanProp> lastBeanProp, ClassInfo type, TypeVariables typeVarImpls) {
+	public org.apache.juneau.commons.bean.BeanInfo<?> resolveType(AnnotationInfo<BeanProp> lastBeanProp, ClassInfo type, TypeVariables typeVarImpls) {
 		return resolveClassMeta(lastBeanProp, type, typeVarImpls);
 	}
 
@@ -5004,7 +5007,7 @@ public class MarshallingContext extends Context implements ConversionFinder, org
 	@SuppressWarnings({
 		"java:S3776" // Cognitive complexity acceptable for ClassMeta resolution with property annotations
 	})
-	protected final <T> ClassMeta<T> resolveClassMeta(AnnotationInfo<org.apache.juneau.commons.bean.BeanProp> p, ClassInfo ci, TypeVariables typeVarImpls) {
+	protected final <T> ClassMeta<T> resolveClassMeta(AnnotationInfo<BeanProp> p, ClassInfo ci, TypeVariables typeVarImpls) {
 		var cm = resolveClassMeta(ci, typeVarImpls);
 		var cm2 = cm;
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
index b1b3103ca5..3e8b3f9241 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshallingSession.java
@@ -32,12 +32,14 @@ import java.util.logging.*;
 
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.collections.*;
+import org.apache.juneau.commons.bean.*;
 import org.apache.juneau.commons.collections.FluentMap;
 import org.apache.juneau.commons.conversion.*;
 import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.json.JsonParserSession;
+import org.apache.juneau.json5.*;
 import org.apache.juneau.swap.*;
-import org.apache.juneau.commons.bean.*;
 
 /**
  * Session object that lives for the duration of a single use of {@link MarshallingContext}.
@@ -1250,17 +1252,9 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 	 *
 	 * <p>
 	 * This is the main factory hook overridden by per-marshaller parser sessions to return their flavored
-	 * {@code XMap} variant (for example, {@link org.apache.juneau.json.JsonParserSession} returns
-	 * {@link JsonMap}; {@link org.apache.juneau.json5.Json5ParserSession} returns
-	 * {@link org.apache.juneau.json5.Json5Map}).
-	 *
-	 * <p>
-	 * As of Phase C of the TODO-34 refactor, the base implementation returns the neutral
-	 * {@link MarshalledMap}. Only {@link org.apache.juneau.json.JsonParserSession} and
-	 * {@link org.apache.juneau.json5.Json5ParserSession} actually override this hook today; every other
-	 * parser session (HTML, URL-encoding, XML, MessagePack, CSV, etc.) inherits the base and therefore
-	 * produces {@link MarshalledMap} instances during generic parsing. Phase D will fan out per-language
-	 * {@code XMap} / {@code XList} variants from those parsers as well.
+	 * {@code XMap} variant (for example, {@link JsonParserSession} returns
+	 * {@link JsonMap}; {@link Json5ParserSession} returns
+	 * {@link Json5Map}).
 	 *
 	 * @return A new map.
 	 */
@@ -1273,17 +1267,9 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 	 *
 	 * <p>
 	 * Per-marshaller parser sessions override this hook to return their flavored {@code XList} variant
-	 * (for example, {@link org.apache.juneau.json.JsonParserSession} returns {@link JsonList};
-	 * {@link org.apache.juneau.json5.Json5ParserSession} returns
-	 * {@link org.apache.juneau.json5.Json5List}).
-	 *
-	 * <p>
-	 * As of Phase C of the TODO-34 refactor, the base implementation returns the neutral
-	 * {@link MarshalledList}. Only {@link org.apache.juneau.json.JsonParserSession} and
-	 * {@link org.apache.juneau.json5.Json5ParserSession} actually override this hook today; every other
-	 * parser session inherits the base and therefore produces {@link MarshalledList} instances during
-	 * generic parsing. Phase D will fan out per-language {@code XList} variants from those parsers as
-	 * well.
+	 * (for example, {@link JsonParserSession} returns {@link JsonList};
+	 * {@link Json5ParserSession} returns
+	 * {@link Json5List}).
 	 *
 	 * @return A new list.
 	 */
@@ -1388,7 +1374,7 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 
 	/**
 	 * Bridge implementation of {@link BeanSession#parseToMap(CharSequence)} that delegates to
-	 * {@link org.apache.juneau.json5.Json5Map#ofJson5(CharSequence)} paired with this session.
+	 * {@link Json5Map#ofText(CharSequence)} paired with this session.
 	 *
 	 * <p>
 	 * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link Map} when
@@ -1396,21 +1382,21 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 	 * references the marshalling-side JSON parser.
 	 *
 	 * <p>
-	 * Uses {@link org.apache.juneau.json5.Json5Map} so that historic JSON5-form inputs (e.g. annotation default
+	 * Uses {@link Json5Map} so that historic JSON5-form inputs (e.g. annotation default
 	 * values written as <c>{a:'b'}</c>) continue to parse — the strict-JSON {@link JsonMap} retargeting in v9.5
 	 * would otherwise break those call sites.
 	 *
 	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
-	 * @return The parsed {@link org.apache.juneau.json5.Json5Map}.
+	 * @return The parsed {@link Json5Map}.
 	 */
 	@Override /* BeanSession */
 	public final java.util.Map<?,?> parseToMap(CharSequence value) {
-		return org.apache.juneau.json5.Json5Map.ofJson5(value).session(this);
+		return Json5Map.ofText(value).session(this);
 	}
 
 	/**
 	 * Bridge implementation of {@link BeanSession#parseToList(CharSequence)} that delegates to
-	 * {@link org.apache.juneau.json5.Json5List#Json5List(CharSequence)} paired with this session.
+	 * {@link Json5List#Json5List(CharSequence)} paired with this session.
 	 *
 	 * <p>
 	 * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link Collection}
@@ -1418,15 +1404,15 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 	 * no longer references the marshalling-side JSON parser.
 	 *
 	 * <p>
-	 * Uses {@link org.apache.juneau.json5.Json5List} for the same back-compat reasons as
+	 * Uses {@link Json5List} for the same back-compat reasons as
 	 * {@link #parseToMap(CharSequence)}.
 	 *
 	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
-	 * @return The parsed {@link org.apache.juneau.json5.Json5List}.
+	 * @return The parsed {@link Json5List}.
 	 */
 	@Override /* BeanSession */
 	public final java.util.Collection<?> parseToList(CharSequence value) {
-		return new org.apache.juneau.json5.Json5List(value).setBeanSession(this);
+		return new Json5List(value).setBeanSession(this);
 	}
 
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/UriContext.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/UriContext.java
index 3a804a61e6..1673177574 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/UriContext.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/UriContext.java
@@ -159,7 +159,7 @@ public class UriContext {
 	 * 	If input string is not a valid JSON object.
 	 */
 	public UriContext(String s) throws ParseException {
-		var m = Json5Map.ofJson5(s);
+		var m = Json5Map.ofText(s);
 		this.authority = nullIfEmpty(trimSlashes(m.getString(PROP_authority)));
 		this.contextRoot = nullIfEmpty(trimSlashes(m.getString(PROP_contextRoot)));
 		this.servletPath = nullIfEmpty(trimSlashes(m.getString(PROP_servletPath)));
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
index 85062b33e4..e413047a7e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonList.java
@@ -64,24 +64,24 @@ import org.apache.juneau.serializer.*;
  * 	<jv>list</jv> = JsonList.<jsm>of</jsm>(<js>"foo"</js>, 123, <jk>true</jk>);  <jc>// Equivalent</jc>
  *
  * 	<jc>// Construct a list of integers from JSON</jc>
- * 	<jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
+ * 	<jv>list</jv> = JsonList.<jsm>ofText</jsm>(<js>"[1,2,3]"</js>);
  *
  * 	<jc>// Construct a list of generic JsonMap objects from JSON</jc>
- * 	<jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
+ * 	<jv>list</jv> = JsonList.<jsm>ofText</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
  *
  * 	<jc>// Construct JSON from JsonList</jc>
- * 	<jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
+ * 	<jv>list</jv> = JsonList.<jsm>ofText</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
  * 	String <jv>json</jv> = <jv>list</jv>.toString();  <jc>// Produces "[{foo:'bar'},{baz:'bing'}]"</jc>
  * 	<jv>json</jv> = <jv>list</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>);  <jc>// Equivalent</jc>
  * 	<jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>);  <jc>// Equivalent</jc>
  *
  * 	<jc>// Get one of the entries in the list as an Integer</jc>
- * 	<jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[1,2,3]"</js>);
+ * 	<jv>list</jv> = JsonList.<jsm>ofText</jsm>(<js>"[1,2,3]"</js>);
  * 	Integer <jv>integer</jv> = <jv>list</jv>.getInt(1);
  * 	<jv>integer</jv> = <jv>list</jv>.get(Integer.<jk>class</jk>, 1);  <jc>// Equivalent</jc>
  *
  * 	<jc>// Iterate over a list of beans using the elements() method</jc>
- * 	<jv>list</jv> = JsonList.<jsm>ofJson</jsm>(<js>"[{name:'John Smith',age:45}]"</js>);
+ * 	<jv>list</jv> = JsonList.<jsm>ofText</jsm>(<js>"[{name:'John Smith',age:45}]"</js>);
  * 	<jk>for</jk> (Person <jv>person</jv> : <jv>list</jv>.elements(Person.<jk>class</jk>) {
  * 		<jc>// Do something with p</jc>
  * 	}
@@ -236,7 +236,7 @@ public class JsonList extends MarshalledList {
 	 * @return A new list or <jk>null</jk> if the string was null.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static JsonList ofJson(CharSequence json) throws ParseException {
+	public static JsonList ofText(CharSequence json) throws ParseException {
 		return json == null ? null : new JsonList(json);
 	}
 
@@ -252,7 +252,7 @@ public class JsonList extends MarshalledList {
 	@SuppressWarnings({
 		"java:S1168"     // TODO: null input = null output by design. Consider empty JsonList.
 	})
-	public static JsonList ofJson(Reader json) throws ParseException {
+	public static JsonList ofText(Reader json) throws ParseException {
 		return json == null ? null : new JsonList(json);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonMap.java
index 3d8c40152d..b69dddf231 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/JsonMap.java
@@ -58,7 +58,7 @@ import org.apache.juneau.serializer.*;
  * 	JsonMap <jv>map</jv> = JsonMap.<jsm>of</jsm>();
  *
  * 	<jc>// Construct a Map from JSON</jc>
- * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
+ * 	<jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
  *
  * 	<jc>// Construct a Map using the append method</jc>
  * 	<jv>map</jv> = JsonMap.<jsm>of</jsm>().a(<js>"foo"</js>,<js>"x"</js>).a(<js>"bar"</js>,123).a(<js>"baz"</js>,<jk>true</jk>);
@@ -72,19 +72,19 @@ import org.apache.juneau.serializer.*;
  * 	<jv>map</jv> = JsonMap.<jsm>of</jsm>(<jv>urlParams</jv>, UrlEncodingParser.<jsf>DEFAULT</jsf>);
  *
  * 	<jc>// Construct JSON from JsonMap</jc>
- * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
+ * 	<jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{foo:'bar'},{baz:[123,true]}"</js>);
  * 	String <jv>json</jv> = <jv>map</jv>.toString();  <jc>// Produces "{foo:'bar'},{baz:[123,true]}"</jc>
  * 	<jv>json</jv> = <jv>map</jv>.toString(JsonSerializer.<jsf>DEFAULT</jsf>);  <jc>// Equivalent</jc>
  * 	<jv>json</jv> = JsonSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);  <jc>// Equivalent</jc>
  *
  * 	<jc>// Get a map entry as an Integer</jc>
- * 	<jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:123}"</js>);
+ * 	<jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{foo:123}"</js>);
  * 	Integer <jv>integer</jv> = <jv>map</jv>.getInt(<js>"foo"</js>);
  * 	<jv>integer</jv> = <jv>map</jv>.get(Integer.<jk>class</jk>, <js>"foo"</js>);  <jc>// Equivalent</jc>
  *
  * 	<jc>// Add an inner map</jc>
- * 	JsonMap <jv>map1</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:1}"</js>);
- * 	JsonMap <jv>map2</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{b:2}"</js>).setInner(<jv>map1</jv>);
+ * 	JsonMap <jv>map1</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{a:1}"</js>);
+ * 	JsonMap <jv>map2</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{b:2}"</js>).setInner(<jv>map1</jv>);
  * 	<jk>int</jk> <jv>_int</jv> = <jv>map2</jv>.getInt(<js>"a"</js>);  <jc>// a == 1 </jc>
  * </p>
  *
@@ -233,7 +233,7 @@ public class JsonMap extends MarshalledMap {
 	 * @return A new map or <jk>null</jk> if the string was null.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static JsonMap ofJson(CharSequence json) throws ParseException {
+	public static JsonMap ofText(CharSequence json) throws ParseException {
 		return json == null ? null : new JsonMap(json);
 	}
 
@@ -246,7 +246,7 @@ public class JsonMap extends MarshalledMap {
 	 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static JsonMap ofJson(Reader json) throws ParseException {
+	public static JsonMap ofText(Reader json) throws ParseException {
 		return json == null ? null : new JsonMap(json);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/package-info.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/package-info.java
index ff7a48a5be..8e3354f86c 100755
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/package-info.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/package-info.java
@@ -28,13 +28,13 @@
  * 		&mdash; strict-JSON-flavored subclasses. Their {@code toString()} renders as strict
  * 		<a href="https://www.rfc-editor.org/rfc/rfc8259" target="_blank">RFC&nbsp;8259</a> JSON via
  * 		{@link org.apache.juneau.marshaller.Json#of(Object)}, and their {@code (CharSequence)} /
- * 		{@code (Reader)} constructors and {@code ofJson(...)} factories default to
+ * 		{@code (Reader)} constructors and {@code ofText(...)} factories default to
  * 		{@link org.apache.juneau.json.JsonParser#DEFAULT}.
  * 	<li>{@link org.apache.juneau.json5.Json5Map} / {@link org.apache.juneau.json5.Json5List}
  * 		(in {@link org.apache.juneau.json5}) &mdash; JSON5-flavored siblings of
  * 		{@code JsonMap} / {@code JsonList}. Their {@code toString()} renders as JSON5 via
  * 		{@link org.apache.juneau.marshaller.Json5#of(Object)}, and their {@code (CharSequence)} /
- * 		{@code (Reader)} constructors and {@code ofJson5(...)} factories default to
+ * 		{@code (Reader)} constructors and {@code ofText(...)} factories default to
  * 		{@link org.apache.juneau.json5.Json5Parser#DEFAULT}. These are the drop-in replacement
  * 		for callers that previously relied on {@code JsonMap.toString()} producing JSON5 or on
  * 		{@code new JsonMap("{unquoted:'json5'}")} parsing JSON5.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializer.java
index 48857ca27c..4b64574043 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializer.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializer.java
@@ -104,9 +104,9 @@ import org.apache.juneau.commons.bean.BeanPropertyMeta;
  * 	<jc>//       &lt;tr&gt;&lt;td&gt;Barney&lt;/td&gt;&lt;td&gt;Miller&lt;/td&gt;&lt;/tr&gt; </jc>
  * 	<jc>//    &lt;/table&gt; </jc>
  * 	<jv>html</jv> = JsonList.<jsm>of</jsm>(
- * 		JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Bob',lastName:'Costas'}"</js>),
- * 		JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>),
- * 		JsonMap.<jsm>ofJson</jsm>(<js>"{firstName:'Barney',lastName:'Miller'}"</js>)
+ * 		JsonMap.<jsm>ofText</jsm>(<js>"{firstName:'Bob',lastName:'Costas'}"</js>),
+ * 		JsonMap.<jsm>ofText</jsm>(<js>"{firstName:'Billy',lastName:'TheKid'}"</js>),
+ * 		JsonMap.<jsm>ofText</jsm>(<js>"{firstName:'Barney',lastName:'Miller'}"</js>)
  * 	);
  * 	String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>list</jv>);
  *
@@ -116,7 +116,7 @@ import org.apache.juneau.commons.bean.BeanPropertyMeta;
  * 	<jc>//       &lt;tr&gt;&lt;td&gt;foo&lt;/td&gt;&lt;td&gt;bar&lt;/td&gt;&lt;/tr&gt; </jc>
  * 	<jc>//       &lt;tr&gt;&lt;td&gt;baz&lt;/td&gt;&lt;td&gt;123&lt;/td&gt;&lt;/tr&gt; </jc>
  * 	<jc>//    &lt;/table&gt; </jc>
- * 	Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
+ * 	Map <jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{foo:'bar',baz:123}"</js>);
  * 	String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);
  *
  * 	<jc>// HTML elements can be nested arbitrarily deep</jc>
@@ -133,9 +133,9 @@ import org.apache.juneau.commons.bean.BeanPropertyMeta;
  * 	<jc>//			&lt;/table&gt; </jc>
  * 	<jc>//		&lt;/td&gt;&lt;/tr&gt; </jc>
  * 	<jc>//	&lt;/table&gt; </jc>
- * 	Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{foo:'bar',baz:123}"</js>);
+ * 	Map <jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{foo:'bar',baz:123}"</js>);
  * 	<jv>map</jv>.put(<js>"someNumbers"</js>, JsonList.<jsm>of</jsm>(1, 2, 3));
- * 	<jv>map</jv>.put(<js>"someSubMap"</js>, JsonMap.<jsm>ofJson</jsm>(<js>"{a:'b'}"</js>));
+ * 	<jv>map</jv>.put(<js>"someSubMap"</js>, JsonMap.<jsm>ofText</jsm>(<js>"{a:'b'}"</js>));
  * 	String <jv>html</jv> = HtmlSerializer.<jsf>DEFAULT</jsf>.serialize(<jv>map</jv>);
  * </p>
  *
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5List.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5List.java
index 2449b02331..8b6c4422d3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5List.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5List.java
@@ -49,7 +49,7 @@ import org.apache.juneau.serializer.*;
  * 	Json5List <jv>list</jv> = Json5List.<jsm>of</jsm>();
  *
  * 	<jc>// Construct a list of generic Json5Map objects from JSON5</jc>
- * 	<jv>list</jv> = Json5List.<jsm>ofJson5</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
+ * 	<jv>list</jv> = Json5List.<jsm>ofText</jsm>(<js>"[{foo:'bar'},{baz:'bing'}]"</js>);
  *
  * 	<jc>// JSON5 round-trip via toString()</jc>
  * 	String <jv>s</jv> = <jv>list</jv>.toString();  <jc>// "[{foo:'bar'},{baz:'bing'}]"</jc>
@@ -200,7 +200,7 @@ public class Json5List extends MarshalledList {
 	 * @return A new list or <jk>null</jk> if the string was null.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static Json5List ofJson5(CharSequence json5) throws ParseException {
+	public static Json5List ofText(CharSequence json5) throws ParseException {
 		return json5 == null ? null : new Json5List(json5);
 	}
 
@@ -214,7 +214,7 @@ public class Json5List extends MarshalledList {
 	@SuppressWarnings({
 		"java:S1168"     // TODO: null input = null output by design. Consider empty Json5List.
 	})
-	public static Json5List ofJson5(Reader json5) throws ParseException {
+	public static Json5List ofText(Reader json5) throws ParseException {
 		return json5 == null ? null : new Json5List(json5);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5Map.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5Map.java
index 4369aa1fb3..df777e52f3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5Map.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json5/Json5Map.java
@@ -49,7 +49,7 @@ import org.apache.juneau.serializer.*;
  * 	Json5Map <jv>map</jv> = Json5Map.<jsm>of</jsm>();
  *
  * 	<jc>// Construct a Map from JSON5</jc>
- * 	<jv>map</jv> = Json5Map.<jsm>ofJson5</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
+ * 	<jv>map</jv> = Json5Map.<jsm>ofText</jsm>(<js>"{a:'A',b:{c:'C',d:123}}"</js>);
  *
  * 	<jc>// JSON5 round-trip via toString()</jc>
  * 	String <jv>s</jv> = <jv>map</jv>.toString();  <jc>// "{a:'A',b:{c:'C',d:123}}"</jc>
@@ -65,7 +65,8 @@ import org.apache.juneau.serializer.*;
 public class Json5Map extends MarshalledMap {
 
 	@SuppressWarnings({
-		"java:S2160" // equals() / hashCode() inherited from Json5Map; map equality is element-based
+		"java:S2160", // equals() / hashCode() inherited from Json5Map; map equality is element-based
+		"java:S110"   // Inheritance depth inherited from MarshalledMap -> LinkedHashMap chain; intentional
 	})
 	private static class UnmodifiableJson5Map extends Json5Map {
 		private static final long serialVersionUID = 1L;
@@ -100,7 +101,8 @@ public class Json5Map extends MarshalledMap {
 	 * @serial exclude
 	 */
 	@SuppressWarnings({
-		"java:S2386" // Public static final field accessed externally, cannot be protected
+		"java:S2386", // Public static final field accessed externally, cannot be protected
+		"java:S110"   // Anonymous subclass of Json5Map inherits the MarshalledMap -> LinkedHashMap chain; intentional
 	})
 	public static final Json5Map EMPTY_MAP = new Json5Map() {
 
@@ -199,7 +201,7 @@ public class Json5Map extends MarshalledMap {
 	 * @return A new map or <jk>null</jk> if the string was null.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static Json5Map ofJson5(CharSequence json5) throws ParseException {
+	public static Json5Map ofText(CharSequence json5) throws ParseException {
 		return json5 == null ? null : new Json5Map(json5);
 	}
 
@@ -211,7 +213,7 @@ public class Json5Map extends MarshalledMap {
 	 * @return A new map or <jk>null</jk> if the input was <jk>null</jk>.
 	 * @throws ParseException Malformed input encountered.
 	 */
-	public static Json5Map ofJson5(Reader json5) throws ParseException {
+	public static Json5Map ofText(Reader json5) throws ParseException {
 		return json5 == null ? null : new Json5Map(json5);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
index 1b1a57edf2..fed5269fe1 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/SchemaUtils.java
@@ -78,7 +78,7 @@ public class SchemaUtils {
 				return JsonMap.of("ignore", true);
 			if (! isProbablyJsonObject(s, true))
 				s = "{" + s + "}";
-			return new JsonMap(Json5Map.ofJson5(s));
+			return new JsonMap(Json5Map.ofText(s));
 		}
 		if (o instanceof JsonMap o2)
 			return o2;
@@ -105,7 +105,7 @@ public class SchemaUtils {
 			return null;
 		if (! isProbablyJsonObject(s, true))
 			s = "{" + s + "}";
-		return new JsonMap(Json5Map.ofJson5(s));
+		return new JsonMap(Json5Map.ofText(s));
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
index 7bb7514ecd..0afdbe8e53 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java
@@ -63,7 +63,7 @@ import org.apache.juneau.parser.*;
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
  * 	<jc>// Construct an unstructured POJO model</jc>
- * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>""</js>
+ * 	JsonMap <jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>""</js>
  * 		+ <js>"{"</js>
  * 		+ <js>"	name:'John Smith', "</js>
  * 		+ <js>"	address:{ "</js>
@@ -111,7 +111,7 @@ import org.apache.juneau.parser.*;
  * 	<jv>johnSmith</jv>.delete(<js>"fico score"</js>);
  *
  * 	<jc>// Add entirely new structures to the tree</jc>
- * 	JsonMap <jv>medicalInfo</jv> = JsonMap.<jsm>ofJson</jsm>(<js>""</js>
+ * 	JsonMap <jv>medicalInfo</jv> = JsonMap.<jsm>ofText</jsm>(<js>""</js>
  * 		+ <js>"{"</js>
  * 		+ <js>"	currentStatus: 'deceased',"</js>
  * 		+ <js>"	health: 'non-existent',"</js>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
index 4bb1615b0e..83576ab95a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
@@ -495,7 +495,7 @@ public class UonParserSession extends ReaderParserSession implements HttpPartPar
 	}
 
 	@SuppressWarnings({
-		"java:S1168",    // TODO: null for empty/EOF or parseAttrName('%00'). Parser state machine.
+		"java:S1168",    // Intentionally returns null for empty/EOF or parseAttrName('%00') in this parser state machine.
 		"java:S2589",    // Final if (state==S4) is always true given prior checks; exhaustive state error-reporting pattern
 		"java:S3776", // Cognitive complexity acceptable for this specific logic
 		"java:S6541", // Single-threaded session contexts do not require synchronization
@@ -618,7 +618,7 @@ public class UonParserSession extends ReaderParserSession implements HttpPartPar
 	}
 
 	@SuppressWarnings({
-		"java:S1168",    // TODO: null for EOF/AMP. Parser state machine.
+		"java:S1168",    // Intentionally returns null for EOF/AMP in this parser state machine.
 		"java:S1854",    // argIndex=0 is the valid starting arg index; Sonar FP on early-return paths that never read it
 		"java:S2583",    // State variables persist across loop iterations
 		"java:S2589",    // Final if (state==S3) is always true given prior check; exhaustive state error-reporting pattern
@@ -713,7 +713,7 @@ public class UonParserSession extends ReaderParserSession implements HttpPartPar
 	}
 
 	@SuppressWarnings({
-		"java:S1168",    // TODO: null for EOF/AMP. Parser state machine.
+		"java:S1168",    // Intentionally returns null for EOF/AMP in this parser state machine.
 		"java:S2589",    // Final if (state==S4) is always true given prior checks; exhaustive state error-reporting pattern
 		"java:S6541",    // Brain method acceptable for parser state machine
 		"java:S3776"     // Cognitive complexity acceptable for parser state machine
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializer.java
index 64d5fd1b02..72510acb43 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializer.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializer.java
@@ -112,7 +112,7 @@ import org.apache.juneau.commons.bean.BeanPropertyMeta;
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
  * 	<jc>// Serialize a Map</jc>
- * 	Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
+ * 	Map <jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
  *
  * 	<jc>// Serialize to value equivalent to JSON.</jc>
  * 	<jc>// Produces "(a=b,c=1,d=false,e=@(f,1,false),g=(h=i))"</jc>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
index c45a773ff4..75194f649a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializer.java
@@ -97,7 +97,7 @@ import org.apache.juneau.commons.bean.BeanPropertyMeta;
  * <h5 class='section'>Example:</h5>
  * <p class='bjava'>
  * 	<jc>// Serialize a Map</jc>
- * 	Map <jv>map</jv> = JsonMap.<jsm>ofJson</jsm>(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
+ * 	Map <jv>map</jv> = JsonMap.<jsm>ofText</jsm>(<js>"{a:'b',c:1,d:false,e:['f',1,false],g:{h:'i'}}"</js>);
  *
  * 	<jc>// Serialize to value equivalent to JSON.</jc>
  * 	<jc>// Produces "a=b&amp;c=1&amp;d=false&amp;e=@(f,1,false)&amp;g=(h=i)"</jc>
diff --git a/juneau-distrib/pom.xml b/juneau-distrib/pom.xml
index 273b361a80..273badcacb 100644
--- a/juneau-distrib/pom.xml
+++ b/juneau-distrib/pom.xml
@@ -533,29 +533,29 @@
 									<destFileName>org.apache.juneau.rest.mock_${project.version}.jar</destFileName>
 								</artifactItem>
 
-								<!-- juneau-microservice-core -->
+								<!-- juneau-microservice -->
 								<artifactItem>
 									<outputDirectory>${project.build.directory}/src/lib</outputDirectory>
 									<groupId>org.apache.juneau</groupId>
-									<artifactId>juneau-microservice-core</artifactId>
+									<artifactId>juneau-microservice</artifactId>
 									<version>${project.version}</version>
 									<type>jar</type>
 									<classifier>sources</classifier>
-									<destFileName>apache-juneau-microservice-core-${project.version}-sources.jar</destFileName>
+									<destFileName>apache-juneau-microservice-${project.version}-sources.jar</destFileName>
 								</artifactItem>
 								<artifactItem>
 									<outputDirectory>${project.build.directory}/bin/lib</outputDirectory>
 									<groupId>org.apache.juneau</groupId>
-									<artifactId>juneau-microservice-core</artifactId>
+									<artifactId>juneau-microservice</artifactId>
 									<version>${project.version}</version>
-									<destFileName>apache-juneau-microservice-core-${project.version}.jar</destFileName>
+									<destFileName>apache-juneau-microservice-${project.version}.jar</destFileName>
 								</artifactItem>
 								<artifactItem>
 									<outputDirectory>${project.build.directory}/bin/osgi</outputDirectory>
 									<groupId>org.apache.juneau</groupId>
-									<artifactId>juneau-microservice-core</artifactId>
+									<artifactId>juneau-microservice</artifactId>
 									<version>${project.version}</version>
-									<destFileName>org.apache.juneau.microservice.core_${project.version}.jar</destFileName>
+									<destFileName>org.apache.juneau.microservice_${project.version}.jar</destFileName>
 								</artifactItem>
 
 								<!-- juneau-microservice-jetty -->
diff --git a/juneau-examples/juneau-examples-rest-jetty-ftest/src/test/java/org/apache/juneau/examples/rest/SamplesMicroservice.java b/juneau-examples/juneau-examples-rest-jetty-ftest/src/test/java/org/apache/juneau/examples/rest/SamplesMicroservice.java
index d6e5f2ce53..9e73ccc997 100644
--- a/juneau-examples/juneau-examples-rest-jetty-ftest/src/test/java/org/apache/juneau/examples/rest/SamplesMicroservice.java
+++ b/juneau-examples/juneau-examples-rest-jetty-ftest/src/test/java/org/apache/juneau/examples/rest/SamplesMicroservice.java
@@ -19,17 +19,21 @@ package org.apache.juneau.examples.rest;
 import java.net.*;
 import java.util.*;
 
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.microservice.jetty.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.rest.client.*;
 import org.apache.juneau.serializer.*;
 
+import jakarta.servlet.*;
+
 /**
  * Utility class for starting up the examples microservice.
  */
 @SuppressWarnings("resource")
 public class SamplesMicroservice {
-	static volatile JettyMicroservice microservice;
+	static volatile Microservice microservice;
 	static URI microserviceURI;
 
 	// Reusable HTTP clients that get created and shut down with the microservice.
@@ -46,8 +50,8 @@ public class SamplesMicroservice {
 			return false;
 		try {
 			Locale.setDefault(Locale.US);
-			microservice = JettyMicroservice.create().workingDir("../juneau-examples-rest-jetty").configName("juneau-examples-rest-jetty.cfg").servlet(RootResources.class).build();
-			microserviceURI = microservice.start().getURI();
+			microservice = Microservice.create().workingDir("../juneau-examples-rest-jetty").configName("juneau-examples-rest-jetty.cfg").configurations(JettyConfiguration.class, AppConfig.class).build();
+			microserviceURI = microservice.start().getBeanStore().getBean(JettyServerComponent.class).orElseThrow().getURI();
 			defaultClient = client().json5().build();
 			defaultClientPlaintext = client().plainText().build();
 			return true;
@@ -107,4 +111,22 @@ public class SamplesMicroservice {
 	public static RestClient.Builder client(Class<? extends Serializer> s, Class<? extends Parser> p) {
 		return client().serializer(s).parser(p);
 	}
+
+	/**
+	 * Application configuration contributing the {@link RootResources} servlet.
+	 */
+	@Configuration
+	public static class AppConfig {
+
+		/**
+		 * Provides the top-level REST servlet, auto-mounted by {@link JettyServerComponent} at
+		 * {@link org.apache.juneau.rest.annotation.Rest#path()}.
+		 *
+		 * @return The root servlet.
+		 */
+		@Bean
+		public Servlet rootResources() {
+			return new RootResources();
+		}
+	}
 }
\ No newline at end of file
diff --git a/juneau-examples/juneau-examples-rest-jetty/src/main/java/org/apache/juneau/examples/rest/jetty/App.java b/juneau-examples/juneau-examples-rest-jetty/src/main/java/org/apache/juneau/examples/rest/jetty/App.java
index c91ed57e07..b46b6cfa7c 100644
--- a/juneau-examples/juneau-examples-rest-jetty/src/main/java/org/apache/juneau/examples/rest/jetty/App.java
+++ b/juneau-examples/juneau-examples-rest-jetty/src/main/java/org/apache/juneau/examples/rest/jetty/App.java
@@ -16,20 +16,19 @@
  */
 package org.apache.juneau.examples.rest.jetty;
 
+import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.examples.rest.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.microservice.jetty.*;
 
+import jakarta.servlet.*;
+
 /**
  * An example of an extended REST microservice.
  *
  * <p>
- * Subclasses can extend from {@link JettyMicroservice} to implement their own custom behavior.
- * However, this is optional and the {@link JettyMicroservice} class can be invoked directly.
- *
- * <p>
- * The {@link JettyMicroservice} class will locate the <c>examples.cfg</c> file in the home directory and initialize
+ * The {@link Microservice} class will locate the <c>examples.cfg</c> file in the home directory and initialize
  * the resources and commands defined in that file.
- *
  */
 public class App {
 
@@ -40,13 +39,33 @@ public class App {
 	 * @throws Exception General exception occurred.
 	 */
 	public static void main(String[] args) throws Exception {
-		JettyMicroservice
+		// @formatter:off
+		Microservice
 			.create()
 			.args(args)
-			.servlet(RootResources.class)
+			.configurations(JettyConfiguration.class, AppConfig.class)
 			.build()
 			.start()
 			.startConsole()
 			.join();
+		// @formatter:on
+	}
+
+	/**
+	 * Application-specific configuration class contributing the top-level REST servlet.
+	 */
+	@Configuration
+	public static class AppConfig {
+
+		/**
+		 * Provides the top-level REST servlet, auto-mounted by {@link JettyServerComponent} at
+		 * {@link org.apache.juneau.rest.annotation.Rest#path()}.
+		 *
+		 * @return The root servlet.
+		 */
+		@Bean
+		public Servlet rootResources() {
+			return new RootResources();
+		}
 	}
-}
\ No newline at end of file
+}
diff --git a/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml b/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml
index d8dc4c18bb..86dedf99d8 100644
--- a/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml
+++ b/juneau-examples/juneau-examples-rest-springboot/build-overlay/pom.xml
@@ -69,7 +69,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>\${juneau.version}</version>
 		</dependency>
 		<dependency>
diff --git a/juneau-microservice/juneau-microservice-jetty/pom.xml b/juneau-microservice/juneau-microservice-jetty/pom.xml
index 406c7ea78a..021df8ed18 100644
--- a/juneau-microservice/juneau-microservice-jetty/pom.xml
+++ b/juneau-microservice/juneau-microservice-jetty/pom.xml
@@ -21,7 +21,7 @@
 
 	<parent>
 		<groupId>org.apache.juneau</groupId>
-		<artifactId>juneau-microservice</artifactId>
+		<artifactId>juneau-microservice-parent</artifactId>
 		<version>9.5.0-SNAPSHOT</version>
 	</parent>
 
@@ -46,7 +46,7 @@
 	<dependencies>
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>${project.version}</version>
 		</dependency>
 		<dependency>
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener.java
deleted file mode 100644
index c3dda0b21d..0000000000
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener.java
+++ /dev/null
@@ -1,54 +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.microservice.jetty;
-
-import org.apache.juneau.microservice.*;
-
-/**
- * Basic extensible microservice listener with default no-op method implementations.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
- * </ul>
- */
-public class BasicJettyMicroserviceListener extends BasicMicroserviceListener implements JettyMicroserviceListener {
-
-	@Override /* Overridden from JettyMicroserviceListener */
-	public void onCreateServer(JettyMicroservice microservice) {
-		// No-op: Intentional empty implementation for optional interface method
-	}
-
-	@Override /* Overridden from JettyMicroserviceListener */
-	public void onPostStartServer(JettyMicroservice microservice) {
-		// No-op: Intentional empty implementation for optional interface method
-	}
-
-	@Override /* Overridden from JettyMicroserviceListener */
-	public void onPostStopServer(JettyMicroservice microservice) {
-		// No-op: Intentional empty implementation for optional interface method
-	}
-
-	@Override /* Overridden from JettyMicroserviceListener */
-	public void onStartServer(JettyMicroservice microservice) {
-		// No-op: Intentional empty implementation for optional interface method
-	}
-
-	@Override /* Overridden from JettyMicroserviceListener */
-	public void onStopServer(JettyMicroservice microservice) {
-		// No-op: Intentional empty implementation for optional interface method
-	}
-}
\ No newline at end of file
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyConfiguration.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyConfiguration.java
new file mode 100644
index 0000000000..42dfe6859f
--- /dev/null
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyConfiguration.java
@@ -0,0 +1,113 @@
+/*
+ * 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.microservice.jetty;
+
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
+
+/**
+ * <c>@Configuration</c> class that wires Jetty into a {@link Microservice} bootstrap.
+ *
+ * <p>
+ * Add this class to {@link org.apache.juneau.microservice.Microservice.Builder#configurations(Class...) configurations(...)} to enable a
+ * Jetty-backed microservice:
+ *
+ * <p class='bjava'>
+ * 	Microservice
+ * 		.<jsm>create</jsm>()
+ * 		.args(<jv>args</jv>)
+ * 		.configurations(JettyConfiguration.<jk>class</jk>, MyAppConfig.<jk>class</jk>)
+ * 		.build()
+ * 		.start()
+ * 		.startConsole()
+ * 		.join();
+ * </p>
+ *
+ * <p>
+ * Contributes the following beans (each gated on {@code @ConditionalOnMissingBean} so user-supplied beans of the
+ * same type win):
+ * <ul>
+ * 	<li>{@link JettyServerFactory} — defaults to {@link BasicJettyServerFactory}, used to build the Jetty
+ * 		{@link org.eclipse.jetty.server.Server Server} from <c>jetty.xml</c>.
+ * 	<li>{@link JettySettings} — empty defaults; user-supplied <c>@Bean JettySettings</c> can override ports,
+ * 		<c>jetty.xml</c> contents, and var-resolution.
+ * 	<li>{@link JettyServerComponent} (always contributed, also registered under {@link MicroserviceListener}) —
+ * 		drives the Jetty server lifecycle via the microservice's {@code start()} / {@code stop()} fan-out.
+ * </ul>
+ *
+ * <p>
+ * <c>@Bean Servlet</c> methods in user-supplied <c>@Configuration</c> classes are auto-mounted at
+ * {@link org.apache.juneau.rest.annotation.Rest#path()} during start-up.  See {@link JettyServerComponent} for
+ * full details.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@Configuration
+public class JettyConfiguration {
+
+	private final JettyServerComponent component = new JettyServerComponent();
+
+	/**
+	 * Contributes the default Jetty server factory used to build the {@link org.eclipse.jetty.server.Server Server}
+	 * from a <c>jetty.xml</c> file.
+	 *
+	 * @return The default factory.
+	 */
+	@Bean
+	@ConditionalOnMissingBean(JettyServerFactory.class)
+	public JettyServerFactory jettyServerFactory() {
+		return new BasicJettyServerFactory();
+	}
+
+	/**
+	 * Contributes an empty default {@link JettySettings} so {@link JettyServerComponent} always finds one in the
+	 * store.  Users who need programmatic overrides supply their own <c>@Bean JettySettings</c>.
+	 *
+	 * @return The default empty settings.
+	 */
+	@Bean
+	@ConditionalOnMissingBean(JettySettings.class)
+	public JettySettings jettySettings() {
+		return JettySettings.create().build();
+	}
+
+	/**
+	 * Contributes the {@link JettyServerComponent} bean under its own type for direct lookup by external callers.
+	 *
+	 * @return The component instance.
+	 */
+	@Bean
+	public JettyServerComponent jettyServerComponent() {
+		return component;
+	}
+
+	/**
+	 * Contributes the same {@link JettyServerComponent} instance under {@link MicroserviceListener} (named
+	 * <c>jettyServerListener</c>) so it participates in the {@link Microservice#start()} / {@link Microservice#stop()}
+	 * fan-out without clobbering any user-supplied unnamed {@code MicroserviceListener}.
+	 *
+	 * @return The component instance.
+	 */
+	@Bean(name = "jettyServerListener")
+	public MicroserviceListener jettyServerListener() {
+		return component;
+	}
+}
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
deleted file mode 100644
index b924f00754..0000000000
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroservice.java
+++ /dev/null
@@ -1,907 +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.microservice.jetty;
-
-import static org.apache.juneau.collections.JsonMap.*;
-import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
-import static org.apache.juneau.commons.utils.CollectionUtils.*;
-import static org.apache.juneau.commons.utils.IoUtils.*;
-import static org.apache.juneau.commons.utils.StringUtils.*;
-import static org.apache.juneau.commons.utils.ThrowableUtils.*;
-import static org.apache.juneau.commons.utils.Utils.*;
-
-import java.io.*;
-import java.net.*;
-import java.nio.file.*;
-import java.util.*;
-import java.util.concurrent.atomic.AtomicReference;
-import java.util.logging.*;
-
-import org.apache.juneau.commons.inject.*;
-import org.apache.juneau.commons.reflect.*;
-import org.apache.juneau.commons.runtime.*;
-import org.apache.juneau.commons.svl.*;
-import org.apache.juneau.config.*;
-import org.apache.juneau.config.store.*;
-import org.apache.juneau.cp.*;
-import org.apache.juneau.microservice.*;
-import org.apache.juneau.microservice.console.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.rest.annotation.*;
-import org.apache.juneau.rest.servlet.*;
-import org.eclipse.jetty.ee11.servlet.*;
-import org.eclipse.jetty.server.*;
-
-import jakarta.servlet.*;
-
-/**
- * Entry point for Juneau microservice that implements a REST interface using Jetty on a single port.
- *
- * <h5 class='topic'>Jetty Server Details</h5>
- *
- * The Jetty server is created by the {@link #createServer()} method and started with the {@link #startServer()} method.
- * These methods can be overridden to provided customized behavior.
- *
- * <h5 class='topic'>Defining REST Resources</h5>
- *
- * Top-level REST resources are defined in the <c>jetty.xml</c> file as normal servlets.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
- * </ul>
- */
-@SuppressWarnings({
-	"java:S115" // Constants use UPPER_snakeCase convention (e.g., KEY_SERVLET_CONTEXT_HANDLER)
-})
-public class JettyMicroservice extends Microservice {
-
-	/**
-	 * Builder class.
-	 */
-	public static class Builder extends Microservice.Builder {
-
-		String jettyXml;
-		int[] ports;
-		Boolean jettyXmlResolveVars;
-		Map<String,Servlet> servlets = map();
-		Map<String,Object> servletAttributes = map();
-		JettyMicroserviceListener listener2;
-		JettyServerFactory factory;
-
-		/**
-		 * Constructor.
-		 */
-		protected Builder() {}
-
-		/**
-		 * Copy constructor.
-		 *
-		 * @param copyFrom The builder to copy settings from.
-		 */
-		protected Builder(Builder copyFrom) {
-			super(copyFrom);
-			this.jettyXml = copyFrom.jettyXml;
-			this.ports = copyFrom.ports;
-			this.jettyXmlResolveVars = copyFrom.jettyXmlResolveVars;
-			this.servlets = copyOf(copyFrom.servlets);
-			this.servletAttributes = copyOf(copyFrom.servletAttributes);
-			this.listener2 = copyFrom.listener2;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder args(Args args) {
-			super.args(args);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder args(String...args) {
-			super.args(args);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public JettyMicroservice build() throws Exception {
-			return new JettyMicroservice(this);
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder config(Config config) {
-			super.config(config);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder configName(String configName) {
-			super.configName(configName);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder configStore(ConfigStore configStore) {
-			super.configStore(configStore);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder console(Scanner consoleReader, PrintWriter consoleWriter) {
-			super.console(consoleReader, consoleWriter);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder consoleCommands(ConsoleCommand...consoleCommands) {
-			super.consoleCommands(consoleCommands);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder consoleEnabled(boolean consoleEnabled) {
-			super.consoleEnabled(consoleEnabled);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder copy() {
-			return new Builder(this);
-		}
-
-		/**
-		 * Specifies the factory to use for creating the Jetty {@link Server} instance.
-		 *
-		 * <p>
-		 * If not specified, uses {@link BasicJettyServerFactory}.
-		 *
-		 * @param value The new value for this property.
-		 * @return This object.
-		 */
-		public Builder jettyServerFactory(JettyServerFactory value) {
-			factory = value;
-			return this;
-		}
-
-		/**
-		 * Specifies the contents or location of the <c>jetty.xml</c> file used by the Jetty server.
-		 *
-		 * <p>
-		 * If you do not specify this value, it is pulled from the following in the specified order:
-		 * <ul class='spaced-list'>
-		 *   <li>
-		 *         <c>Jetty/config</c> setting in the config file.
-		 *         <c>Jetty-Config</c> setting in the manifest file.
-		 * </ul>
-		 *
-		 * <p>
-		 * By default, we look for the <c>jetty.xml</c> file in the following locations:
-		 * <ul class='spaced-list'>
-		 *     <li><c>jetty.xml</c> in home directory.
-		 *     <li><c>files/jetty.xml</c> in home directory.
-		 *     <li><c>/jetty.xml</c> in classpath.
-		 *     <li><c>/files/jetty.xml</c> in classpath.
-		 * </ul>
-		 *
-		 * @param jettyXml
-		 *     The contents or location of the file.
-		 *     <br>Can be any of the following:
-		 *     <ul>
-		 *      <li>{@link String} - Relative path to file on file system or classpath.
-		 *      <li>{@link File} - File on file system.
-		 *      <li>{@link Path} - Path on file system.
-		 *      <li>{@link InputStream} - Raw contents as <c>UTF-8</c> encoded stream.
-		 *      <li>{@link Reader} - Raw contents.
-		 *     </ul>
-		 *
-		 * @param resolveVars
-		 *     If <jk>true</jk>, SVL variables in the file will automatically be resolved.
-		 * @return This object.
-		 * @throws IOException Thrown by underlying stream.
-		 */
-		public Builder jettyXml(Object jettyXml, boolean resolveVars) throws IOException {
-			if (jettyXml instanceof String jettyXml2)
-				this.jettyXml = read(resolveFile(jettyXml2));
-			else if (jettyXml instanceof File jettyXml2)
-				this.jettyXml = read(jettyXml2);
-			else if (jettyXml instanceof Path jettyXml2)
-				this.jettyXml = read(jettyXml2);
-			else if (jettyXml instanceof InputStream jettyXml2)
-				this.jettyXml = read(jettyXml2);
-			else if (jettyXml instanceof Reader jettyXml2)
-				this.jettyXml = read(jettyXml2);
-			else
-				throw rex("Invalid object type passed to jettyXml(Object): {0}", cn(jettyXml));
-			this.jettyXmlResolveVars = resolveVars;
-			return this;
-		}
-
-		/**
-		 * Registers an event listener for this microservice.
-		 *
-		 * @param listener An event listener for this microservice.
-		 * @return This object.
-		 */
-		public Builder listener(JettyMicroserviceListener listener) {
-			super.listener(listener);
-			this.listener2 = listener;
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder logger(Logger logger) {
-			super.logger(logger);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder manifest(Object manifest) throws IOException {
-			super.manifest(manifest);
-			return this;
-		}
-
-		/**
-		 * Specifies the ports to use for the web server.
-		 *
-		 * <p>
-		 * You can specify multiple ports.  The first available will be used.  <js>'0'</js> indicates to try a random port.
-		 * The resulting available port gets set as the system property <js>"availablePort"</js> which can be referenced in the
-		 * <c>jetty.xml</c> file as <js>"$S{availablePort}"</js> (assuming resolveVars is enabled).
-		 *
-		 * <p>
-		 * If you do not specify this value, it is pulled from the following in the specified order:
-		 * <ul class='spaced-list'>
-		 * 	<li>
-		 * 		<c>Jetty/port</c> setting in the config file.
-		 * 	<li>
-		 * 		<c>Jetty-Port</c> setting in the manifest file.
-		 * 	<li>
-		 * 		<c>8000</c>
-		 * </ul>
-		 *
-		 * Jetty/port", mf.getWithDefault("Jetty-Port", ints(8000)
-		 * @param ports The ports to use for the web server.
-		 * @return This object.
-		 */
-		public Builder ports(int...ports) {
-			this.ports = ports;
-			return this;
-		}
-
-		/**
-		 * Adds a servlet to the servlet container.
-		 *
-		 * <p>
-		 * This method can only be used with servlets with no-arg constructors.
-		 * <br>The path is pulled from the {@link Rest#path()} annotation.
-		 *
-		 * @param c The servlet to add to the servlet container.
-		 * @return This object.
-		 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
-		 */
-		public Builder servlet(Class<? extends RestServlet> c) throws ExecutableException {
-			RestServlet rs;
-			try {
-				rs = c.getDeclaredConstructor().newInstance();
-			} catch (Exception e) {
-				throw new ExecutableException(e);
-			}
-			return servlet(rs, '/' + rs.getPath());
-		}
-
-		/**
-		 * Adds a servlet to the servlet container.
-		 *
-		 * <p>
-		 * This method can only be used with servlets with no-arg constructors.
-		 *
-		 * @param c The servlet to add to the servlet container.
-		 * @param path The servlet path spec.
-		 * @return This object.
-		 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
-		 */
-		public Builder servlet(Class<? extends Servlet> c, String path) throws ExecutableException {
-			try {
-				return servlet(c.getDeclaredConstructor().newInstance(), path);
-			} catch (Exception e) {
-				throw new ExecutableException(e);
-			}
-		}
-
-		/**
-		 * Adds a servlet instance to the servlet container.
-		 *
-		 * @param servlet The servlet to add to the servlet container.
-		 * @param path The servlet path spec.
-		 * @return This object.
-		 */
-		public Builder servlet(Servlet servlet, String path) {
-			servlets.put(path, servlet);
-			return this;
-		}
-
-		/**
-		 * Adds a set of servlet attributes to the servlet container.
-		 *
-		 * @param values The map of attributes.
-		 * @return This object.
-		 */
-		public Builder servletAttribute(Map<String,Object> values) {
-			if (nn(values))
-				this.servletAttributes.putAll(values);
-			return this;
-		}
-
-		/**
-		 * Adds a servlet attribute to the servlet container.
-		 *
-		 * @param name The attribute name.
-		 * @param value The attribute value.
-		 * @return This object.
-		 */
-		public Builder servletAttribute(String name, Object value) {
-			this.servletAttributes.put(name, value);
-			return this;
-		}
-
-		/**
-		 * Adds a set of servlets to the servlet container.
-		 *
-		 * @param servlets
-		 * 	A map of servlets to add to the servlet container.
-		 * 	<br>Keys are path specs for the servlet.
-		 * @return This object.
-		 */
-		public Builder servlets(Map<String,Servlet> servlets) {
-			if (nn(servlets))
-				this.servlets.putAll(servlets);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public <T> Builder varBean(Class<T> c, T value) {
-			super.varBean(c, value);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		@SuppressWarnings({
-			"unchecked" // Type erasure requires unchecked cast for varargs
-		})
-		public Builder vars(Class<? extends Var>...vars) {
-			super.vars(vars);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder workingDir(File path) {
-			super.workingDir(path);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder configurations(Class<?>...configurations) {
-			super.configurations(configurations);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder configurations(List<Class<?>> configurations) {
-			super.configurations(configurations);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder beanStore(WritableBeanStore beanStore) {
-			super.beanStore(beanStore);
-			return this;
-		}
-
-		@Override /* Overridden from MicroserviceBuilder */
-		public Builder workingDir(String path) {
-			super.workingDir(path);
-			return this;
-		}
-	}
-
-	private static final String KEY_SERVLET_CONTEXT_HANDLER = "ServletContextHandler";
-	private static final Random RANDOM = new Random();
-
-	private static final AtomicReference<JettyMicroservice> instance = new AtomicReference<>();
-
-	/**
-	 * Creates a new microservice builder.
-	 *
-	 * @return A new microservice builder.
-	 */
-	public static Builder create() {
-		return new Builder();
-	}
-
-	/**
-	 * Returns the Microservice instance.
-	 * <p>
-	 * This method only works if there's only one Microservice instance in a JVM.
-	 * Otherwise, it's just overwritten by the last instantiated microservice.
-	 *
-	 * @return The Microservice instance, or <jk>null</jk> if there isn't one.
-	 */
-	public static JettyMicroservice getInstance() {
-		return instance.get();
-	}
-
-	/**
-	 * Entry-point method.
-	 *
-	 * @param args Command line arguments.
-	 * @throws Exception Error occurred.
-	 */
-	public static void main(String[] args) throws Exception {
-		// @formatter:off
-		JettyMicroservice
-			.create()
-			.args(args)
-			.build()
-			.start()
-			.startConsole()
-			.join();
-		// @formatter:on
-	}
-
-	private static int[] parseIntArray(String csv) {
-		if (csv == null || csv.isEmpty())
-			return new int[0];
-		var parts = csv.split(",");
-		var out = new int[parts.length];
-		for (var i = 0; i < parts.length; i++)
-			out[i] = Integer.parseInt(parts[i].trim());
-		return out;
-	}
-
-	private static int findOpenPort(int[] ports) {
-		for (var port : ports) {
-			// If port is 0, try a random port between ports[0] and 32767.
-			if (port == 0)
-				port = RANDOM.nextInt(32767 - ports[0] + 1) + ports[0];
-			try (var ss = new ServerSocket(port)) {
-				return port;
-			} catch (@SuppressWarnings("unused") IOException e) {
-				// Port is in use, try next port in array
-			}
-		}
-		return 0;
-	}
-
-	private static void setInstance(JettyMicroservice m) {
-		instance.set(m);
-	}
-
-	final Messages messages2 = Messages.of(JettyMicroservice.class);
-	private final Builder builder;
-	final JettyMicroserviceListener listener;
-
-	private final JettyServerFactory factory;
-
-	private final AtomicReference<Server> server = new AtomicReference<>();
-
-	/**
-	 * Constructor.
-	 *
-	 * @param builder The constructor arguments.
-	 * @throws IOException Problem occurred reading file.
-	 * @throws ParseException Malformed content found in config file.
-	 */
-	@SuppressWarnings({
-		"resource" // getBeanStore() is microservice-owned and closed in Microservice.stop(); fluent addBean() should not be auto-closed here.
-	})
-	protected JettyMicroservice(Builder builder) throws ParseException, IOException {
-		super(builder);
-		setInstance(this);
-		this.builder = builder.copy();
-		var store = getBeanStore();
-		// Listener: explicit builder > @Bean (Jetty-specific first, then core) > default.
-		this.listener = nn(builder.listener2)
-			? builder.listener2
-			: store.getBean(JettyMicroserviceListener.class).orElseGet(BasicJettyMicroserviceListener::new);
-		store.addBean(JettyMicroserviceListener.class, this.listener);
-		// Factory: explicit builder > @Bean > default.
-		this.factory = nn(builder.factory)
-			? builder.factory
-			: store.getBean(JettyServerFactory.class).orElseGet(BasicJettyServerFactory::new);
-		store.addBean(JettyServerFactory.class, this.factory);
-		// Self-register so @Bean factory methods can take JettyMicroservice as a constructor/method param.
-		store.addBean(JettyMicroservice.class, this);
-	}
-
-	/**
-	 * Adds an arbitrary servlet to this microservice.
-	 *
-	 * @param servlet The servlet instance.
-	 * @param pathSpec The context path of the servlet.
-	 * @return This object.
-	 * @throws RuntimeException if {@link #createServer()} has not previously been called.
-	 */
-	public JettyMicroservice addServlet(Servlet servlet, String pathSpec) {
-		var sh = new ServletHolder(servlet);
-		if (nn(pathSpec) && ! pathSpec.endsWith("/*"))
-			pathSpec = trimTrailingSlashes(pathSpec) + "/*";
-		getServletContextHandler().addServlet(sh, pathSpec);
-		return this;
-	}
-
-	/**
-	 * Adds a servlet attribute to the Jetty server.
-	 *
-	 * @param name The server attribute name.
-	 * @param value The context path of the servlet.
-	 * @return This object.
-	 * @throws RuntimeException if {@link #createServer()} has not previously been called.
-	 */
-	public JettyMicroservice addServletAttribute(String name, Object value) {
-		getServer().setAttribute(name, value);
-		return this;
-	}
-
-	/**
-	 * Method used to create (but not start) an instance of a Jetty server.
-	 *
-	 * <p>
-	 * Subclasses can override this method to customize the Jetty server before it is started.
-	 *
-	 * <p>
-	 * The default implementation is configured by the following values in the config file
-	 * if a jetty.xml is not specified via a <c>REST/jettyXml</c> setting:
-	 * <p class='bini'>
-	 * 	<cc>#================================================================================
-	 * 	# Jetty settings
-	 * 	#================================================================================</cc>
-	 * 	<cs>[Jetty]</cs>
-	 *
-	 * 	<cc># Path of the jetty.xml file used to configure the Jetty server.</cc>
-	 * 	<ck>config</ck> = jetty.xml
-	 *
-	 * 	<cc># Resolve Juneau variables in the jetty.xml file.</cc>
-	 * 	<ck>resolveVars</ck> = true
-	 *
-	 * 	<cc># Port to use for the jetty server.
-	 * 	# You can specify multiple ports.  The first available will be used.  '0' indicates to try a random port.
-	 * 	# The resulting available port gets set as the system property "availablePort" which can be referenced in the
-	 * 	# jetty.xml file as "$S{availablePort}" (assuming resolveVars is enabled).</cc>
-	 * 	<ck>port</ck> = 10000,0,0,0
-	 * </p>
-	 *
-	 * @return The newly-created server.
-	 * @throws ParseException Configuration file contains malformed input.
-	 * @throws IOException File could not be read.
-	 * @throws ExecutableException Exception occurred on invoked constructor/method/field.
-	 */
-	@SuppressWarnings({
-		"java:S3776", // Cognitive complexity acceptable for server creation logic
-		"resource" // getBeanStore() is owned by the microservice lifecycle; do not close in createServer().
-	})
-	public Server createServer() throws ParseException, IOException, ExecutableException {
-		listener.onCreateServer(this);
-
-		var cf = getConfig();
-		var mf = getManifest();
-		var vr = getVarResolver();
-		var store = getBeanStore();
-
-		var ports = firstNonNull(builder.ports, cf.get("Jetty/port").as(int[].class).orElseGet(() -> mf.get("Jetty-Port").map(JettyMicroservice::parseIntArray).orElseGet(() -> ints(8000))));
-		var availablePort = findOpenPort(ports);
-
-		if (env("availablePort").isEmpty())
-			System.setProperty("availablePort", String.valueOf(availablePort));
-
-		// Prefer a @Bean-supplied Server if one was contributed via @Configuration.
-		// Otherwise build from jetty.xml via the configured factory.
-		var injectedServer = store.getBean(Server.class).orElse(null);
-		if (nn(injectedServer)) {
-			server.set(injectedServer);
-		} else {
-			var jettyXml = builder.jettyXml;
-			var jettyConfig = cf.get("Jetty/config").orElseGet(() -> mf.get("Jetty-Config").orElse("jetty.xml"));
-			var resolveVars = firstNonNull(builder.jettyXmlResolveVars, cf.get("Jetty/resolveVars").asBoolean().orElse(false));
-			boolean resolveVars2 = isTrue(resolveVars);
-
-			if (jettyXml == null)
-				jettyXml = loadSystemResourceAsString("jetty.xml", ".", "files");
-			if (jettyXml == null)
-				throw rex("jetty.xml file ''{0}'' was not found on the file system or classpath.", jettyConfig);
-
-			if (resolveVars2)
-				jettyXml = vr.resolve(jettyXml);
-
-			getLogger().info(jettyXml);
-
-			try {
-				server.set(factory.create(jettyXml));
-			} catch (Exception e2) {
-				throw new ExecutableException(e2);
-			}
-		}
-		// Publish the Server back to the store so @Bean factory methods of other beans can depend on it.
-		store.addBean(Server.class, server.get());
-
-		// Track each servlet pathSpec with its declaring source so we can fail loudly on collisions.
-		var mountedPaths = new LinkedHashMap<String,String>();
-
-		for (var s : cf.get("Jetty/servlets").asStringArray().orElse(new String[0])) {
-			try {
-				var c = info(Class.forName(s));
-				if (c.isAssignableTo(RestServlet.class)) {
-					var rs = (RestServlet)c.newInstance();
-					mountWithCollisionCheck(rs, rs.getPath(), "Jetty/servlets[" + s + "]", mountedPaths);
-				} else {
-					throw rex("Invalid servlet specified in Jetty/servlets.  Must be a subclass of RestServlet: {0}", s);
-				}
-			} catch (ClassNotFoundException e1) {
-				throw new ExecutableException(e1);
-			}
-		}
-
-		cf.get("Jetty/servletMap").asMap().orElse(EMPTY_MAP).forEach((k, v) -> {
-			try {
-				var c = info(Class.forName(v.toString()));
-				if (c.isAssignableTo(Servlet.class)) {
-					var rs = (Servlet)c.newInstance();
-					mountWithCollisionCheck(rs, k, "Jetty/servletMap[" + k + "]", mountedPaths);
-				} else {
-					throw rex("Invalid servlet specified in Jetty/servletMap.  Must be a subclass of Servlet: {0}", cn(v));
-				}
-			} catch (ClassNotFoundException e1) {
-				throw new ExecutableException(e1);
-			}
-		});
-
-		cf.get("Jetty/servletAttributes").asMap().orElse(EMPTY_MAP).forEach(this::addServletAttribute);
-
-		builder.servlets.forEach((k, v) -> mountWithCollisionCheck(v, k, "Builder.servlet(" + cn(v) + ")", mountedPaths));
-
-		builder.servletAttributes.forEach(this::addServletAttribute);
-
-		// Auto-discover @Rest servlets contributed via @Configuration/@Bean methods.
-		// Path source precedence: @Rest(path=...) on the resource class, else "/".
-		// Builder-supplied servlets already won (registered above), so duplicate mount paths fail hard.
-		for (var e : store.getBeansOfType(Servlet.class).entrySet()) {
-			var servlet = e.getValue();
-			var cls = servlet.getClass();
-			if (cls.getAnnotation(Rest.class) == null)
-				continue;
-			var pathSpec = restPathFor(cls);
-			mountWithCollisionCheck(servlet, pathSpec, "@Bean " + cls.getName() + (ne(e.getKey()) ? "[" + e.getKey() + "]" : ""), mountedPaths);
-		}
-
-		if (env("juneau.serverPort").isEmpty())
-			System.setProperty("juneau.serverPort", String.valueOf(availablePort));
-
-		return server.get();
-	}
-
-	private void mountWithCollisionCheck(Servlet servlet, String rawPath, String source, Map<String,String> mountedPaths) {
-		var pathSpec = normalizePathSpec(rawPath);
-		var prior = mountedPaths.get(pathSpec);
-		if (nn(prior))
-			throw rex("Servlet mount path collision: ''{0}'' is already mounted by {1}; refused by {2}.", pathSpec, prior, source);
-		mountedPaths.put(pathSpec, source);
-		addServlet(servlet, pathSpec);
-	}
-
-	private static String normalizePathSpec(String rawPath) {
-		var p = rawPath == null ? "" : rawPath;
-		if (p.isEmpty() || "/".equals(p))
-			return "/*";
-		if (! p.startsWith("/"))
-			p = "/" + p;
-		if (! p.endsWith("/*"))
-			p = trimTrailingSlashes(p) + "/*";
-		return p;
-	}
-
-	private static String restPathFor(Class<?> cls) {
-		var r = cls.getAnnotation(Rest.class);
-		if (r == null || r.path().isEmpty())
-			return "/";
-		return r.path();
-	}
-
-	/**
-	 * Calls {@link Server#destroy()} on the underlying Jetty server if it exists.
-	 *
-	 * @throws Exception Error occurred.
-	 */
-	@SuppressWarnings({
-		"java:S112" // throws Exception intentional - callback/lifecycle method
-	})
-	public void destroyServer() throws Exception {
-		var s = server.getAndSet(null);
-		if (nn(s))
-			s.destroy();
-	}
-
-	/**
-	 * Returns the context path that this microservice is using.
-	 * <p>
-	 * The value is determined by looking at the <c>Server/Handlers[ServletContextHandler]/contextPath</c> value
-	 * in the Jetty configuration.
-	 *
-	 * @return The context path that this microservice is using.
-	 */
-	public String getContextPath() { return getServletContextHandler().getContextPath(); }
-
-	/**
-	 * Returns the hostname of this microservice.
-	 * <p>
-	 * Simply uses <c>InetAddress.getLocalHost().getHostName()</c>.
-	 *
-	 * @return The hostname of this microservice.
-	 */
-	public String getHostName() {
-		String hostname = "localhost";
-		try {
-			hostname = InetAddress.getLocalHost().getHostName();
-		} catch (@SuppressWarnings("unused") UnknownHostException e) {
-			// Cannot determine hostname, use default "localhost"
-		}
-		return hostname;
-	}
-
-	/**
-	 * Returns the port that this microservice started up on.
-	 * <p>
-	 * The value is determined by looking at the <c>Server/Connectors[ServerConnector]/port</c> value in the
-	 * Jetty configuration.
-	 *
-	 * @return The port that this microservice started up on.
-	 */
-	public int getPort() {
-		for (var c : getServer().getConnectors())
-			if (c instanceof ServerConnector c2)
-				return c2.getPort();
-		throw new IllegalStateException("Could not locate ServerConnector in Jetty server.");
-	}
-
-	/**
-	 * Returns whether this microservice is using <js>"http"</js> or <js>"https"</js>.
-	 * <p>
-	 * The value is determined by looking for the existence of an SSL Connection Factorie by looking for the
-	 * <c>Server/Connectors[ServerConnector]/ConnectionFactories[SslConnectionFactory]</c> value in the Jetty
-	 * configuration.
-	 *
-	 * @return Whether this microservice is using <js>"http"</js> or <js>"https"</js>.
-	 */
-	public String getProtocol() {
-		for (var c : getServer().getConnectors())
-			if (c instanceof ServerConnector c2)
-				for (var cf : c2.getConnectionFactories())
-					if (cf instanceof SslConnectionFactory)
-						return "https";
-		return "http";
-	}
-
-	/**
-	 * Returns the underlying Jetty server.
-	 *
-	 * @return The underlying Jetty server, or <jk>null</jk> if {@link #createServer()} has not yet been called.
-	 */
-	public Server getServer() { return Objects.requireNonNull(server.get(), "Server not found.  createServer() must be called first."); }
-
-	/**
-	 * Finds and returns the servlet context handler defined in the Jetty container.
-	 *
-	 * @return The servlet context handler.
-	 * @throws RuntimeException if context handler is not defined.
-	 */
-	public ServletContextHandler getServletContextHandler() {
-		var obj = getServer().getAttribute(KEY_SERVLET_CONTEXT_HANDLER);
-		if (obj instanceof ServletContextHandler obj2)
-			return obj2;
-		throw new IllegalStateException("Servlet context handler not found in jetty server or at attribute '" + KEY_SERVLET_CONTEXT_HANDLER + "'");
-	}
-
-	/**
-	 * Returns the URI where this microservice is listening on.
-	 *
-	 * @return The URI where this microservice is listening on.
-	 */
-	public URI getURI() {
-		var cp = getContextPath();
-		try {
-			return new URI(getProtocol(), null, getHostName(), getPort(), "/".equals(cp) ? null : cp, null, null);
-		} catch (URISyntaxException e) {
-			throw toRex(e);
-		}
-	}
-
-	@Override /* Overridden from Microservice */
-	public synchronized JettyMicroservice init() throws ParseException, IOException {
-		super.init();
-		return this;
-	}
-
-	@Override /* Overridden from Microservice */
-	public JettyMicroservice join() throws Exception {
-		server.get().join();
-		return this;
-	}
-
-	@Override /* Overridden from Microservice */
-	public synchronized JettyMicroservice start() throws Exception {
-		super.start();
-		createServer();
-		startServer();
-		return this;
-	}
-
-	@Override /* Overridden from Microservice */
-	public synchronized JettyMicroservice startConsole() throws Exception {
-		super.startConsole();
-		return this;
-	}
-
-	@Override /* Overridden from Microservice */
-	public synchronized JettyMicroservice stop() throws Exception {
-		final Logger logger = getLogger();
-		final Messages mb2 = messages2;
-		var t = new Thread("JettyMicroserviceStop") {
-			@Override /* Overridden from Thread */
-			public void run() {
-				try {
-					var s = server.get();
-					if (s == null || s.isStopping() || s.isStopped())
-						return;
-					listener.onStopServer(JettyMicroservice.this);
-					out(mb2, "StoppingServer");
-					s.stop();
-					out(mb2, "ServerStopped");
-					listener.onPostStopServer(JettyMicroservice.this);
-				} catch (Exception e) {
-					logger.log(Level.WARNING, lm(e), e);
-				}
-			}
-		};
-		t.start();
-		try {
-			t.join();
-		} catch (@SuppressWarnings("unused") InterruptedException e) {
-			Thread.currentThread().interrupt();
-		}
-		super.stop();
-
-		return this;
-	}
-
-	@Override /* Overridden from Microservice */
-	public synchronized JettyMicroservice stopConsole() throws Exception {
-		super.stopConsole();
-		return this;
-	}
-
-	/**
-	 * Method used to start the Jetty server created by {@link #createServer()}.
-	 *
-	 * <p>
-	 * Subclasses can override this method to customize server startup.
-	 *
-	 * @return The port that this server started on.
-	 * @throws Exception Error occurred.
-	 */
-	protected int startServer() throws Exception {
-		listener.onStartServer(this);
-		server.get().start();
-		out(messages2, "ServerStarted", getPort());
-		listener.onPostStartServer(this);
-		return getPort();
-	}
-}
\ No newline at end of file
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroserviceListener.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroserviceListener.java
deleted file mode 100644
index 4649e087c0..0000000000
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyMicroserviceListener.java
+++ /dev/null
@@ -1,64 +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.microservice.jetty;
-
-import org.apache.juneau.microservice.*;
-
-/**
- * Listener class for Jetty microservice lifecycle events.
- *
- * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
- * </ul>
- */
-public interface JettyMicroserviceListener extends MicroserviceListener {
-
-	/**
-	 * Called before the Jetty server is created.
-	 *
-	 * @param microservice Reference to microservice.
-	 */
-	void onCreateServer(JettyMicroservice microservice);
-
-	/**
-	 * Called after the Jetty server is started.
-	 *
-	 * @param microservice Reference to microservice.
-	 */
-	void onPostStartServer(JettyMicroservice microservice);
-
-	/**
-	 * Called after the Jetty server is stopped.
-	 *
-	 * @param microservice Reference to microservice.
-	 */
-	void onPostStopServer(JettyMicroservice microservice);
-
-	/**
-	 * Called before the Jetty server is started.
-	 *
-	 * @param microservice Reference to microservice.
-	 */
-	void onStartServer(JettyMicroservice microservice);
-
-	/**
-	 * Called before the Jetty server is stopped.
-	 *
-	 * @param microservice Reference to microservice.
-	 */
-	void onStopServer(JettyMicroservice microservice);
-}
\ No newline at end of file
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
new file mode 100644
index 0000000000..77458fd459
--- /dev/null
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettyServerComponent.java
@@ -0,0 +1,398 @@
+/*
+ * 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.microservice.jetty;
+
+import static org.apache.juneau.collections.JsonMap.*;
+import static org.apache.juneau.commons.reflect.ReflectionUtils.*;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.IoUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.net.*;
+import java.util.*;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.logging.*;
+
+import org.apache.juneau.config.event.*;
+import org.apache.juneau.cp.*;
+import org.apache.juneau.microservice.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.*;
+import org.eclipse.jetty.ee11.servlet.*;
+import org.eclipse.jetty.server.*;
+
+import jakarta.servlet.*;
+
+/**
+ * Drives the Jetty server lifecycle on behalf of the microservice via the {@link MicroserviceListener} fan-out.
+ *
+ * <p>
+ * Contributed by {@link JettyConfiguration} as a <c>@Bean</c>.  The microservice's {@link Microservice#start()}
+ * call invokes {@link #onStart(Microservice)}, which:
+ * <ol>
+ * 	<li>Picks the first available port from the resolved {@link JettySettings#getPorts() ports} (falling back to
+ * 		<c>Jetty/port</c> config, then <c>Jetty-Port</c> manifest, then <c>8000</c>).
+ * 	<li>Resolves the Jetty {@link Server} — preferring an externally-supplied <c>@Bean Server</c>, otherwise
+ * 		building one from <c>jetty.xml</c> via the {@link JettyServerFactory}.
+ * 	<li>Mounts servlets from {@code Jetty/servlets}, {@code Jetty/servletMap}, {@code Jetty/servletAttributes}
+ * 		config sections, plus auto-mounts every {@code @Bean Servlet} whose runtime class is annotated with
+ * 		{@link Rest @Rest} (at {@link Rest#path()}, or <c>"/"</c> when no path is set).
+ * 	<li>Starts the server and publishes the bound port via the {@code availablePort} / {@code juneau.serverPort}
+ * 		system properties.
+ * </ol>
+ *
+ * <p>
+ * {@link #onStop(Microservice)} performs the inverse: stops the server gracefully in a worker thread.
+ *
+ * <p>
+ * External callers reach this component via
+ * {@code Microservice.getInstance().getBeanStore().getBean(JettyServerComponent.class).orElseThrow()}.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+@SuppressWarnings({
+	"java:S115" // Constants use UPPER_snakeCase convention (e.g., KEY_SERVLET_CONTEXT_HANDLER)
+})
+public class JettyServerComponent implements MicroserviceListener {
+
+	private static final String KEY_SERVLET_CONTEXT_HANDLER = "ServletContextHandler";
+	private static final Random RANDOM = new Random();
+
+	private final Messages messages = Messages.of(JettyServerComponent.class);
+	private final AtomicReference<Server> server = new AtomicReference<>();
+	private final AtomicReference<Microservice> microservice = new AtomicReference<>();
+
+	private static int[] parseIntArray(String csv) {
+		if (csv == null || csv.isEmpty())
+			return new int[0];
+		var parts = csv.split(",");
+		var out = new int[parts.length];
+		for (var i = 0; i < parts.length; i++)
+			out[i] = Integer.parseInt(parts[i].trim());
+		return out;
+	}
+
+	private static int findOpenPort(int[] ports) {
+		for (var port : ports) {
+			if (port == 0)
+				port = RANDOM.nextInt(32767 - ports[0] + 1) + ports[0];
+			try (var ss = new ServerSocket(port)) {
+				return port;
+			} catch (@SuppressWarnings("unused") IOException e) {
+				// Port is in use, try next port in array
+			}
+		}
+		return 0;
+	}
+
+	private static String normalizePathSpec(String rawPath) {
+		var p = rawPath == null ? "" : rawPath;
+		if (p.isEmpty() || "/".equals(p))
+			return "/*";
+		if (! p.startsWith("/"))
+			p = "/" + p;
+		if (! p.endsWith("/*"))
+			p = trimTrailingSlashes(p) + "/*";
+		return p;
+	}
+
+	private static String restPathFor(Class<?> cls) {
+		var r = cls.getAnnotation(Rest.class);
+		if (r == null || r.path().isEmpty())
+			return "/";
+		return r.path();
+	}
+
+	/**
+	 * Constructor.
+	 */
+	@SuppressWarnings({
+		"java:S1186" // Empty constructor; all state is initialized via onStart(Microservice) when wired through the bean store.
+	})
+	public JettyServerComponent() {}
+
+	@Override /* Overridden from MicroserviceListener */
+	@SuppressWarnings({
+		"java:S3776", // Cognitive complexity acceptable for server creation logic
+		"java:S1141", // Nested try blocks scope checked-exception translation tightly to single call sites
+		"resource"    // ms.getBeanStore() is owned by the microservice lifecycle; do not close here.
+	})
+	public void onStart(Microservice ms) {
+		try {
+			microservice.set(ms);
+			var store = ms.getBeanStore();
+			var cf = ms.getConfig();
+			var mf = ms.getManifest();
+			var vr = ms.getVarResolver();
+			var settings = store.getBean(JettySettings.class).orElseGet(() -> JettySettings.create().build());
+
+			// Port resolution: settings > Jetty/port config > Jetty-Port manifest > 8000
+			var ports = settings.getPorts();
+			if (ports == null)
+				ports = cf.get("Jetty/port").as(int[].class)
+					.orElseGet(() -> mf.get("Jetty-Port").map(JettyServerComponent::parseIntArray).orElseGet(() -> ints(8000)));
+			var availablePort = findOpenPort(ports);
+
+			if (env("availablePort").isEmpty())
+				System.setProperty("availablePort", String.valueOf(availablePort));
+
+			// Prefer a @Bean-supplied Server, else build one from jetty.xml.
+			var injectedServer = store.getBean(Server.class).orElse(null);
+			if (nn(injectedServer)) {
+				server.set(injectedServer);
+			} else {
+				var factory = store.getBean(JettyServerFactory.class).orElseGet(BasicJettyServerFactory::new);
+				var jettyXml = settings.getJettyXml();
+				var jettyConfig = cf.get("Jetty/config").orElseGet(() -> mf.get("Jetty-Config").orElse("jetty.xml"));
+				var resolveVars = firstNonNull(settings.getJettyXmlResolveVars(), cf.get("Jetty/resolveVars").asBoolean().orElse(false));
+				boolean resolveVars2 = isTrue(resolveVars);
+
+				if (jettyXml == null)
+					jettyXml = loadSystemResourceAsString("jetty.xml", ".", "files");
+				if (jettyXml == null)
+					throw rex("jetty.xml file ''{0}'' was not found on the file system or classpath.", jettyConfig);
+
+				if (resolveVars2)
+					jettyXml = vr.resolve(jettyXml);
+
+				ms.getLogger().info(jettyXml);
+
+				try {
+					server.set(factory.create(jettyXml));
+				} catch (Exception e) {
+					throw toRex(e);
+				}
+			}
+
+			// Publish back to the store for downstream beans / lookups.
+			store.addBean(Server.class, server.get());
+
+			// Track each servlet pathSpec with its declaring source so we can fail loudly on collisions.
+			var mountedPaths = new LinkedHashMap<String,String>();
+
+			for (var s : cf.get("Jetty/servlets").asStringArray().orElse(new String[0])) {
+				try {
+					var c = info(Class.forName(s));
+					if (c.isAssignableTo(RestServlet.class)) {
+						var rs = (RestServlet)c.newInstance();
+						mountWithCollisionCheck(rs, rs.getPath(), "Jetty/servlets[" + s + "]", mountedPaths);
+					} else {
+						throw rex("Invalid servlet specified in Jetty/servlets.  Must be a subclass of RestServlet: {0}", s);
+					}
+				} catch (ClassNotFoundException e) {
+					throw toRex(e);
+				}
+			}
+
+			cf.get("Jetty/servletMap").asMap().orElse(EMPTY_MAP).forEach((k, v) -> {
+				try {
+					var c = info(Class.forName(v.toString()));
+					if (c.isAssignableTo(Servlet.class)) {
+						var rs = (Servlet)c.newInstance();
+						mountWithCollisionCheck(rs, k, "Jetty/servletMap[" + k + "]", mountedPaths);
+					} else {
+						throw rex("Invalid servlet specified in Jetty/servletMap.  Must be a subclass of Servlet: {0}", cn(v));
+					}
+				} catch (ClassNotFoundException e) {
+					throw toRex(e);
+				}
+			});
+
+			cf.get("Jetty/servletAttributes").asMap().orElse(EMPTY_MAP).forEach(this::addServletAttribute);
+
+			// Auto-discover @Rest servlets contributed via @Configuration/@Bean methods.
+			// Path source precedence: @Rest(path=...) on the resource class, else "/".
+			for (var e : store.getBeansOfType(Servlet.class).entrySet()) {
+				var servlet = e.getValue();
+				var cls = servlet.getClass();
+				if (cls.getAnnotation(Rest.class) == null)
+					continue;
+				var pathSpec = restPathFor(cls);
+				mountWithCollisionCheck(servlet, pathSpec, "@Bean " + cls.getName() + (ne(e.getKey()) ? "[" + e.getKey() + "]" : ""), mountedPaths);
+			}
+
+			if (env("juneau.serverPort").isEmpty())
+				System.setProperty("juneau.serverPort", String.valueOf(availablePort));
+
+			server.get().start();
+			ms.out(messages, "ServerStarted", getPort());
+		} catch (Exception e) {
+			throw toRex(e);
+		}
+	}
+
+	@Override /* Overridden from MicroserviceListener */
+	public void onStop(Microservice ms) {
+		final Logger logger = ms.getLogger();
+		var t = new Thread("JettyServerComponentStop") {
+			@Override /* Overridden from Thread */
+			public void run() {
+				try {
+					var s = server.get();
+					if (s == null || s.isStopping() || s.isStopped())
+						return;
+					ms.out(messages, "StoppingServer");
+					s.stop();
+					ms.out(messages, "ServerStopped");
+				} catch (Exception e) {
+					logger.log(Level.WARNING, lm(e), e);
+				}
+			}
+		};
+		t.start();
+		try {
+			t.join();
+		} catch (@SuppressWarnings("unused") InterruptedException e) {
+			Thread.currentThread().interrupt();
+		}
+	}
+
+	@Override /* Overridden from MicroserviceListener */
+	public void onConfigChange(Microservice ms, ConfigEvents events) {
+		// No-op: Intentional empty implementation - this component is purely lifecycle-driven.
+	}
+
+	/**
+	 * Adds an arbitrary servlet to this Jetty server.
+	 *
+	 * @param servlet The servlet instance.
+	 * @param pathSpec The context path of the servlet.
+	 * @return This object.
+	 */
+	public JettyServerComponent addServlet(Servlet servlet, String pathSpec) {
+		var sh = new ServletHolder(servlet);
+		if (nn(pathSpec) && ! pathSpec.endsWith("/*"))
+			pathSpec = trimTrailingSlashes(pathSpec) + "/*";
+		getServletContextHandler().addServlet(sh, pathSpec);
+		return this;
+	}
+
+	/**
+	 * Adds an attribute to the Jetty server.
+	 *
+	 * @param name The server attribute name.
+	 * @param value The attribute value.
+	 * @return This object.
+	 */
+	public JettyServerComponent addServletAttribute(String name, Object value) {
+		getServer().setAttribute(name, value);
+		return this;
+	}
+
+	/**
+	 * Returns the context path that this server is using.
+	 *
+	 * @return The context path.
+	 */
+	public String getContextPath() {
+		return getServletContextHandler().getContextPath();
+	}
+
+	/**
+	 * Returns the hostname of this microservice.
+	 *
+	 * @return The hostname.
+	 */
+	public String getHostName() {
+		String hostname = "localhost";
+		try {
+			hostname = InetAddress.getLocalHost().getHostName();
+		} catch (@SuppressWarnings("unused") UnknownHostException e) {
+			// Cannot determine hostname, use default "localhost"
+		}
+		return hostname;
+	}
+
+	/**
+	 * Returns the port that this server is listening on.
+	 *
+	 * @return The port.
+	 */
+	public int getPort() {
+		for (var c : getServer().getConnectors())
+			if (c instanceof ServerConnector c2)
+				return c2.getPort();
+		throw new IllegalStateException("Could not locate ServerConnector in Jetty server.");
+	}
+
+	/**
+	 * Returns whether this server is using <js>"http"</js> or <js>"https"</js>.
+	 *
+	 * @return The protocol.
+	 */
+	public String getProtocol() {
+		for (var c : getServer().getConnectors())
+			if (c instanceof ServerConnector c2)
+				for (var cf : c2.getConnectionFactories())
+					if (cf instanceof SslConnectionFactory)
+						return "https";
+		return "http";
+	}
+
+	/**
+	 * Returns the underlying Jetty server.
+	 *
+	 * @return The underlying Jetty server.
+	 * @throws IllegalStateException If the server has not been created yet.
+	 */
+	public Server getServer() {
+		return Objects.requireNonNull(server.get(), "Server not found.  Microservice has not been started yet.");
+	}
+
+	/**
+	 * Finds and returns the servlet context handler defined in the Jetty container.
+	 *
+	 * @return The servlet context handler.
+	 * @throws IllegalStateException If the context handler is not defined.
+	 */
+	public ServletContextHandler getServletContextHandler() {
+		var obj = getServer().getAttribute(KEY_SERVLET_CONTEXT_HANDLER);
+		if (obj instanceof ServletContextHandler obj2)
+			return obj2;
+		throw new IllegalStateException("Servlet context handler not found in jetty server or at attribute '" + KEY_SERVLET_CONTEXT_HANDLER + "'");
+	}
+
+	/**
+	 * Returns the URI where this server is listening.
+	 *
+	 * @return The URI.
+	 */
+	public URI getURI() {
+		var cp = getContextPath();
+		try {
+			return new URI(getProtocol(), null, getHostName(), getPort(), "/".equals(cp) ? null : cp, null, null);
+		} catch (URISyntaxException e) {
+			throw toRex(e);
+		}
+	}
+
+	private void mountWithCollisionCheck(Servlet servlet, String rawPath, String source, Map<String,String> mountedPaths) {
+		var pathSpec = normalizePathSpec(rawPath);
+		var prior = mountedPaths.get(pathSpec);
+		if (nn(prior))
+			throw rex("Servlet mount path collision: ''{0}'' is already mounted by {1}; refused by {2}.", pathSpec, prior, source);
+		mountedPaths.put(pathSpec, source);
+		addServlet(servlet, pathSpec);
+	}
+}
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettySettings.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettySettings.java
new file mode 100644
index 0000000000..639641934d
--- /dev/null
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/JettySettings.java
@@ -0,0 +1,177 @@
+/*
+ * 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.microservice.jetty;
+
+import static org.apache.juneau.commons.utils.IoUtils.*;
+import static org.apache.juneau.commons.utils.ThrowableUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.io.*;
+import java.nio.file.*;
+
+/**
+ * Programmatic settings for the Jetty server contributed by {@link JettyConfiguration}.
+ *
+ * <p>
+ * Users who need to override the defaults pulled from <c>Config</c> / <c>ManifestFile</c> should
+ * contribute a <c>@Bean JettySettings</c> from their own <c>@Configuration</c> class.  Any value
+ * left {@code null} on this settings bean falls back to the corresponding <c>Config</c> / <c>ManifestFile</c>
+ * entry, then to a hard-coded default.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bjava'>
+ * 	<ja>@Configuration</ja>
+ * 	<jk>public class</jk> MyAppConfig {
+ * 		<ja>@Bean</ja>
+ * 		<jk>public</jk> JettySettings jettySettings() {
+ * 			<jk>return</jk> JettySettings.<jsm>create</jsm>()
+ * 				.ports(8080, 0)
+ * 				.build();
+ * 		}
+ * 	}
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceJettyBasics">juneau-microservice-jetty Basics</a>
+ * </ul>
+ *
+ * @since 9.5.0
+ */
+public class JettySettings {
+
+	/**
+	 * Builder for {@link JettySettings}.
+	 */
+	public static class Builder {
+
+		int[] ports;
+		String jettyXml;
+		Boolean jettyXmlResolveVars;
+
+		Builder() {}
+
+		/**
+		 * Builds a new {@link JettySettings} from this builder.
+		 *
+		 * @return A new settings instance.
+		 */
+		public JettySettings build() {
+			return new JettySettings(this);
+		}
+
+		/**
+		 * Specifies the ports to try when binding the Jetty server.
+		 *
+		 * <p>
+		 * The first available port is used.  A value of <c>0</c> indicates "try a random port".  When set, this
+		 * value takes precedence over the <c>Jetty/port</c> config-file entry and the <c>Jetty-Port</c>
+		 * manifest entry.
+		 *
+		 * @param value The candidate ports, in priority order.  Can be <jk>null</jk> to defer to config / manifest.
+		 * @return This object.
+		 */
+		public Builder ports(int...value) {
+			ports = value;
+			return this;
+		}
+
+		/**
+		 * Specifies the contents or location of the <c>jetty.xml</c> file used to build the Jetty server.
+		 *
+		 * <p>
+		 * When set, this value takes precedence over the <c>Jetty/config</c> config-file entry and the
+		 * <c>Jetty-Config</c> manifest entry.  When not set, the component falls back to those values and
+		 * finally to a <c>jetty.xml</c> file located on the file system or classpath.
+		 *
+		 * @param value The contents or location of the file.  Can be any of:
+		 * 	<ul>
+		 * 		<li>{@link String} - Relative path to a file on the file system or classpath, or the raw XML content if it begins with <c>&lt;</c>.
+		 * 		<li>{@link File} - File on the file system.
+		 * 		<li>{@link Path} - Path on the file system.
+		 * 		<li>{@link InputStream} - Raw contents as a <c>UTF-8</c> encoded stream.
+		 * 		<li>{@link Reader} - Raw contents.
+		 * 	</ul>
+		 * @param resolveVars If <jk>true</jk>, SVL variables in the file will automatically be resolved.
+		 * @return This object.
+		 * @throws IOException Thrown by underlying stream.
+		 */
+		public Builder jettyXml(Object value, boolean resolveVars) throws IOException {
+			if (value == null)
+				jettyXml = null;
+			else if (value instanceof String value2)
+				jettyXml = value2.startsWith("<") ? value2 : read(new File(value2));
+			else if (value instanceof File value2)
+				jettyXml = read(value2);
+			else if (value instanceof Path value2)
+				jettyXml = read(value2);
+			else if (value instanceof InputStream value2)
+				jettyXml = read(value2);
+			else if (value instanceof Reader value2)
+				jettyXml = read(value2);
+			else
+				throw rex("Invalid object type passed to jettyXml(Object): {0}", cn(value));
+			jettyXmlResolveVars = resolveVars;
+			return this;
+		}
+	}
+
+	/**
+	 * Creates a new builder for this object.
+	 *
+	 * @return A new builder.
+	 */
+	public static Builder create() {
+		return new Builder();
+	}
+
+	private final int[] ports;
+	private final String jettyXml;
+	private final Boolean jettyXmlResolveVars;
+
+	JettySettings(Builder b) {
+		ports = b.ports;
+		jettyXml = b.jettyXml;
+		jettyXmlResolveVars = b.jettyXmlResolveVars;
+	}
+
+	/**
+	 * Returns the candidate ports to try when binding the Jetty server, in priority order.
+	 *
+	 * @return The ports, or <jk>null</jk> if not set.
+	 */
+	public int[] getPorts() {
+		return ports == null ? null : ports.clone();
+	}
+
+	/**
+	 * Returns the raw <c>jetty.xml</c> contents to use when building the server.
+	 *
+	 * @return The XML contents, or <jk>null</jk> if not set.
+	 */
+	public String getJettyXml() {
+		return jettyXml;
+	}
+
+	/**
+	 * Returns whether SVL variables in the <c>jetty.xml</c> file should be resolved.
+	 *
+	 * @return The resolve-vars flag, or <jk>null</jk> if not set.
+	 */
+	public Boolean getJettyXmlResolveVars() {
+		return jettyXmlResolveVars;
+	}
+}
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
index 0cc6e5a1d1..032ce79bed 100644
--- a/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
+++ b/juneau-microservice/juneau-microservice-jetty/src/main/java/org/apache/juneau/microservice/jetty/resources/DebugResource.java
@@ -20,6 +20,7 @@ import java.io.*;
 
 import org.apache.juneau.html.annotation.*;
 import org.apache.juneau.http.response.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.microservice.jetty.*;
 import org.apache.juneau.rest.*;
 import org.apache.juneau.rest.annotation.*;
@@ -68,7 +69,7 @@ public class DebugResource extends BasicRestServlet {
 		"java:S112" // throws Exception intentional - callback/lifecycle method
 	})
 	public Ok createJettyDump(RestRequest req, RestResponse res) throws Exception {
-		var dump = JettyMicroservice.getInstance().getServer().dump();
+		var dump = jettyServerComponent().getServer().dump();
 		try (var fw = new FileWriter(req.getConfig().get("Logging/logDir").orElse("") + "/jetty-thread-dump.log")) {
 			fw.write(dump);
 		}
@@ -99,6 +100,14 @@ public class DebugResource extends BasicRestServlet {
 	@RestGet(path = "/jetty/dump", description = "Generates and retrieves the jetty thread dump.")
 	public Reader getJettyDump(RestRequest req, RestResponse res) {
 		res.setContentType("text/plain");
-		return new StringReader(JettyMicroservice.getInstance().getServer().dump());
+		return new StringReader(jettyServerComponent().getServer().dump());
+	}
+
+	@SuppressWarnings({
+		"resource" // BeanStore returned by Microservice.getBeanStore() is owned by the microservice lifecycle; do not close here.
+	})
+	private static JettyServerComponent jettyServerComponent() {
+		return Microservice.getInstance().getBeanStore().getBean(JettyServerComponent.class)
+			.orElseThrow(() -> new IllegalStateException("JettyServerComponent bean not found.  Did you forget to add JettyConfiguration.class to Microservice.Builder.configurations(...)?"));
 	}
 }
\ No newline at end of file
diff --git a/juneau-microservice/juneau-microservice-jetty/src/main/resources/org/apache/juneau/microservice/jetty/JettyMicroservice.properties b/juneau-microservice/juneau-microservice-jetty/src/main/resources/org/apache/juneau/microservice/jetty/JettyServerComponent.properties
similarity index 100%
rename from juneau-microservice/juneau-microservice-jetty/src/main/resources/org/apache/juneau/microservice/jetty/JettyMicroservice.properties
rename to juneau-microservice/juneau-microservice-jetty/src/main/resources/org/apache/juneau/microservice/jetty/JettyServerComponent.properties
diff --git a/juneau-microservice/juneau-microservice-core/.gitignore b/juneau-microservice/juneau-microservice/.gitignore
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/.gitignore
rename to juneau-microservice/juneau-microservice/.gitignore
diff --git a/juneau-microservice/juneau-microservice-core/Dockerfile b/juneau-microservice/juneau-microservice/Dockerfile
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/Dockerfile
rename to juneau-microservice/juneau-microservice/Dockerfile
diff --git a/juneau-microservice/juneau-microservice-core/pom.xml b/juneau-microservice/juneau-microservice/pom.xml
similarity index 92%
rename from juneau-microservice/juneau-microservice-core/pom.xml
rename to juneau-microservice/juneau-microservice/pom.xml
index 3f867c5c31..d79c98b73a 100644
--- a/juneau-microservice/juneau-microservice-core/pom.xml
+++ b/juneau-microservice/juneau-microservice/pom.xml
@@ -21,15 +21,15 @@
 
 	<parent>
 		<groupId>org.apache.juneau</groupId>
-		<artifactId>juneau-microservice</artifactId>
+		<artifactId>juneau-microservice-parent</artifactId>
 		<version>9.5.0-SNAPSHOT</version>
 	</parent>
 
-	<artifactId>juneau-microservice-core</artifactId>
+	<artifactId>juneau-microservice</artifactId>
 	<packaging>bundle</packaging>
 
-	<name>Apache Juneau Microservice Core</name>
-	<description>Apache Juneau Microservice Core API</description>
+	<name>Apache Juneau Microservice</name>
+	<description>Apache Juneau Microservice API</description>
 
 	<properties>
 		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java
index 82ddcf7898..ec561e22cd 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/BasicMicroserviceListener.java
@@ -22,7 +22,7 @@ import org.apache.juneau.config.event.*;
  * Basic extensible microservice listener with default no-op method implementations.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public class BasicMicroserviceListener implements MicroserviceListener {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/LogConfig.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/LogConfig.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/LogConfig.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/LogConfig.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
index 150aa67fc9..573c576148 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/Microservice.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/Microservice.java
@@ -92,8 +92,7 @@ import org.apache.juneau.parser.ParseException;
  * </ul>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
-
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 @SuppressWarnings({
@@ -620,7 +619,6 @@ public class Microservice implements ConfigEventListener {
 	private final Config config;
 	private final ManifestFile manifest;
 	private final VarResolver varResolver;
-	private final MicroserviceListener listener;
 	private final Map<String,ConsoleCommand> consoleCommandMap = new ConcurrentHashMap<>();
 	private final boolean consoleEnabled;
 	private final Scanner consoleReader;
@@ -629,6 +627,7 @@ public class Microservice implements ConfigEventListener {
 	final File workingDir;
 	private final String configName;
 	private final WritableBeanStore beanStore;
+	private volatile boolean stopped;
 
 	private final AtomicReference<Logger> logger = new AtomicReference<>();
 
@@ -785,11 +784,15 @@ public class Microservice implements ConfigEventListener {
 			this.consoleWriter = null;
 			this.consoleThread = null;
 		}
-		// Resolve listener: builder > @Bean > default.
-		this.listener = nn(builder.listener)
-			? builder.listener
-			: beanStore.getBean(MicroserviceListener.class).orElseGet(BasicMicroserviceListener::new);
-		beanStore.addBean(MicroserviceListener.class, this.listener);
+		// Resolve listeners.
+		// If the builder supplies an explicit listener, register it under the unnamed key (overwriting any
+		// same-key @Bean MicroserviceListener); it joins the fan-out alongside any other @Bean-supplied listeners.
+		// If, after that, no MicroserviceListener exists in the store at all, register the default
+		// BasicMicroserviceListener so the fan-out has at least one no-op participant.
+		if (nn(builder.listener))
+			beanStore.addBean(MicroserviceListener.class, builder.listener);
+		if (beanStore.getBeansOfType(MicroserviceListener.class).isEmpty())
+			beanStore.addBean(MicroserviceListener.class, new BasicMicroserviceListener());
 
 		// Self-register so @Bean factory methods can take Microservice as a constructor/method param.
 		beanStore.addBean(Microservice.class, this);
@@ -1012,9 +1015,10 @@ public class Microservice implements ConfigEventListener {
 	 * {@link Builder#configurations(Class...)}.
 	 *
 	 * <p>
-	 * Subclasses (e.g. <c>JettyMicroservice</c>) consult the store at start-up to discover additional
-	 * beans (servlets, listeners, factories) to register.  External callers can query the store directly
-	 * to look up beans contributed by user-supplied <c>@Configuration</c> classes.
+	 * Lifecycle integration beans (e.g. <c>JettyServerComponent</c> contributed by <c>JettyConfiguration</c>)
+	 * implement {@link MicroserviceListener} and participate in the {@link #start()} / {@link #stop()}
+	 * fan-out.  External callers can query the store directly to look up beans contributed by user-supplied
+	 * <c>@Configuration</c> classes.
 	 *
 	 * <p>
 	 * The store is closed during {@link #stop()}, which triggers <c>@PreDestroy</c> hooks on resolved
@@ -1160,7 +1164,8 @@ public class Microservice implements ConfigEventListener {
 
 	@Override /* Overridden from ConfigChangeListener */
 	public void onConfigChange(ConfigEvents events) {
-		listener.onConfigChange(this, events);
+		for (var l : beanStore.getBeansOfType(MicroserviceListener.class).values())
+			l.onConfigChange(this, events);
 	}
 
 	/**
@@ -1187,7 +1192,8 @@ public class Microservice implements ConfigEventListener {
 	 * Start this application.
 	 *
 	 * <p>
-	 * Overridden methods MUST call this method FIRST so that the {@link MicroserviceListener#onStart(Microservice)} method is called.
+	 * Iterates over every {@link MicroserviceListener} bean in the {@linkplain #getBeanStore() bean store} (in
+	 * registration order) and calls {@link MicroserviceListener#onStart(Microservice)} on each.
 	 *
 	 * @return This object.
 	 * @throws Exception Error occurred.
@@ -1214,7 +1220,8 @@ public class Microservice implements ConfigEventListener {
 			}
 		});
 
-		listener.onStart(this);
+		for (var l : beanStore.getBeansOfType(MicroserviceListener.class).values())
+			l.onStart(this);
 
 		return this;
 	}
@@ -1238,7 +1245,9 @@ public class Microservice implements ConfigEventListener {
 	 * Stop this application.
 	 *
 	 * <p>
-	 * Overridden methods MUST call this method LAST so that the {@link MicroserviceListener#onStop(Microservice)} method is called.
+	 * Iterates over every {@link MicroserviceListener} bean in the {@linkplain #getBeanStore() bean store} in
+	 * <i>reverse</i> registration order and calls {@link MicroserviceListener#onStop(Microservice)} on each, then
+	 * closes the bean store (firing any {@code @PreDestroy} hooks).
 	 *
 	 * @return This object.
 	 * @throws Exception Error occurred
@@ -1246,8 +1255,14 @@ public class Microservice implements ConfigEventListener {
 	@SuppressWarnings({
 		"java:S112" // throws Exception intentional - callback/lifecycle method
 	})
-	public Microservice stop() throws Exception {
-		listener.onStop(this);
+	public synchronized Microservice stop() throws Exception {
+		if (stopped)
+			return this;
+		stopped = true;
+		var listeners = new ArrayList<>(beanStore.getBeansOfType(MicroserviceListener.class).values());
+		Collections.reverse(listeners);
+		for (var l : listeners)
+			l.onStop(this);
 		try {
 			beanStore.close();
 		} catch (Exception e) {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java
index e95b01d382..b46e3b3d68 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/MicroserviceListener.java
@@ -22,7 +22,7 @@ import org.apache.juneau.config.event.*;
  * Listener class for microservice lifecycle events.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public interface MicroserviceListener {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java
similarity index 97%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java
index db03b99b13..d1f037a713 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConfigCommand.java
@@ -29,7 +29,7 @@ import org.apache.juneau.microservice.*;
  * Implements the 'config' console command to get or set configuration.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 @SuppressWarnings({
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java
similarity index 97%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java
index 8b246f6df4..fc7a20b984 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ConsoleCommand.java
@@ -42,7 +42,7 @@ import org.apache.juneau.commons.runtime.*;
  * For example, the {@link HelpCommand} is used to provide help on other commands.
  *
  * <p class='bconsole'>
- * 	Running class 'JettyMicroservice' using config file 'examples.cfg'.
+ * 	Running class 'Microservice' using config file 'examples.cfg'.
  * 	Server started on port 10000
  *
  * 	List of available commands:
@@ -97,7 +97,7 @@ import org.apache.juneau.commons.runtime.*;
  * </p>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public abstract class ConsoleCommand {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java
index 46a8e4e97e..c70c6a6a7e 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/ExitCommand.java
@@ -27,7 +27,7 @@ import org.apache.juneau.microservice.*;
  * Implements the 'exit' console command to gracefully shut down the microservice and JVM.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public class ExitCommand extends ConsoleCommand {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java
similarity index 97%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java
index f0b99e9b02..b967f70f6b 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/HelpCommand.java
@@ -29,7 +29,7 @@ import org.apache.juneau.microservice.*;
  * Implements the 'restart' console command to gracefully shut down and restart the microservice.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public class HelpCommand extends ConsoleCommand {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java
index 2c95e40d17..4a5cb4e344 100644
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/RestartCommand.java
@@ -27,7 +27,7 @@ import org.apache.juneau.microservice.*;
  * Implements the 'restart' console command to gracefully shut down and restart the microservice.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  */
 public class RestartCommand extends ConsoleCommand {
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/package-info.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/package-info.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/console/package-info.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/console/package-info.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/package-info.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/package-info.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/package-info.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/package-info.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigEdit.html b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ConfigEdit.html
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigEdit.html
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ConfigEdit.html
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
similarity index 99%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
index 14f57513a7..c5799ee9f5 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ConfigResource.java
@@ -34,7 +34,7 @@ import org.apache.juneau.rest.servlet.*;
  * Shows contents of the microservice configuration file.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  *
  * @serial exclude
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
similarity index 99%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
index 1371638eb7..b118b46529 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/DirectoryResource.java
@@ -62,7 +62,7 @@ import org.apache.juneau.commons.bean.*;
  * </ul>
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  *
  * @serial exclude
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogEntryFormatter.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogParser.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogParser.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/LogsResource.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java
similarity index 94%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java
index 9390ca274e..e9262bbbcf 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/SampleRootResource.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
  * Sample root REST resource.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  *
  * @serial exclude
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java
similarity index 95%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java
index 327e52d155..b684e419fe 100755
--- a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java
+++ b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/ShutdownResource.java
@@ -23,7 +23,7 @@ import org.apache.juneau.rest.servlet.*;
  * Provides the capability to shut down this REST microservice through a REST call.
  *
  * <h5 class='section'>See Also:</h5><ul>
- * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceCoreBasics">juneau-microservice-core Basics</a>
+ * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/JuneauMicroserviceBasics">juneau-microservice Basics</a>
  * </ul>
  *
  * @serial exclude
diff --git a/juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/package-info.java b/juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/package-info.java
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/java/org/apache/juneau/microservice/resources/package-info.java
rename to juneau-microservice/juneau-microservice/src/main/java/org/apache/juneau/microservice/resources/package-info.java
diff --git a/juneau-microservice/juneau-microservice-core/src/main/resources/org/apache/juneau/microservice/Microservice.properties b/juneau-microservice/juneau-microservice/src/main/resources/org/apache/juneau/microservice/Microservice.properties
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/resources/org/apache/juneau/microservice/Microservice.properties
rename to juneau-microservice/juneau-microservice/src/main/resources/org/apache/juneau/microservice/Microservice.properties
diff --git a/juneau-microservice/juneau-microservice-core/src/main/resources/org/apache/juneau/microservice/console/Messages.properties b/juneau-microservice/juneau-microservice/src/main/resources/org/apache/juneau/microservice/console/Messages.properties
similarity index 100%
rename from juneau-microservice/juneau-microservice-core/src/main/resources/org/apache/juneau/microservice/console/Messages.properties
rename to juneau-microservice/juneau-microservice/src/main/resources/org/apache/juneau/microservice/console/Messages.properties
diff --git a/juneau-microservice/juneau-my-jetty-microservice/pom.xml b/juneau-microservice/juneau-my-jetty-microservice/pom.xml
index 1ee41f7ef1..ded790f7ce 100644
--- a/juneau-microservice/juneau-my-jetty-microservice/pom.xml
+++ b/juneau-microservice/juneau-my-jetty-microservice/pom.xml
@@ -29,7 +29,7 @@
 
 	<parent>
 		<groupId>org.apache.juneau</groupId>
-		<artifactId>juneau-microservice</artifactId>
+		<artifactId>juneau-microservice-parent</artifactId>
 		<version>9.5.0-SNAPSHOT</version>
 	</parent>
 
diff --git a/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/App.java b/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/App.java
index 99df365c0d..ab3a899e00 100644
--- a/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/App.java
+++ b/juneau-microservice/juneau-my-jetty-microservice/src/main/java/org/apache/juneau/microservice/jetty/template/App.java
@@ -16,13 +16,17 @@
  */
 package org.apache.juneau.microservice.jetty.template;
 
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.microservice.jetty.*;
 
+import jakarta.servlet.*;
+
 /**
  * Entry-point for your microservice.
  *
  * <p>
- * The {@link JettyMicroservice} class will locate the <code>my-microservice.cfg</code> file in the home directory and initialize
+ * The {@link Microservice} class will locate the <code>my-microservice.cfg</code> file in the home directory and initialize
  * the resources and commands defined in that file.
  *
  * <h5 class='section'>See Also:</h5><ul>
@@ -39,14 +43,32 @@ public class App {
 	 */
 	public static void main(String[] args) throws Exception {
 		// @formatter:off
-		JettyMicroservice
+		Microservice
 			.create()
 			.args(args)
-			.servlet(RootResources.class)
+			.configurations(JettyConfiguration.class, AppConfig.class)
 			.build()
 			.start()
 			.startConsole()
 			.join();
 		// @formatter:on
 	}
-}
\ No newline at end of file
+
+	/**
+	 * Application-specific configuration class contributing the top-level REST servlet.
+	 */
+	@Configuration
+	public static class AppConfig {
+
+		/**
+		 * Provides the top-level REST servlet, auto-mounted by {@link JettyServerComponent} at
+		 * {@link org.apache.juneau.rest.annotation.Rest#path()}.
+		 *
+		 * @return The root servlet.
+		 */
+		@Bean
+		public Servlet rootResources() {
+			return new RootResources();
+		}
+	}
+}
diff --git a/juneau-microservice/juneau-my-springboot-microservice/build-overlay/pom.xml.2 b/juneau-microservice/juneau-my-springboot-microservice/build-overlay/pom.xml.2
index 155295243b..d9fe3ef72e 100644
--- a/juneau-microservice/juneau-my-springboot-microservice/build-overlay/pom.xml.2
+++ b/juneau-microservice/juneau-my-springboot-microservice/build-overlay/pom.xml.2
@@ -63,7 +63,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>\${juneau.version}</version>
 		</dependency>
 		<dependency>
diff --git a/juneau-microservice/juneau-my-springboot-microservice/pom.xml b/juneau-microservice/juneau-my-springboot-microservice/pom.xml
index d7968e6089..a75353eea6 100644
--- a/juneau-microservice/juneau-my-springboot-microservice/pom.xml
+++ b/juneau-microservice/juneau-my-springboot-microservice/pom.xml
@@ -29,7 +29,7 @@
 
 	<parent>
 		<groupId>org.apache.juneau</groupId>
-		<artifactId>juneau-microservice</artifactId>
+		<artifactId>juneau-microservice-parent</artifactId>
 		<version>9.5.0-SNAPSHOT</version>
 	</parent>
 
@@ -50,7 +50,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>${juneau.version}</version>
 		</dependency>
 	</dependencies>
diff --git a/juneau-microservice/pom.xml b/juneau-microservice/pom.xml
index f1d92163d6..db8ee3bafa 100644
--- a/juneau-microservice/pom.xml
+++ b/juneau-microservice/pom.xml
@@ -25,13 +25,13 @@
 		<version>9.5.0-SNAPSHOT</version>
 	</parent>
 
-	<artifactId>juneau-microservice</artifactId>
+	<artifactId>juneau-microservice-parent</artifactId>
 	<packaging>pom</packaging>
 	<name>Apache Juneau Microservices</name>
 	<description>Apache Juneau Microservice APIs</description>
 
 	<modules>
-		<module>juneau-microservice-core</module>
+		<module>juneau-microservice</module>
 		<module>juneau-microservice-jetty</module>
 		<module>juneau-my-jetty-microservice</module>
 		<module>juneau-my-springboot-microservice</module>
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index ad36d22617..11cd9646c4 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -6581,7 +6581,7 @@ public class RestClient extends MarshallingContextable implements HttpClient, Cl
 		try {
 			var req = request(method, uri, ne(content));
 			if (nn(headers))
-				Json5Map.ofJson5(headers).forEach((k, v) -> req.header(stringHeader(k, s(v))));
+				Json5Map.ofText(headers).forEach((k, v) -> req.header(stringHeader(k, s(v))));
 			if (ne(content))
 				req.contentString(content);
 			return req;
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 61f9406fdf..cfd6c2485d 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
@@ -1750,11 +1750,6 @@ public class RestContext extends Context {
 	 * Called during servlet destruction to invoke all {@link RestDestroy} methods.
 	 */
 	public void destroy() {
-		// @RestDestroy hooks first, then children (recursive cleanup), then close this context's bean store.
-		// Destroying children before closing the bean store is required: getRestChildren() reads from the bean
-		// store and BasicBeanStore.getBean() rejects calls after close(), so the previous post-close lookup was
-		// a latent bug that only surfaced when destroy() was invoked outside the JVM-shutdown happy path
-		// (e.g. dynamic RestChildren.removeChild — see TODO-33).
 		for (var x : destroyInvokerPair.get().invokers) {
 			try {
 				x.invoke(beanStore, getResource());
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
index 962dae4c1a..96c18e31b0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
@@ -326,7 +326,7 @@ public class RestDeleteAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestDelete} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
index 6baa5b2500..b083463e1b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
@@ -366,7 +366,7 @@ public class RestGetAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings (serializers/encoders/converters/guards/matchers/clientVersion/headers/attributes/path
 	// etc.) are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestInit.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestInit.java
index dce6698d41..5955bcfddb 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestInit.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestInit.java
@@ -45,7 +45,7 @@ import jakarta.servlet.*;
  *
  * <p>
  * <b>Note (9.5):</b> two related Builder-injection protocols have been removed in this release. They had zero
- * real-world callers across the codebase before deletion (TODO-16 Phase C-3):
+ * real-world callers across the codebase before deletion (work item 16 Phase C-3):
  * <ul>
  * 	<li><b>Per-operation:</b> {@code @RestInit public void init(RestOpContext.Builder b)} (invoked once per
  * 		<code>@RestOp</code>-annotated method) — replaced by declarative <code>@RestOp(...)</code> attributes,
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
index 09340d05cb..18824f32b7 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
@@ -440,7 +440,7 @@ public class RestOpAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestOp} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
index b0f48aa24a..dda49c392d 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
@@ -366,7 +366,7 @@ public class RestOptionsAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestOptions} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
index f413203609..b746d90027 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
@@ -426,7 +426,7 @@ public class RestPatchAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestPatch} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
index 5bfaef7ce8..de4ae6a671 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
@@ -426,7 +426,7 @@ public class RestPostAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestPost} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
index 9854a9a815..87d971e456 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
@@ -426,7 +426,7 @@ public class RestPutAnnotation {
 
 	}
 
-	// Phase D-2 (TODO-16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
+	// Phase D-2 (work item 16, 2026-04-19): RestOpContextApply removed entirely. All previously-applied
 	// settings are now resolved via direct annotation walking on the corresponding {@code findXxx()}
 	// memoizers in {@link org.apache.juneau.rest.RestOpContext}; the {@code @ContextApply} reference
 	// on {@code @RestPut} has been dropped.
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/PlainTextPojoProcessor.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/PlainTextPojoProcessor.java
index 51790a63be..e26bb1bbd1 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/PlainTextPojoProcessor.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/PlainTextPojoProcessor.java
@@ -57,10 +57,6 @@ public class PlainTextPojoProcessor implements ResponseProcessor {
 		if (o == null) {
 			w.append("null");
 		} else if (o instanceof Map || o instanceof Collection || o.getClass().isArray()) {
-			// Historical contract: when no serializer matched and the response is a Map/Collection/array,
-			// render it as JSON5 so callers that asserted on JsonMap/JsonList.toString() output before the
-			// TODO-34 Phase C retargeting (which switched JsonMap/JsonList toString to strict JSON)
-			// continue to see the same plain-text body.
 			w.append(Json5.of(o));
 		} else {
 			w.append(req.getMarshallingSession().getClassMetaForObject(o).toString(o));
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
index 4daa988677..6d03fee97d 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java
@@ -1025,7 +1025,7 @@ public class BasicSwaggerProviderSession {
 			s = resolve(s);
 			if (! isProbablyJsonArray(s, true))
 				s = "[" + s + "]";
-			return Json5List.ofJson5(s);
+			return Json5List.ofText(s);
 		} catch (ParseException e) {
 			throw new SwaggerException(e, "Malformed swagger JSON array encountered in " + location + ".", locationArgs);
 		}
@@ -1058,7 +1058,7 @@ public class BasicSwaggerProviderSession {
 				return Json5Map.of(SWAGGER_ignore, true);
 			if (! isProbablyJsonObject(o2, true))
 				o2 = "{" + o2 + "}";
-			return Json5Map.ofJson5(o2);
+			return Json5Map.ofText(o2);
 		}
 		if (o instanceof Json5Map o2)
 			return o2;
diff --git a/juneau-sc/juneau-sc-server/src/main/java/org/apache/juneau/server/config/App.java b/juneau-sc/juneau-sc-server/src/main/java/org/apache/juneau/server/config/App.java
index 29782c9b37..47b7135f91 100644
--- a/juneau-sc/juneau-sc-server/src/main/java/org/apache/juneau/server/config/App.java
+++ b/juneau-sc/juneau-sc-server/src/main/java/org/apache/juneau/server/config/App.java
@@ -16,9 +16,13 @@
  */
 package org.apache.juneau.server.config;
 
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.microservice.jetty.*;
 import org.apache.juneau.server.config.rest.*;
 
+import jakarta.servlet.*;
+
 /**
  * Entry-point class.
  */
@@ -32,16 +36,32 @@ public class App {
 	 */
 	public static void main(String[] args) throws Exception {
 		// @formatter:off
-		JettyMicroservice
-				.create()
-				.args(args)
-				.servlet(LoadConfigResource.class)
-				.build()
-				.start()
-				.startConsole()
-				.join();
+		Microservice
+			.create()
+			.args(args)
+			.configurations(JettyConfiguration.class, AppConfig.class)
+			.build()
+			.start()
+			.startConsole()
+			.join();
 		// @formatter:on
 	}
+
+	/**
+	 * Application-specific configuration class contributing the {@link LoadConfigResource} servlet.
+	 */
+	@Configuration
+	public static class AppConfig {
+
+		/**
+		 * Provides the {@link LoadConfigResource} REST servlet, auto-mounted by {@link JettyServerComponent} at
+		 * {@link org.apache.juneau.rest.annotation.Rest#path()}.
+		 *
+		 * @return The servlet.
+		 */
+		@Bean
+		public Servlet loadConfigResource() {
+			return new LoadConfigResource();
+		}
+	}
 }
-// ter o xml do jetty padrão na aplicação
-// posibilitar a remoção do bin.xml
diff --git a/juneau-shaded/juneau-shaded-all/pom.xml b/juneau-shaded/juneau-shaded-all/pom.xml
index 3d1bf176db..68d3264f43 100644
--- a/juneau-shaded/juneau-shaded-all/pom.xml
+++ b/juneau-shaded/juneau-shaded-all/pom.xml
@@ -95,7 +95,7 @@
 		<!-- Microservice modules -->
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>${project.version}</version>
 		</dependency>
 		<dependency>
diff --git a/juneau-utest/pom.xml b/juneau-utest/pom.xml
index 578748c105..42be9b65ca 100644
--- a/juneau-utest/pom.xml
+++ b/juneau-utest/pom.xml
@@ -150,7 +150,7 @@
 		</dependency>
 		<dependency>
 			<groupId>org.apache.juneau</groupId>
-			<artifactId>juneau-microservice-core</artifactId>
+			<artifactId>juneau-microservice</artifactId>
 			<version>${project.version}</version>
 		</dependency>
 		<dependency>
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
index 76fcd94c32..8599808d2c 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanMap_Test.java
@@ -223,11 +223,11 @@ class BeanMap_Test extends TestBase {
 		var m = bc.toBeanMap(t);
 
 		// Non-initialized list fields.
-		m.put("l1", JsonList.ofJson("[1,2,3]"));
-		m.put("al1", JsonList.ofJson("[1,2,3]"));
-		m.put("ll1", JsonList.ofJson("[1,2,3]"));
-		m.put("c1", JsonList.ofJson("[1,2,3]"));
-		m.put("jl1", JsonList.ofJson("[1,2,3]"));
+		m.put("l1", JsonList.ofText("[1,2,3]"));
+		m.put("al1", JsonList.ofText("[1,2,3]"));
+		m.put("ll1", JsonList.ofText("[1,2,3]"));
+		m.put("c1", JsonList.ofText("[1,2,3]"));
+		m.put("jl1", JsonList.ofText("[1,2,3]"));
 
 		// al1 should be initialized with an ArrayList, since it's not a superclass of JsonList.
 		// The rest are proper superclasses of JsonList.
@@ -238,10 +238,10 @@ class BeanMap_Test extends TestBase {
 		);
 
 		// Non-initialized map fields.
-		m.put("m1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("hm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("tm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
+		m.put("m1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("hm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("tm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
 
 		// tm1 should be initialized with TreeMap, since it's not a superclass of JsonMap.
 		// The rest are proper superclasses of JsonMap
@@ -252,15 +252,15 @@ class BeanMap_Test extends TestBase {
 		);
 
 		// Initialized fields should reuse existing field value.
-		m.put("l2", JsonList.ofJson("[1,2,3]"));
-		m.put("al2", JsonList.ofJson("[1,2,3]"));
-		m.put("ll2", JsonList.ofJson("[1,2,3]"));
-		m.put("c2", JsonList.ofJson("[1,2,3]"));
-		m.put("m2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("hm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("tm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jl2", JsonList.ofJson("[1,2,3]"));
+		m.put("l2", JsonList.ofText("[1,2,3]"));
+		m.put("al2", JsonList.ofText("[1,2,3]"));
+		m.put("ll2", JsonList.ofText("[1,2,3]"));
+		m.put("c2", JsonList.ofText("[1,2,3]"));
+		m.put("m2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("hm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("tm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jl2", JsonList.ofText("[1,2,3]"));
 
 		assertBean(
 			m,
@@ -298,11 +298,11 @@ class BeanMap_Test extends TestBase {
 		var m = bc.toBeanMap(t);
 
 		// Non-initialized list fields.
-		m.put("l1", JsonList.ofJson("[1,2,3]"));
-		m.put("al1", JsonList.ofJson("[1,2,3]"));
-		m.put("ll1", JsonList.ofJson("[1,2,3]"));
-		m.put("c1", JsonList.ofJson("[1,2,3]"));
-		m.put("jl1", JsonList.ofJson("[1,2,3]"));
+		m.put("l1", JsonList.ofText("[1,2,3]"));
+		m.put("al1", JsonList.ofText("[1,2,3]"));
+		m.put("ll1", JsonList.ofText("[1,2,3]"));
+		m.put("c1", JsonList.ofText("[1,2,3]"));
+		m.put("jl1", JsonList.ofText("[1,2,3]"));
 
 		// al1 should be initialized with an ArrayList, since it's not a superclass of JsonList.
 		// The rest are proper superclasses of JsonList.
@@ -313,10 +313,10 @@ class BeanMap_Test extends TestBase {
 		);
 
 		// Non-initialized map fields.
-		m.put("m1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("hm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("tm1", JsonMap.ofJson("{\"foo\":\"bar\"}"));
+		m.put("m1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("hm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("tm1", JsonMap.ofText("{\"foo\":\"bar\"}"));
 
 		// tm1 should be initialized with TreeMap, since it's not a superclass of JsonMap.
 		// The rest are proper superclasses of JsonMap
@@ -327,15 +327,15 @@ class BeanMap_Test extends TestBase {
 		);
 
 		// Initialized fields should reuse existing field value.
-		m.put("l2", JsonList.ofJson("[1,2,3]"));
-		m.put("al2", JsonList.ofJson("[1,2,3]"));
-		m.put("ll2", JsonList.ofJson("[1,2,3]"));
-		m.put("c2", JsonList.ofJson("[1,2,3]"));
-		m.put("m2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("hm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("tm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jm2", JsonMap.ofJson("{\"foo\":\"bar\"}"));
-		m.put("jl2", JsonList.ofJson("[1,2,3]"));
+		m.put("l2", JsonList.ofText("[1,2,3]"));
+		m.put("al2", JsonList.ofText("[1,2,3]"));
+		m.put("ll2", JsonList.ofText("[1,2,3]"));
+		m.put("c2", JsonList.ofText("[1,2,3]"));
+		m.put("m2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("hm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("tm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jm2", JsonMap.ofText("{\"foo\":\"bar\"}"));
+		m.put("jl2", JsonList.ofText("[1,2,3]"));
 
 		assertBean(
 			m,
@@ -424,7 +424,7 @@ class BeanMap_Test extends TestBase {
 	@Test void a05_arrayProperties() {
 		var t = new D1();
 		var m = bc.toBeanMap(t);
-		m.put("b", JsonMap.ofJson("{\"s\":\"foo\"}"));
+		m.put("b", JsonMap.ofText("{\"s\":\"foo\"}"));
 		assertNotNull(t.b);
 		assertEquals("foo", t.b.s);
 
@@ -467,7 +467,7 @@ class BeanMap_Test extends TestBase {
 	@Test void a06_arrayProperties_usingConfig() {
 		var t = new D1c();
 		var m = bc.toBeanMap(t);
-		m.put("b", JsonMap.ofJson("{\"s\":\"foo\"}"));
+		m.put("b", JsonMap.ofText("{\"s\":\"foo\"}"));
 		assertNotNull(t.b);
 		assertBean(t.b, "s", "foo");
 
@@ -517,13 +517,13 @@ class BeanMap_Test extends TestBase {
 	@Test void a07_arrayPropertiesInJsonList() {
 		var t = new E();
 		var m = bc.toBeanMap(t);
-		m.put("s", JsonList.ofJson("[\"foo\"]"));
-		m.put("s2", JsonList.ofJson("[[\"foo\"]]"));
-		m.put("i", JsonList.ofJson("[1,2,3]"));
-		m.put("i2", JsonList.ofJson("[[1,2,3],[4,5,6]]"));
+		m.put("s", JsonList.ofText("[\"foo\"]"));
+		m.put("s2", JsonList.ofText("[[\"foo\"]]"));
+		m.put("i", JsonList.ofText("[1,2,3]"));
+		m.put("i2", JsonList.ofText("[[1,2,3],[4,5,6]]"));
 		assertEquals("{s:['foo'],s2:[['foo']],i:[1,2,3],i2:[[1,2,3],[4,5,6]]}", Json5Serializer.DEFAULT.serialize(t));
-		m.put("i", JsonList.ofJson("[null,null,null]"));
-		m.put("i2", JsonList.ofJson("[[null,null,null],[null,null,null]]"));
+		m.put("i", JsonList.ofText("[null,null,null]"));
+		m.put("i2", JsonList.ofText("[[null,null,null],[null,null,null]]"));
 		assertEquals("{s:['foo'],s2:[['foo']],i:[0,0,0],i2:[[0,0,0],[0,0,0]]}", Json5Serializer.DEFAULT.serialize(t));
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/DataConversion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/DataConversion_Test.java
index 1f5b0a7059..363d650506 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/DataConversion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/DataConversion_Test.java
@@ -71,7 +71,7 @@ class DataConversion_Test extends TestBase {
 		assertEquals("{}", m.getString("x"));
 
 		// *** JsonMap ***
-		m.put("x", Json5Map.ofJson5("{foo:123}"));
+		m.put("x", Json5Map.ofText("{foo:123}"));
 		assertEquals("{foo:123}", m.getString("x"));
 
 		// *** Collection ***
@@ -81,7 +81,7 @@ class DataConversion_Test extends TestBase {
 		assertEquals("[123]", m.getString("x"));
 
 		// *** JsonList ***
-		m.put("x", JsonList.ofJson("[123]"));
+		m.put("x", JsonList.ofText("[123]"));
 		assertEquals("[123]", m.getString("x"));
 		assertSize(1, m.getList("x"));
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/JsonList_Test.java b/juneau-utest/src/test/java/org/apache/juneau/JsonList_Test.java
index b2fdc0fdd2..d375b74c79 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/JsonList_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/JsonList_Test.java
@@ -92,7 +92,7 @@ class JsonList_Test extends TestBase {
 		r = l.getAt("0/foo", String.class);
 		assertEquals("bing", r);
 
-		l.postAt("", Json5Map.ofJson5("{a:'b'}"));
+		l.postAt("", Json5Map.ofText("{a:'b'}"));
 		r = l.getAt("2/a", String.class);
 		assertEquals("b", r);
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/JsonMap_Test.java b/juneau-utest/src/test/java/org/apache/juneau/JsonMap_Test.java
index 9241c16c86..33d6812a0f 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/JsonMap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/JsonMap_Test.java
@@ -37,207 +37,207 @@ class JsonMap_Test extends TestBase {
 	@Test void a01_basic() throws Exception {
 		var in = "{A:'asdf'}";
 
-		checkStep(1, in, Json5Map.ofJson5(in).getString("A"), "asdf");
+		checkStep(1, in, Json5Map.ofText(in).getString("A"), "asdf");
 
 		in = "{A:{B:'asdf'}}";
-		checkStep(2, in, getDeepString(Json5Map.ofJson5(in), "A/B"), "asdf");
-		checkStep(3, in, Json5Map.ofJson5(in).getString("A"), "{B:'asdf'}");
+		checkStep(2, in, getDeepString(Json5Map.ofText(in), "A/B"), "asdf");
+		checkStep(3, in, Json5Map.ofText(in).getString("A"), "{B:'asdf'}");
 
 		in = "{A:{B:'asdf'+\"asdf\"}}";
-		checkStep(4, in, getDeepString(Json5Map.ofJson5(in), "A/B"), "asdfasdf");
-		checkStep(5, in, Json5Map.ofJson5(in).getString("A"), "{B:'asdfasdf'}");
+		checkStep(4, in, getDeepString(Json5Map.ofText(in), "A/B"), "asdfasdf");
+		checkStep(5, in, Json5Map.ofText(in).getString("A"), "{B:'asdfasdf'}");
 
 		in = "{A:{B:'asdf' + \n\t \"asdf\"}}";
-		checkStep(6, in, getDeepString(Json5Map.ofJson5(in), "A/B"), "asdfasdf");
-		checkStep(7, in, Json5Map.ofJson5(in).getString("A"), "{B:'asdfasdf'}");
+		checkStep(6, in, getDeepString(Json5Map.ofText(in), "A/B"), "asdfasdf");
+		checkStep(7, in, Json5Map.ofText(in).getString("A"), "{B:'asdfasdf'}");
 
 		in = "{A:{B:'asdf\"asdf', C:\"asdf'asdf\", D : \"asdf\\\"asdf\", E: 'asdf\\\'asdf', F:\"asdf\\\'asdf\", G:'asdf\\\"asdf'}}";
-		checkStep(8, in, getDeepString(Json5Map.ofJson5(in), "A/B"), "asdf\"asdf");
-		checkStep(9, in, getDeepString(Json5Map.ofJson5(in), "A/C"), "asdf'asdf");
-		checkStep(10, in, getDeepString(Json5Map.ofJson5(in), "A/D"), "asdf\"asdf");
-		checkStep(11, in, getDeepString(Json5Map.ofJson5(in), "A/E"), "asdf'asdf");
-		checkStep(12, in, getDeepString(Json5Map.ofJson5(in), "A/F"), "asdf'asdf");
-		checkStep(13, in, getDeepString(Json5Map.ofJson5(in), "A/G"), "asdf\"asdf");
+		checkStep(8, in, getDeepString(Json5Map.ofText(in), "A/B"), "asdf\"asdf");
+		checkStep(9, in, getDeepString(Json5Map.ofText(in), "A/C"), "asdf'asdf");
+		checkStep(10, in, getDeepString(Json5Map.ofText(in), "A/D"), "asdf\"asdf");
+		checkStep(11, in, getDeepString(Json5Map.ofText(in), "A/E"), "asdf'asdf");
+		checkStep(12, in, getDeepString(Json5Map.ofText(in), "A/F"), "asdf'asdf");
+		checkStep(13, in, getDeepString(Json5Map.ofText(in), "A/G"), "asdf\"asdf");
 
 		in = "{A:123, B: 123}";
-		checkStep(16, in, Json5Map.ofJson5(in).getInt("A").toString(), "123");
-		checkStep(17, in, Json5Map.ofJson5(in).getInt("B").toString(), "123");
+		checkStep(16, in, Json5Map.ofText(in).getInt("A").toString(), "123");
+		checkStep(17, in, Json5Map.ofText(in).getInt("B").toString(), "123");
 
 		in = "{A:true, B: true, C:false, D: false}";
-		checkStep(18, in, Json5Map.ofJson5(in).getBoolean("A").toString(), "true");
-		checkStep(19, in, Json5Map.ofJson5(in).getBoolean("B").toString(), "true");
-		checkStep(20, in, Json5Map.ofJson5(in).getBoolean("C").toString(), "false");
-		checkStep(21, in, Json5Map.ofJson5(in).getBoolean("D").toString(), "false");
+		checkStep(18, in, Json5Map.ofText(in).getBoolean("A").toString(), "true");
+		checkStep(19, in, Json5Map.ofText(in).getBoolean("B").toString(), "true");
+		checkStep(20, in, Json5Map.ofText(in).getBoolean("C").toString(), "false");
+		checkStep(21, in, Json5Map.ofText(in).getBoolean("D").toString(), "false");
 
 		in = "{'AAA':{\"BBB\":\"CCC\",'DDD':false}}";
-		checkStep(31, in, getDeepString(Json5Map.ofJson5(in), "AAA/BBB"), "CCC");
-		checkStep(32, in, getDeepBoolean(Json5Map.ofJson5(in), "AAA/DDD").toString(), "false");
+		checkStep(31, in, getDeepString(Json5Map.ofText(in), "AAA/BBB"), "CCC");
+		checkStep(32, in, getDeepBoolean(Json5Map.ofText(in), "AAA/DDD").toString(), "false");
 
 		in = " \n\n\t {  'AAA' : { \"BBB\" : \"CCC\" , 'DDD' : false } } \n\t";
-		checkStep(33, in, getDeepString(Json5Map.ofJson5(in), "AAA/BBB"), "CCC");
-		checkStep(34, in, getDeepBoolean(Json5Map.ofJson5(in), "AAA/DDD").toString(), "false");
+		checkStep(33, in, getDeepString(Json5Map.ofText(in), "AAA/BBB"), "CCC");
+		checkStep(34, in, getDeepBoolean(Json5Map.ofText(in), "AAA/DDD").toString(), "false");
 
 		in = "/*x*/{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(100, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(100, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{/*x*/A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(101, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(101, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A/*x*/:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(102, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(102, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:/*x*/'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(103, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(103, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:'/*x*/B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(104, in, Json5Map.ofJson5(in).getString("A"), "/*x*/B");
+		checkStep(104, in, Json5Map.ofText(in).getString("A"), "/*x*/B");
 		in = "{A:'B/*x*/','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(105, in, Json5Map.ofJson5(in).getString("A"), "B/*x*/");
+		checkStep(105, in, Json5Map.ofText(in).getString("A"), "B/*x*/");
 		in = "{A:'B'/*x*/,'C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(106, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(106, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:'B',/*x*/'C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(107, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(107, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':/*x*/1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(108, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(108, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':1/*x*/,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(109, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(109, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':1,/*x*/\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(110, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(110, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"/*x*/E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(111, in, Json5Map.ofJson5(in).getList("/*x*/E").getString(0), "1");
+		checkStep(111, in, Json5Map.ofText(in).getList("/*x*/E").getString(0), "1");
 		in = "{A:'B','C':1,\"E/*x*/\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(112, in, Json5Map.ofJson5(in).getList("E/*x*/").getString(0), "1");
+		checkStep(112, in, Json5Map.ofText(in).getList("E/*x*/").getString(0), "1");
 		in = "{A:'B','C':1,\"E\"/*x*/:[1,2,3],G:['g1','g2','g3']}";
-		checkStep(113, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(113, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":/*x*/[1,2,3],G:['g1','g2','g3']}";
-		checkStep(114, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(114, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[/*x*/1,2,3],G:['g1','g2','g3']}";
-		checkStep(115, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(115, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[1/*x*/,2,3],G:['g1','g2','g3']}";
-		checkStep(116, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(116, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[1,/*x*/2,3],G:['g1','g2','g3']}";
-		checkStep(117, in, Json5Map.ofJson5(in).getList("E").getString(1), "2");
+		checkStep(117, in, Json5Map.ofText(in).getList("E").getString(1), "2");
 		in = "{A:'B','C':1,\"E\":[1,2/*x*/,3],G:['g1','g2','g3']}";
-		checkStep(118, in, Json5Map.ofJson5(in).getList("E").getString(1), "2");
+		checkStep(118, in, Json5Map.ofText(in).getList("E").getString(1), "2");
 		in = "{A:'B','C':1,\"E\":[1,2,/*x*/3],G:['g1','g2','g3']}";
-		checkStep(119, in, Json5Map.ofJson5(in).getList("E").getString(2), "3");
+		checkStep(119, in, Json5Map.ofText(in).getList("E").getString(2), "3");
 		in = "{A:'B','C':1,\"E\":[1,2,3]/*x*/,G:['g1','g2','g3']}";
-		checkStep(120, in, Json5Map.ofJson5(in).getList("E").getString(2), "3");
+		checkStep(120, in, Json5Map.ofText(in).getList("E").getString(2), "3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],/*x*/G:['g1','g2','g3']}";
-		checkStep(121, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(121, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:[/*x*/'g1','g2','g3']}";
-		checkStep(122, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(122, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['/*x*/g1','g2','g3']}";
-		checkStep(123, in, Json5Map.ofJson5(in).getList("G").getString(0), "/*x*/g1");
+		checkStep(123, in, Json5Map.ofText(in).getList("G").getString(0), "/*x*/g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1'/*x*/,'g2','g3']}";
-		checkStep(124, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(124, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1',/*x*/'g2','g3']}";
-		checkStep(125, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(125, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2'/*x*/,'g3']}";
-		checkStep(126, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(126, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2',/*x*/'g3']}";
-		checkStep(127, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(127, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3'/*x*/]}";
-		checkStep(128, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(128, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']/*x*/}";
-		checkStep(129, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(129, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}/*x*/";
-		checkStep(130, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(130, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 
 		in = "/*\tx\t*///\tx\t\n\t/*\tx\t*/{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(201, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(201, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{/*\tx\t*///\tx\t\n\t/*\tx\t*/A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(202, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(202, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A/*\tx\t*///\tx\t\n\t/*\tx\t*/:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(203, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(203, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:/*\tx\t*///\tx\t\n\t/*\tx\t*/'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(204, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(204, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:'/*\tx\t*///\tx\t\n\t/*\tx\t*/B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(205, in, Json5Map.ofJson5(in).getString("A"), "/*\tx\t*///\tx\t\n\t/*\tx\t*/B");
+		checkStep(205, in, Json5Map.ofText(in).getString("A"), "/*\tx\t*///\tx\t\n\t/*\tx\t*/B");
 		in = "{A:'B/*\tx\t*///\tx\t\n\t/*\tx\t*/','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(206, in, Json5Map.ofJson5(in).getString("A"), "B/*\tx\t*///\tx\t\n\t/*\tx\t*/");
+		checkStep(206, in, Json5Map.ofText(in).getString("A"), "B/*\tx\t*///\tx\t\n\t/*\tx\t*/");
 		in = "{A:'B'/*\tx\t*///\tx\t\n\t/*\tx\t*/,'C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(207, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(207, in, Json5Map.ofText(in).getString("A"), "B");
 		in = "{A:'B',/*\tx\t*///\tx\t\n\t/*\tx\t*/'C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(208, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(208, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':/*\tx\t*///\tx\t\n\t/*\tx\t*/1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(209, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(209, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':1/*\tx\t*///\tx\t\n\t/*\tx\t*/,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(210, in, Json5Map.ofJson5(in).getString("C"), "1");
+		checkStep(210, in, Json5Map.ofText(in).getString("C"), "1");
 		in = "{A:'B','C':1,/*\tx\t*///\tx\t\n\t/*\tx\t*/\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(211, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(211, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"/*\tx\t*///\tx\t\n\t/*\tx\t*/E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(212, in, Json5Map.ofJson5(in).getList("/*\tx\t*///\tx\t\n\t/*\tx\t*/E").getString(0), "1");
+		checkStep(212, in, Json5Map.ofText(in).getList("/*\tx\t*///\tx\t\n\t/*\tx\t*/E").getString(0), "1");
 		in = "{A:'B','C':1,\"E/*\tx\t*///\tx\t\n\t/*\tx\t*/\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(213, in, Json5Map.ofJson5(in).getList("E/*\tx\t*///\tx\t\n\t/*\tx\t*/").getString(0), "1");
+		checkStep(213, in, Json5Map.ofText(in).getList("E/*\tx\t*///\tx\t\n\t/*\tx\t*/").getString(0), "1");
 		in = "{A:'B','C':1,\"E\"/*\tx\t*///\tx\t\n\t/*\tx\t*/:[1,2,3],G:['g1','g2','g3']}";
-		checkStep(214, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(214, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":/*\tx\t*///\tx\t\n\t/*\tx\t*/[1,2,3],G:['g1','g2','g3']}";
-		checkStep(215, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(215, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[/*\tx\t*///\tx\t\n\t/*\tx\t*/1,2,3],G:['g1','g2','g3']}";
-		checkStep(216, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(216, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[1/*\tx\t*///\tx\t\n\t/*\tx\t*/,2,3],G:['g1','g2','g3']}";
-		checkStep(217, in, Json5Map.ofJson5(in).getList("E").getString(0), "1");
+		checkStep(217, in, Json5Map.ofText(in).getList("E").getString(0), "1");
 		in = "{A:'B','C':1,\"E\":[1,/*\tx\t*///\tx\t\n\t/*\tx\t*/2,3],G:['g1','g2','g3']}";
-		checkStep(218, in, Json5Map.ofJson5(in).getList("E").getString(1), "2");
+		checkStep(218, in, Json5Map.ofText(in).getList("E").getString(1), "2");
 		in = "{A:'B','C':1,\"E\":[1,2/*\tx\t*///\tx\t\n\t/*\tx\t*/,3],G:['g1','g2','g3']}";
-		checkStep(219, in, Json5Map.ofJson5(in).getList("E").getString(1), "2");
+		checkStep(219, in, Json5Map.ofText(in).getList("E").getString(1), "2");
 		in = "{A:'B','C':1,\"E\":[1,2,/*\tx\t*///\tx\t\n\t/*\tx\t*/3],G:['g1','g2','g3']}";
-		checkStep(220, in, Json5Map.ofJson5(in).getList("E").getString(2), "3");
+		checkStep(220, in, Json5Map.ofText(in).getList("E").getString(2), "3");
 		in = "{A:'B','C':1,\"E\":[1,2,3]/*\tx\t*///\tx\t\n\t/*\tx\t*/,G:['g1','g2','g3']}";
-		checkStep(221, in, Json5Map.ofJson5(in).getList("E").getString(2), "3");
+		checkStep(221, in, Json5Map.ofText(in).getList("E").getString(2), "3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],/*\tx\t*///\tx\t\n\t/*\tx\t*/G:['g1','g2','g3']}";
-		checkStep(222, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(222, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:[/*\tx\t*///\tx\t\n\t/*\tx\t*/'g1','g2','g3']}";
-		checkStep(223, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(223, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['/*\tx\t*///\tx\t\n\t/*\tx\t*/g1','g2','g3']}";
-		checkStep(224, in, Json5Map.ofJson5(in).getList("G").getString(0), "/*\tx\t*///\tx\t\n\t/*\tx\t*/g1");
+		checkStep(224, in, Json5Map.ofText(in).getList("G").getString(0), "/*\tx\t*///\tx\t\n\t/*\tx\t*/g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1'/*\tx\t*///\tx\t\n\t/*\tx\t*/,'g2','g3']}";
-		checkStep(225, in, Json5Map.ofJson5(in).getList("G").getString(0), "g1");
+		checkStep(225, in, Json5Map.ofText(in).getList("G").getString(0), "g1");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1',/*\tx\t*///\tx\t\n\t/*\tx\t*/'g2','g3']}";
-		checkStep(226, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(226, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2'/*\tx\t*///\tx\t\n\t/*\tx\t*/,'g3']}";
-		checkStep(227, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(227, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2',/*\tx\t*///\tx\t\n\t/*\tx\t*/'g3']}";
-		checkStep(228, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(228, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3'/*\tx\t*///\tx\t\n\t/*\tx\t*/]}";
-		checkStep(229, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(229, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']/*\tx\t*///\tx\t\n\t/*\tx\t*/}";
-		checkStep(230, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(230, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}/*\tx\t*///\tx\t\n\t/*\tx\t*/";
-		checkStep(231, in, Json5Map.ofJson5(in).getList("G").getString(2), "g3");
+		checkStep(231, in, Json5Map.ofText(in).getList("G").getString(2), "g3");
 
 		in = "{  /*  x  */  //  x  \n  /*  x  */  A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(240, in, Json5Map.ofJson5(in).getString("A"), "B");
+		checkStep(240, in, Json5Map.ofText(in).getString("A"), "B");
 
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(301, in, Json5Map.ofJson5(in).getString("A", "default"), "B");
+		checkStep(301, in, Json5Map.ofText(in).getString("A", "default"), "B");
 		in = "{/*A:'B',*/'C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(302, in, Json5Map.ofJson5(in).getString("A", "default"), "default");
+		checkStep(302, in, Json5Map.ofText(in).getString("A", "default"), "default");
 		in = "{A:'B',/*'C':1,*/\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(303, in, Json5Map.ofJson5(in).getString("C", "default"), "default");
+		checkStep(303, in, Json5Map.ofText(in).getString("C", "default"), "default");
 		in = "{A:'B','C':1,/*\"E\":[1,2,3],*/G:['g1','g2','g3']}";
-		checkStep(304, in, Json5Map.ofJson5(in).getString("E", "default"), "default");
+		checkStep(304, in, Json5Map.ofText(in).getString("E", "default"), "default");
 		in = "{A:'B','C':1,\"E\":[/*1,*/2,3],G:['g1','g2','g3']}";
-		checkStep(305, in, Json5Map.ofJson5(in).getList("E").getString(0), "2");
+		checkStep(305, in, Json5Map.ofText(in).getList("E").getString(0), "2");
 		in = "{A:'B','C':1,\"E\":[1,/*2,*/3],G:['g1','g2','g3']}";
-		checkStep(306, in, Json5Map.ofJson5(in).getList("E").getString(1), "3");
+		checkStep(306, in, Json5Map.ofText(in).getList("E").getString(1), "3");
 		in = "{A:'B','C':1,\"E\":[1,2/*,3*/],G:['g1','g2','g3']}";
-		checkStep(307, in, Json5Map.ofJson5(in).getList("E").getString(1), "2");
+		checkStep(307, in, Json5Map.ofText(in).getList("E").getString(1), "2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:[/*'g1',*/'g2','g3']}";
-		checkStep(308, in, Json5Map.ofJson5(in).getList("G").getString(0), "g2");
+		checkStep(308, in, Json5Map.ofText(in).getList("G").getString(0), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1'/*,'g2'*/,'g3']}";
-		checkStep(309, in, Json5Map.ofJson5(in).getList("G").getString(1), "g3");
+		checkStep(309, in, Json5Map.ofText(in).getList("G").getString(1), "g3");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2'/*,'g3'*/]}";
-		checkStep(310, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(310, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 		in = "{A:'B','C':1,\"E\":[1,2,3],G:['g1','g2','g3']}";
-		checkStep(310, in, Json5Map.ofJson5(in).getList("G").getString(1), "g2");
+		checkStep(310, in, Json5Map.ofText(in).getList("G").getString(1), "g2");
 
 		// Check keys that contain array indexes
 		in = "{A:{B:[{C:'c0'},{C:'c1'},{C:'c2'}]}}";
-		checkStep(401, in, getDeepString(Json5Map.ofJson5(in), "A/B/0/C"), "c0");
-		checkStep(402, in, getDeepString(Json5Map.ofJson5(in), "A/B/1/C"), "c1");
-		checkStep(403, in, getDeepString(Json5Map.ofJson5(in), "A/B/2/C"), "c2");
+		checkStep(401, in, getDeepString(Json5Map.ofText(in), "A/B/0/C"), "c0");
+		checkStep(402, in, getDeepString(Json5Map.ofText(in), "A/B/1/C"), "c1");
+		checkStep(403, in, getDeepString(Json5Map.ofText(in), "A/B/2/C"), "c2");
 
 		// Check extended unicode characters.
 		in = "{'𤭢𤭢':'𤭢𤭢'}";
-		checkStep(1, in, Json5Map.ofJson5(in).getString("𤭢𤭢"), "𤭢𤭢");
+		checkStep(1, in, Json5Map.ofText(in).getString("𤭢𤭢"), "𤭢𤭢");
 	}
 
 	private static String getDeepString(Json5Map m, String url) {
@@ -261,8 +261,8 @@ class JsonMap_Test extends TestBase {
 	// testComparison
 	//====================================================================================================
 	@Test void a02_comparison() throws Exception {
-		var m1 = Json5Map.ofJson5("{ firstName:'John', lastName:'Smith', age:123, isDeceased:false }");
-		var m2 = Json5Map.ofJson5("{ age:123, isDeceased:false, lastName:'Smith', firstName:'John' }");
+		var m1 = Json5Map.ofText("{ firstName:'John', lastName:'Smith', age:123, isDeceased:false }");
+		var m2 = Json5Map.ofText("{ age:123, isDeceased:false, lastName:'Smith', firstName:'John' }");
 
 		assertEquals(m1, m2);
 	}
@@ -271,8 +271,8 @@ class JsonMap_Test extends TestBase {
 	// testParent
 	//====================================================================================================
 	@Test void a03_parent() throws Exception {
-		var m1 = Json5Map.ofJson5("{a:1}");
-		var m2 = Json5Map.ofJson5("{b:2}").inner(m1);
+		var m1 = Json5Map.ofText("{a:1}");
+		var m2 = Json5Map.ofText("{b:2}").inner(m1);
 
 		assertEquals(Integer.valueOf(1), m2.getInt("a"));
 	}
@@ -281,13 +281,13 @@ class JsonMap_Test extends TestBase {
 	// testUpdatability
 	//====================================================================================================
 	@Test void a04_updatability() throws Exception {
-		var m = Json5Map.ofJson5("{a:[{b:'c'}]}");
+		var m = Json5Map.ofText("{a:[{b:'c'}]}");
 		var l = m.getList("a");
 		var m2 = l.getMap(0);
 		m2.put("b", "x");
 		assertBean(m, "a", "[{b=x}]");
 
-		m = Json5Map.ofJson5("{a:[{b:'c'}]}");
+		m = Json5Map.ofText("{a:[{b:'c'}]}");
 		for (var m3 : m.getList("a").elements(Json5Map.class))
 			m3.put("b", "y");
 
@@ -298,7 +298,7 @@ class JsonMap_Test extends TestBase {
 	// testAtMethods
 	//====================================================================================================
 	@Test void a05_atMethods() throws Exception {
-		var m = Json5Map.ofJson5("{a:[{b:'c'}]}");
+		var m = Json5Map.ofText("{a:[{b:'c'}]}");
 		var r = m.getAt("a/0/b", String.class);
 
 		assertEquals("c", r);
@@ -319,14 +319,14 @@ class JsonMap_Test extends TestBase {
 	// JsonMap(Reader)
 	//====================================================================================================
 	@Test void a06_fromReader() throws Exception {
-		assertBean(Json5Map.ofJson5(reader("{foo:'bar'}")), "foo", "bar");
+		assertBean(Json5Map.ofText(reader("{foo:'bar'}")), "foo", "bar");
 	}
 
 	//====================================================================================================
 	// testGetMap
 	//====================================================================================================
 	@Test void a07_getMap() throws Exception {
-		var m = Json5Map.ofJson5("{a:{1:'true',2:'false'}}");
+		var m = Json5Map.ofText("{a:{1:'true',2:'false'}}");
 		var m2 = m.getMap("a", Integer.class, Boolean.class, null);
 		assertJson("{'1':true,'2':false}", m2);
 		assertEquals(Integer.class, m2.keySet().iterator().next().getClass());
@@ -348,7 +348,7 @@ class JsonMap_Test extends TestBase {
 	// testGetList
 	//====================================================================================================
 	@Test void a08_getList() throws Exception {
-		var m = Json5Map.ofJson5("{a:['123','456']}");
+		var m = Json5Map.ofText("{a:['123','456']}");
 		var l2 = m.getList("a", Integer.class, null);
 		assertList(l2, "123", "456");
 		assertEquals(Integer.class, l2.iterator().next().getClass());
@@ -368,7 +368,7 @@ class JsonMap_Test extends TestBase {
 	// toX serialization methods
 	//====================================================================================================
 	@Test void a09_toX() {
-		var m = Json5Map.ofJson5("{b:'2',a:'1'}");
+		var m = Json5Map.ofText("{b:'2',a:'1'}");
 
 		// toJson5 — JSON5 (unquoted keys where possible), same as toString()
 		assertString("{b:'2',a:'1'}", m.toJson5());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/MarshalledConfig_Test.java b/juneau-utest/src/test/java/org/apache/juneau/MarshalledConfig_Test.java
index 586110f836..e9f866d09b 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/MarshalledConfig_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/MarshalledConfig_Test.java
@@ -314,7 +314,7 @@ class MarshalledConfig_Test extends TestBase {
 		assertEquals("{name:'x',age:123}", bc.convertToType(o, String.class));
 
 		// List of Maps to array of beans.
-		var o2 = JsonList.of(JsonMap.ofJson("{\"name\":\"x\",\"age\":1}"), JsonMap.ofJson("{\"name\":\"y\",\"age\":2}"));
+		var o2 = JsonList.of(JsonMap.ofText("{\"name\":\"x\",\"age\":1}"), JsonMap.ofText("{\"name\":\"y\",\"age\":2}"));
 		assertEquals(1, bc.convertToType(o2, ReadOnlyPerson[].class)[0].getAge());
 	}
 
@@ -346,7 +346,7 @@ class MarshalledConfig_Test extends TestBase {
 		assertEquals("{name:'x',age:123}", bc.convertToType(o, String.class));
 
 		// List of Maps to array of beans.
-		var o2 = JsonList.of(JsonMap.ofJson("{\"name\":\"x\",\"age\":1}"), JsonMap.ofJson("{\"name\":\"y\",\"age\":2}"));
+		var o2 = JsonList.of(JsonMap.ofText("{\"name\":\"x\",\"age\":1}"), JsonMap.ofText("{\"name\":\"y\",\"age\":2}"));
 		assertEquals(1, bc.convertToType(o2, ReadOnlyPerson2[].class)[0].getAge());
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java b/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
index 2040dd1612..3f1ad2ba59 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ObjectSwap_Test.java
@@ -39,7 +39,7 @@ class ObjectSwap_Test extends TestBase {
 		r = p.parse(r, String.class);
 		assertEquals("foobar", r);
 
-		var m = Json5Map.ofJson5("{foo:'bar'}");
+		var m = Json5Map.ofText("{foo:'bar'}");
 		r = s.serialize(m);
 		assertEquals("{xfoox:'xbarx'}", r);
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/JsonMaps_RoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/JsonMaps_RoundTripTest.java
index 7e8f247a3b..26ddf62bad 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/JsonMaps_RoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/JsonMaps_RoundTripTest.java
@@ -42,7 +42,7 @@ class JsonMaps_RoundTripTest extends RoundTripTest_Base {
 	@ParameterizedTest
 	@MethodSource("testers")
 	void a01_basic(RoundTrip_Tester t) throws Exception {
-		var x1 = new A(JsonMap.ofJson("{\"f1\":\"a\",\"f2\":2}"));
+		var x1 = new A(JsonMap.ofText("{\"f1\":\"a\",\"f2\":2}"));
 		x1 = t.roundTrip(x1, A.class);
 		assertBean(x1, "f1,f2", "a,2");
 
@@ -50,11 +50,11 @@ class JsonMaps_RoundTripTest extends RoundTripTest_Base {
 		x2 = t.roundTrip(x2, A[].class);
 		assertBean(x2, "length,#{f1,f2}", "1,[{a,2}]");
 
-		var x3 = l(new A(JsonMap.ofJson("{\"f1\":\"a\",\"f2\":2}")));
+		var x3 = l(new A(JsonMap.ofText("{\"f1\":\"a\",\"f2\":2}")));
 		x3 = t.roundTrip(x3, List.class, A.class);
 		assertBean(x3, "size,0{f1,f2}", "1,{a,2}");
 
-		var x4 = m("a",new A(JsonMap.ofJson("{\"f1\":\"a\",\"f2\":2}")));
+		var x4 = m("a",new A(JsonMap.ofText("{\"f1\":\"a\",\"f2\":2}")));
 		x4 = t.roundTrip(x4, Map.class, String.class, A.class);
 		assertBean(x4, "size,a{f1,f2}", "1,{a,2}");
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
index 229aef1b95..9fdded2b01 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/ParentProperty_RoundTripTest.java
@@ -35,7 +35,6 @@ class ParentProperty_RoundTripTest extends RoundTripTest_Base {
 	@ParameterizedTest
 	@MethodSource("testers")
 	void a01_parentProperty(RoundTrip_Tester t) throws Exception {
-		// @ParentProperty not supported for inline JSON5 beans in Markdown (see TODO-9-markdown-remaining-issues.md #9)
 		if (t.label.contains("Markdown"))
 			return;
 		var x = new ParentPropertyMethodContainer().init();
@@ -78,7 +77,7 @@ class ParentProperty_RoundTripTest extends RoundTripTest_Base {
 	@ParameterizedTest
 	@MethodSource("testers")
 	void a02_parentPropertyField(RoundTrip_Tester t) throws Exception {
-		// @ParentProperty not supported for inline JSON5 beans in Markdown (see TODO-9-markdown-remaining-issues.md #9)
+		// @ParentProperty not supported for inline JSON5 beans in Markdown (see work-item-9 markdown remaining issues, item 9)
 		if (t.label.contains("Markdown"))
 			return;
 		var x = new ParentPropertyFieldContainer().init();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/SimpleObjects_RoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/SimpleObjects_RoundTripTest.java
index aa7620456f..e0b2b494d0 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/SimpleObjects_RoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/SimpleObjects_RoundTripTest.java
@@ -450,7 +450,7 @@ class SimpleObjects_RoundTripTest extends RoundTripTest_Base {
 	void a51_jsonMap(RoundTrip_Tester t) throws Exception {
 		if (isRdf(t))
 			return;
-		var x = JsonMap.ofJson("{\"a\":\"b\",\"c\":123,\"d\":false,\"e\":null,\"f\":[123,\"abc\",true,false,null]}");
+		var x = JsonMap.ofText("{\"a\":\"b\",\"c\":123,\"d\":false,\"e\":null,\"f\":[123,\"abc\",true,false,null]}");
 		x = t.roundTrip(x);
 		assertEquals("b", x.get("a"));
 		assertEquals(123, ((Number)x.get("c")).intValue());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/TrimStrings_RoundTripTest.java b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/TrimStrings_RoundTripTest.java
index adbadc70d6..9c7faafe7a 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/TrimStrings_RoundTripTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/TrimStrings_RoundTripTest.java
@@ -54,8 +54,8 @@ class TrimStrings_RoundTripTest extends RoundTripTest_Base {
 		a = p2.parse(s.serialize(in), String.class);
 		assertEquals(json(a), json(e));
 
-		in = JsonMap.ofJson("{\" foo \": \" bar \"}");
-		e = JsonMap.ofJson("{\"foo\":\"bar\"}");
+		in = JsonMap.ofText("{\" foo \": \" bar \"}");
+		e = JsonMap.ofText("{\"foo\":\"bar\"}");
 		a = p.parse(s2.serialize(in), JsonMap.class);
 		assertEquals(json(a), json(e));
 		a = p2.parse(s.serialize(in), JsonMap.class);
@@ -89,16 +89,16 @@ class TrimStrings_RoundTripTest extends RoundTripTest_Base {
 		public A init1() throws Exception {
 			f1 = " f1 ";
 			f2 = a(" f2a ", " f2b ");
-			f3 = JsonList.ofJson("[\" f3a \",\" f3b \"]");
-			f4 = JsonMap.ofJson("{\" foo \":\" bar \"}");
+			f3 = JsonList.ofText("[\" f3a \",\" f3b \"]");
+			f4 = JsonMap.ofText("{\" foo \":\" bar \"}");
 			return this;
 		}
 
 		public A init2() throws Exception {
 			f1 = "f1";
 			f2 = a("f2a", "f2b");
-			f3 = JsonList.ofJson("[\"f3a\",\"f3b\"]");
-			f4 = JsonMap.ofJson("{\"foo\":\"bar\"}");
+			f3 = JsonList.ofText("[\"f3a\",\"f3b\"]");
+			f4 = JsonMap.ofText("{\"foo\":\"bar\"}");
 			return this;
 		}
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
index 5ce495f007..7b0929b4b7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/bean/jsonschema/JsonSchemaBeanGenerator_Test.java
@@ -96,7 +96,7 @@ class JsonSchemaBeanGenerator_Test extends TestBase {
 	}
 
 	@Test void a08_toBeanConversion() {
-		var map = new JsonMap(Json5Map.ofJson5("{type:'integer',format:'int32','$comment':'c',deprecated:true}"));
+		var map = new JsonMap(Json5Map.ofText("{type:'integer',format:'int32','$comment':'c',deprecated:true}"));
 		var bean = JsonSchemaBeanGenerator.toBean(map);
 		assertEquals("integer", bean.getTypeAsJsonType().toString().toLowerCase(Locale.ROOT));
 		assertEquals("int32", bean.getFormat());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborCompliance_Test.java b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborCompliance_Test.java
index c7e5212012..22fc4a79ef 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborCompliance_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborCompliance_Test.java
@@ -90,7 +90,7 @@ class CborCompliance_Test extends TestBase {
 	void g17_simpleArray() throws Exception { enc(ints(1, 2, 3), "83 01 02 03"); }
 
 	@Test
-	void g18_emptyMap() throws Exception { enc(JsonMap.ofJson("{}"), "A0"); }
+	void g18_emptyMap() throws Exception { enc(JsonMap.ofText("{}"), "A0"); }
 
 	@Test
 	void g19_false() throws Exception { enc(false, "F4"); }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborOutputStream_Test.java b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborOutputStream_Test.java
index b907df0be8..3666a90edc 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborOutputStream_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborOutputStream_Test.java
@@ -219,12 +219,12 @@ class CborOutputStream_Test extends TestBase {
 
 	@Test
 	void a29_emptyMap() throws Exception {
-		enc(JsonMap.ofJson("{}"), "A0");
+		enc(JsonMap.ofText("{}"), "A0");
 	}
 
 	@Test
 	void a30_shortMap() throws Exception {
-		enc(JsonMap.ofJson("{\"a\":1,\"b\":2}"), "A2 61 61 01 61 62 02");
+		enc(JsonMap.ofText("{\"a\":1,\"b\":2}"), "A2 61 61 01 61 62 02");
 	}
 
 	@Test
diff --git a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborRoundTrip_Test.java b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborRoundTrip_Test.java
index f3d21accb6..9c1f4a737f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborRoundTrip_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborRoundTrip_Test.java
@@ -130,7 +130,7 @@ class CborRoundTrip_Test extends TestBase {
 	void e13_emptyCollectionsRoundTrip() throws Exception {
 		var emptyList = CborParser.DEFAULT.parse(CborSerializer.DEFAULT.serialize(list()), JsonList.class);
 		assertTrue(emptyList.isEmpty());
-		var emptyMap = CborParser.DEFAULT.parse(CborSerializer.DEFAULT.serialize(JsonMap.ofJson("{}")), JsonMap.class);
+		var emptyMap = CborParser.DEFAULT.parse(CborSerializer.DEFAULT.serialize(JsonMap.ofText("{}")), JsonMap.class);
 		assertTrue(emptyMap.isEmpty());
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborSerializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborSerializer_Test.java
index efe6e9616b..a9f69d806b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/cbor/CborSerializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/cbor/CborSerializer_Test.java
@@ -55,7 +55,7 @@ class CborSerializer_Test extends TestBase {
 		test("a", "61 61");
 		test(ints(), "80");
 		test(ints(1, 2, 3), "83 01 02 03");
-		test(JsonMap.ofJson("{}"), "A0");
+		test(JsonMap.ofText("{}"), "A0");
 	}
 
 	public static class Person {
@@ -190,7 +190,7 @@ class CborSerializer_Test extends TestBase {
 
 	@Test
 	void c13_emptyBean() throws Exception {
-		var bytes = CborSerializer.DEFAULT.serialize(JsonMap.ofJson("{}"));
+		var bytes = CborSerializer.DEFAULT.serialize(JsonMap.ofText("{}"));
 		assertEquals("A0", toSpacedHex(bytes));
 		var parsed = CborParser.DEFAULT.parse(bytes, JsonMap.class);
 		assertTrue(parsed.isEmpty());
@@ -199,7 +199,7 @@ class CborSerializer_Test extends TestBase {
 	@Test
 	void c14_emptyCollections() throws Exception {
 		test(ints(), "80");
-		test(JsonMap.ofJson("{}"), "A0");
+		test(JsonMap.ofText("{}"), "A0");
 	}
 
 	@Test
diff --git a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BasicBeanStore_Test.java b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BasicBeanStore_Test.java
index 29cf2d1086..4c0a005e10 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BasicBeanStore_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/commons/inject/BasicBeanStore_Test.java
@@ -1525,10 +1525,6 @@ class BasicBeanStore_Test extends TestBase {
 		assertSame(cause, ex.getCause());
 	}
 
-	//------------------------------------------------------------------------------------------------
-	// Coverage gap fillers — targeted tests added during the TODO-24 coverage pass.
-	//------------------------------------------------------------------------------------------------
-
 	@Test
 	void z01_trackResolved_nullSupplierResult_isNotTracked() {
 		// A supplier that returns null exercises BasicBeanStore.trackResolved(null) — most easily
diff --git a/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
index e4db0e9820..09c9e7b640 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/httppart/OpenApiPartSerializer_Test.java
@@ -799,8 +799,8 @@ class OpenApiPartSerializer_Test extends TestBase {
 		var ps = tObject().allowEmptyValue().build();
 		assertEquals("f1=1,f2=2,f3=true", serialize(ps, new H1("1",2,true)));
 		assertEquals("", serialize(ps, new H1(null,null,null)));
-		assertEquals("f1=1,f2=2,f3=true", serialize(ps, Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")));
-		assertEquals("f1=null,f2=null,f3=null", serialize(ps, Json5Map.ofJson5("{f1:null,f2:null,f3:null}")));
+		assertEquals("f1=1,f2=2,f3=true", serialize(ps, Json5Map.ofText("{f1:'1',f2:2,f3:true}")));
+		assertEquals("f1=null,f2=null,f3=null", serialize(ps, Json5Map.ofText("{f1:null,f2:null,f3:null}")));
 		assertEquals("null", serialize(ps, null));
 	}
 
@@ -808,8 +808,8 @@ class OpenApiPartSerializer_Test extends TestBase {
 		var ps = T_OBJECT_UON;
 		assertEquals("(f1='1',f2=2,f3=true)", serialize(ps, new H1("1",2,true)));
 		assertEquals("()", serialize(ps, new H1(null,null,null)));
-		assertEquals("(f1='1',f2=2,f3=true)", serialize(ps, Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")));
-		assertEquals("(f1=null,f2=null,f3=null)", serialize(ps, Json5Map.ofJson5("{f1:null,f2:null,f3:null}")));
+		assertEquals("(f1='1',f2=2,f3=true)", serialize(ps, Json5Map.ofText("{f1:'1',f2:2,f3:true}")));
+		assertEquals("(f1=null,f2=null,f3=null)", serialize(ps, Json5Map.ofText("{f1:null,f2:null,f3:null}")));
 		assertEquals("null", serialize(ps, null));
 	}
 
@@ -817,30 +817,30 @@ class OpenApiPartSerializer_Test extends TestBase {
 		var ps = tArray(tObject().allowEmptyValue()).build();
 		assertEquals("f1=1\\,f2=2\\,f3=true,,null", serialize(ps, a(new H1("1",2,true),new H1(null,null,null),null)));
 		assertEquals("f1=1\\,f2=2\\,f3=true,,null", serialize(ps, l(new H1("1",2,true),new H1(null,null,null),null)));
-		assertEquals("f1=1\\,f2=2\\,f3=true,f1=null\\,f2=null\\,f3=null,null", serialize(ps, a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("f1=1\\,f2=2\\,f3=true,f1=null\\,f2=null\\,f3=null,null", serialize(ps, l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("f1=1\\,f2=2\\,f3=true,f1=1\\,f2=2\\,f3=true,null", serialize(ps, ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
-		assertEquals("f1=1\\,f2=2\\,f3=true,f1=1\\,f2=2\\,f3=true,null", serialize(ps, l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("f1=1\\,f2=2\\,f3=true,f1=null\\,f2=null\\,f3=null,null", serialize(ps, a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("f1=1\\,f2=2\\,f3=true,f1=null\\,f2=null\\,f3=null,null", serialize(ps, l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("f1=1\\,f2=2\\,f3=true,f1=1\\,f2=2\\,f3=true,null", serialize(ps, ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("f1=1\\,f2=2\\,f3=true,f1=1\\,f2=2\\,f3=true,null", serialize(ps, l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
 	}
 
 	@Test void h03_objectType_2d_pipes() throws Exception {
 		var ps = tArrayPipes(tObject().allowEmptyValue()).build();
 		assertEquals("f1=1,f2=2,f3=true||null", serialize(ps, a(new H1("1",2,true),new H1(null,null,null),null)));
 		assertEquals("f1=1,f2=2,f3=true||null", serialize(ps, l(new H1("1",2,true),new H1(null,null,null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=null,f2=null,f3=null|null", serialize(ps, a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=null,f2=null,f3=null|null", serialize(ps, l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true|null", serialize(ps, ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true|null", serialize(ps, l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=null,f2=null,f3=null|null", serialize(ps, a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=null,f2=null,f3=null|null", serialize(ps, l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true|null", serialize(ps, ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true|null", serialize(ps, l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
 	}
 
 	@Test void h04_objectType_2d_uon() throws Exception {
 		var ps = tArrayUon(tObject()).build();
 		assertEquals("@((f1='1',f2=2,f3=true),(),null)", serialize(ps, a(new H1("1",2,true),new H1(null,null,null),null)));
 		assertEquals("@((f1='1',f2=2,f3=true),(),null)", serialize(ps, l(new H1("1",2,true),new H1(null,null,null),null)));
-		assertEquals("@((f1='1',f2=2,f3=true),(f1=null,f2=null,f3=null),null)", serialize(ps, a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("@((f1='1',f2=2,f3=true),(f1=null,f2=null,f3=null),null)", serialize(ps, l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null)));
-		assertEquals("@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true),null)", serialize(ps, ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
-		assertEquals("@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true),null)", serialize(ps, l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("@((f1='1',f2=2,f3=true),(f1=null,f2=null,f3=null),null)", serialize(ps, a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("@((f1='1',f2=2,f3=true),(f1=null,f2=null,f3=null),null)", serialize(ps, l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null)));
+		assertEquals("@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true),null)", serialize(ps, ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
+		assertEquals("@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true),null)", serialize(ps, l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}"),null)));
 	}
 
 	@Test void h03_objectType_3d() throws Exception {
@@ -848,12 +848,12 @@ class OpenApiPartSerializer_Test extends TestBase {
 		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=3\\\\\\,f3=false,\\,null,null", serialize(ps, a(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=3\\\\\\,f3=false,\\,null,null", serialize(ps, l(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=3\\\\\\,f3=false,\\,null,null", serialize(ps, l(l(new H1("1",2,true),new H1("x",3,false)),l(new H1(null,null,null),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, a(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, a(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=x\\\\\\,f2=4\\\\\\,f3=false,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1\\\\\\,f2=2\\\\\\,f3=true\\,f1=1\\\\\\,f2=2\\\\\\,f3=true,\\,f1=null\\\\\\,f2=null\\\\\\,f3=null\\,null,null", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
 	}
 
 	@Test void h03_objectType_3d_ssvAndPipes() throws Exception {
@@ -863,12 +863,12 @@ class OpenApiPartSerializer_Test extends TestBase {
 		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=3,f3=false |null null", serialize(ps, a(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=3,f3=false |null null", serialize(ps, l(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=3,f3=false |null null", serialize(ps, l(l(new H1("1",2,true),new H1("x",3,false)),l(new H1(null,null,null),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, a(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, l(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, l(l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, a(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, l(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=x,f2=4,f3=false f1=null,f2=null,f3=null|null null", serialize(ps, l(l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("f1=1,f2=2,f3=true|f1=1,f2=2,f3=true |f1=null,f2=null,f3=null|null null", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
 	}
 
 	@Test void h03_objectType_3d_uon() throws Exception {
@@ -876,12 +876,12 @@ class OpenApiPartSerializer_Test extends TestBase {
 		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=3,f3=false)),@((),null),null)", serialize(ps, a(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=3,f3=false)),@((),null),null)", serialize(ps, l(a(new H1("1",2,true),new H1("x",3,false)),a(new H1(null,null,null),null),null)));
 		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=3,f3=false)),@((),null),null)", serialize(ps, l(l(new H1("1",2,true),new H1("x",3,false)),l(new H1(null,null,null),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, a(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, l(a(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, l(l(Json5Map.ofJson5("{f1:'1',f2:2,f3:true}"),Json5Map.ofJson5("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
-		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofJson5("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofJson5("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, a(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, l(a(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),a(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1=x,f2=4,f3=false)),@((f1=null,f2=null,f3=null),null),null)", serialize(ps, l(l(Json5Map.ofText("{f1:'1',f2:2,f3:true}"),Json5Map.ofText("{f1:'x',f2:4,f3:false}")),l(Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, a2(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, l(ao(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),ao(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
+		assertEquals("@(@((f1='1',f2=2,f3=true),(f1='1',f2=2,f3=true)),@((),(f1=null,f2=null,f3=null),null),null)", serialize(ps, l(l(new H1("1",2,true),Json5Map.ofText("{f1:'1',f2:2,f3:true}")),l(new H1(null,null,null),Json5Map.ofText("{f1:null,f2:null,f3:null}"),null),null)));
 	}
 
 	public static class H2 {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/json/Json_Test.java b/juneau-utest/src/test/java/org/apache/juneau/json/Json_Test.java
index 368f07213f..41993c1000 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/json/Json_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/json/Json_Test.java
@@ -62,7 +62,7 @@ class Json_Test extends TestBase{
 		var r = "\\w[\\w\\-\\.]{3,}\\w";
 		m.put("x", r);
 		assertEquals("{\"x\":\"\\\\w[\\\\w\\\\-\\\\.]{3,}\\\\w\"}", s2.serialize(m));
-		assertEquals(r, JsonMap.ofJson(s2.serialize(m)).getString("x"));
+		assertEquals(r, JsonMap.ofText(s2.serialize(m)).getString("x"));
 
 		// String = [foo\bar]
 		m.clear();
@@ -138,7 +138,7 @@ class Json_Test extends TestBase{
 	// Indentation
 	//====================================================================================================
 	@Test void a04_indentation() throws Exception {
-		var m = Json5Map.ofJson5("{J:{B:['c',{D:'e'},['f',{G:'h'},1,false]]},I:'j'}");
+		var m = Json5Map.ofText("{J:{B:['c',{D:'e'},['f',{G:'h'},1,false]]},I:'j'}");
 		var e = """
 			{
 				J: {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/json5/Json5List_Test.java b/juneau-utest/src/test/java/org/apache/juneau/json5/Json5List_Test.java
index 671cecca50..21b32ae3b0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/json5/Json5List_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/json5/Json5List_Test.java
@@ -39,8 +39,8 @@ class Json5List_Test extends TestBase {
 		assertEquals("[1,'two',true]", l.toString());
 	}
 
-	@Test void a03_factoryOfJson5() throws Exception {
-		var l = Json5List.ofJson5("[1,'two',true]");
+	@Test void a03_factoryOfText() throws Exception {
+		var l = Json5List.ofText("[1,'two',true]");
 		assertNotNull(l);
 		assertEquals(3, l.size());
 		assertEquals(1, l.getInt(0));
@@ -48,8 +48,8 @@ class Json5List_Test extends TestBase {
 		assertTrue(l.getBoolean(2));
 	}
 
-	@Test void a04_factoryOfJson5NullInput() throws Exception {
-		assertNull(Json5List.ofJson5((CharSequence)null));
+	@Test void a04_factoryOfTextNullInput() throws Exception {
+		assertNull(Json5List.ofText((CharSequence)null));
 	}
 
 	@Test void a05_factoryCreateAndAppend() {
@@ -139,7 +139,7 @@ class Json5List_Test extends TestBase {
 	@Test void a17_roundTripJson5() throws Exception {
 		var original = Json5List.of(1, "two", true);
 		var serialized = original.toString();
-		var parsed = Json5List.ofJson5(serialized);
+		var parsed = Json5List.ofText(serialized);
 		assertEquals(1, parsed.getInt(0));
 		assertEquals("two", parsed.getString(1));
 		assertTrue(parsed.getBoolean(2));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/json5/Json5Map_Test.java b/juneau-utest/src/test/java/org/apache/juneau/json5/Json5Map_Test.java
index 5b0d50b59c..2b07a3dc28 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/json5/Json5Map_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/json5/Json5Map_Test.java
@@ -31,7 +31,7 @@ import org.junit.jupiter.api.*;
  * These confirm that:
  * <ul>
  * 	<li>{@link Json5Map#toString()} produces JSON5 form.
- * 	<li>JSON5 default-parser constructors and {@code ofJson5} factories work.
+ * 	<li>JSON5 default-parser constructors and {@code ofText} factories work.
  * 	<li>Covariant overrides on the get/fluent surface return {@code Json5Map} / {@code Json5List}, not the neutral base.
  * 	<li>{@code writeTo(Writer)} uses {@link Json5Serializer#DEFAULT}.
  * </ul>
@@ -49,15 +49,15 @@ class Json5Map_Test extends TestBase {
 		assertEquals("{a:1,b:'two'}", m.toString());
 	}
 
-	@Test void a03_factoryOfJson5() throws Exception {
-		var m = Json5Map.ofJson5("{a:1,b:'two'}");
+	@Test void a03_factoryOfText() throws Exception {
+		var m = Json5Map.ofText("{a:1,b:'two'}");
 		assertNotNull(m);
 		assertEquals(1, m.getInt("a"));
 		assertEquals("two", m.getString("b"));
 	}
 
-	@Test void a04_factoryOfJson5NullInput() throws Exception {
-		assertNull(Json5Map.ofJson5((CharSequence)null));
+	@Test void a04_factoryOfTextNullInput() throws Exception {
+		assertNull(Json5Map.ofText((CharSequence)null));
 	}
 
 	@Test void a05_factoryOfCreateAppend() {
@@ -180,7 +180,7 @@ class Json5Map_Test extends TestBase {
 	@Test void a21_roundTripJson5() throws Exception {
 		var original = Json5Map.of("a", 1, "b", "two", "c", true);
 		var serialized = original.toString();
-		var parsed = Json5Map.ofJson5(serialized);
+		var parsed = Json5Map.ofText(serialized);
 		assertEquals(1, parsed.getInt("a"));
 		assertEquals("two", parsed.getString("b"));
 		assertTrue(parsed.getBoolean("c"));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/jsonschema/JsonSchemaGeneratorTest.java b/juneau-utest/src/test/java/org/apache/juneau/jsonschema/JsonSchemaGeneratorTest.java
index 56ae6ec6c7..62dbac8a21 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/jsonschema/JsonSchemaGeneratorTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/jsonschema/JsonSchemaGeneratorTest.java
@@ -1522,10 +1522,6 @@ class JsonSchemaGeneratorTest extends TestBase {
 		public int value;
 	}
 
-	//====================================================================================================
-	// summary (TODO-6, since 9.5.0)
-	//====================================================================================================
-
 	@Test void summary_typeLevel() throws Exception {
 		var s = JsonSchemaGenerator.DEFAULT.getSession();
 		assertBean(s.getSchema(SummaryBean.class), "type,summary,description", "object,A pet,Long description");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/jsonschema/annotation/SchemaAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/jsonschema/annotation/SchemaAnnotation_Test.java
index 29f145b9b5..e441d8193c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/jsonschema/annotation/SchemaAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/jsonschema/annotation/SchemaAnnotation_Test.java
@@ -447,10 +447,6 @@ class SchemaAnnotation_Test extends TestBase {
 		assertBean(mixed, "exclusiveMaximum,exclusiveMinimum,exclusiveMaximumValue,exclusiveMinimumValue", "false,false,100,0");
 	}
 
-	//------------------------------------------------------------------------------------------------------------------
-	// summary / su (TODO-6, since 9.5.0)
-	//------------------------------------------------------------------------------------------------------------------
-
 	@Test void g01_summary_asMap_emptyAnnotationOmitsKey() throws Exception {
 		assertFalse(SchemaAnnotation.asMap(SchemaAnnotation.DEFAULT).containsKey("summary"));
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/microservice/MicroserviceTestFixture.java b/juneau-utest/src/test/java/org/apache/juneau/microservice/MicroserviceTestFixture.java
index 931d4a6694..3d0b7f1033 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/microservice/MicroserviceTestFixture.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/microservice/MicroserviceTestFixture.java
@@ -26,7 +26,8 @@ import org.eclipse.jetty.server.*;
 import org.junit.jupiter.api.extension.*;
 
 /**
- * JUnit 5 extension that boots a {@link JettyMicroservice} on an ephemeral port for the duration of a test class.
+ * JUnit 5 extension that boots a {@link Microservice} backed by {@link JettyConfiguration} on an ephemeral port for
+ * the duration of a test class.
  *
  * <p>
  * Typical usage:
@@ -50,15 +51,14 @@ import org.junit.jupiter.api.extension.*;
  *
  * <h5 class='section'>How it works:</h5>
  * <ul>
- * 	<li>The fixture supplies its own {@link Server} {@link Bean} factory ({@link EphemeralJettyServerConfig}) that
- * 		binds to port {@code 0} (OS-assigned ephemeral port). User-supplied configurations should typically
- * 		contribute {@code @Bean Servlet} definitions (which the {@link JettyMicroservice} auto-mounts at
- * 		{@code @Rest(path=...)}); a user-supplied {@code @Bean Server} will conflict and is not supported here.
- * 	<li>{@link #beforeAll(ExtensionContext) beforeAll} builds the microservice, calls
- * 		{@link JettyMicroservice#createServer() createServer()}, and then
- * 		{@link JettyMicroservice#start() start()}.
- * 	<li>{@link #afterAll(ExtensionContext) afterAll} calls {@link JettyMicroservice#stop() stop()} so the bound
- * 		port is released and {@code @PreDestroy} hooks on bean-store beans fire.
+ * 	<li>The fixture installs {@link JettyConfiguration} plus a private {@link EphemeralJettyServerConfig}
+ * 		{@code @Configuration} that contributes a Jetty {@link Server} bound to port {@code 0} (OS-assigned
+ * 		ephemeral port). User-supplied configurations should typically contribute {@code @Bean Servlet} definitions
+ * 		which {@link JettyServerComponent} auto-mounts at {@code @Rest(path=...)}.
+ * 	<li>{@link #beforeAll(ExtensionContext) beforeAll} builds and {@link Microservice#start() starts} the service,
+ * 		then probes the bound port via the {@link Server}'s {@link ServerConnector}.
+ * 	<li>{@link #afterAll(ExtensionContext) afterAll} calls {@link Microservice#stop() stop()} so the bound port is
+ * 		released and {@code @PreDestroy} hooks on bean-store beans fire.
  * </ul>
  *
  * <p>
@@ -69,7 +69,7 @@ import org.junit.jupiter.api.extension.*;
 public final class MicroserviceTestFixture implements BeforeAllCallback, AfterAllCallback {
 
 	private final List<Class<?>> configurations = new ArrayList<>();
-	private JettyMicroservice microservice;
+	private Microservice microservice;
 	private URI rootUrl;
 
 	private MicroserviceTestFixture() {}
@@ -118,26 +118,38 @@ public final class MicroserviceTestFixture implements BeforeAllCallback, AfterAl
 	 *
 	 * @return The microservice.
 	 */
-	public JettyMicroservice getMicroservice() {
+	public Microservice getMicroservice() {
 		return microservice;
 	}
 
+	/**
+	 * Returns the {@link JettyServerComponent} bean for the running microservice.
+	 *
+	 * @return The Jetty server component.
+	 */
+	public JettyServerComponent getJettyServerComponent() {
+		return microservice.getBeanStore().getBean(JettyServerComponent.class).orElseThrow();
+	}
+
 	@Override
 	public void beforeAll(ExtensionContext ctx) throws Exception {
-		// The user's configurations come first (so @Bean Servlet methods are visible). Our default Server
-		// factory is registered last via configurations() — JettyMicroservice resolves Server from the bean
-		// store by type, and BeanStore.getBean(...) returns the first registered match.
+		// The user's configurations come first (so @Bean Servlet methods are visible); EphemeralJettyServerConfig
+		// supplies the Jetty Server bound to port 0; JettyConfiguration wires the lifecycle and remaining defaults.
+		// JettyServerComponent resolves Server from the bean store by type, and BeanStore.getBean(...) returns the
+		// first registered match (so a user-supplied @Bean Server would still win).
 		var classes = new ArrayList<>(configurations);
 		classes.add(EphemeralJettyServerConfig.class);
-		microservice = JettyMicroservice.create()
+		classes.add(JettyConfiguration.class);
+		microservice = Microservice.create()
 			.configurations(classes.toArray(new Class<?>[0]))
 			.build();
 		microservice.start();
-		// JettyMicroservice.getURI() uses InetAddress.getLocalHost().getHostName() (may resolve to a non-loopback
+		// JettyServerComponent.getURI() uses InetAddress.getLocalHost().getHostName() (may resolve to a non-loopback
 		// IP on some machines) and ServerConnector.getPort() (returns the configured port = 0, not the bound port).
 		// For test fixtures we want a deterministic loopback URL with the actual bound port.
+		var server = getJettyServerComponent().getServer();
 		var localPort = -1;
-		for (var c : microservice.getServer().getConnectors()) {
+		for (var c : server.getConnectors()) {
 			if (c instanceof ServerConnector sc) {
 				localPort = sc.getLocalPort();
 				break;
@@ -159,15 +171,15 @@ public final class MicroserviceTestFixture implements BeforeAllCallback, AfterAl
 	 *
 	 * <p>
 	 * The server has a single {@link ServerConnector} on port 0 (OS-assigned) and a single root
-	 * {@link ServletContextHandler} at context path {@code "/"}. {@code JettyMicroservice} discovers the handler
+	 * {@link ServletContextHandler} at context path {@code "/"}. {@link JettyServerComponent} discovers the handler
 	 * via the {@code "ServletContextHandler"} server attribute (the same convention used by {@code jetty.xml}).
 	 */
 	@Configuration
 	public static class EphemeralJettyServerConfig {
 
 		/**
-		 * Provides the bean-supplied Jetty {@link Server} that the microservice consumes during
-		 * {@link JettyMicroservice#createServer()}.
+		 * Provides the bean-supplied Jetty {@link Server} that {@link JettyServerComponent} consumes during
+		 * {@code onStart()}.
 		 *
 		 * @return A configured {@link Server} bound to port 0.
 		 */
diff --git a/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_Listener_Fanout_Test.java b/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_Listener_Fanout_Test.java
new file mode 100644
index 0000000000..04d8b9ce18
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/microservice/Microservice_Listener_Fanout_Test.java
@@ -0,0 +1,141 @@
+/*
+ * 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.microservice;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.config.event.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Verifies the {@link MicroserviceListener} fan-out behavior in {@link Microservice}.
+ *
+ * <p>
+ * Listeners contributed via {@link Microservice.Builder#listener(MicroserviceListener)} and any
+ * {@code @Bean MicroserviceListener} methods supplied by {@code @Configuration} classes are all invoked
+ * for each lifecycle event ({@link Microservice#start()}, {@link Microservice#stop()}, and config-change
+ * notifications). {@code onStart} runs in registration order; {@code onStop} runs in the reverse order.
+ */
+class Microservice_Listener_Fanout_Test extends TestBase {
+
+	/**
+	 * Recording listener that captures invocation order across instances.
+	 */
+	static final class Recorder implements MicroserviceListener {
+		final String name;
+		final List<String> log;
+
+		Recorder(String name, List<String> log) {
+			this.name = name;
+			this.log = log;
+		}
+
+		@Override
+		public void onStart(Microservice ms) {
+			log.add("start:" + name);
+		}
+
+		@Override
+		public void onStop(Microservice ms) {
+			log.add("stop:" + name);
+		}
+
+		@Override
+		public void onConfigChange(Microservice ms, ConfigEvents events) {
+			log.add("config:" + name);
+		}
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// A.  Fan-out: explicit + @Bean listeners both fire in registration order on start, reverse on stop.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	static final List<String> A_LOG = Collections.synchronizedList(new ArrayList<>());
+
+	@Configuration
+	static class A_TwoBeanListeners {
+		@Bean(name = "first") MicroserviceListener first() { return new Recorder("first", A_LOG); }
+		@Bean(name = "second") MicroserviceListener second() { return new Recorder("second", A_LOG); }
+	}
+
+	@Test void a01_fanOut_startThenReverseStop() throws Exception {
+		A_LOG.clear();
+		var explicit = new Recorder("explicit", A_LOG);
+		var ms = Microservice.create()
+			.listener(explicit)
+			.configurations(A_TwoBeanListeners.class)
+			.build();
+		try {
+			ms.start();
+		} finally {
+			ms.stop();
+		}
+		// All three listeners must fire once for start and once for stop.
+		assertEquals(6, A_LOG.size(), () -> "log=" + A_LOG);
+		var startLog = A_LOG.subList(0, 3);
+		var stopLog = A_LOG.subList(3, 6);
+		assertTrue(startLog.stream().allMatch(s -> s.startsWith("start:")));
+		assertTrue(stopLog.stream().allMatch(s -> s.startsWith("stop:")));
+		// Each listener fired in both phases.
+		for (var who : List.of("first", "second", "explicit")) {
+			assertTrue(startLog.contains("start:" + who), () -> "missing start:" + who + " in " + startLog);
+			assertTrue(stopLog.contains("stop:" + who), () -> "missing stop:" + who + " in " + stopLog);
+		}
+		// stop order is the exact reverse of start order — this is the lifecycle contract.
+		var startNames = startLog.stream().map(s -> s.substring("start:".length())).toList();
+		var stopNames = stopLog.stream().map(s -> s.substring("stop:".length())).toList();
+		var reversed = new ArrayList<>(startNames);
+		Collections.reverse(reversed);
+		assertEquals(reversed, stopNames, "stop order must be reverse of start order");
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// B.  Default listener: registered iff no listeners exist after configurations + builder are processed.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@Test void b01_noListeners_defaultBasicListenerRegistered() throws Exception {
+		var ms = Microservice.create().build();
+		try {
+			var listeners = ms.getBeanStore().getBeansOfType(MicroserviceListener.class).values();
+			assertEquals(1, listeners.size());
+			assertInstanceOf(BasicMicroserviceListener.class, listeners.iterator().next());
+		} finally {
+			ms.stop();
+		}
+	}
+
+	@Configuration
+	static class B_NamedListenerOnly {
+		@Bean(name = "only") MicroserviceListener only() { return new BasicMicroserviceListener(); }
+	}
+
+	@Test void b02_namedListenerPresent_noDefaultAdded() throws Exception {
+		var ms = Microservice.create().configurations(B_NamedListenerOnly.class).build();
+		try {
+			// Only the named listener should be present; no unnamed default added.
+			var listeners = ms.getBeanStore().getBeansOfType(MicroserviceListener.class);
+			assertEquals(1, listeners.size());
+			assertEquals("only", listeners.keySet().iterator().next());
+		} finally {
+			ms.stop();
+		}
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener_Test.java b/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener_Test.java
deleted file mode 100644
index bfe7f0b71c..0000000000
--- a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/BasicJettyMicroserviceListener_Test.java
+++ /dev/null
@@ -1,52 +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.microservice.jetty;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import org.apache.juneau.*;
-import org.junit.jupiter.api.*;
-
-/**
- * Tests for {@link BasicJettyMicroserviceListener}.
- */
-class BasicJettyMicroserviceListener_Test extends TestBase {
-
-	@Test void a01_instantiate() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener());
-	}
-
-	@Test void a02_onCreateServer() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener().onCreateServer(null));
-	}
-
-	@Test void a03_onPostStartServer() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener().onPostStartServer(null));
-	}
-
-	@Test void a04_onPostStopServer() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener().onPostStopServer(null));
-	}
-
-	@Test void a05_onStartServer() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener().onStartServer(null));
-	}
-
-	@Test void a06_onStopServer() {
-		assertDoesNotThrow(() -> new BasicJettyMicroserviceListener().onStopServer(null));
-	}
-}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Inject_Test.java b/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyConfiguration_Test.java
similarity index 67%
rename from juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Inject_Test.java
rename to juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyConfiguration_Test.java
index 5f3fcfe947..d15b8c0cbd 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Inject_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyConfiguration_Test.java
@@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.microservice.*;
 import org.apache.juneau.rest.annotation.*;
 import org.apache.juneau.rest.servlet.*;
 import org.eclipse.jetty.ee11.servlet.*;
@@ -30,40 +31,47 @@ import jakarta.servlet.*;
 import jakarta.servlet.http.*;
 
 /**
- * Tests for the inject-aware {@link JettyMicroservice} bootstrap.
+ * Tests for the inject-aware Jetty microservice bootstrap via {@link JettyConfiguration}.
  *
  * <p>
- * Verifies that <c>@Configuration</c>-supplied <c>JettyServerFactory</c> /
- * <c>JettyMicroserviceListener</c> / <c>Server</c> / <c>Servlet</c> beans are picked up,
- * that explicit builder calls take precedence, that auto-discovered <c>@Rest</c> servlets
- * are mounted at <c>@Rest(path=...)</c>, and that duplicate mount paths fail fast.
+ * Verifies that <c>@Configuration</c>-supplied <c>JettyServerFactory</c> / <c>JettyServerComponent</c>
+ * / <c>Server</c> / <c>Servlet</c> beans are picked up via the {@link JettyConfiguration} bean wiring,
+ * that auto-discovered <c>@Rest</c> servlets are mounted at <c>@Rest(path=...)</c>, and that duplicate
+ * mount paths fail fast.
  */
-class JettyMicroservice_Inject_Test extends TestBase {
+class JettyConfiguration_Test extends TestBase {
+
+	private static Microservice create(Class<?>... configurations) throws Exception {
+		var classes = new Class<?>[configurations.length + 1];
+		System.arraycopy(configurations, 0, classes, 0, configurations.length);
+		classes[configurations.length] = JettyConfiguration.class;
+		return Microservice.create().configurations(classes).build();
+	}
 
 	//-----------------------------------------------------------------------------------------------------------------
 	// A.  Bean store presence + self-registration of Jetty-specific beans.
 	//-----------------------------------------------------------------------------------------------------------------
 
-	@Test void a01_beanStore_containsJettyMicroservice() throws Exception {
-		var ms = JettyMicroservice.create().build();
+	@Test void a01_beanStore_containsMicroservice() throws Exception {
+		var ms = create();
 		try {
-			assertSame(ms, ms.getBeanStore().getBean(JettyMicroservice.class).orElse(null));
+			assertSame(ms, ms.getBeanStore().getBean(Microservice.class).orElse(null));
 		} finally {
 			ms.stop();
 		}
 	}
 
-	@Test void a02_beanStore_containsJettyListener() throws Exception {
-		var ms = JettyMicroservice.create().build();
+	@Test void a02_beanStore_containsJettyServerComponent() throws Exception {
+		var ms = create();
 		try {
-			assertNotNull(ms.getBeanStore().getBean(JettyMicroserviceListener.class).orElse(null));
+			assertNotNull(ms.getBeanStore().getBean(JettyServerComponent.class).orElse(null));
 		} finally {
 			ms.stop();
 		}
 	}
 
 	@Test void a03_beanStore_containsJettyServerFactory() throws Exception {
-		var ms = JettyMicroservice.create().build();
+		var ms = create();
 		try {
 			assertNotNull(ms.getBeanStore().getBean(JettyServerFactory.class).orElse(null));
 		} finally {
@@ -71,8 +79,17 @@ class JettyMicroservice_Inject_Test extends TestBase {
 		}
 	}
 
+	@Test void a04_beanStore_containsJettySettings() throws Exception {
+		var ms = create();
+		try {
+			assertNotNull(ms.getBeanStore().getBean(JettySettings.class).orElse(null));
+		} finally {
+			ms.stop();
+		}
+	}
+
 	//-----------------------------------------------------------------------------------------------------------------
-	// B.  Resolution priority: explicit builder > @Bean > default.
+	// B.  Resolution priority: user-supplied @Bean beats JettyConfiguration default.
 	//-----------------------------------------------------------------------------------------------------------------
 
 	static class TestFactory extends BasicJettyServerFactory {
@@ -84,23 +101,8 @@ class JettyMicroservice_Inject_Test extends TestBase {
 		@Bean JettyServerFactory factory() { return new TestFactory(); }
 	}
 
-	@Test void b01_explicitFactory_beatsConfigurationFactory() throws Exception {
-		var explicit = new BasicJettyServerFactory();
-		var ms = JettyMicroservice.create()
-			.jettyServerFactory(explicit)
-			.configurations(B_FactoryConfig.class)
-			.build();
-		try {
-			assertSame(explicit, ms.getBeanStore().getBean(JettyServerFactory.class).orElse(null));
-		} finally {
-			ms.stop();
-		}
-	}
-
-	@Test void b02_configurationFactory_usedWhenNoBuilderFactory() throws Exception {
-		var ms = JettyMicroservice.create()
-			.configurations(B_FactoryConfig.class)
-			.build();
+	@Test void b01_userFactory_beatsConfigurationDefault() throws Exception {
+		var ms = create(B_FactoryConfig.class);
 		try {
 			assertInstanceOf(TestFactory.class, ms.getBeanStore().getBean(JettyServerFactory.class).orElse(null));
 		} finally {
@@ -109,19 +111,15 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	}
 
 	@Configuration
-	static class B_ListenerConfig {
-		@Bean JettyMicroserviceListener listener() { return new BasicJettyMicroserviceListener() {}; }
+	static class B_SettingsConfig {
+		@Bean JettySettings jettySettings() { return JettySettings.create().ports(9999).build(); }
 	}
 
-	@Test void b03_configurationListener_usedWhenNoBuilderListener() throws Exception {
-		var ms = JettyMicroservice.create()
-			.configurations(B_ListenerConfig.class)
-			.build();
+	@Test void b02_userSettings_beatsConfigurationDefault() throws Exception {
+		var ms = create(B_SettingsConfig.class);
 		try {
-			// The contributed listener bean should be the resolved one.
-			var configured = ms.getBeanStore().getBeansOfType(JettyMicroserviceListener.class).values().iterator().next();
-			assertNotNull(configured);
-			assertNotSame(BasicJettyMicroserviceListener.class, configured.getClass()); // it's the anonymous subclass
+			var resolved = ms.getBeanStore().getBean(JettySettings.class).orElseThrow();
+			assertArrayEquals(new int[]{9999}, resolved.getPorts());
 		} finally {
 			ms.stop();
 		}
@@ -144,6 +142,9 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	static class C_AutoMountConfig {
 		@Bean Server jettyServer() {
 			var server = new Server();
+			var connector = new ServerConnector(server);
+			connector.setPort(0);
+			server.addConnector(connector);
 			var ctx = new ServletContextHandler();
 			ctx.setContextPath("/");
 			server.setAttribute("ServletContextHandler", ctx);
@@ -156,12 +157,10 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	}
 
 	@Test void c01_restServletAutoMounted_atRestPath() throws Exception {
-		var ms = JettyMicroservice.create()
-			.configurations(C_AutoMountConfig.class)
-			.build();
+		var ms = create(C_AutoMountConfig.class);
 		try {
-			ms.createServer();
-			var ctx = ms.getServletContextHandler();
+			ms.start();
+			var ctx = ms.getBeanStore().getBean(JettyServerComponent.class).orElseThrow().getServletContextHandler();
 			var mounted = false;
 			for (var h : ctx.getServletHandler().getServletMappings()) {
 				for (var p : h.getPathSpecs()) {
@@ -181,6 +180,9 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	static class C_NoRestServletConfig {
 		@Bean Server jettyServer() {
 			var server = new Server();
+			var connector = new ServerConnector(server);
+			connector.setPort(0);
+			server.addConnector(connector);
 			var ctx = new ServletContextHandler();
 			ctx.setContextPath("/");
 			server.setAttribute("ServletContextHandler", ctx);
@@ -191,15 +193,16 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	}
 
 	@Test void c02_servletWithoutRestAnnotation_isNotAutoMounted() throws Exception {
-		var ms = JettyMicroservice.create()
-			.configurations(C_NoRestServletConfig.class)
-			.build();
+		var ms = create(C_NoRestServletConfig.class);
 		try {
-			ms.createServer();
-			var ctx = ms.getServletContextHandler();
-			// PlainServlet has no @Rest, so it should NOT be auto-mounted.
-			assertEquals(0, ctx.getServletHandler().getServletMappings().length,
-				"plain Servlet without @Rest should not be auto-mounted");
+			ms.start();
+			var ctx = ms.getBeanStore().getBean(JettyServerComponent.class).orElseThrow().getServletContextHandler();
+			// PlainServlet has no @Rest, so its class name should not appear in any servlet mapping.
+			var plainName = PlainServlet.class.getName();
+			for (var holder : ctx.getServletHandler().getServlets()) {
+				assertNotEquals(plainName, holder.getClassName(),
+					"plain Servlet without @Rest should not be auto-mounted");
+			}
 		} finally {
 			ms.stop();
 		}
@@ -213,6 +216,9 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	static class D_CollisionConfig {
 		@Bean Server jettyServer() {
 			var server = new Server();
+			var connector = new ServerConnector(server);
+			connector.setPort(0);
+			server.addConnector(connector);
 			var ctx = new ServletContextHandler();
 			ctx.setContextPath("/");
 			server.setAttribute("ServletContextHandler", ctx);
@@ -224,11 +230,9 @@ class JettyMicroservice_Inject_Test extends TestBase {
 	}
 
 	@Test void d01_pathCollision_failsHard() throws Exception {
-		var ms = JettyMicroservice.create()
-			.configurations(D_CollisionConfig.class)
-			.build();
+		var ms = create(D_CollisionConfig.class);
 		try {
-			var ex = assertThrows(Exception.class, ms::createServer);
+			var ex = assertThrows(Exception.class, ms::start);
 			var root = ThrowableUtils.getCause(ex);
 			assertTrue(root.getMessage().contains("Servlet mount path collision"),
 				"expected path-collision error, got: " + root.getMessage());
diff --git a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Builder_Test.java b/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Builder_Test.java
deleted file mode 100644
index 2deafbcbf6..0000000000
--- a/juneau-utest/src/test/java/org/apache/juneau/microservice/jetty/JettyMicroservice_Builder_Test.java
+++ /dev/null
@@ -1,181 +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.microservice.jetty;
-
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.io.*;
-import java.util.*;
-
-import org.apache.juneau.*;
-import org.apache.juneau.microservice.console.*;
-import org.junit.jupiter.api.*;
-
-import jakarta.servlet.*;
-
-/**
- * Tests for {@link JettyMicroservice.Builder} methods.
- */
-class JettyMicroservice_Builder_Test extends TestBase {
-
-	@Test void a01_create() {
-		var b = JettyMicroservice.create();
-		assertNotNull(b);
-	}
-
-	@Test void a02_getInstance_noInstance() {
-		assertDoesNotThrow(() -> JettyMicroservice.getInstance());
-	}
-
-	@Test void a03_builder_args_array() {
-		// Just verify the call doesn't throw - field is package-private in parent
-		assertDoesNotThrow(() -> JettyMicroservice.create().args("--port", "8080"));
-	}
-
-	@Test void a04_builder_configName() {
-		// Just verify fluent return works - field is package-private in parent
-		var b = JettyMicroservice.create().configName("myapp.cfg");
-		assertNotNull(b);
-	}
-
-	@Test void a05_builder_consoleEnabled() {
-		var b = JettyMicroservice.create().consoleEnabled(true);
-		assertNotNull(b);
-	}
-
-	@Test void a06_builder_consoleCommands() {
-		var cmd = new ExitCommand();
-		var b = JettyMicroservice.create().consoleCommands(cmd);
-		assertNotNull(b);
-	}
-
-	@Test void a07_builder_listener() {
-		var listener = new BasicJettyMicroserviceListener();
-		var b = JettyMicroservice.create().listener(listener);
-		// listener2 is accessible from within the jetty package
-		assertSame(listener, b.listener2);
-	}
-
-	@Test void a08_builder_jettyServerFactory() {
-		var factory = new BasicJettyServerFactory();
-		var b = JettyMicroservice.create().jettyServerFactory(factory);
-		assertSame(factory, b.factory);
-	}
-
-	@Test void a09_builder_ports() {
-		var b = JettyMicroservice.create().ports(8080, 8443);
-		assertArrayEquals(new int[]{8080, 8443}, b.ports);
-	}
-
-	@Test void a10_builder_servlet_instance() {
-		var servlet = new MockServlet();
-		var b = JettyMicroservice.create().servlet(servlet, "/test");
-		assertTrue(b.servlets.containsKey("/test"));
-		assertSame(servlet, b.servlets.get("/test"));
-	}
-
-	@Test void a11_builder_servletAttribute_nameValue() {
-		var b = JettyMicroservice.create().servletAttribute("key1", "value1");
-		assertEquals("value1", b.servletAttributes.get("key1"));
-	}
-
-	@Test void a12_builder_servletAttribute_map() {
-		var b = JettyMicroservice.create().servletAttribute(Map.of("key2", "value2", "key3", "value3"));
-		assertEquals("value2", b.servletAttributes.get("key2"));
-		assertEquals("value3", b.servletAttributes.get("key3"));
-	}
-
-	@Test void a13_builder_servletAttribute_nullMap() {
-		var b = JettyMicroservice.create().servletAttribute(null);
-		assertTrue(b.servletAttributes.isEmpty());
-	}
-
-	@Test void a14_builder_servlets_map() {
-		var servlet = new MockServlet();
-		var b = JettyMicroservice.create().servlets(Map.of("/path", servlet));
-		assertSame(servlet, b.servlets.get("/path"));
-	}
-
-	@Test void a15_builder_servlets_nullMap() {
-		var b = JettyMicroservice.create().servlets(null);
-		assertTrue(b.servlets.isEmpty());
-	}
-
-	@Test void a16_builder_workingDir_file() {
-		assertDoesNotThrow(() -> JettyMicroservice.create().workingDir(new File("/tmp")));
-	}
-
-	@Test void a17_builder_workingDir_string() {
-		assertDoesNotThrow(() -> JettyMicroservice.create().workingDir("/tmp"));
-	}
-
-	@Test void a18_builder_copy() {
-		var b = JettyMicroservice.create()
-			.ports(9090)
-			.servletAttribute("attr1", "val1");
-		var copy = b.copy();
-		assertNotSame(b, copy);
-		assertArrayEquals(new int[]{9090}, copy.ports);
-		assertEquals("val1", copy.servletAttributes.get("attr1"));
-	}
-
-	@Test void a19_builder_jettyXml_reader() throws Exception {
-		var xml = "<Configure />";
-		var b = JettyMicroservice.create().jettyXml(new StringReader(xml), false);
-		assertEquals(xml, b.jettyXml);
-		assertEquals(Boolean.FALSE, b.jettyXmlResolveVars);
-	}
-
-	@Test void a20_builder_jettyXml_inputStream() throws Exception {
-		var xml = "<Configure />";
-		var b = JettyMicroservice.create().jettyXml(new ByteArrayInputStream(xml.getBytes()), false);
-		assertEquals(xml, b.jettyXml);
-	}
-
-	@Test void a21_builder_jettyXml_invalidType() {
-		assertThrows(RuntimeException.class, () -> JettyMicroservice.create().jettyXml(42, false));
-	}
-
-	@Test void a22_builder_jettyXml_resolveVars_true() throws Exception {
-		var xml = "<Configure />";
-		var b = JettyMicroservice.create().jettyXml(new StringReader(xml), true);
-		assertEquals(Boolean.TRUE, b.jettyXmlResolveVars);
-	}
-
-	@Test void a23_builder_console() {
-		var reader = new java.util.Scanner(System.in);
-		var writer = new PrintWriter(System.out);
-		assertDoesNotThrow(() -> JettyMicroservice.create().console(reader, writer));
-	}
-
-	@Test void a24_builder_logger() {
-		assertDoesNotThrow(() -> JettyMicroservice.create().logger(java.util.logging.Logger.getLogger("test")));
-	}
-
-	@Test void a25_builder_manifest_null() {
-		assertDoesNotThrow(() -> JettyMicroservice.create().manifest((Object)null));
-	}
-
-	// Minimal no-op servlet for testing.
-	static class MockServlet implements Servlet {
-		@Override public void init(ServletConfig config) {}
-		@Override public ServletConfig getServletConfig() { return null; }
-		@Override public void service(ServletRequest req, ServletResponse res) {}
-		@Override public String getServletInfo() { return "mock"; }
-		@Override public void destroy() {}
-	}
-}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/msgpack/MsgPackSerializerTest.java b/juneau-utest/src/test/java/org/apache/juneau/msgpack/MsgPackSerializerTest.java
index b65bffa494..a3e029a4dc 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/msgpack/MsgPackSerializerTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/msgpack/MsgPackSerializerTest.java
@@ -206,10 +206,10 @@ class MsgPackSerializerTest extends TestBase {
 		//		* odd elements in objects are keys of a map
 		//		* the next element of a key is its associated value
 
-		test(Json5Map.ofJson5("{}"), "80");
-		test(Json5Map.ofJson5("{1:1}"), "81 A1 31 01");
-		test(Json5Map.ofJson5("{1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1,a:1,b:1,c:1,d:1,e:1,f:1}"), "8F A1 31 01 A1 32 01 A1 33 01 A1 34 01 A1 35 01 A1 36 01 A1 37 01 A1 38 01 A1 39 01 A1 61 01 A1 62 01 A1 63 01 A1 64 01 A1 65 01 A1 66 01");
-		test(Json5Map.ofJson5("{1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1,a:1,b:1,c:1,d:1,e:1,f:1,g:1}"), "DE 00 10 A1 31 01 A1 32 01 A1 33 01 A1 34 01 A1 35 01 A1 36 01 A1 37 01 A1 38 01 A1 39 01 A1 61 01 A1 62 01 A1 63 01 A1 64 01 A1 65 01 A1 66 01 A1 67 01");
+		test(Json5Map.ofText("{}"), "80");
+		test(Json5Map.ofText("{1:1}"), "81 A1 31 01");
+		test(Json5Map.ofText("{1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1,a:1,b:1,c:1,d:1,e:1,f:1}"), "8F A1 31 01 A1 32 01 A1 33 01 A1 34 01 A1 35 01 A1 36 01 A1 37 01 A1 38 01 A1 39 01 A1 61 01 A1 62 01 A1 63 01 A1 64 01 A1 65 01 A1 66 01");
+		test(Json5Map.ofText("{1:1,2:1,3:1,4:1,5:1,6:1,7:1,8:1,9:1,a:1,b:1,c:1,d:1,e:1,f:1,g:1}"), "DE 00 10 A1 31 01 A1 32 01 A1 33 01 A1 34 01 A1 35 01 A1 36 01 A1 37 01 A1 38 01 A1 39 01 A1 61 01 A1 62 01 A1 63 01 A1 64 01 A1 65 01 A1 66 01 A1 67 01");
 	}
 
 	public static class Person {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
index 71d4d0e14f..d1448ace77 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/objecttools/ObjectRest_Test.java
@@ -174,11 +174,11 @@ class ObjectRest_Test extends TestBase {
 		assertEquals("{}", s);
 
 		// Make sure doing a PUT against "" or "/" replaces the root object.
-		var m2 = Json5Map.ofJson5("{x:1}");
+		var m2 = Json5Map.ofText("{x:1}");
 		model.put("", m2);
 		s = model.get("").toString();
 		assertEquals("{x:1}", s);
-		m2 = Json5Map.ofJson5("{x:2}");
+		m2 = Json5Map.ofText("{x:2}");
 		model.put("/", m2);
 		s = model.get("").toString();
 		assertEquals("{x:2}", s);
@@ -325,8 +325,8 @@ class ObjectRest_Test extends TestBase {
 	//====================================================================================================
 	@Test void f01_getMethods() throws Exception {
 		var model = ObjectRest.create(new A());
-		var l = Json5List.ofJson5("[{a:'b'}]");
-		var m = Json5Map.ofJson5("{a:'b'}");
+		var l = Json5List.ofText("[{a:'b'}]");
+		var m = Json5Map.ofText("{a:'b'}");
 		var jm = new JsonMap(m);
 		var jl = new JsonList(l);
 
@@ -719,10 +719,10 @@ class ObjectRest_Test extends TestBase {
 			f3a = 3L;
 			f4a = true;
 			try {
-				f5 = Json5Map.ofJson5("{f5a:'a'}");
-				f6 = Json5List.ofJson5("[{f6a:'a'}]");
-				f7 = Json5Map.ofJson5("{f5a:'a'}");
-				f8 = Json5List.ofJson5("[{f6a:'a'}]");
+				f5 = Json5Map.ofText("{f5a:'a'}");
+				f6 = Json5List.ofText("[{f6a:'a'}]");
+				f7 = Json5Map.ofText("{f5a:'a'}");
+				f8 = Json5List.ofText("[{f6a:'a'}]");
 			} catch (ParseException e) {
 				throw new RuntimeException(e);
 			}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestInit_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestInit_Test.java
index 2420b59671..533cf381cd 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestInit_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestInit_Test.java
@@ -26,16 +26,6 @@ import jakarta.servlet.*;
 
 class RestInit_Test extends TestBase {
 
-	//------------------------------------------------------------------------------------------------------------------
-	// @RestInit
-	//
-	// Pre-9.5 this test also covered `@RestInit public void initX(RestContext.Builder b)` — the framework
-	// injected the in-flight RestContext.Builder so user code could imperatively configure the context.
-	// That protocol was deleted in TODO-16 Phase C-3 (Builder is no longer a user-visible bean), so the
-	// `init1c(RestContext.Builder)` cases were dropped along with it. The remaining cases still cover
-	// no-arg invocation, ServletConfig injection, and inheritance / override ordering.
-	//------------------------------------------------------------------------------------------------------------------
-
 	@Rest(children={A_Super.class,A_Sub.class})
 	public static class A {}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
index 1203a338b5..af55f150fb 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/Rest_BeanCreatorOverrides_Test.java
@@ -35,10 +35,6 @@ import org.junit.jupiter.api.*;
  * walks the `@Rest` hierarchy via `getRestAnnotationsForProperty(...)` and picks the first
  * non-{@code Void} class. This test exercises the "non-Void annotation wins" branch that was
  * previously uncovered by tests.
- *
- * <p>
- * See {@code todo/TODO-16-restcontext-memoized-fields.md} (Decision #21) for the coverage
- * mandate.
  */
 class Rest_BeanCreatorOverrides_Test extends TestBase {
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/uon/UonSerializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/uon/UonSerializer_Test.java
index a0e65bd68d..1dbff17c94 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/uon/UonSerializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/uon/UonSerializer_Test.java
@@ -43,26 +43,26 @@ class UonSerializer_Test {
 		assertEquals("a", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{a:'a'}");
+		t = Json5Map.ofText("{a:'a'}");
 		assertEquals("(a=a)", s.serialize(t));
 		assertEquals("(a=a)", su.serialize(t));
 		assertEquals("(\n\ta=a\n)", sr.serialize(t));
 
 		// Simple map
 		// Top level
-		t = Json5Map.ofJson5("{a:'b',c:123,d:false,e:true,f:null}");
+		t = Json5Map.ofText("{a:'b',c:123,d:false,e:true,f:null}");
 		assertEquals("(a=b,c=123,d=false,e=true,f=null)", s.serialize(t));
 		assertEquals("(a=b,c=123,d=false,e=true,f=null)", su.serialize(t));
 		assertEquals("(\n\ta=b,\n\tc=123,\n\td=false,\n\te=true,\n\tf=null\n)", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{a:{a:'b',c:123,d:false,e:true,f:null}}");
+		t = Json5Map.ofText("{a:{a:'b',c:123,d:false,e:true,f:null}}");
 		assertEquals("(a=(a=b,c=123,d=false,e=true,f=null))", s.serialize(t));
 		assertEquals("(a=(a=b,c=123,d=false,e=true,f=null))", su.serialize(t));
 		assertEquals("(\n\ta=(\n\t\ta=b,\n\t\tc=123,\n\t\td=false,\n\t\te=true,\n\t\tf=null\n\t)\n)", sr.serialize(t));
 
 		// Simple map with primitives as literals
-		t = Json5Map.ofJson5("{a:'b',c:'123',d:'false',e:'true',f:'null'}");
+		t = Json5Map.ofText("{a:'b',c:'123',d:'false',e:'true',f:'null'}");
 		assertEquals("(a=b,c='123',d='false',e='true',f='null')", s.serialize(t));
 		assertEquals("(a=b,c='123',d='false',e='true',f='null')", su.serialize(t));
 		assertEquals("(\n\ta=b,\n\tc='123',\n\td='false',\n\te='true',\n\tf='null'\n)", sr.serialize(t));
@@ -76,13 +76,13 @@ class UonSerializer_Test {
 		assertEquals("null", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{null:null}");
+		t = Json5Map.ofText("{null:null}");
 		assertEquals("(null=null)", s.serialize(t));
 		assertEquals("(null=null)", su.serialize(t));
 		assertEquals("(\n\tnull=null\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{null:{null:null}}");
+		t = Json5Map.ofText("{null:{null:null}}");
 		assertEquals("(null=(null=null))", s.serialize(t));
 		assertEquals("(null=(null=null))", su.serialize(t));
 		assertEquals("(\n\tnull=(\n\t\tnull=null\n\t)\n)", sr.serialize(t));
@@ -95,7 +95,7 @@ class UonSerializer_Test {
 		assertEquals("@()", sr.serialize(t));
 
 		// 2nd level in map
-		t = Json5Map.ofJson5("{x:[]}");
+		t = Json5Map.ofText("{x:[]}");
 		assertEquals("(x=@())", s.serialize(t));
 		assertEquals("(x=@())", su.serialize(t));
 		assertEquals("(\n\tx=@()\n)", sr.serialize(t));
@@ -114,7 +114,7 @@ class UonSerializer_Test {
 		assertEquals("@(\n\t''\n)", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:['']}");
+		t = Json5Map.ofText("{x:['']}");
 		assertEquals("(x=@(''))", s.serialize(t));
 		assertEquals("(x=@(''))", su.serialize(t));
 		assertEquals("(\n\tx=@(\n\t\t''\n\t)\n)", sr.serialize(t));
@@ -133,7 +133,7 @@ class UonSerializer_Test {
 		assertEquals("\u0000", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'\u0000':'\u0000'}");
+		t = Json5Map.ofText("{'\u0000':'\u0000'}");
 		assertEquals("(%00=%00)", s.serialize(t));
 		assertEquals("(\u0000=\u0000)", su.serialize(t));
 		assertEquals("(\n\t\u0000=\u0000\n)", sr.serialize(t));
@@ -146,7 +146,7 @@ class UonSerializer_Test {
 		assertEquals("false", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:false}");
+		t = Json5Map.ofText("{x:false}");
 		assertEquals("(x=false)", s.serialize(t));
 		assertEquals("(x=false)", su.serialize(t));
 		assertEquals("(\n\tx=false\n)", sr.serialize(t));
@@ -159,7 +159,7 @@ class UonSerializer_Test {
 		assertEquals("123", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:123}");
+		t = Json5Map.ofText("{x:123}");
 		assertEquals("(x=123)", s.serialize(t));
 		assertEquals("(x=123)", su.serialize(t));
 		assertEquals("(\n\tx=123\n)", sr.serialize(t));
@@ -172,7 +172,7 @@ class UonSerializer_Test {
 		assertEquals("x;/?:@-_.!*~'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:'x;/?:@-_.!*\\''}");
+		t = Json5Map.ofText("{x:'x;/?:@-_.!*\\''}");
 		assertEquals("(x=x;/?:@-_.!*~')", s.serialize(t));
 		assertEquals("(x=x;/?:@-_.!*~')", su.serialize(t));
 		assertEquals("(\n\tx=x;/?:@-_.!*~'\n)", sr.serialize(t));
@@ -185,7 +185,7 @@ class UonSerializer_Test {
 		assertEquals("x{}|\\^[]`<>#%\"&+", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'x{}|\\\\^[]`<>#%\"&+':'x{}|\\\\^[]`<>#%\"&+'}");
+		t = Json5Map.ofText("{'x{}|\\\\^[]`<>#%\"&+':'x{}|\\\\^[]`<>#%\"&+'}");
 		assertEquals("(x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B)", s.serialize(t));
 		assertEquals("(x{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+)", su.serialize(t));
 		assertEquals("(\n\tx{}|\\^[]`<>#%\"&+=x{}|\\^[]`<>#%\"&+\n)", sr.serialize(t));
@@ -199,14 +199,14 @@ class UonSerializer_Test {
 
 		// 2nd level
 		// Note behavior on serializeParams() is different since 2nd-level is top level.
-		t = Json5Map.ofJson5("{'x$,()~':'x$,()~'}");
+		t = Json5Map.ofText("{'x$,()~':'x$,()~'}");
 		assertEquals("('x$,()~~'='x$,()~~')", s.serialize(t));
 		assertEquals("('x$,()~~'='x$,()~~')", su.serialize(t));
 		assertEquals("(\n\t'x$,()~~'='x$,()~~'\n)", sr.serialize(t));
 
 		// 3rd level
 		// Note behavior on serializeParams().
-		t = Json5Map.ofJson5("{'x$,()~':{'x$,()~':'x$,()~'}}");
+		t = Json5Map.ofText("{'x$,()~':{'x$,()~':'x$,()~'}}");
 		assertEquals("('x$,()~~'=('x$,()~~'='x$,()~~'))", s.serialize(t));
 		assertEquals("('x$,()~~'=('x$,()~~'='x$,()~~'))", su.serialize(t));
 		assertEquals("(\n\t'x$,()~~'=(\n\t\t'x$,()~~'='x$,()~~'\n\t)\n)", sr.serialize(t));
@@ -220,13 +220,13 @@ class UonSerializer_Test {
 		assertEquals("'x='", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'x=':'x='}");
+		t = Json5Map.ofText("{'x=':'x='}");
 		assertEquals("('x='='x=')", s.serialize(t));
 		assertEquals("('x='='x=')", su.serialize(t));
 		assertEquals("(\n\t'x='='x='\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'x=':{'x=':'x='}}");
+		t = Json5Map.ofText("{'x=':{'x=':'x='}}");
 		assertEquals("('x='=('x='='x='))", s.serialize(t));
 		assertEquals("('x='=('x='='x='))", su.serialize(t));
 		assertEquals("(\n\t'x='=(\n\t\t'x='='x='\n\t)\n)", sr.serialize(t));
@@ -239,7 +239,7 @@ class UonSerializer_Test {
 		assertEquals("'()'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'()':'()'}");
+		t = Json5Map.ofText("{'()':'()'}");
 		assertEquals("('()'='()')", s.serialize(t));
 		assertEquals("('()'='()')", su.serialize(t));
 		assertEquals("(\n\t'()'='()'\n)", sr.serialize(t));
@@ -252,7 +252,7 @@ class UonSerializer_Test {
 		assertEquals("$a", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{$a:'$a'}");
+		t = Json5Map.ofText("{$a:'$a'}");
 		assertEquals("($a=$a)", s.serialize(t));
 		assertEquals("($a=$a)", su.serialize(t));
 		assertEquals("(\n\t$a=$a\n)", sr.serialize(t));
@@ -265,13 +265,13 @@ class UonSerializer_Test {
 		assertEquals("''", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'':''}");
+		t = Json5Map.ofText("{'':''}");
 		assertEquals("(''='')", s.serialize(t));
 		assertEquals("(''='')", su.serialize(t));
 		assertEquals("(\n\t''=''\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'':{'':''}}");
+		t = Json5Map.ofText("{'':{'':''}}");
 		assertEquals("(''=(''=''))", s.serialize(t));
 		assertEquals("(''=(''=''))", su.serialize(t));
 		assertEquals("(\n\t''=(\n\t\t''=''\n\t)\n)", sr.serialize(t));
@@ -284,13 +284,13 @@ class UonSerializer_Test {
 		assertEquals("'\n'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'\n':'\n'}");
+		t = Json5Map.ofText("{'\n':'\n'}");
 		assertEquals("('%0A'='%0A')", s.serialize(t));
 		assertEquals("('\n'='\n')", su.serialize(t));
 		assertEquals("(\n\t'\n'='\n'\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'\n':{'\n':'\n'}}");
+		t = Json5Map.ofText("{'\n':{'\n':'\n'}}");
 		assertEquals("('%0A'=('%0A'='%0A'))", s.serialize(t));
 		assertEquals("('\n'=('\n'='\n'))", su.serialize(t));
 		assertEquals("(\n\t'\n'=(\n\t\t'\n'='\n'\n\t)\n)", sr.serialize(t));
@@ -309,13 +309,13 @@ class UonSerializer_Test {
 		assertEquals("¢", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'¢':'¢'}");
+		t = Json5Map.ofText("{'¢':'¢'}");
 		assertEquals("(%C2%A2=%C2%A2)", s.serialize(t));
 		assertEquals("(¢=¢)", su.serialize(t));
 		assertEquals("(\n\t¢=¢\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'¢':{'¢':'¢'}}");
+		t = Json5Map.ofText("{'¢':{'¢':'¢'}}");
 		assertEquals("(%C2%A2=(%C2%A2=%C2%A2))", s.serialize(t));
 		assertEquals("(¢=(¢=¢))", su.serialize(t));
 		assertEquals("(\n\t¢=(\n\t\t¢=¢\n\t)\n)", sr.serialize(t));
@@ -328,13 +328,13 @@ class UonSerializer_Test {
 		assertEquals("€", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'€':'€'}");
+		t = Json5Map.ofText("{'€':'€'}");
 		assertEquals("(%E2%82%AC=%E2%82%AC)", s.serialize(t));
 		assertEquals("(€=€)", su.serialize(t));
 		assertEquals("(\n\t€=€\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'€':{'€':'€'}}");
+		t = Json5Map.ofText("{'€':{'€':'€'}}");
 		assertEquals("(%E2%82%AC=(%E2%82%AC=%E2%82%AC))", s.serialize(t));
 		assertEquals("(€=(€=€))", su.serialize(t));
 		assertEquals("(\n\t€=(\n\t\t€=€\n\t)\n)", sr.serialize(t));
@@ -347,13 +347,13 @@ class UonSerializer_Test {
 		assertEquals("𤭢", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'𤭢':'𤭢'}");
+		t = Json5Map.ofText("{'𤭢':'𤭢'}");
 		assertEquals("(%F0%A4%AD%A2=%F0%A4%AD%A2)", s.serialize(t));
 		assertEquals("(𤭢=𤭢)", su.serialize(t));
 		assertEquals("(\n\t𤭢=𤭢\n)", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'𤭢':{'𤭢':'𤭢'}}");
+		t = Json5Map.ofText("{'𤭢':{'𤭢':'𤭢'}}");
 		assertEquals("(%F0%A4%AD%A2=(%F0%A4%AD%A2=%F0%A4%AD%A2))", s.serialize(t));
 		assertEquals("(𤭢=(𤭢=𤭢))", su.serialize(t));
 		assertEquals("(\n\t𤭢=(\n\t\t𤭢=𤭢\n\t)\n)", sr.serialize(t));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializer_Test.java b/juneau-utest/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializer_Test.java
index 0138d753ce..e5bd922856 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializer_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/urlencoding/UrlEncodingSerializer_Test.java
@@ -44,23 +44,23 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=a", s.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{a:'a'}");
+		t = Json5Map.ofText("{a:'a'}");
 		assertEquals("a=a", s.serialize(t));
 		assertEquals("a=a", sr.serialize(t));
 
 		// Simple map
 		// Top level
-		t = Json5Map.ofJson5("{a:'b',c:123,d:false,e:true,f:null}");
+		t = Json5Map.ofText("{a:'b',c:123,d:false,e:true,f:null}");
 		assertEquals("a=b&c=123&d=false&e=true&f=null", s.serialize(t));
 		assertEquals("a=b\n&c=123\n&d=false\n&e=true\n&f=null", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{a:{a:'b',c:123,d:false,e:true,f:null}}");
+		t = Json5Map.ofText("{a:{a:'b',c:123,d:false,e:true,f:null}}");
 		assertEquals("a=(a=b,c=123,d=false,e=true,f=null)", s.serialize(t));
 		assertEquals("a=(\n\ta=b,\n\tc=123,\n\td=false,\n\te=true,\n\tf=null\n)", sr.serialize(t));
 
 		// Simple map with primitives as literals
-		t = Json5Map.ofJson5("{a:'b',c:'123',d:'false',e:'true',f:'null'}");
+		t = Json5Map.ofText("{a:'b',c:'123',d:'false',e:'true',f:'null'}");
 		assertEquals("a=b&c='123'&d='false'&e='true'&f='null'", s.serialize(t));
 		assertEquals("a=b\n&c='123'\n&d='false'\n&e='true'\n&f='null'", sr.serialize(t));
 
@@ -72,12 +72,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=null", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{null:null}");
+		t = Json5Map.ofText("{null:null}");
 		assertEquals("null=null", s.serialize(t));
 		assertEquals("null=null", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{null:{null:null}}");
+		t = Json5Map.ofText("{null:{null:null}}");
 		assertEquals("null=(null=null)", s.serialize(t));
 		assertEquals("null=(\n\tnull=null\n)", sr.serialize(t));
 
@@ -88,7 +88,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("", sr.serialize(t));
 
 		// 2nd level in map
-		t = Json5Map.ofJson5("{x:[]}");
+		t = Json5Map.ofText("{x:[]}");
 		assertEquals("x=@()", s.serialize(t));
 		assertEquals("x=@()", sr.serialize(t));
 
@@ -104,7 +104,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("0=''", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:['']}");
+		t = Json5Map.ofText("{x:['']}");
 		assertEquals("x=@('')", s.serialize(t));
 		assertEquals("x=@(\n\t''\n)", sr.serialize(t));
 
@@ -120,7 +120,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=%00", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'\u0000':'\u0000'}");
+		t = Json5Map.ofText("{'\u0000':'\u0000'}");
 		assertEquals("%00=%00", s.serialize(t));
 		assertEquals("%00=%00", sr.serialize(t));
 
@@ -131,7 +131,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=false", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:false}");
+		t = Json5Map.ofText("{x:false}");
 		assertEquals("x=false", s.serialize(t));
 		assertEquals("x=false", sr.serialize(t));
 
@@ -142,7 +142,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=123", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:123}");
+		t = Json5Map.ofText("{x:123}");
 		assertEquals("x=123", s.serialize(t));
 		assertEquals("x=123", sr.serialize(t));
 
@@ -153,7 +153,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=x;/?:@-_.!*~'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{x:'x;/?:@-_.!*\\''}");
+		t = Json5Map.ofText("{x:'x;/?:@-_.!*\\''}");
 		assertEquals("x=x;/?:@-_.!*~'", s.serialize(t));
 		assertEquals("x=x;/?:@-_.!*~'", sr.serialize(t));
 
@@ -164,7 +164,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'x{}|\\\\^[]`<>#%\"&+':'x{}|\\\\^[]`<>#%\"&+'}");
+		t = Json5Map.ofText("{'x{}|\\\\^[]`<>#%\"&+':'x{}|\\\\^[]`<>#%\"&+'}");
 		assertEquals("x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B", s.serialize(t));
 		assertEquals("x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B=x%7B%7D%7C%5C%5E%5B%5D%60%3C%3E%23%25%22%26%2B", sr.serialize(t));
 
@@ -175,12 +175,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value='x$,()~~'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'x$,()~':'x$,()~'}");
+		t = Json5Map.ofText("{'x$,()~':'x$,()~'}");
 		assertEquals("'x$,()~~'='x$,()~~'", s.serialize(t));
 		assertEquals("'x$,()~~'='x$,()~~'", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'x$,()~':{'x$,()~':'x$,()~'}}");
+		t = Json5Map.ofText("{'x$,()~':{'x$,()~':'x$,()~'}}");
 		assertEquals("'x$,()~~'=('x$,()~~'='x$,()~~')", s.serialize(t));
 		assertEquals("'x$,()~~'=(\n\t'x$,()~~'='x$,()~~'\n)", sr.serialize(t));
 
@@ -192,12 +192,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value='x='", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'x=':'x='}");
+		t = Json5Map.ofText("{'x=':'x='}");
 		assertEquals("'x%3D'='x='", s.serialize(t));
 		assertEquals("'x%3D'='x='", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'x=':{'x=':'x='}}");
+		t = Json5Map.ofText("{'x=':{'x=':'x='}}");
 		assertEquals("'x%3D'=('x='='x=')", s.serialize(t));
 		assertEquals("'x%3D'=(\n\t'x='='x='\n)", sr.serialize(t));
 
@@ -208,7 +208,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value='()'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'()':'()'}");
+		t = Json5Map.ofText("{'()':'()'}");
 		assertEquals("'()'='()'", s.serialize(t));
 		assertEquals("'()'='()'", sr.serialize(t));
 
@@ -219,7 +219,7 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=$a", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{$a:'$a'}");
+		t = Json5Map.ofText("{$a:'$a'}");
 		assertEquals("$a=$a", s.serialize(t));
 		assertEquals("$a=$a", sr.serialize(t));
 
@@ -230,12 +230,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=''", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'':''}");
+		t = Json5Map.ofText("{'':''}");
 		assertEquals("''=''", s.serialize(t));
 		assertEquals("''=''", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'':{'':''}}");
+		t = Json5Map.ofText("{'':{'':''}}");
 		assertEquals("''=(''='')", s.serialize(t));
 		assertEquals("''=(\n\t''=''\n)", sr.serialize(t));
 
@@ -246,12 +246,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value='%0A'", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'\n':'\n'}");
+		t = Json5Map.ofText("{'\n':'\n'}");
 		assertEquals("'%0A'='%0A'", s.serialize(t));
 		assertEquals("'%0A'='%0A'", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'\n':{'\n':'\n'}}");
+		t = Json5Map.ofText("{'\n':{'\n':'\n'}}");
 		assertEquals("'%0A'=('%0A'='%0A')", s.serialize(t));
 		assertEquals("'%0A'=(\n\t'%0A'='%0A'\n)", sr.serialize(t));
 	}
@@ -268,12 +268,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=%C2%A2", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'¢':'¢'}");
+		t = Json5Map.ofText("{'¢':'¢'}");
 		assertEquals("%C2%A2=%C2%A2", s.serialize(t));
 		assertEquals("%C2%A2=%C2%A2", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'¢':{'¢':'¢'}}");
+		t = Json5Map.ofText("{'¢':{'¢':'¢'}}");
 		assertEquals("%C2%A2=(%C2%A2=%C2%A2)", s.serialize(t));
 		assertEquals("%C2%A2=(\n\t%C2%A2=%C2%A2\n)", sr.serialize(t));
 
@@ -284,12 +284,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=%E2%82%AC", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'€':'€'}");
+		t = Json5Map.ofText("{'€':'€'}");
 		assertEquals("%E2%82%AC=%E2%82%AC", s.serialize(t));
 		assertEquals("%E2%82%AC=%E2%82%AC", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'€':{'€':'€'}}");
+		t = Json5Map.ofText("{'€':{'€':'€'}}");
 		assertEquals("%E2%82%AC=(%E2%82%AC=%E2%82%AC)", s.serialize(t));
 		assertEquals("%E2%82%AC=(\n\t%E2%82%AC=%E2%82%AC\n)", sr.serialize(t));
 
@@ -300,12 +300,12 @@ class UrlEncodingSerializer_Test extends TestBase {
 		assertEquals("_value=%F0%A4%AD%A2", sr.serialize(t));
 
 		// 2nd level
-		t = Json5Map.ofJson5("{'𤭢':'𤭢'}");
+		t = Json5Map.ofText("{'𤭢':'𤭢'}");
 		assertEquals("%F0%A4%AD%A2=%F0%A4%AD%A2", s.serialize(t));
 		assertEquals("%F0%A4%AD%A2=%F0%A4%AD%A2", sr.serialize(t));
 
 		// 3rd level
-		t = Json5Map.ofJson5("{'𤭢':{'𤭢':'𤭢'}}");
+		t = Json5Map.ofText("{'𤭢':{'𤭢':'𤭢'}}");
 		assertEquals("%F0%A4%AD%A2=(%F0%A4%AD%A2=%F0%A4%AD%A2)", s.serialize(t));
 		assertEquals("%F0%A4%AD%A2=(\n\t%F0%A4%AD%A2=%F0%A4%AD%A2\n)", sr.serialize(t));
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/utils/FilteredMapTest.java b/juneau-utest/src/test/java/org/apache/juneau/utils/FilteredMapTest.java
index 3aa5189ff4..90b4a08c5d 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/FilteredMapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/utils/FilteredMapTest.java
@@ -35,7 +35,7 @@ class FilteredMapTest extends TestBase {
 	// testBasic
 	//====================================================================================================
 	@Test void a01_basic() throws Exception {
-		var m = Json5Map.ofJson5("{a:'1',b:'2'}");
+		var m = Json5Map.ofText("{a:'1',b:'2'}");
 
 		ClassMeta<Map<String,Object>> cm = MarshallingContext.DEFAULT.getClassMeta(Map.class, String.class, Object.class);
 		ClassMeta<Map<String,String>> cm2 = MarshallingContext.DEFAULT.getClassMeta(Map.class, String.class, String.class);
diff --git a/scripts/apply-eclipse-prefs.py b/scripts/apply-eclipse-prefs.py
index 39aa576ff9..07d187a0a1 100755
--- a/scripts/apply-eclipse-prefs.py
+++ b/scripts/apply-eclipse-prefs.py
@@ -51,7 +51,7 @@ SOURCE_PROJECTS = [
     "juneau-examples/juneau-examples-rest",
     "juneau-examples/juneau-examples-rest-jetty",
     "juneau-examples/juneau-examples-rest-springboot",
-    "juneau-microservice/juneau-microservice-core",
+    "juneau-microservice/juneau-microservice",
     "juneau-microservice/juneau-microservice-jetty",
     "juneau-microservice/juneau-my-jetty-microservice",
     "juneau-microservice/juneau-my-springboot-microservice",
diff --git a/todo/TODO-36-jetty-as-bean.md b/todo/FINISHED-36-jetty-as-bean.md
similarity index 100%
rename from todo/TODO-36-jetty-as-bean.md
rename to todo/FINISHED-36-jetty-as-bean.md
diff --git a/todo/TODO.md b/todo/TODO.md
index 112080d651..08a8a2d80b 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -23,8 +23,6 @@
 
 - [TODO-35] Add support for overriding injected beans for tests (test-time replacement/override of beans in the inject/DI container so tests can swap real beans for stubs/mocks).
 
-- [TODO-36] Rework `juneau-microservice-jetty` as a predefined `@Configuration` bean on top of a single `Microservice` class (rename `juneau-microservice-core` → `juneau-microservice`, delete `JettyMicroservice`, drive Jetty lifecycle through a fanned-out `MicroserviceListener`). See `todo/TODO-36-jetty-as-bean.md`.
-
 - [TODO-38] Rename `juneau-rest-client` → `juneau-rest-client-classic` and promote the `juneau-ng-rest-client-*` family + `org.apache.juneau.ng.*` packages to the canonical `juneau-rest-client` / `org.apache.juneau.*` names. See `todo/TODO-38-rename-rest-client-to-classic.md`.
 
 - [TODO-39] Add a `/sonarqube` Cursor command (and matching `scripts/sonarqube.py` helper) that runs SonarQube analysis against a given source file, package, or module — analogous to `/coverage` / `scripts/coverage.py`. Should auto-detect the Maven module from the path, invoke the SonarQube/SonarLint scanner, and print a concise per-file summary of issues (rule id, severity, line, message) so the user can quickly triage Sonar findings the same way they do JaCoCo coverage.