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/13 21:30:05 UTC

(juneau) 13/21: refactor: lift BeanMeta constructor body + CharSequence parsing to commons SPI (TODO-5 Step 8b-ii Phase C tasks 3 + 4-deferred)

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

commit 4f2f61c4cb677ed52c016ed61348a24c3dc7af32
Author: James Bognar <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
AuthorDate: Wed May 13 08:36:32 2026 -0400

    refactor: lift BeanMeta constructor body + CharSequence parsing to commons SPI (TODO-5 Step 8b-ii Phase C tasks 3 + 4-deferred)
    
    Co-authored-by: Cursor <cu...@cursor.com>
---
 .../apache/juneau/commons/bean/BeanSession.java    |  32 +++
 .../src/main/java/org/apache/juneau/BeanMeta.java  | 210 +++---------------
 .../java/org/apache/juneau/BeanPropertyMeta.java   |   5 +-
 .../juneau/MarshalledBeanMetaInitializer.java      | 242 +++++++++++++++++++++
 .../juneau/MarshalledPropertyPostProcessor.java    |  76 +++++++
 .../java/org/apache/juneau/MarshallingSession.java |  34 +++
 todo/TODO-5-bean-runtime-types-to-commons.md       |  72 ++++--
 7 files changed, 478 insertions(+), 193 deletions(-)

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 8b4b511079..5823355100 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
@@ -70,6 +70,38 @@ public interface BeanSession {
 	 */
 	Object convertToMemberType(Object outer, Object value, Object targetType);
 
+	/**
+	 * Parses the specified JSON-formatted character sequence into a {@link java.util.Map}.
+	 *
+	 * <p>
+	 * Used by {@code BeanPropertyMeta.setPropertyValue} when a {@link CharSequence} value is supplied for a
+	 * {@code Map}-typed property — the bean-modeling layer cannot reference the marshalling-side JSON parser
+	 * directly, so the parse is delegated to the session via this SPI.
+	 *
+	 * <p>
+	 * Implementations typically route through {@code JsonMap.ofJson(value).session(this)}.
+	 *
+	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
+	 * @return The parsed map.
+	 */
+	java.util.Map<?,?> parseToMap(CharSequence value);
+
+	/**
+	 * Parses the specified JSON-formatted character sequence into a {@link java.util.Collection}.
+	 *
+	 * <p>
+	 * Used by {@code BeanPropertyMeta.setPropertyValue} when a {@link CharSequence} value is supplied for a
+	 * {@code Collection}-typed property — the bean-modeling layer cannot reference the marshalling-side JSON
+	 * parser directly, so the parse is delegated to the session via this SPI.
+	 *
+	 * <p>
+	 * Implementations typically route through {@code new JsonList(value).setBeanSession(this)}.
+	 *
+	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
+	 * @return The parsed collection.
+	 */
+	java.util.Collection<?> parseToList(CharSequence value);
+
 	/**
 	 * Wraps the specified bean in a {@code BeanMap}.
 	 *
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index ae900b20f8..1136b1e611 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -40,9 +40,6 @@ import org.apache.juneau.commons.reflect.Visibility;
 import org.apache.juneau.commons.utils.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.commons.bean.*;
-import org.apache.juneau.parser.*;
-import org.apache.juneau.serializer.*;
-import org.apache.juneau.swap.*;
 
 /**
  * Encapsulates all access to the properties of a bean class (like a souped-up {@link java.beans.BeanInfo}).
@@ -226,16 +223,16 @@ public class BeanMeta<T> {
 			if (bc.isNotABean(cm))
 				return notABean("Class matches exclude-class list");
 
-			if (bc.isBeansRequireSerializable() && ! cm.isAssignableTo(Serializable.class) && ! ap.has(Marshalled.class, cm) && ! ap.has(org.apache.juneau.commons.bean.BeanType.class, cm))
+			if (bc.isBeansRequireSerializable() && ! cm.isAssignableTo(Serializable.class) && ! ap.has(Marshalled.class, cm) && ! ap.has(BeanType.class, cm))
 				return notABean("Class is not serializable");
 
 			if (ap.has(BeanIgnore.class, cm))
 				return notABean("Class is annotated with @BeanIgnore");
 
-		if ((! bc.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! ap.has(Marshalled.class, cm) && ! ap.has(org.apache.juneau.commons.bean.BeanType.class, cm))
+		if ((! bc.getBeanClassVisibility().isVisible(cm.getModifiers()) || cm.isAnonymousClass()) && ! ap.has(Marshalled.class, cm) && ! ap.has(BeanType.class, cm))
 			return notABean("Class is not public");
 
-			var bm = new BeanMeta<>(cm, findMarshalledFilter(cm), null, implClass);
+			var bm = new BeanMeta<>(cm, MarshalledBeanMetaInitializer.findMarshalledFilter(cm), null, implClass);
 
 			if (nn(bm.notABeanReason))
 				return notABean(bm.notABeanReason);
@@ -275,46 +272,6 @@ public class BeanMeta<T> {
 		return name.orElse(null);
 	}
 
-	/*
-	 * Finds and creates the bean filter for the specified class metadata.
-	 *
-	 * <p>
-	 * Searches for {@link Marshalled @Marshalled} annotations on the class and its parent classes/interfaces. If found, creates a
-	 * {@link MarshalledFilter} that applies the configuration from those annotations.
-	 *
-	 * <p>
-	 * When multiple {@link Marshalled @Marshalled} annotations are found (e.g., on a parent class and a child class), they are
-	 * applied in reverse order (parent classes first, then child classes). This ensures that child class annotations
-	 * override parent class annotations, allowing child classes to customize or extend the bean configuration.
-	 *
-	 * <p>
-	 * The bean filter controls various aspects of bean serialization and parsing, such as:
-	 * <ul>
-	 * 	<li>Property inclusion/exclusion lists
-	 * 	<li>Property ordering and sorting
-	 * 	<li>Type name mapping for dictionary lookups
-	 * 	<li>Fluent setter detection
-	 * 	<li>Read-only and write-only property definitions
-	 * </ul>
-	 *
-	 * @param <T> The class type.
-	 * @param cm The class metadata to find the filter for.
-	 * @return The bean filter, or <jk>null</jk> if no {@link Marshalled @Marshalled} annotations are found on the class or its hierarchy.
-	 * @see Marshalled
-	 * @see MarshalledFilter
-	 */
-	private static <T> MarshalledFilter findMarshalledFilter(ClassMeta<T> cm) {
-		var ap = cm.getMarshallingContext().getAnnotationProvider();
-		var l = ap.find(Marshalled.class, cm);
-		var bt = ap.find(org.apache.juneau.commons.bean.BeanType.class, cm);
-		if (l.isEmpty() && bt.isEmpty())
-			return null;
-		return MarshalledFilter.create(cm)
-			.applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList()))
-			.applyBeanTypeAnnotations(reverse(bt.stream().map(AnnotationInfo::inner).toList()))
-			.build();
-	}
-
 	/*
 	 * Extracts the property name from a single {@link BeanProp @BeanProp} or {@link Name @Name} annotation.
 	 *
@@ -359,7 +316,7 @@ public class BeanMeta<T> {
 	}
 
 	private BeanConstructor beanConstructor;                                   // The constructor for this bean.
-	private final MarshallingContext marshallingContext;                       // The bean context that created this metadata object.  Null when constructed via {@link #of(Class, BeanConfigContext)}.
+	private final Object marshallingContext;                                   // MarshallingContext, but Object-typed so the field can live in commons.bean.  Cast to MarshallingContext at marshalling-side use sites.  Null when constructed via {@link #of(Class, BeanConfigContext)}.
 	private final BeanConfigContext config;                                    // Bean-modeling settings facade — always non-null.  Sources: marshallingContext.getBeanConfigContext() (marshalling-side) or the explicit BeanConfigContext (commons-side).
 	private final BeanFilter beanFilter;                                       // Optional bean filter associated with the target class.  Typed as the bean-modeling-side SPI seam; marshalling-side callers cast back to {@link MarshalledFilter} via {@link #getMarshalledFilter()}.
 	private final NullableSupplier<InvocationHandler> beanProxyInvocationHandler;  // The invocation handler for this bean (if it's an interface).
@@ -445,7 +402,7 @@ public class BeanMeta<T> {
 	 * @param implClass Optional implementation class constructor to use if one cannot be found. Can be <jk>null</jk>.
 	 */
 	protected BeanMeta(BeanTypeInfo<T> cm, BeanFilter bf, String[] pNames, ClassInfo implClass) {
-		this((ClassMeta<T>) cm, (ClassMeta<T>) cm, ((ClassMeta<T>) cm).getMarshallingContext().getBeanConfigContext(), ((ClassMeta<T>) cm).getMarshallingContext(), bf, pNames, implClass);
+		this(cm, MarshalledBeanMetaInitializer.classInfoOf(cm), MarshalledBeanMetaInitializer.configOf(cm), MarshalledBeanMetaInitializer.contextOf(cm), bf, pNames, implClass);
 	}
 
 	/**
@@ -468,7 +425,7 @@ public class BeanMeta<T> {
 		"java:S3776", // Cognitive complexity acceptable for bean metadata initialization
 		"java:S107"   // 7 parameters needed to support both construction paths
 	})
-	private BeanMeta(ClassMeta<T> cm, ClassInfo ci0, BeanConfigContext config, MarshallingContext mc, BeanFilter bf, String[] pNames, ClassInfo implClass) {
+	private BeanMeta(BeanTypeInfo<T> cm, ClassInfo ci0, BeanConfigContext config, Object mc, BeanFilter bf, String[] pNames, ClassInfo implClass) {
 		classMeta = cm;
 		classInfo = ci0;
 		this.config = config;
@@ -621,18 +578,18 @@ public class BeanMeta<T> {
 
 			propertiesValue.set(unsortedPropertiesTemp ? map() : sortedMap());
 
-			normalProps.forEach((k, v) -> {
-				var pMeta = v.build();
-				if (pMeta.isDyna())
-					dynaPropertyValue.set(pMeta);
-				propertiesValue.get().put(k, pMeta);
-				// Build the property-level BeanRegistry side-map entry from the builder's accumulated
-				// dictionary classes.  Parents to the bean-level registry so @MarshalledProp(dictionary={})
-				// entries chain on top of bean and global dictionaries.  Skipped on the commons-side path
-				// (no marshallingContext means no BeanRegistry construction).
-				if (nn(marshallingContext) && nn(v.dictionaryClasses))
-					propertyBeanRegistriesTemp.put(pMeta, new BeanRegistry(marshallingContext, (BeanRegistry) beanRegistry.get(), v.dictionaryClasses));
-			});
+		normalProps.forEach((k, v) -> {
+			var pMeta = v.build();
+			if (pMeta.isDyna())
+				dynaPropertyValue.set(pMeta);
+			propertiesValue.get().put(k, pMeta);
+			// Build the property-level BeanRegistry side-map entry from the builder's accumulated
+			// dictionary classes.  Parents to the bean-level registry so @MarshalledProp(dictionary={})
+			// entries chain on top of bean and global dictionaries.  Skipped on the commons-side path
+			// (no marshallingContext means no BeanRegistry construction).
+			if (nn(marshallingContext) && nn(v.dictionaryClasses))
+				propertyBeanRegistriesTemp.put(pMeta, MarshalledBeanMetaInitializer.buildPropertyBeanRegistry(marshallingContext, beanRegistry.get(), v.dictionaryClasses));
+		});
 
 			// If a beanFilter is defined, look for inclusion and exclusion lists.
 			if (bf != null) {
@@ -702,16 +659,15 @@ public class BeanMeta<T> {
 
 			// When constructed via the commons-side path, marshallingContext is null and validate() runs in
 			// raw-reflection mode (no ClassMeta/ObjectSwap/BeanRegistry resolution).
-			if (p.validate(marshallingContext, typeVarImpls, readOnlyProps, writeOnlyProps)) {
+			if (p.validate((BeanTypeResolver) marshallingContext, typeVarImpls, readOnlyProps, writeOnlyProps)) {
 
 				// Marshalling-side post-processor — applies @MarshalledProp / @Swap annotation effects
-				// (swap detection, properties override, dictionary classes) after validate() succeeds.
-				// Skipped on the commons-side path: those annotations require a MarshallingContext to
-				// resolve ObjectSwap/StringFormatSwap/Surrogate types and the swap class meta.
+				// (swap detection, properties override, dictionary classes) and installs swap-aware
+				// read/write transforms.  Skipped on the commons-side path: those annotations require a
+				// MarshallingContext to resolve ObjectSwap/StringFormatSwap/Surrogate types and the swap
+				// class meta.
 				if (nn(marshallingContext))
-					MarshalledPropertyPostProcessor.process(marshallingContext, p);
-
-				installSwapAwareTransforms(p);
+					MarshalledPropertyPostProcessor.process((MarshallingContext) marshallingContext, p);
 
 				if (nn(p.getter))
 					getterProps.put(p.getter.inner(), p.name);
@@ -727,75 +683,6 @@ public class BeanMeta<T> {
 		}
 	}
 
-	/**
-	 * Installs swap-aware read/write transforms on a {@link BeanPropertyMeta.Builder} after validation.
-	 *
-	 * <p>
-	 * After {@link BeanPropertyMeta.Builder#validate validate()} succeeds, the builder's {@code swap} and
-	 * {@code rawTypeMeta} fields describe whether the property has a configured {@link ObjectSwap} (via
-	 * {@link org.apache.juneau.annotation.MarshalledProp @MarshalledProp(format=...)} or
-	 * {@link org.apache.juneau.annotation.Swap @Swap}) and whether the property's raw type has child swaps registered
-	 * on it.  This method packages those concerns into install-time closures so the marshalling-side swap behavior is
-	 * established as data on the {@link BeanPropertyMeta} rather than executed by the bean-modeling
-	 * {@link BeanPropertyMeta#get get}/{@link BeanPropertyMeta#set set} methods themselves.
-	 *
-	 * <p>
-	 * If neither a property-level swap nor a child swap on the raw type meta is present, no transforms are installed
-	 * and the property's {@code get}/{@code set} fall through to identity (raw access).
-	 *
-	 * @param p The builder to attach swap-aware transforms to.
-	 */
-	@SuppressWarnings({
-		"rawtypes",   // ObjectSwap used raw to mirror BeanPropertyMeta's field declaration.
-		"unchecked"   // Wildcard ObjectSwap captured by raw alias to allow runtime polymorphic dispatch.
-	})
-	private static void installSwapAwareTransforms(BeanPropertyMeta.Builder p) {
-		ObjectSwap sw = (ObjectSwap) p.swap;
-		ClassMeta<?> rtm = (ClassMeta<?>) p.rawTypeMeta;
-		if (sw == null && (rtm == null || ! rtm.hasChildSwaps()))
-			return;
-		if (p.readTransform == null) {
-			p.readTransform = (session, o) -> {
-				try {
-					if (nn(sw))
-						return sw.swap(session, o);
-					if (o == null)
-						return null;
-					if (rtm.hasChildSwaps()) {
-						ObjectSwap f = rtm.getChildObjectSwapForSwap(o.getClass());
-						if (nn(f))
-							return f.swap(session, o);
-					}
-					return o;
-				} catch (RuntimeException e) {
-					throw e;
-				} catch (Exception e) {
-					throw new SerializeException(e);
-				}
-			};
-		}
-		if (p.writeTransform == null) {
-			p.writeTransform = (session, o) -> {
-				try {
-					if (nn(sw))
-						return sw.unswap(session, o, rtm);
-					if (o == null)
-						return null;
-					if (rtm.hasChildSwaps()) {
-						ObjectSwap f = rtm.getChildObjectSwapForUnswap(o.getClass());
-						if (nn(f))
-							return f.unswap(session, o, rtm);
-					}
-					return o;
-				} catch (RuntimeException e) {
-					throw e;
-				} catch (Exception e) {
-					throw new ParseException(e);
-				}
-			};
-		}
-	}
-
 	@Override /* Overridden from Object */
 	public boolean equals(Object o) {
 		return (o instanceof BeanMeta<?> o2) && eq(this, o2, (x, y) -> eq(x.classInfo, y.classInfo));
@@ -1042,7 +929,7 @@ public class BeanMeta<T> {
 	 *
 	 * @return The bean context, or <jk>null</jk> for bean-modeling-only construction.
 	 */
-	protected MarshallingContext getMarshallingContext() { return marshallingContext; }
+	protected MarshallingContext getMarshallingContext() { return (MarshallingContext) marshallingContext; }
 
 	/**
 	 * Returns the constructor for this bean, if one was found.
@@ -1541,19 +1428,9 @@ public class BeanMeta<T> {
 	 */
 	private BeanRegistryLookup findBeanRegistry() {
 		// BeanRegistry is a marshalling-side concern (polymorphic dispatch via @Marshalled(typeName)/dictionary).
-		// On the commons-side construction path there is no MarshallingContext to drive it — return null and let
-		// callers route through getPropertyBeanRegistry(...) (which is also empty on this path).
-		if (marshallingContext == null)
-			return null;
-
-		// Bean dictionary on bean filter.
-		var beanDictionaryClasses = opt(beanFilter).map(x -> new ArrayList<>(x.getBeanDictionary())).orElse(new ArrayList<>());
-
-		// Bean dictionary from @Marshalled(typeName) annotation.
-		var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
-		ba.stream().map(x -> x.inner().typeName()).filter(Utils::ne).findFirst().ifPresent(x -> beanDictionaryClasses.add(classInfo));
-
-		return new BeanRegistry(marshallingContext, null, beanDictionaryClasses);
+		// Lifted out to {@link MarshalledBeanMetaInitializer} so this class no longer references
+		// {@link BeanRegistry} directly.  Returns null on the commons-side path (no marshallingContext).
+		return MarshalledBeanMetaInitializer.buildBeanRegistry(marshallingContext, beanFilter, classInfo, config);
 	}
 
 	/*
@@ -1664,32 +1541,13 @@ public class BeanMeta<T> {
 		}
 
 		// Parent-class BeanRegistry lookup is a marshalling-side concern: it walks parents and interfaces to
-		// see if any of THEIR ClassMeta-backed BeanRegistries declares a typeName for our raw class.  On the
-		// commons-side path (no marshallingContext) we skip this entirely.
-		if (nn(marshallingContext)) {
-			var n = classInfo
-				.getParentsAndInterfaces()
-				.stream()
-				.skip(1)
-				.map(marshallingContext::getClassMeta)
-				.map(ClassMeta::getBeanRegistry)
-				.filter(Objects::nonNull)
-				.map(x -> x.getTypeName(rawClass))
-				.filter(Objects::nonNull)
-				.findFirst()
-				.orElse(null);
-
-			if (n != null)
-				return n;
-		}
+		// see if any of THEIR ClassMeta-backed BeanRegistries declares a typeName for our raw class.  Lifted
+		// out to {@link MarshalledBeanMetaInitializer}.  Returns null on the commons-side path.
+		var n = MarshalledBeanMetaInitializer.findTypeNameInParents(marshallingContext, classInfo, rawClass);
+		if (n != null)
+			return n;
 
-		return config.getAnnotationProvider().find(Marshalled.class, classInfo)
-			.stream()
-			.map(AnnotationInfo::inner)
-			.filter(x -> ! x.typeName().isEmpty())
-			.map(Marshalled::typeName)
-			.findFirst()
-			.orElse(null);
+		return MarshalledBeanMetaInitializer.findMarshalledTypeName(config, classInfo);
 	}
 
 	/*
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index fe365128a6..81cda3d4ad 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -33,7 +33,6 @@ import java.util.function.*;
 import java.util.stream.*;
 
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.bean.*;
 import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.lang.*;
@@ -1144,7 +1143,7 @@ public class BeanPropertyMeta implements Comparable<BeanPropertyMeta> {
 
 				if (! (value1 instanceof Map)) {
 					if (value1 instanceof CharSequence value21)
-						value1 = JsonMap.ofJson(value21).session(session);
+						value1 = session.parseToMap(value21);
 					else
 						throw bex(beanMeta.getClassMeta(), "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), cn(value1));
 				}
@@ -1202,7 +1201,7 @@ public class BeanPropertyMeta implements Comparable<BeanPropertyMeta> {
 
 				if (! (value1 instanceof Collection)) {
 					if (value1 instanceof CharSequence value2)
-						value1 = new JsonList(value2).setBeanSession(session);
+						value1 = session.parseToList(value2);
 					else
 						throw bex(beanMeta.getClassMeta(), "Cannot set property ''{0}'' of type ''{1}'' to object of type ''{2}''", name, propertyClass.getName(), cn(value1));
 				}
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
new file mode 100644
index 0000000000..3b0c49c0a1
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java
@@ -0,0 +1,242 @@
+/*
+ * 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;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.commons.utils.StringUtils.*;
+import static org.apache.juneau.commons.utils.Utils.*;
+
+import java.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.commons.bean.*;
+import org.apache.juneau.commons.reflect.*;
+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.
+ */
+final class MarshalledBeanMetaInitializer {
+
+	private MarshalledBeanMetaInitializer() {}
+
+	/**
+	 * Builds the bean-level {@link BeanRegistry} for a given bean class.
+	 *
+	 * <p>
+	 * Replaces the body of {@link BeanMeta}'s former private {@code findBeanRegistry()} method.  Returns
+	 * <jk>null</jk> when {@code marshallingContext} is <jk>null</jk> (commons-side construction path).
+	 *
+	 * @param marshallingContext The marshalling context.  May be <jk>null</jk>.
+	 * @param beanFilter The bean filter applied to this bean meta.  May be <jk>null</jk>.
+	 * @param classInfo The class info of the bean.  Must not be <jk>null</jk>.
+	 * @param config The bean-modeling configuration.  Must not be <jk>null</jk>.
+	 * @return The bean-level {@link BeanRegistryLookup}, or <jk>null</jk> on the commons-side path.
+	 */
+	static BeanRegistryLookup buildBeanRegistry(Object marshallingContext, BeanFilter beanFilter, ClassInfo classInfo, BeanConfigContext config) {
+		if (marshallingContext == null)
+			return null;
+		MarshallingContext mc = (MarshallingContext) marshallingContext;
+
+		// Bean dictionary on bean filter.
+		var beanDictionaryClasses = opt(beanFilter).map(x -> new ArrayList<>(x.getBeanDictionary())).orElse(new ArrayList<>());
+
+		// Bean dictionary from @Marshalled(typeName) annotation.
+		var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
+		ba.stream().map(x -> x.inner().typeName()).filter(Utils::ne).findFirst().ifPresent(x -> beanDictionaryClasses.add(classInfo));
+
+		return new BeanRegistry(mc, null, beanDictionaryClasses);
+	}
+
+	/**
+	 * Constructs a per-property {@link BeanRegistry} chained on top of the bean-level registry.
+	 *
+	 * <p>
+	 * Replaces the inline {@code new BeanRegistry(marshallingContext, beanRegistry, dictionaryClasses)} call that
+	 * lived inside {@link BeanMeta}'s property-iteration loop.
+	 *
+	 * @param marshallingContext The marshalling context.  Must not be <jk>null</jk>.
+	 * @param parent The bean-level registry (chained behind the per-property one).  May be <jk>null</jk>.
+	 * @param dictionaryClasses The per-property {@link MarshalledProp#dictionary() @MarshalledProp(dictionary)} classes.  Must not be <jk>null</jk>.
+	 * @return The per-property {@link BeanRegistryLookup}.
+	 */
+	static BeanRegistryLookup buildPropertyBeanRegistry(Object marshallingContext, BeanRegistryLookup parent, List<ClassInfo> dictionaryClasses) {
+		return new BeanRegistry((MarshallingContext) marshallingContext, (BeanRegistry) parent, dictionaryClasses);
+	}
+
+	/**
+	 * Walks the parent classes/interfaces of {@code classInfo} and returns the first
+	 * {@link Marshalled#typeName() @Marshalled(typeName)} mapping for {@code rawClass} found in any parent's
+	 * {@link BeanRegistry}.
+	 *
+	 * <p>
+	 * Replaces the parent-walk stream that previously used {@code marshallingContext::getClassMeta} directly
+	 * inside {@link BeanMeta#findDictionaryName()}.
+	 *
+	 * @param marshallingContext The marshalling context.  May be <jk>null</jk>.
+	 * @param classInfo The bean's class info (used to derive the parents/interfaces walk).
+	 * @param rawClass The raw class to look up a type name for.
+	 * @return The dictionary name found in a parent's registry, or <jk>null</jk>.
+	 */
+	static String findTypeNameInParents(Object marshallingContext, ClassInfo classInfo, Class<?> rawClass) {
+		if (marshallingContext == null)
+			return null;
+		MarshallingContext mc = (MarshallingContext) marshallingContext;
+		return classInfo
+			.getParentsAndInterfaces()
+			.stream()
+			.skip(1)
+			.map(mc::getClassMeta)
+			.map(ClassMeta::getBeanRegistry)
+			.filter(Objects::nonNull)
+			.map(x -> x.getTypeName(rawClass))
+			.filter(Objects::nonNull)
+			.findFirst()
+			.orElse(null);
+	}
+
+	/**
+	 * Extracts the {@link BeanConfigContext} embedded in a marshalling-side {@link BeanTypeInfo} (which is always
+	 * a {@link ClassMeta}).
+	 *
+	 * <p>
+	 * Used by the protected {@code BeanMeta(BeanTypeInfo, BeanFilter, String[], ClassInfo)} bridging constructor —
+	 * lifts the {@code ((ClassMeta<T>) cm).getMarshallingContext().getBeanConfigContext()} cast chain out of the
+	 * commons-side {@link BeanMeta} body.
+	 *
+	 * @param cm The bean type info.  Must be a marshalling-side {@link ClassMeta} instance.
+	 * @return The bean-modeling configuration of the marshalling context.
+	 */
+	static BeanConfigContext configOf(BeanTypeInfo<?> cm) {
+		return ((ClassMeta<?>) cm).getMarshallingContext().getBeanConfigContext();
+	}
+
+	/**
+	 * Extracts the {@link MarshallingContext} embedded in a marshalling-side {@link BeanTypeInfo} (which is always
+	 * a {@link ClassMeta}) and returns it as an {@link Object} so the caller does not need to import
+	 * {@link MarshallingContext}.
+	 *
+	 * @param cm The bean type info.  Must be a marshalling-side {@link ClassMeta} instance.
+	 * @return The marshalling context, as an {@link Object}.
+	 */
+	static Object contextOf(BeanTypeInfo<?> cm) {
+		return ((ClassMeta<?>) cm).getMarshallingContext();
+	}
+
+	/**
+	 * Narrows a marshalling-side {@link BeanTypeInfo} (which is always a {@link ClassMeta}) to {@link ClassInfo}.
+	 *
+	 * <p>
+	 * Since {@link ClassMeta} extends {@link ClassInfo}, the cast is direct.  Provided as a helper so the commons-side
+	 * caller does not have to import {@link ClassMeta}.
+	 *
+	 * @param cm The bean type info.  Must be a marshalling-side {@link ClassMeta} instance.
+	 * @return The class info for the underlying bean class.
+	 */
+	static ClassInfo classInfoOf(BeanTypeInfo<?> cm) {
+		return (ClassMeta<?>) cm;
+	}
+
+	/**
+	 * Looks up the {@link Marshalled#typePropertyName() @Marshalled(typePropertyName)} value for a bean class.
+	 *
+	 * <p>
+	 * Returns the configured value, or an empty string if no {@link Marshalled @Marshalled} annotation specifies one.
+	 * The caller (typically {@link BeanMeta}'s constructor) falls back to a config-supplied default when this method
+	 * returns an empty string.
+	 *
+	 * @param config The bean-modeling configuration (used to access the annotation provider).
+	 * @param classInfo The bean's class info.
+	 * @return The configured type property name, or empty if not set.
+	 */
+	static String findTypePropertyName(BeanConfigContext config, ClassInfo classInfo) {
+		var ba = config.getAnnotationProvider().find(Marshalled.class, classInfo);
+		return ba.stream().map(x -> x.inner().typePropertyName()).filter(Utils::ne).findFirst().orElse("");
+	}
+
+	/**
+	 * Looks up the {@link Marshalled#typeName() @Marshalled(typeName)} value for a bean class.
+	 *
+	 * <p>
+	 * Used by {@link BeanMeta#findDictionaryName()} as the last fallback when no other dictionary name is found.
+	 *
+	 * @param config The bean-modeling configuration (used to access the annotation provider).
+	 * @param classInfo The bean's class info.
+	 * @return The configured type name, or <jk>null</jk> if not set.
+	 */
+	static String findMarshalledTypeName(BeanConfigContext config, ClassInfo classInfo) {
+		return config.getAnnotationProvider().find(Marshalled.class, classInfo)
+			.stream()
+			.map(AnnotationInfo::inner)
+			.filter(x -> ! x.typeName().isEmpty())
+			.map(Marshalled::typeName)
+			.findFirst()
+			.orElse(null);
+	}
+
+	/**
+	 * Determines whether the class has any "registered as bean" annotation
+	 * ({@link Marshalled @Marshalled} or {@link BeanType @BeanType}).
+	 *
+	 * <p>
+	 * Used by {@link BeanMeta#findBeanConstructor()} to decide whether private constructors are permissible.
+	 *
+	 * @param config The bean-modeling configuration (used to access the annotation provider).
+	 * @param classInfo The bean's class info.
+	 * @return <jk>true</jk> if the class has either annotation.
+	 */
+	static boolean hasBeanRegistrationAnnotation(BeanConfigContext config, ClassInfo classInfo) {
+		var ap = config.getAnnotationProvider();
+		return ! ap.find(Marshalled.class, classInfo).isEmpty()
+			|| ! ap.find(BeanType.class, classInfo).isEmpty();
+	}
+
+	/**
+	 * Resolves the bean filter for a marshalling-side {@link BeanMeta}.
+	 *
+	 * <p>
+	 * Lifted out of {@link BeanMeta}'s former private {@code findMarshalledFilter(ClassMeta)} static helper.  Returns
+	 * a {@link MarshalledFilter} (the only concrete {@link BeanFilter} implementation in-tree) built from the
+	 * {@link Marshalled @Marshalled} and {@link BeanType @BeanType} annotations on the class.
+	 *
+	 * @param cm The class meta for the bean class.
+	 * @return The bean filter, or <jk>null</jk> if no relevant annotations are present.
+	 */
+	static <T> MarshalledFilter findMarshalledFilter(ClassMeta<T> cm) {
+		var ap = cm.getMarshallingContext().getAnnotationProvider();
+		var l = ap.find(Marshalled.class, cm);
+		var bt = ap.find(BeanType.class, cm);
+		if (l.isEmpty() && bt.isEmpty())
+			return null;
+		return MarshalledFilter.create(cm)
+			.applyAnnotations(reverse(l.stream().map(AnnotationInfo::inner).toList()))
+			.applyBeanTypeAnnotations(reverse(bt.stream().map(AnnotationInfo::inner).toList()))
+			.build();
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
index c0f1332bc0..7f6defe51b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
@@ -28,6 +28,8 @@ import java.util.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
 import org.apache.juneau.swap.*;
 import org.apache.juneau.swaps.*;
 
@@ -121,6 +123,80 @@ final class MarshalledPropertyPostProcessor {
 		// layer and cannot reference ObjectSwap directly).
 		if (nn(b.swap) && nn(b.rawTypeMeta))
 			b.typeMeta = bc.getClassMeta(((ObjectSwap) b.swap).getSwapClass());
+
+		// Install swap-aware read/write transforms on the builder.
+		// Previously lived as a private static helper on {@link BeanMeta}; moved here as part of TODO-5 Step 8b-ii
+		// Phase C Task 3 so {@link BeanMeta} no longer references {@link ObjectSwap}/{@link ParseException}/{@link SerializeException}.
+		installSwapAwareTransforms(b);
+	}
+
+	/**
+	 * Installs swap-aware read/write transforms on a {@link BeanPropertyMeta.Builder} after validation.
+	 *
+	 * <p>
+	 * After {@link BeanPropertyMeta.Builder#validate validate()} succeeds, the builder's {@code swap} and
+	 * {@code rawTypeMeta} fields describe whether the property has a configured {@link ObjectSwap} (via
+	 * {@link org.apache.juneau.annotation.MarshalledProp @MarshalledProp(format=...)} or
+	 * {@link org.apache.juneau.annotation.Swap @Swap}) and whether the property's raw type has child swaps registered
+	 * on it.  This method packages those concerns into install-time closures so the marshalling-side swap behavior is
+	 * established as data on the {@link BeanPropertyMeta} rather than executed by the bean-modeling
+	 * {@link BeanPropertyMeta#get get}/{@link BeanPropertyMeta#set set} methods themselves.
+	 *
+	 * <p>
+	 * If neither a property-level swap nor a child swap on the raw type meta is present, no transforms are installed
+	 * and the property's {@code get}/{@code set} fall through to identity (raw access).
+	 *
+	 * @param p The builder to attach swap-aware transforms to.
+	 */
+	@SuppressWarnings({
+		"rawtypes",   // ObjectSwap used raw to mirror BeanPropertyMeta's field declaration.
+		"unchecked"   // Wildcard ObjectSwap captured by raw alias to allow runtime polymorphic dispatch.
+	})
+	static void installSwapAwareTransforms(BeanPropertyMeta.Builder p) {
+		ObjectSwap sw = (ObjectSwap) p.swap;
+		ClassMeta<?> rtm = (ClassMeta<?>) p.rawTypeMeta;
+		if (sw == null && (rtm == null || ! rtm.hasChildSwaps()))
+			return;
+		if (p.readTransform == null) {
+			p.readTransform = (session, o) -> {
+				try {
+					if (nn(sw))
+						return sw.swap(session, o);
+					if (o == null)
+						return null;
+					if (rtm.hasChildSwaps()) {
+						ObjectSwap f = rtm.getChildObjectSwapForSwap(o.getClass());
+						if (nn(f))
+							return f.swap(session, o);
+					}
+					return o;
+				} catch (RuntimeException e) {
+					throw e;
+				} catch (Exception e) {
+					throw new SerializeException(e);
+				}
+			};
+		}
+		if (p.writeTransform == null) {
+			p.writeTransform = (session, o) -> {
+				try {
+					if (nn(sw))
+						return sw.unswap(session, o, rtm);
+					if (o == null)
+						return null;
+					if (rtm.hasChildSwaps()) {
+						ObjectSwap f = rtm.getChildObjectSwapForUnswap(o.getClass());
+						if (nn(f))
+							return f.unswap(session, o, rtm);
+					}
+					return o;
+				} catch (RuntimeException e) {
+					throw e;
+				} catch (Exception e) {
+					throw new ParseException(e);
+				}
+			};
+		}
 	}
 
 	private static ObjectSwap marshalledPropSwap(AnnotationInfo<MarshalledProp> ai) {
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 66e50ee20d..f4f84bb73c 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
@@ -1321,4 +1321,38 @@ public class MarshallingSession extends ContextSession implements ConverterSessi
 		throw illegalArg("Unsupported targetType for convertToMemberType: {0}", targetType.getClass().getName());
 	}
 
+	/**
+	 * Bridge implementation of {@link BeanSession#parseToMap(CharSequence)} that delegates to
+	 * {@link org.apache.juneau.collections.JsonMap#ofJson(CharSequence)} paired with this session.
+	 *
+	 * <p>
+	 * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link Map} when
+	 * the property is map-typed.  Lifted out of {@link BeanPropertyMeta} so the bean-modeling layer no longer
+	 * references the marshalling-side JSON parser.
+	 *
+	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
+	 * @return The parsed {@link org.apache.juneau.collections.JsonMap}.
+	 */
+	@Override /* BeanSession */
+	public final java.util.Map<?,?> parseToMap(CharSequence value) {
+		return org.apache.juneau.collections.JsonMap.ofJson(value).session(this);
+	}
+
+	/**
+	 * Bridge implementation of {@link BeanSession#parseToList(CharSequence)} that delegates to
+	 * {@link org.apache.juneau.collections.JsonList#JsonList(CharSequence)} paired with this session.
+	 *
+	 * <p>
+	 * Used by {@link BeanPropertyMeta#setPropertyValue} to parse a {@link CharSequence} into a {@link java.util.Collection}
+	 * when the property is collection-typed.  Lifted out of {@link BeanPropertyMeta} so the bean-modeling layer
+	 * no longer references the marshalling-side JSON parser.
+	 *
+	 * @param value The JSON-formatted character sequence to parse.  Must not be <jk>null</jk>.
+	 * @return The parsed {@link org.apache.juneau.collections.JsonList}.
+	 */
+	@Override /* BeanSession */
+	public final java.util.Collection<?> parseToList(CharSequence value) {
+		return new org.apache.juneau.collections.JsonList(value).setBeanSession(this);
+	}
+
 }
\ No newline at end of file
diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md b/todo/TODO-5-bean-runtime-types-to-commons.md
index 1be86c3f7c..b7c19d95ed 100644
--- a/todo/TODO-5-bean-runtime-types-to-commons.md
+++ b/todo/TODO-5-bean-runtime-types-to-commons.md
@@ -4,9 +4,16 @@ This is the remaining work from **Phase 5 of the bean-layer split**. Phase 5a (t
 
 ---
 
-## Status (as of Phase 5h checkpoint + Step 8b-i)
+## Status (as of Phase C Tasks 1-2-3-4-4-deferred checkpoint, uncommitted)
 
-**Step 8b-i complete (additional SPI-seam extraction, uncommitted).** Three new SPIs landed in the working tree (no commit yet — left for user review). Build and full test suite green. See "Step 8b-i" entry in the step list below for full detail. Summary: `BeanTypeInfo<T>` (abstract class — ClassMeta extends), `BeanFilter` interface (MarshalledFilter implements), `BeanRegistryLookup` interface (BeanRegistry implements), and `Delegate<T>` physically moved to `commons.bean`. The remaining  [...]
+**Phase C Tasks 1, 2, 3, 4, 4-deferred LANDED (working tree, uncommitted).** Build + full test green. See "Phase C status" block under Step 8b-ii for full detail. Summary:
+- **Task 1** — Public getter widening (`getClassMeta()` → `BeanTypeInfo<?>`, `getBeanRegistry()` → `BeanRegistryLookup`) committed locally at `48621576d7`.
+- **Task 2** — `validate(...)` body lift-out via new `BeanTypeResolver` SPI committed locally.
+- **Task 3** — `BeanMeta` constructor body lift-out via new `MarshalledBeanMetaInitializer` (this checkpoint). Includes relocation of `installSwapAwareTransforms` from `BeanMeta` into `MarshalledPropertyPostProcessor`, retype of `BeanMeta.marshallingContext` field to `Object`, and removal of `parser.*`/`serializer.*`/`swap.*` imports from `BeanMeta`.
+- **Task 4** — `setPropertyValue` Collection-branch JsonList wrap → ArrayList wrap (committed earlier).
+- **Task 4-deferred** — CharSequence-parsing sites now route through new `BeanSession.parseToMap(CharSequence)` / `BeanSession.parseToList(CharSequence)` SPI methods (this checkpoint). `BeanPropertyMeta` dropped its `import org.apache.juneau.collections.*`.
+
+**Phase C Task 5 (physical `git mv` + reference sweep) — NOT STARTED.** Survey revealed the remaining marshalling-side coupling on the 8 files is deeper than the original prep scope estimated. The bulk of remaining cleanup is lifting ~20 marshalling-side annotation reads (`@Marshalled`, `@BeanProp`, etc.) from `BeanMeta`/`BeanPropertyMeta` to `MarshalledBeanMetaInitializer`, plus retyping `BiFunction<MarshallingSession,...>` field types to use the `BeanSession` SPI seam, plus relocating  [...]
 
 **Step 8a complete (SPI-seam extraction).** Commit `3a74fcd50a`. The minimum SPI surface that the 8 target types need from the marshalling layer is now in place:
 
@@ -110,17 +117,54 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la
   - **Sub-item 10 (`JsonList` / `JsonMap` → JDK collections)** — partial. The `propertyCache` allocations inside `BeanPropertyMeta.add(BeanMap,String,Object)` and `add(BeanMap,String,String,Object)` now use `new ArrayList<>()` / `new LinkedHashMap<>()` (test-invisible — they're internal caches for read-only beans). The `c = new JsonList(session)` / `map = new JsonMap(session)` fallback constructions in `add(...)` likewise flipped to `new ArrayList<>()` / `new LinkedHashMap<>()`. **Deferr [...]
   - **Sub-item 8 (`BeanMap.load(...)` → `BeanMapLoader`)** — created `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java` with `static <T> BeanMap<T> load(BeanMap<T> m, String input) throws ParseException` and `static <T> BeanMap<T> load(BeanMap<T> m, Reader r, ReaderParser p) throws ParseException, IOException`. Removed `BeanMap.load(Reader, ReaderParser)` and `BeanMap.load(String)` from `BeanMap.java`. Updated 2 call sites in `MarshallingContext.java` (`bs.n [...]
 
-  **Phase C status (uncommitted, working tree) — IN PROGRESS. Tasks 1, 2, 4 LANDED, build + full test green. Tasks 3, 5 NOT STARTED.**
+  **Phase C status (uncommitted, working tree) — Tasks 1, 2, 3, 4, 4-deferred LANDED, build + full test green. Task 5 NOT STARTED (deeper coupling discovered).**
 
-  Phase C-tasks-1-2-and-4 landed:
+  Phase C-tasks-1-2-3-4-and-4-deferred landed:
   - **Task 1 (public getter widening) — COMPLETE.** `BeanPropertyMeta.getClassMeta()` now returns `BeanTypeInfo<?>` (was `ClassMeta<?>`). `BeanMeta.getClassMeta()` now returns `BeanTypeInfo<T>` (was `ClassMeta<T>`). `BeanMeta.getBeanRegistry()` and `BeanMeta.getPropertyBeanRegistry(BeanPropertyMeta)` now return `BeanRegistryLookup` (was `BeanRegistry`). `BeanMap.getClassMeta()` now returns `BeanTypeInfo<T>` (was `ClassMeta<T>`). The cascade across ~30 marshalling-side files needed explic [...]
-  - **Task 2 (`validate(...)` body lift-out) — COMPLETE (signature seam).** Introduced new SPI `BeanTypeResolver` in `org.apache.juneau.commons.bean` (3-method interface: `resolveType(AnnotationInfo<BeanProp>, ClassInfo, TypeVariables)`, `objectType()`, `getAnnotationProvider()`). `MarshallingContext` now `implements BeanTypeResolver` and exposes two new public bridge methods (`resolveType` delegating to the existing protected `resolveClassMeta`, and `objectType` returning `cmObject`). ` [...]
-  - **Task 4 (`new JsonList(valueList)` → `new ArrayList<>(valueList)`) — COMPLETE.** `BeanPropertyMeta.setPropertyValue`'s abstract-Collection branch (the path that builds a typed collection by copying then converting elements) now constructs `new ArrayList<>(valueList)` instead of `new JsonList(valueList)`. Three `BeanMap_Test` assertions (`a05_arrayProperties`, `a06_arrayProperties_usingConfig`, `a09_beanPropertyAnnotation`) updated to expect `ArrayList` for the corresponding `lb1`/`l [...]
-
-  Phase C-tasks-3-and-5 NOT started:
-  - **Task 3 — `BeanMeta` constructor body lift-out** — the `BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], ClassInfo)` constructor still contains marshalling-layer-aware logic (cast back to `ClassMeta<T>` to read `getMarshallingContext()` / `getBeanConfigContext()`, builds swap-aware transforms, registers per-property `BeanRegistry`). Moving that body to a marshalling-side helper (`MarshalledBeanMetaInitializer`) so the commons-side constructor body is commons-clean is still pending.
-  - **Task 4 (deferred CharSequence-parsing sites)** — two `CharSequence`-handling sites inside `setPropertyValue` are still in place: `value1 = JsonMap.ofJson(value21).session(session)` (line 1147, Map branch) and `value1 = new JsonList(value2).setBeanSession(session)` (line 1205, Collection branch). These invoke the marshalling-side JSON parser and should move to a marshalling-side conversion helper before `BeanPropertyMeta` physically moves (or be lifted into a pre-`set(...)` conversi [...]
-  - **Task 5 — Physical `git mv` + reference sweep** — the 8 target files (`BeanMap`, `BeanMapEntry`, `BeanMeta`, `BeanMetaFiltered`, `BeanPropertyMeta`, `BeanPropertyValue`, `BeanPropertyConsumer`, `BeanProxyInvocationHandler`) are still in `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/`. Before the move can succeed cleanly, the marshalling-side imports in the 8 files still need cleanup: `BeanMap` imports `annotation.*`, `internal.*`, `swap.*`; `BeanMapEntry` imports `ann [...]
+  - **Task 2 (`validate(...)` body lift-out) — COMPLETE (signature seam).** Introduced new SPI `BeanTypeResolver` in `org.apache.juneau.commons.bean` (3-method interface: `resolveType(AnnotationInfo<BeanProp>, ClassInfo, TypeVariables)`, `objectType()`, `getAnnotationProvider()`). `MarshallingContext` now `implements BeanTypeResolver` and exposes two new public bridge methods (`resolveType` delegating to the existing protected `resolveClassMeta`, and `objectType` returning `cmObject`). ` [...]
+  - **Task 3 (`BeanMeta` constructor body lift-out) — COMPLETE.** New marshalling-side helper `MarshalledBeanMetaInitializer` in `juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledBeanMetaInitializer.java`. It owns the marshalling-aware helpers that used to live as private code paths inside `BeanMeta`'s constructor and helpers: `buildBeanRegistry(Object marshallingContext, BeanFilter, ClassInfo, BeanConfigContext)` (replaces `findBeanRegistry()` body), `buildPropertyB [...]
+  - **Task 4 (`new JsonList(valueList)` → `new ArrayList<>(valueList)`) — COMPLETE.** `BeanPropertyMeta.setPropertyValue`'s abstract-Collection branch (the path that builds a typed collection by copying then converting elements) now constructs `new ArrayList<>(valueList)` instead of `new JsonList(valueList)`. Three `BeanMap_Test` assertions (`a05_arrayProperties`, `a06_arrayProperties_usingConfig`, `a09_beanPropertyAnnotation`) updated to expect `ArrayList` for the corresponding `lb1`/`l [...]
+  - **Task 4-deferred (CharSequence-parsing sites in `setPropertyValue`) — COMPLETE.** Added two new methods to the `BeanSession` SPI in `commons.bean`: `Map<?,?> parseToMap(CharSequence)` and `Collection<?> parseToList(CharSequence)`. `MarshallingSession` implements both as bridge methods that delegate to `JsonMap.ofJson(value).session(this)` / `new JsonList(value).setBeanSession(this)` respectively (the original inline calls). `BeanPropertyMeta.setPropertyValue`'s two `CharSequence` br [...]
+
+  **Phase C Task 5 status — NOT STARTED.** Surveying the 8 target files in light of the lifts from Tasks 1-4-deferred revealed that the remaining marshalling-side coupling is deeper than the original Task 5 prep scope. To be done in a follow-up pass; this checkpoint stops at "all SPI extractions + helper lifts done; physical move blocked on deeper coupling cleanup".
+
+  Inventory of what's still blocking the physical `git mv` (remaining marshalling-side surface area on the 8 target files):
+
+  - **`BeanPropertyMeta`** — still imports `annotation.*`, `internal.*`, `parser.*`, `serializer.*`, `swap.*`.
+    - `org.apache.juneau.annotation.*` — Javadoc references to `@MarshalledProp`, `@Marshalled`, plus `AnnotationInfo`/`AnnotationProvider` (those live in `commons.reflect` though — confirm).
+    - `org.apache.juneau.internal.*` — uses `TypeVariables`/`ClassInfo` helpers (`TypeVariables` is in `internal`, check it's not commons-reachable).
+    - `org.apache.juneau.parser.ParseException` (catch on line 1120 of `set(...)`).
+    - `org.apache.juneau.swap.ObjectSwap` (cast on line 1259 of `setPropertyValue`'s defensive double-unswap check).
+    - `MarshallingSession`-typed parameter on `setPropertyValue`, `applyChildPropertiesFilter(MarshallingSession, ClassMeta, Object)`, `swapAndFilterProperty(MarshallingSession, Object)`, plus `BiFunction<MarshallingSession,Object,Object>` field types on `readTransform`/`writeTransform` and `Builder.readTransform`/`Builder.writeTransform` setter parameters.
+    - Three remaining `((MarshallingContext) bc).X()` casts: `Builder.rawMetaType(Class<?>)` (`getClassMeta(value)`), the constructor's `ap` initialization (`getAnnotationProvider()`), and `applyChildPropertiesFilter` (`getBeanMeta(o.getClass())`).
+    - `applyChildPropertiesFilter` signature: `(MarshallingSession session, ClassMeta cm, Object o)` — both `MarshallingSession` and `ClassMeta` are marshalling-side; lifting this method to a marshalling-side post-processor (or accepting an `Object`-typed signature with marshalling-side narrowing) is the cleanest move.
+    - `newBeanMap(MarshallingSession session, Object o, BeanMetaFiltered meta)` private static helper — `BeanMetaFiltered` is one of the 8 (moves with cluster); `MarshallingSession` is the blocker.
+    - `BeanMeta#installSwapAwareTransforms` — already moved to `MarshalledPropertyPostProcessor`, but `BeanPropertyMeta.Builder` still has package-private `swap` / `rawTypeMeta` fields read from `MarshalledPropertyPostProcessor` (cross-package access becomes a problem after the move).
+  - **`BeanMeta`** — still imports `annotation.*`.
+    - `@Marshalled`, `@BeanType`, `@BeanProp`, `@BeanCtor`, `@BeanIgnore`, `@Transient`, `@Name`, `@Uri` annotation reads scattered across `create(...)`, the constructor body (line ~453: `ap.find(Marshalled.class, classInfo)`), `findBeanConstructor` (`ap.has(BeanType.class, cm)` / `ap.has(Marshalled.class, cm)`), `findFluentSetters` (uses `@Marshalled`), `findClassFieldMeta` (uses `@BeanProp`, `@Name`, `@BeanIgnore`, `@Transient`), `findGetters`/`findSetters` (uses `@BeanProp`, `@Name`,  [...]
+    - `MarshalledFilter` (`beanFilter` is typed `BeanFilter` per Step 8b-i but `MarshalledFilter` cast surfaces in `getMarshalledFilter()`).
+    - `create(ClassMeta<T>, ClassInfo)` static factory — uses `ClassMeta`/`MarshallingContext.getAnnotationProvider()`; could relocate to `MarshalledBeanMetaInitializer` as `MarshalledBeanMetaInitializer.create(...)` returning `BeanMetaValue<T>`.
+  - **`BeanMap`** — still imports `annotation.*`, `internal.*`, `swap.*`.
+    - `@Marshalled` Javadoc reference and the `BeanMap.of(T)` factory which routes through `MarshallingContext.DEFAULT_SESSION.toBeanMap(bean)` — easiest cleanup is to delete `BeanMap.of(T)` and update callers to use `MarshallingContext.DEFAULT_SESSION.toBeanMap(bean)` directly (callers in `juneau-assertions` need updating; `juneau-assertions` already depends on `juneau-marshall`).
+    - `ObjectSwap` cast inside `BeanMap.getBean()` Optional handling.
+    - `org.apache.juneau.internal.*` — likely `ClassInfo` and `TypeVariables`-like helpers; check if commons-reachable.
+  - **`BeanMapEntry`** — imports `annotation.*` (Javadoc references to `@Marshalled` / `@MarshalledProp`) + `swap.*` (Javadoc `ObjectSwap`). Body references: none that survive Phase B/C. Should be a trivial cleanup: replace Javadoc fully-qualified names with `{@link org.apache.juneau.swap.ObjectSwap}` form so the imports can be dropped.
+  - **`BeanMetaFiltered`** — imports `annotation.*` (just `MarshalledFilter` and `@Marshalled` Javadoc). Single line `super(innerMeta.getClassMeta(), (MarshalledFilter) innerMeta.getMarshalledFilter(), pNames, null);` — body is marshalling-aware. After the cluster moves, this becomes a marshalling-side type that lives in `commons.bean` purely because `BeanMeta` does; the constructor stays unchanged because it threads marshalling-side data through `BeanMeta`'s protected constructor.
+  - **`BeanPropertyValue`** — clean as of Phase C Task 1 (no marshalling-side imports survive). Ready to move.
+  - **`BeanPropertyConsumer`** — clean (functional interface, no marshalling-side references). Ready to move.
+  - **`BeanProxyInvocationHandler`** — still uses `meta.getMarshallingContext().toBeanMap(arg)` inside `equals(Object)`. Route this through `BeanSession.toBeanMap(arg)` using the SPI seam; the field is already typed against an SPI that `MarshallingContext` exposes.
+
+  **Phase C Task 5 prerequisites (estimated effort):**
+  - Lift the ~20 `@Marshalled`/`@BeanProp`/`@BeanCtor`/`@BeanIgnore`/`@Transient`/`@Name`/`@Uri` annotation reads from `BeanMeta` / `BeanPropertyMeta` to `MarshalledBeanMetaInitializer` (or move `@BeanType`/`@Name`/`@Uri` to `commons.bean.annotation` since they're bean-modeling concerns). Estimated: ~150-200 lines of helper code, ~30 call-site changes in `BeanMeta`/`BeanPropertyMeta`.
+  - Replace the three `((MarshallingContext) bc).X()` casts in `BeanPropertyMeta` with SPI calls (`Builder.rawMetaType(Class<?>)`, constructor `ap`, `applyChildPropertiesFilter`). Either extend `BeanTypeResolver` with `getClassMeta(Class<?>)` and `getBeanMeta(Class<?>)` methods, or move the call sites to a marshalling-side helper.
+  - Move `applyChildPropertiesFilter(MarshallingSession, ClassMeta, Object)` to a marshalling-side helper (it's not callable from the commons-side path anyway — it throws `UnsupportedOperationException` when `bc == null`).
+  - Retype `BiFunction<MarshallingSession,Object,Object>` fields on `BeanPropertyMeta`/`BeanPropertyMeta.Builder` to `BiFunction<BeanSession,Object,Object>` (or `BiFunction<Object,Object,Object>`), updating the marshalling-side `MarshalledPropertyPostProcessor.installSwapAwareTransforms` to cast back.
+  - Route `BeanProxyInvocationHandler`'s `meta.getMarshallingContext().toBeanMap(arg)` through `BeanSession.toBeanMap(arg)` (the SPI already supports this — just need to use a `BeanSession`-typed handle).
+  - Delete `BeanMap.of(T)` (and update `juneau-assertions` callers to use `MarshallingContext.DEFAULT_SESSION.toBeanMap` directly) to remove the `MarshallingContext.DEFAULT_SESSION` reference from `BeanMap`.
+  - Clean up Javadoc references in `BeanMapEntry`, `BeanMap`, `BeanMeta`, `BeanPropertyMeta` to fully-qualify marshalling-side links so the wildcard imports can be dropped.
+  - **Then** the physical `git mv` + reference sweep across ~80-120 files + verify `juneau-commons` standalone compile + full test suite.
+
+  Total estimated effort for Task 5 completion: ~600-1000 lines of helper code lifts + careful import surgery on the 8 files + the actual physical move + reference sweep. Recommend a fresh checkpoint pass focused exclusively on Task 5 prep + Task 5.
 
   **Phase B status (uncommitted, working tree) — COMPLETE, build + full test green (49,912 tests pass).**
 
@@ -138,9 +182,9 @@ Known limitations of the commons-side path (acceptable for Step 6, scoped for la
 
   - **Phase C Task 1 — Public API widen.** — DONE. See Phase C status block above. `BeanPropertyMeta.getClassMeta()` → `BeanTypeInfo<?>`, `BeanMeta.getClassMeta()` → `BeanTypeInfo<T>`, `BeanMeta.getBeanRegistry()` / `getPropertyBeanRegistry(...)` → `BeanRegistryLookup`, `BeanMap.getClassMeta()` → `BeanTypeInfo<T>`. ~26 marshalling-side callers updated with explicit `(ClassMeta<?>)` / `(BeanRegistry)` casts at the call sites that invoke `ClassMeta`-only / `BeanRegistry`-only methods. Buil [...]
   - **Phase C Task 2 — `validate(...)` body lift-out.** — DONE (signature seam). See Phase C status block above. New `BeanTypeResolver` SPI in `commons.bean`. `MarshallingContext` implements it. `Builder.validate(BeanTypeResolver bc, ...)` parameter retyped. `bc.resolveClassMeta(...)` → `bc.resolveType(...)`, `bc.object()` → `bc.objectType()`. Build + full test green. Note: three other marshalling-only seams on `BeanPropertyMeta` remain (Builder `rawMetaType(Class<?>)`, constructor `ap`  [...]
-  - **Phase C Task 3 — Marshalling-side `BeanMeta` factory helper.** — NOT STARTED. The `BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], ClassInfo)` protected constructor still contains marshalling-layer-aware logic (cast back to `ClassMeta<T>` to read `getMarshallingContext()` / `getBeanConfigContext()`, builds swap-aware transforms, registers per-property `BeanRegistry`). Move the body of that constructor out to a marshalling-side helper that sets `classMeta` and `marshallingContext` v [...]
-  - **Phase C Task 4 — `JsonList`/`JsonMap` final cleanup.** — PARTIAL. Done: `setPropertyValue`'s `new JsonList(valueList)` flipped to `new ArrayList<>(valueList)` (typed-element abstract-Collection branch). Three test assertions updated. Pass-through behavior preserved for raw-`Object`-element collections (a03/a04 tests still green). Deferred (test-invisible at the moment but still marshalling-layer leaks): two `CharSequence`-handling sites inside `setPropertyValue` (`value1 = JsonMap. [...]
-  - **Phase C Task 5 — Physical `git mv` + reference sweep.** — NOT STARTED. Prerequisites: Phase C Tasks 2, 3, and the deferred Task 4 CharSequence-parsing sites. Before the move, the 8 files still need to drop their marshalling-side imports (`annotation.*`, `internal.*`, `collections.*`, `parser.*`, `serializer.*`, `swap.*`). After those are cleaned up, `git mv` the 8 files into `juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`, update `package` declarations, r [...]
+  - **Phase C Task 3 — Marshalling-side `BeanMeta` factory helper.** — DONE. See Phase C status block above. New `MarshalledBeanMetaInitializer` in `juneau-marshall` owns the marshalling-aware helpers (`buildBeanRegistry`, `buildPropertyBeanRegistry`, `findTypeNameInParents`, `findMarshalledTypeName`, plus extractors `configOf`/`contextOf`/`classInfoOf` and the relocated `findMarshalledFilter`). `installSwapAwareTransforms` relocated from `BeanMeta` to `MarshalledPropertyPostProcessor` ( [...]
+  - **Phase C Task 4 — `JsonList`/`JsonMap` final cleanup.** — DONE. The remaining CharSequence-handling sites (`JsonMap.ofJson(value21).session(session)` and `new JsonList(value2).setBeanSession(session)`) now route through new `BeanSession` SPI methods `parseToMap(CharSequence)` / `parseToList(CharSequence)`. `MarshallingSession` implements both as bridge methods delegating to the original `JsonMap.ofJson` / `new JsonList` calls. `BeanPropertyMeta` dropped its `import org.apache.juneau [...]
+  - **Phase C Task 5 — Physical `git mv` + reference sweep.** — NOT STARTED. Prerequisites partially met (Tasks 1-4-deferred done). The remaining marshalling-side coupling in the 8 files is deeper than the original Task 5 prep scope (see "Phase C Task 5 status" block above for full inventory). Recommend a fresh checkpoint pass focused exclusively on: (a) lifting ~20 `@Marshalled`/`@BeanProp`/`@BeanCtor`/`@BeanIgnore`/`@Transient`/`@Name`/`@Uri` annotation reads from `BeanMeta`/`BeanPrope [...]
 - [ ] **Step 8c** — (optional) Cleanup pass for anything that comes up during 8b-ii: deprecated bridges, stale imports, package-info docs, etc.
 - [ ] **Step 9** — Reference sweep: 80–120 unique files (mostly inside `juneau-marshall`). Update imports, Javadoc `{@link …}` references, package-info docs.
 - [ ] **Step 10** — Update `juneau-docs` release notes / migration guide (`docs/pages/release-notes/9.5.0.md`, `## Package Moves` section) with the bean-runtime relocations.