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 42176847af11a29e1e5007247302f9a2d379e648
Author: James Bognar <[email protected]>
AuthorDate: Tue May 12 17:31:49 2026 -0400

    refactor: lift marshalling-only logic from bean-runtime types and retype 
fields to commons SPI seams (TODO-5 Step 8b-ii Phases A+B)
    
    Co-authored-by: Cursor <[email protected]>
---
 .../src/main/java/org/apache/juneau/BeanMap.java   |  37 +----
 .../main/java/org/apache/juneau/BeanMapLoader.java |  78 +++++++++++
 .../src/main/java/org/apache/juneau/BeanMeta.java  |  33 +++--
 .../java/org/apache/juneau/BeanPropertyMeta.java   | 132 +++++------------
 .../apache/juneau/BeanProxyInvocationHandler.java  |   4 +-
 .../juneau/MarshalledPropertyPostProcessor.java    | 156 +++++++++++++++++++++
 .../java/org/apache/juneau/MarshallingContext.java |   4 +-
 .../java/org/apache/juneau/MarshallingSession.java |   8 +-
 .../java/org/apache/juneau/Annotations_Test.java   |  10 +-
 .../test/java/org/apache/juneau/BeanMap_Test.java  |   8 +-
 todo/TODO-5-bean-runtime-types-to-commons.md       |  46 +++---
 11 files changed, 339 insertions(+), 177 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index 2024ecf378..226e12adc9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -22,17 +22,13 @@ 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.util.*;
 import java.util.function.*;
 
 import org.apache.juneau.annotation.*;
-import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.bean.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.internal.*;
-import org.apache.juneau.json5.*;
-import org.apache.juneau.parser.*;
 import org.apache.juneau.swap.*;
 
 /**
@@ -494,7 +490,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
                                propertyCache = null;
                        } catch (IllegalArgumentException e) {
                                throw bex(e, meta.getClassMeta().inner(), 
"IllegalArgumentException occurred on call to class constructor ''{0}'' with 
argument types ''{1}''", c.getNameSimple(),
-                                       
Json5Serializer.DEFAULT.toString(getClasses(args)));
+                                       Arrays.toString(getClasses(args)));
                        } catch (Exception e) {
                                throw bex(e);
                        }
@@ -622,37 +618,6 @@ public class BeanMap<T> extends AbstractMap<String,Object> 
implements Delegate<T
                return this;
        }
 
-       /**
-        * Convenience method for setting multiple property values by passing 
in a reader.
-        *
-        * @param r The text that will get parsed into a map and then added to 
this map.
-        * @param p The parser to use to parse the text.
-        * @return This object.
-        * @throws ParseException Malformed input encountered.
-        * @throws IOException Thrown by <c>Reader</c>.
-        */
-       public BeanMap<T> load(Reader r, ReaderParser p) throws ParseException, 
IOException {
-               putAll(JsonMap.ofText(r, p));
-               return this;
-       }
-
-       /**
-        * Convenience method for setting multiple property values by passing 
in JSON text.
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *      <jv>beanMap</jv>.load(<js>"{name:'John Smith',age:21}"</js>)
-        * </p>
-        *
-        * @param input The text that will get parsed into a map and then added 
to this map.
-        * @return This object.
-        * @throws ParseException Malformed input encountered.
-        */
-       public BeanMap<T> load(String input) throws ParseException {
-               putAll(JsonMap.ofJson(input));
-               return this;
-       }
-
        /**
         * Sets a property on the bean.
         *
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
new file mode 100644
index 0000000000..754f59f744
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMapLoader.java
@@ -0,0 +1,78 @@
+/*
+ * 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 java.io.*;
+
+import org.apache.juneau.collections.*;
+import org.apache.juneau.parser.*;
+
+/**
+ * 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>
+ *     BeanMap&lt;Person&gt; <jv>m</jv> = 
<jv>bc</jv>.newBeanMap(Person.<jk>class</jk>);
+ *     BeanMapLoader.<jsm>load</jsm>(<jv>m</jv>, 
<js>"{name:'John',age:21}"</js>);
+ * </p>
+ */
+public final class BeanMapLoader {
+
+       private BeanMapLoader() {}
+
+       /**
+        * Populates the supplied {@link BeanMap} with the contents of the JSON 
text in {@code input}.
+        *
+        * <p>
+        * Equivalent to the legacy {@code BeanMap.load(String)} method.
+        *
+        * @param <T> The bean type.
+        * @param m The bean map to populate.
+        * @param input The text that will get parsed into a map and then added 
to {@code m}.
+        * @return The supplied bean map for fluent chaining.
+        * @throws ParseException Malformed input encountered.
+        */
+       public static <T> BeanMap<T> load(BeanMap<T> m, String input) throws 
ParseException {
+               m.putAll(JsonMap.ofJson(input));
+               return m;
+       }
+
+       /**
+        * Populates the supplied {@link BeanMap} with the contents of the 
reader using the specified {@link ReaderParser}.
+        *
+        * <p>
+        * Equivalent to the legacy {@code BeanMap.load(Reader, ReaderParser)} 
method.
+        *
+        * @param <T> The bean type.
+        * @param m The bean map to populate.
+        * @param r The reader containing serialized text.
+        * @param p The parser to use to parse the text.
+        * @return The supplied bean map for fluent chaining.
+        * @throws ParseException Malformed input encountered.
+        * @throws IOException Thrown by the reader.
+        */
+       public static <T> BeanMap<T> load(BeanMap<T> m, Reader r, ReaderParser 
p) throws ParseException, IOException {
+               m.putAll(JsonMap.ofText(r, p));
+               return m;
+       }
+}
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 8795a98aba..a85d3577b9 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
@@ -363,9 +363,9 @@ public class BeanMeta<T> {
        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).
-       private final Supplier<BeanRegistry> beanRegistry;                      
   // The bean registry for this bean.
+       private final Supplier<BeanRegistryLookup> beanRegistry;                
   // The bean registry for this bean.  Typed against the commons.bean SPI 
seam; marshalling-side callers (and the {@link #getBeanRegistry()} getter) cast 
back to {@link BeanRegistry} — the only concrete in-tree implementation.
        private final Supplier<List<ClassInfo>> classHierarchy;                 
   // List of all classes traversed in the class hierarchy.
-       private final ClassMeta<T> classMeta;                                   
   // The target class type that this meta object describes.  Null when 
constructed via {@link #of(Class, BeanConfigContext)}.
+       private final BeanTypeInfo<T> classMeta;                                
   // The target class type that this meta object describes.  Null when 
constructed via {@link #of(Class, BeanConfigContext)}.  Concrete instances are 
always {@link ClassMeta}; typed against the bean-modeling SPI seam for the 
eventual move to commons.bean.
        private final ClassInfo classInfo;                                      
   // Pure-reflection view of the bean class (Step 2 of TODO-5 — decouples bean 
modeling from ClassMeta).  Always non-null.
        private final Supplier<String> dictionaryName;                          
   // The @Marshalled(typeName) annotation defined on this bean class.
        private final BeanPropertyMeta dynaProperty;                            
   // "extras" property.
@@ -382,7 +382,7 @@ public class BeanMeta<T> {
        private final ClassInfo stopClass;                                      
    // The stop class for hierarchy traversal.
        private final BeanPropertyMeta typeProperty;                            
   // "_type" mock bean property.
        private final String typePropertyName;                                  
   // "_type" property actual name.
-       private final Map<BeanPropertyMeta,BeanRegistry> 
propertyBeanRegistries;   // Per-property BeanRegistry side-map (Step 5 of 
TODO-5 — keeps BeanRegistry off BeanPropertyMeta itself).
+       private final Map<BeanPropertyMeta,BeanRegistryLookup> 
propertyBeanRegistries;   // Per-property BeanRegistry side-map (Step 5 of 
TODO-5 — keeps BeanRegistry off BeanPropertyMeta itself).  Typed against the 
commons.bean SPI seam; the {@link #getPropertyBeanRegistry(BeanPropertyMeta)} 
getter casts back to {@link BeanRegistry}.
 
        /**
         * Creates a {@link BeanMeta} for the specified class using the 
supplied {@link BeanConfigContext}.
@@ -444,8 +444,8 @@ public class BeanMeta<T> {
         * @param pNames Explicit list of property names and order. If 
<jk>null</jk>, properties are determined automatically.
         * @param implClass Optional implementation class constructor to use if 
one cannot be found. Can be <jk>null</jk>.
         */
-       protected BeanMeta(ClassMeta<T> cm, BeanFilter bf, String[] pNames, 
ClassInfo implClass) {
-               this(cm, cm, cm.getMarshallingContext().getBeanConfigContext(), 
cm.getMarshallingContext(), bf, pNames, implClass);
+       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);
        }
 
        /**
@@ -491,7 +491,7 @@ public class BeanMeta<T> {
                var getterPropsMap = CollectionUtils.<Method,String>map();  // 
Convert to MethodInfo keys
                var setterPropsMap = CollectionUtils.<Method,String>map();
                var dynaPropertyValue = Value.<BeanPropertyMeta>empty();
-               var propertyBeanRegistriesTemp = 
CollectionUtils.<BeanPropertyMeta,BeanRegistry>map();  // Per-property 
BeanRegistry side-map (TODO-5 Step 5).
+               var propertyBeanRegistriesTemp = 
CollectionUtils.<BeanPropertyMeta,BeanRegistryLookup>map();  // Per-property 
BeanRegistry side-map (TODO-5 Step 5).
                var unsortedPropertiesTemp = false;
                var ba = ap.find(Marshalled.class, classInfo);
                var btList = 
ap.find(org.apache.juneau.commons.bean.BeanType.class, classInfo);
@@ -631,7 +631,7 @@ public class BeanMeta<T> {
                                // 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.get(), v.dictionaryClasses));
+                                       propertyBeanRegistriesTemp.put(pMeta, 
new BeanRegistry(marshallingContext, (BeanRegistry) beanRegistry.get(), 
v.dictionaryClasses));
                        });
 
                        // If a beanFilter is defined, look for inclusion and 
exclusion lists.
@@ -704,6 +704,13 @@ public class BeanMeta<T> {
                        // raw-reflection mode (no 
ClassMeta/ObjectSwap/BeanRegistry resolution).
                        if (p.validate(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.
+                               if (nn(marshallingContext))
+                                       
MarshalledPropertyPostProcessor.process(marshallingContext, p);
+
                                installSwapAwareTransforms(p);
 
                                if (nn(p.getter))
@@ -743,8 +750,8 @@ public class BeanMeta<T> {
                "unchecked"   // Wildcard ObjectSwap captured by raw alias to 
allow runtime polymorphic dispatch.
        })
        private static void installSwapAwareTransforms(BeanPropertyMeta.Builder 
p) {
-               ObjectSwap sw = p.swap;
-               ClassMeta<?> rtm = p.rawTypeMeta;
+               ObjectSwap sw = (ObjectSwap) p.swap;
+               ClassMeta<?> rtm = (ClassMeta<?>) p.rawTypeMeta;
                if (sw == null && (rtm == null || ! rtm.hasChildSwaps()))
                        return;
                if (p.readTransform == null) {
@@ -832,7 +839,7 @@ public class BeanMeta<T> {
         *
         * @return The bean registry for this bean, or <jk>null</jk> if no bean 
registry is associated with it.
         */
-       public BeanRegistry getBeanRegistry() { return beanRegistry.get(); }
+       public BeanRegistry getBeanRegistry() { return (BeanRegistry) 
beanRegistry.get(); }
 
        /**
         * Returns the per-property {@link BeanRegistry} associated with the 
given {@link BeanPropertyMeta}.
@@ -857,7 +864,7 @@ public class BeanMeta<T> {
         *      belongs to a different bean meta).
         */
        public BeanRegistry getPropertyBeanRegistry(BeanPropertyMeta p) {
-               return propertyBeanRegistries.get(p);
+               return (BeanRegistry) propertyBeanRegistries.get(p);
        }
 
        /**
@@ -870,7 +877,7 @@ public class BeanMeta<T> {
         *
         * @return The {@link ClassMeta} of this bean, or <jk>null</jk> for 
bean-modeling-only construction.
         */
-       public ClassMeta<T> getClassMeta() { return classMeta; }
+       public ClassMeta<T> getClassMeta() { return (ClassMeta<T>) classMeta; }
 
        /**
         * Returns the {@link ClassInfo} of this bean.
@@ -1523,7 +1530,7 @@ public class BeanMeta<T> {
         * @return A new {@link BeanRegistry} containing the dictionary classes 
for this bean, or an empty registry if
         *      no dictionary classes are found.
         */
-       private BeanRegistry findBeanRegistry() {
+       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).
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 d21237c5a9..f56a3b2a45 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
@@ -44,7 +44,6 @@ import org.apache.juneau.internal.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.swap.*;
-import org.apache.juneau.swaps.*;
 
 /**
  * Contains metadata about a bean property.
@@ -85,7 +84,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
         */
        public static class Builder {
                BeanMeta<?> beanMeta;  // Package-private for BeanMeta access
-               MarshallingContext bc;  // Package-private for BeanMeta access. 
 Null when the owning BeanMeta was built via the commons-side path.
+               Object bc;  // Object-typed (was MarshallingContext) so the 
field can live in commons.bean; cast to MarshallingContext at marshalling-side 
use sites.  Null when the owning BeanMeta was built via the commons-side path.
                BeanConfigContext config;  // Package-private for BeanMeta 
access.  Always non-null — sourced from the owning BeanMeta.
                String name;  // Package-private for BeanMeta access
                FieldInfo field;  // Package-private for BeanMeta access
@@ -93,8 +92,8 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                MethodInfo getter;  // Package-private for BeanMeta access
                MethodInfo setter;  // Package-private for BeanMeta access
                MethodInfo extraKeys;  // Package-private for BeanMeta access
-               ClassMeta<?> rawTypeMeta;  // Package-private for BeanMeta 
access (used to install swap-aware transforms).  Null on commons-side path (no 
type resolution).
-               ObjectSwap swap;  // Package-private for BeanMeta access (used 
to install swap-aware transforms)
+               BeanTypeInfo<?> rawTypeMeta;  // Package-private for BeanMeta 
access (used to install swap-aware transforms).  Null on commons-side path (no 
type resolution).  Concrete instances are always {@link ClassMeta} since it's 
the only in-tree implementation; the field is typed against the bean-modeling 
SPI seam so the field can live in commons.bean.
+               Object swap;  // Object-typed so the field can live in 
commons.bean; cast to ObjectSwap by marshalling-side consumers.  Set only via 
MarshalledPropertyPostProcessor (marshalling-side post-processor).
                BiFunction<MarshallingSession,Object,Object> readTransform;  // 
Package-private; defaults to identity if null.
                BiFunction<MarshallingSession,Object,Object> writeTransform; // 
Package-private; defaults to identity if null.
                List<ClassInfo> dictionaryClasses;  // Package-private for 
BeanMeta access; @MarshalledProp(dictionary={}) classes scanned during 
validate().
@@ -102,8 +101,8 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                private boolean isUri;
                private boolean isDyna;
                private boolean isDynaGetterMap;
-               private ClassMeta<?> typeMeta;
-               private List<String> properties;
+               BeanTypeInfo<?> typeMeta;  // Package-private so the 
marshalling-side post-processor can override after @Swap/@MarshalledProp 
detection.  Concrete instances are always {@link ClassMeta}; typed against the 
bean-modeling SPI seam.
+               List<String> properties;  // Package-private so the 
marshalling-side post-processor can install @MarshalledProp(properties) 
override list.
                private Object overrideValue;
                private BeanPropertyMeta delegateFor;
                private boolean canRead;
@@ -219,38 +218,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        assertArgNotNull(ARG_value, value);
                        if (bc == null)
                                return this;
-                       return rawMetaType(bc.getClassMeta(value));
-               }
-
-               private static ObjectSwap 
marshalledPropSwap(AnnotationInfo<MarshalledProp> ai) {
-                       var p = ai.inner();
-                       if (! p.format().isEmpty())
-                               return 
BeanInstantiator.of(ObjectSwap.class).type(StringFormatSwap.class).addBean(String.class,
 p.format()).run();
-                       return null;
-               }
-
-               @SuppressWarnings({
-                       "java:S112" // throws RuntimeException intentional - 
callback/lifecycle method for swap initialization
-               })
-               private static ObjectSwap swapSwap(AnnotationInfo<Swap> ai) 
throws RuntimeException {
-                       var s = ai.inner();
-                       var c = s.value();
-                       if (isVoid(c))
-                               c = s.impl();
-                       if (isVoid(c))
-                               return null;
-                       var ci = info(c);
-                       if (ci.isAssignableTo(ObjectSwap.class)) {
-                               var ps = 
BeanInstantiator.of(ObjectSwap.class).type(ci).run();
-                               if (nn(ps.forMediaTypes()))
-                                       throw unsupportedOp("TODO - Media types 
on swaps not yet supported on bean properties.");
-                               if (nn(ps.withTemplate()))
-                                       throw unsupportedOp("TODO - Templates 
on swaps not yet supported on bean properties.");
-                               return ps;
-                       }
-                       if (ci.isAssignableTo(Surrogate.class))
-                               throw unsupportedOp("TODO - Surrogate swaps not 
yet supported on bean properties.");
-                       throw rex("Invalid class used in @Swap annotation.  
Must be a subclass of ObjectSwap or Surrogate. {0}", cn(c));
+                       return rawMetaType(((MarshallingContext) 
bc).getClassMeta(value));
                }
 
                /**
@@ -373,7 +341,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                })
                public boolean validate(MarshallingContext bc, TypeVariables 
typeVarImpls, Set<String> bpro, Set<String> bpwo) throws Exception {
 
-                       var bdClasses = list();
                        var ap = nn(bc) ? bc.getAnnotationProvider() : 
config.getAnnotationProvider();
 
                        if (field == null && getter == null && setter == null)
@@ -390,9 +357,14 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        var gi = getter;
                        var si = setter;
 
+                       // @MarshalledProp / @Swap annotation reads have been 
lifted out to the marshalling-side post-processor
+                       // (see {@link 
MarshalledPropertyPostProcessor#process}).  They are processed by {@link 
BeanMeta}
+                       // after this method returns.  The post-processor 
mutates {@link #swap}, {@link #properties},
+                       // {@link #dictionaryClasses}, and {@link #typeMeta} 
(when a swap is detected) on this builder.
+                       dictionaryClasses = liste();
+
                        if (nn(innerField)) {
                                var lbp = ap.find(BeanProp.class, ifi);
-                               var lmp = ap.find(MarshalledProp.class, ifi);
                                if (nn(bc) && (nn(field) || ne(lbp))) {
                                        // Only use field type if it's a bean 
property or has @BeanProp annotation.
                                        // Otherwise, we want to infer the type 
from the getter or setter.
@@ -406,21 +378,11 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                        if (ne(beanp.wo()))
                                                writeOnly = bool(beanp.wo());
                                });
-                               lmp.forEach(x -> {
-                                       var beanp = x.inner();
-                                       if (swap == null)
-                                               swap = marshalledPropSwap(x);
-                                       if (ne(beanp.properties()))
-                                               properties = 
split(beanp.properties());
-                                       bdClasses.addAll(l(beanp.dictionary()));
-                               });
-                               ap.find(Swap.class, 
ifi).stream().findFirst().ifPresent(x -> swap = swapSwap(x));
                                isUri |= ap.has(Uri.class, ifi);
                        }
 
                        if (nn(getter)) {
                                var lbp = ap.find(BeanProp.class, gi);
-                               var lmp = ap.find(MarshalledProp.class, gi);
                                if (nn(bc) && rawTypeMeta == null)
                                        rawTypeMeta = 
bc.resolveClassMeta(opt(last(lbp)).orElse(null), getter.getReturnType(), 
typeVarImpls);
                                if (nn(rawTypeMeta))
@@ -433,20 +395,10 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                        if (ne(beanp.wo()))
                                                writeOnly = bool(beanp.wo());
                                });
-                               lmp.forEach(x -> {
-                                       var beanp = x.inner();
-                                       if (swap == null)
-                                               swap = marshalledPropSwap(x);
-                                       if (nn(properties) && 
ne(beanp.properties()))
-                                               properties = 
split(beanp.properties());
-                                       bdClasses.addAll(l(beanp.dictionary()));
-                               });
-                               ap.find(Swap.class, gi).stream().forEach(x -> 
swap = swapSwap(x));
                        }
 
                        if (nn(setter)) {
                                var lbp = ap.find(BeanProp.class, si);
-                               var lmp = ap.find(MarshalledProp.class, si);
                                if (nn(bc) && rawTypeMeta == null)
                                        rawTypeMeta = 
bc.resolveClassMeta(opt(last(lbp)).orElse(null), 
setter.getParameterTypes().get(0), typeVarImpls);
                                if (nn(rawTypeMeta))
@@ -459,15 +411,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                        if (ne(beanp.wo()))
                                                writeOnly = bool(beanp.wo());
                                });
-                               lmp.forEach(x -> {
-                                       var beanp = x.inner();
-                                       if (swap == null)
-                                               swap = marshalledPropSwap(x);
-                                       if (nn(properties) && 
ne(beanp.properties()))
-                                               properties = 
split(beanp.properties());
-                                       bdClasses.addAll(l(beanp.dictionary()));
-                               });
-                               ap.find(Swap.class, si).stream().forEach(x -> 
swap = swapSwap(x));
                        }
 
                        // On the commons-side path (bc == null), rawTypeMeta 
stays null and validate() accepts the property
@@ -475,8 +418,6 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                        if (nn(bc) && rawTypeMeta == null)
                                return false;
 
-                       dictionaryClasses = 
bdClasses.stream().map(ReflectionUtils::info).toList();
-
                        isDyna = "*".equals(name);
 
                        // Do some annotation validation.  Type-aware 
validation requires rawTypeMeta — on the commons-side
@@ -528,13 +469,10 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                }
 
                                if (typeMeta == null) {
-                                       if (nn(swap) && nn(bc)) {
-                                               typeMeta = 
bc.getClassMeta(swap.getSwapClass());
-                                       } else if (rawTypeMeta == null && 
nn(bc)) {
+                                       if (rawTypeMeta == null && nn(bc))
                                                typeMeta = bc.object();
-                                       } else {
+                                       else
                                                typeMeta = rawTypeMeta;
-                                       }
                                }
                                if (typeMeta == null)
                                        typeMeta = rawTypeMeta;
@@ -563,7 +501,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
 
        private final AnnotationProvider ap;                             // 
Annotation provider for finding annotations on this property.  Sourced from bc 
(marshalling-side) or beanMeta.getConfig() (commons-side).
        private final Supplier<List<AnnotationInfo<?>>> annotations;     // 
Memoized list of all annotations on this property.
-       private final MarshallingContext bc;                             // The 
context that created this meta.  Null when the owning BeanMeta was built via 
the commons-side path.
+       private final Object bc;                                         // 
MarshallingContext, but Object-typed so the field can live in commons.bean.  
Cast at marshalling-side use sites.  Null when the owning BeanMeta was built 
via the commons-side path.
        private final BeanConfigContext config;                          // 
Bean-modeling settings facade — always non-null.  Mirrors the BeanMeta's config.
        private final BeanMeta<?> beanMeta;                              // The 
bean that this property belongs to.
        private final boolean canRead;                                   // 
True if this property can be read.
@@ -580,12 +518,12 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
        private final String name;                                       // The 
name of the property.
        private final Object overrideValue;                              // The 
bean property value (if it's an overridden delegate).
        private final List<String> properties;                           // The 
value of the @MarshalledProp(properties) annotation (unmodifiable).
-       private final ClassMeta<?> rawTypeMeta;                          // The 
real class type of the bean property.
+       private final BeanTypeInfo<?> rawTypeMeta;                       // The 
real class type of the bean property.  Concrete instances are always {@link 
ClassMeta}; typed against the bean-modeling SPI seam for the eventual move to 
commons.bean.
        private final BiFunction<MarshallingSession,Object,Object> 
readTransform;  // Applied to raw getter result; identity by default.
        private final boolean readOnly;                                  // 
True if this property is read-only.
        private final MethodInfo setter;                                 // The 
bean property setter.
-       private final ObjectSwap swap;                                   // 
ObjectSwap defined only via @MarshalledProp(format=...) annotation.
-       private final ClassMeta<?> typeMeta;                             // The 
transformed class type of the bean property.
+       private final Object swap;                                       // 
ObjectSwap, but Object-typed so the field can live in commons.bean; cast at 
marshalling-side use sites.  Defined only via @MarshalledProp(format=...) or 
@Swap.
+       private final BeanTypeInfo<?> typeMeta;                          // The 
transformed class type of the bean property.  Concrete instances are always 
{@link ClassMeta}; typed against the bean-modeling SPI seam.
        private final BiFunction<MarshallingSession,Object,Object> 
writeTransform; // Applied to incoming value before raw setter; identity by 
default.
        private final boolean writeOnly;                                 // 
True if this property is write-only.
 
@@ -621,7 +559,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                readTransform = b.readTransform != null ? b.readTransform : 
(session, o) -> o;
                writeTransform = b.writeTransform != null ? b.writeTransform : 
(session, o) -> o;
 
-               ap = nn(bc) ? bc.getAnnotationProvider() : 
b.config.getAnnotationProvider();
+               ap = nn(bc) ? ((MarshallingContext) bc).getAnnotationProvider() 
: b.config.getAnnotationProvider();
                hashCode = h(beanMeta, name);
        }
 
@@ -661,8 +599,8 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                // Read-only beans get their properties stored in a cache.
                if (m.bean == null) {
                        if (! m.propertyCache.containsKey(name))
-                               m.propertyCache.put(name, new 
JsonList(m.getMarshallingSession()));
-                       ((JsonList)m.propertyCache.get(name)).add(value);
+                               m.propertyCache.put(name, new ArrayList<>());
+                       ((List)m.propertyCache.get(name)).add(value);
                        return;
                }
 
@@ -696,7 +634,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                if (rawTypeMeta.canCreateNewInstance())
                                        c = 
(Collection)rawTypeMeta.newInstance();
                                else
-                                       c = new JsonList(session);
+                                       c = new ArrayList<>();
 
                                if (c2 != null)
                                        c.addAll(c2);
@@ -764,8 +702,8 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                // Read-only beans get their properties stored in a cache.
                if (m.bean == null) {
                        if (! m.propertyCache.containsKey(name))
-                               m.propertyCache.put(name, new 
JsonMap(m.getMarshallingSession()));
-                       ((JsonMap)m.propertyCache.get(name)).append(key, value);
+                               m.propertyCache.put(name, new 
LinkedHashMap<>());
+                       ((Map)m.propertyCache.get(name)).put(key, value);
                        return;
                }
 
@@ -793,7 +731,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                if (rawTypeMeta.canCreateNewInstance())
                                        map = (Map)rawTypeMeta.newInstance();
                                else
-                                       map = new JsonMap(session);
+                                       map = new LinkedHashMap<>();
 
                                map.put(key, v);
 
@@ -965,7 +903,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
         *
         * @return The {@link ClassMeta} of the class of this property.
         */
-       public ClassMeta<?> getClassMeta() { return typeMeta; }
+       public ClassMeta<?> getClassMeta() { return (ClassMeta<?>) typeMeta; }
 
        /**
         * Returns the metadata on the property that this metadata is a 
delegate for.
@@ -1184,7 +1122,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                "java:S3776", // Cognitive complexity acceptable for complex 
property setter
                "java:S6541" // Brain method acceptable - complex property 
value setting logic requires high LOC/complexity
        })
-       private Object setPropertyValue(BeanMap<?> m, String pName, Object 
value1, Object bean, boolean isMap, boolean isCollection, MarshallingSession 
session) throws ParseException {
+       private Object setPropertyValue(BeanMap<?> m, String pName, Object 
value1, Object bean, boolean isMap, boolean isCollection, MarshallingSession 
session) {
                try {
                        var r = (config.isBeanMapPutReturnsOldValue() || isMap 
|| isCollection) && (nn(getter) || nn(field)) ? get(m, pName) : null;
                        var propertyClass = rawTypeMeta.inner();
@@ -1314,10 +1252,12 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                });
 
                        } else {
-                               if (nn(swap) && value1 != null && 
swap.getSwapClass().isAssignableFrom(value1.getClass())) {
+                               if (nn(swap) && value1 != null && 
((ObjectSwap)swap).getSwapClass().isAssignableFrom(value1.getClass())) {
                                        // Defensive double-unswap path: value1 
is still in swapped form (the outer writeTransform
                                        // did not normalize it for some 
reason).  Route through the install-time write transform so
-                                       // BeanPropertyMeta itself does not 
invoke ObjectSwap directly.
+                                       // BeanPropertyMeta itself does not 
invoke ObjectSwap directly.  Note: cast lives here because
+                                       // the field is Object-typed for the 
eventual move to commons.bean; this entire branch is
+                                       // expected to migrate to a 
marshalling-side post-processor in Phase C.
                                        value1 = writeTransform.apply(session, 
value1);
                                } else {
                                        // Pass bean as outer for non-static 
inner class instantiation (e.g. J2 with string constructor)
@@ -1384,7 +1324,7 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                                var propsArray = properties == null ? null : 
properties.toArray(new String[0]);
                                return new FilteredKeyMap(cm, o2, propsArray);
                        }
-                       var bm = bc.getBeanMeta(o.getClass());
+                       var bm = ((MarshallingContext) 
bc).getBeanMeta(o.getClass());
                        if (nn(bm))
                                return newBeanMap(session, o, new 
BeanMetaFiltered(cm.getBeanMeta(), properties));
                }
@@ -1442,19 +1382,19 @@ public class BeanPropertyMeta implements 
Comparable<BeanPropertyMeta> {
                if (nn(properties) && nn(rawTypeMeta)) {
                        if (rawTypeMeta.isArray()) {
                                var a = (Object[])o;
-                               var l1 = new DelegateList(rawTypeMeta);
-                               var childType1 = rawTypeMeta.getElementType();
+                               var l1 = new DelegateList((ClassMeta<?>) 
rawTypeMeta);
+                               var childType1 = (ClassMeta<?>) 
rawTypeMeta.getElementType();
                                for (var c1 : a)
                                        
l1.add(applyChildPropertiesFilter(session, childType1, c1));
                                return l1;
                        } else if (rawTypeMeta.isCollection()) {
                                var c = (Collection)o;
                                var l = listOfSize(c.size());
-                               var childType = rawTypeMeta.getElementType();
+                               var childType = (ClassMeta<?>) 
rawTypeMeta.getElementType();
                                c.forEach(x -> 
l.add(applyChildPropertiesFilter(session, childType, x)));
                                return l;
                        } else {
-                               return applyChildPropertiesFilter(session, 
rawTypeMeta, o);
+                               return applyChildPropertiesFilter(session, 
(ClassMeta<?>) rawTypeMeta, o);
                        }
                }
                return o;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
index 9f5bb836db..be571d4962 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanProxyInvocationHandler.java
@@ -23,8 +23,6 @@ import static org.apache.juneau.commons.utils.Utils.*;
 import java.lang.reflect.*;
 import java.util.*;
 
-import org.apache.juneau.json5.*;
-
 /**
  * Provides an {@link InvocationHandler} for creating dynamic proxy instances 
of bean interfaces.
  *
@@ -140,7 +138,7 @@ public class BeanProxyInvocationHandler<T> implements 
InvocationHandler {
                        return Integer.valueOf(this.beanProps.hashCode());
 
                if (mi.hasName("toString") && mi.getParameterCount() == 0)
-                       return Json5Serializer.DEFAULT.toString(this.beanProps);
+                       return Objects.toString(this.beanProps);
 
                var prop = meta.getGetterProps().get(method);
                if (nn(prop))
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
new file mode 100644
index 0000000000..c0f1332bc0
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java
@@ -0,0 +1,156 @@
+/*
+ * 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.reflect.ReflectionUtils.*;
+import static org.apache.juneau.commons.utils.ClassUtils.*;
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+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.util.*;
+
+import org.apache.juneau.annotation.*;
+import org.apache.juneau.commons.inject.*;
+import org.apache.juneau.commons.reflect.*;
+import org.apache.juneau.swap.*;
+import org.apache.juneau.swaps.*;
+
+/**
+ * Marshalling-side post-processor that applies {@link MarshalledProp 
@MarshalledProp} and {@link Swap @Swap}
+ * annotation effects to a {@link BeanPropertyMeta.Builder} after {@link 
BeanPropertyMeta.Builder#validate validate()}
+ * runs successfully.
+ *
+ * <p>
+ * The reads here used to live inside {@link 
BeanPropertyMeta.Builder#validate}.  They were lifted out as part of
+ * TODO-5 Step 8b-ii so the bean-modeling builder no longer references the 
marshalling-side
+ * {@link ObjectSwap}/{@link StringFormatSwap}/{@link Surrogate} types or the 
{@link MarshalledProp} annotation.
+ *
+ * <p>
+ * This post-processor mutates the following fields on the supplied builder 
when it runs:
+ * <ul>
+ *     <li>{@code swap} — set to a {@link StringFormatSwap} (from {@link 
MarshalledProp#format()}) or a custom
+ *             {@link ObjectSwap} (from {@link Swap}).
+ *     <li>{@code properties} — set to the property-override list from {@link 
MarshalledProp#properties()}.
+ *     <li>{@code dictionaryClasses} — appended with the {@link 
MarshalledProp#dictionary()} entries.
+ *     <li>{@code typeMeta} — refreshed to the swap class meta when a swap is 
installed.
+ * </ul>
+ *
+ * <p>
+ * Only invoked from {@link BeanMeta#validateAndRegisterProperty} on the 
marshalling-side construction path
+ * (i.e. when {@link MarshallingContext} is non-null).  The bean-modeling-only 
path
+ * ({@link BeanMeta#of(Class, BeanConfigContext)}) does not run this 
post-processor.
+ */
+final class MarshalledPropertyPostProcessor {
+
+       private MarshalledPropertyPostProcessor() {}
+
+       /**
+        * Applies {@code @MarshalledProp}/{@code @Swap} annotation effects to 
{@code b} using {@code bc} for
+        * {@link ClassMeta} resolution.
+        *
+        * @param bc The marshalling context.  Must not be <jk>null</jk>.
+        * @param b The bean-property builder.  Must not be <jk>null</jk>.
+        */
+       static void process(MarshallingContext bc, BeanPropertyMeta.Builder b) {
+               var ap = bc.getAnnotationProvider();
+               var bdClasses = new ArrayList<Class<?>>();
+
+               // innerField, getter, setter — same order as the original 
validate() loop.
+               if (nn(b.innerField)) {
+                       ap.find(MarshalledProp.class, b.innerField).forEach(x 
-> {
+                               var mp = x.inner();
+                               if (b.swap == null)
+                                       b.swap = marshalledPropSwap(x);
+                               if (ne(mp.properties()))
+                                       b.properties = split(mp.properties());
+                               bdClasses.addAll(l(mp.dictionary()));
+                       });
+                       ap.find(Swap.class, 
b.innerField).stream().findFirst().ifPresent(x -> b.swap = swapSwap(x));
+               }
+
+               if (nn(b.getter)) {
+                       ap.find(MarshalledProp.class, b.getter).forEach(x -> {
+                               var mp = x.inner();
+                               if (b.swap == null)
+                                       b.swap = marshalledPropSwap(x);
+                               if (nn(b.properties) && ne(mp.properties()))
+                                       b.properties = split(mp.properties());
+                               bdClasses.addAll(l(mp.dictionary()));
+                       });
+                       ap.find(Swap.class, b.getter).stream().forEach(x -> 
b.swap = swapSwap(x));
+               }
+
+               if (nn(b.setter)) {
+                       ap.find(MarshalledProp.class, b.setter).forEach(x -> {
+                               var mp = x.inner();
+                               if (b.swap == null)
+                                       b.swap = marshalledPropSwap(x);
+                               if (nn(b.properties) && ne(mp.properties()))
+                                       b.properties = split(mp.properties());
+                               bdClasses.addAll(l(mp.dictionary()));
+                       });
+                       ap.find(Swap.class, b.setter).stream().forEach(x -> 
b.swap = swapSwap(x));
+               }
+
+               if (! bdClasses.isEmpty()) {
+                       var infos = new 
ArrayList<ClassInfo>(b.dictionaryClasses.size() + bdClasses.size());
+                       infos.addAll(b.dictionaryClasses);
+                       bdClasses.forEach(c -> infos.add(info(c)));
+                       b.dictionaryClasses = infos;
+               }
+
+               // If a swap was installed and we have a rawTypeMeta, refresh 
typeMeta to the swap's swap-class meta.
+               // Matches the original behavior inside validate() which routed 
swap-class meta through bc.getClassMeta.
+               // Cast to ObjectSwap because b.swap is Object-typed 
(BeanPropertyMeta.Builder lives in the bean-modeling
+               // layer and cannot reference ObjectSwap directly).
+               if (nn(b.swap) && nn(b.rawTypeMeta))
+                       b.typeMeta = bc.getClassMeta(((ObjectSwap) 
b.swap).getSwapClass());
+       }
+
+       private static ObjectSwap 
marshalledPropSwap(AnnotationInfo<MarshalledProp> ai) {
+               var p = ai.inner();
+               if (! p.format().isEmpty())
+                       return 
BeanInstantiator.of(ObjectSwap.class).type(StringFormatSwap.class).addBean(String.class,
 p.format()).run();
+               return null;
+       }
+
+       @SuppressWarnings({
+               "java:S112" // throws RuntimeException intentional - 
callback/lifecycle method for swap initialization
+       })
+       private static ObjectSwap swapSwap(AnnotationInfo<Swap> ai) {
+               var s = ai.inner();
+               var c = s.value();
+               if (isVoid(c))
+                       c = s.impl();
+               if (isVoid(c))
+                       return null;
+               var ci = info(c);
+               if (ci.isAssignableTo(ObjectSwap.class)) {
+                       var ps = 
BeanInstantiator.of(ObjectSwap.class).type(ci).run();
+                       if (nn(ps.forMediaTypes()))
+                               throw unsupportedOp("TODO - Media types on 
swaps not yet supported on bean properties.");
+                       if (nn(ps.withTemplate()))
+                               throw unsupportedOp("TODO - Templates on swaps 
not yet supported on bean properties.");
+                       return ps;
+               }
+               if (ci.isAssignableTo(Surrogate.class))
+                       throw unsupportedOp("TODO - Surrogate swaps not yet 
supported on bean properties.");
+               throw rex("Invalid class used in @Swap annotation.  Must be a 
subclass of ObjectSwap or Surrogate. {0}", cn(c));
+       }
+}
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 91073ef242..be8e32c3f0 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
@@ -4184,7 +4184,7 @@ public class MarshallingContext extends Context 
implements ConversionFinder {
                        return (in, memberOf, session, args) -> {
                                try {
                                        var bs = beanSessionOrDefault(session);
-                                       return 
bs.newBeanMap(toMeta.inner()).load(in.toString()).getBean();
+                                       return 
BeanMapLoader.load(bs.newBeanMap(toMeta.inner()), in.toString()).getBean();
                                } catch (Exception e) {
                                        throw rex(e);
                                }
@@ -4312,7 +4312,7 @@ public class MarshallingContext extends Context 
implements ConversionFinder {
                        return (in, memberOf, session, args) -> {
                                try {
                                        var bs = beanSessionOrDefault(session);
-                                       return 
bs.newBeanMap(toMeta.inner()).load(in.toString()).getBean();
+                                       return 
BeanMapLoader.load(bs.newBeanMap(toMeta.inner()), in.toString()).getBean();
                                } catch (Exception e) {
                                        throw rex(e);
                                }
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 0b5358bdec..66e50ee20d 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
@@ -1290,11 +1290,13 @@ public class MarshallingSession extends ContextSession 
implements ConverterSessi
         */
        @Override /* BeanSession */
        public final Object convertToType(Object value, Object targetType) {
+               if (targetType == null)
+                       return convertToType(value, (ClassMeta<?>) null);
                if (targetType instanceof ClassMeta<?> cm)
                        return convertToType(value, cm);
                if (targetType instanceof Class<?> c)
                        return convertToType(value, c);
-               throw illegalArg("Unsupported targetType for convertToType: 
{0}", targetType == null ? "null" : targetType.getClass().getName());
+               throw illegalArg("Unsupported targetType for convertToType: 
{0}", targetType.getClass().getName());
        }
 
        /**
@@ -1310,11 +1312,13 @@ public class MarshallingSession extends ContextSession 
implements ConverterSessi
         */
        @Override /* BeanSession */
        public final Object convertToMemberType(Object outer, Object value, 
Object targetType) {
+               if (targetType == null)
+                       return convertToMemberType(outer, value, (ClassMeta<?>) 
null);
                if (targetType instanceof ClassMeta<?> cm)
                        return convertToMemberType(outer, value, cm);
                if (targetType instanceof Class<?> c)
                        return convertToMemberType(outer, value, c);
-               throw illegalArg("Unsupported targetType for 
convertToMemberType: {0}", targetType == null ? "null" : 
targetType.getClass().getName());
+               throw illegalArg("Unsupported targetType for 
convertToMemberType: {0}", targetType.getClass().getName());
        }
 
 }
\ No newline at end of file
diff --git a/juneau-utest/src/test/java/org/apache/juneau/Annotations_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/Annotations_Test.java
index 0b2ee38fbb..ce35d6cb22 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/Annotations_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/Annotations_Test.java
@@ -34,7 +34,7 @@ class Annotations_Test extends TestBase {
                var bc = MarshallingContext.DEFAULT;
 
                // Basic test
-               var bm = 
bc.newBeanMap(Person1.class).load("{age:21,name:'foobar'}");
+               var bm = BeanMapLoader.load(bc.newBeanMap(Person1.class), 
"{age:21,name:'foobar'}");
                assertBean(bm.getBean(), "name,age", "foobar,21");
 
                bm.put("age", 65);
@@ -57,7 +57,7 @@ class Annotations_Test extends TestBase {
                var bc = MarshallingContext.DEFAULT;
 
                // Basic test
-               var bm = 
bc.newBeanMap(Person2.class).load("{age:21,name:'foobar'}");
+               var bm = BeanMapLoader.load(bc.newBeanMap(Person2.class), 
"{age:21,name:'foobar'}");
                assertBean(bm.getBean(), "name,age", "foobar,21");
 
                bm.put("age", 65);
@@ -80,7 +80,7 @@ class Annotations_Test extends TestBase {
                var bc = MarshallingContext.DEFAULT;
 
                // Basic test
-               var bm = 
bc.newBeanMap(Person3.class).load("{age:21,name:'foobar'}");
+               var bm = BeanMapLoader.load(bc.newBeanMap(Person3.class), 
"{age:21,name:'foobar'}");
                assertBean(bm.getBean(), "name,age", "foobar,21");
 
                bm.put("age", 65);
@@ -103,7 +103,7 @@ class Annotations_Test extends TestBase {
                var bc = 
MarshallingContext.DEFAULT.copy().applyAnnotations(PersonConfig.class).build();
 
                // Basic test
-               var bm = 
bc.newBeanMap(Person4.class).load("{age:21,name:'foobar'}");
+               var bm = BeanMapLoader.load(bc.newBeanMap(Person4.class), 
"{age:21,name:'foobar'}");
                assertBean(bm.getBean(), "name,age", "foobar,21");
 
                bm.put("age", 65);
@@ -131,7 +131,7 @@ class Annotations_Test extends TestBase {
                var bc = MarshallingContext.DEFAULT;
 
                // Make sure only public fields are detected
-               var bm = bc.newBeanMap(A.class).load("{publicField:123}");
+               var bm = BeanMapLoader.load(bc.newBeanMap(A.class), 
"{publicField:123}");
                assertBean(bm, "publicField", "123");
        }
 
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 28c8f62b55..021e82bfff 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
@@ -639,7 +639,7 @@ class BeanMap_Test extends TestBase {
                assertBean(t7, "enum1,enum2", "ONE,TWO");
 
                // Use MarshallingContext to create bean instance.
-               m = 
MarshallingContext.DEFAULT.newBeanMap(H.class).load("{enum1:'TWO',enum2:'THREE'}");
+               m = 
BeanMapLoader.load(MarshallingContext.DEFAULT.newBeanMap(H.class), 
"{enum1:'TWO',enum2:'THREE'}");
                assertEquals("{_type:'H',enum1:'TWO',enum2:'THREE'}", 
serializer.serialize(m.getBean()));
                t7 = m.getBean();
                assertBean(t7, "enum1,enum2", "TWO,THREE");
@@ -921,7 +921,7 @@ class BeanMap_Test extends TestBase {
        // testPropertyNameFactoryDashedLC1
        
//====================================================================================================
        @Test void a18_propertyNameFactoryDashedLC1() {
-               var m = 
bc.newBeanMap(P1.class).load("{'foo':1,'bar-baz':2,'bing-boo-url':3}");
+               var m = BeanMapLoader.load(bc.newBeanMap(P1.class), 
"{'foo':1,'bar-baz':2,'bing-boo-url':3}");
                assertBean(m, "foo,bar-baz,bing-boo-url", "1,2,3");
                assertBean(m.getBean(), "foo,barBaz,bingBooURL", "1,2,3");
                m.put("foo", 4);
@@ -936,7 +936,7 @@ class BeanMap_Test extends TestBase {
        }
 
        @Test void a19_propertyNameFactoryDashedLC1_usingConfig() {
-               var m = 
bc.copy().applyAnnotations(P1cConfig.class).build().newBeanMap(P1c.class).load("{'foo':1,'bar-baz':2,'bing-boo-url':3}");
+               var m = 
BeanMapLoader.load(bc.copy().applyAnnotations(P1cConfig.class).build().newBeanMap(P1c.class),
 "{'foo':1,'bar-baz':2,'bing-boo-url':3}");
                assertBean(m, "foo,bar-baz,bing-boo-url", "1,2,3");
                assertBean(m.getBean(), "foo,barBaz,bingBooURL", "1,2,3");
                m.put("foo", 4);
@@ -959,7 +959,7 @@ class BeanMap_Test extends TestBase {
        
//====================================================================================================
        @Test void a20_propertyNameFactoryDashedLC2() {
                var bc2 = MarshallingContext.DEFAULT;
-               var m = 
bc2.newBeanMap(P2.class).load("{'foo-bar':1,'baz-bing':2}");
+               var m = BeanMapLoader.load(bc2.newBeanMap(P2.class), 
"{'foo-bar':1,'baz-bing':2}");
                assertBean(m, "foo-bar,baz-bing", "1,2");
                assertBean(m.getBean(), "fooBar,bazBING", "1,2");
                m.put("foo-bar", 3);
diff --git a/todo/TODO-5-bean-runtime-types-to-commons.md 
b/todo/TODO-5-bean-runtime-types-to-commons.md
index fd6b4ee8e1..5cd96bef5e 100644
--- a/todo/TODO-5-bean-runtime-types-to-commons.md
+++ b/todo/TODO-5-bean-runtime-types-to-commons.md
@@ -99,22 +99,36 @@ Known limitations of the commons-side path (acceptable for 
Step 6, scoped for la
   - **`Delegate<T>` moved** from `org.apache.juneau` to 
`org.apache.juneau.commons.bean` via `git mv`. The interface's `getClassMeta()` 
method now returns `BeanTypeInfo<T>` instead of `ClassMeta<T>`. All five 
in-tree `Delegate` implementations (`BeanMap`, `DelegateBeanMap`, 
`DelegateList`, `DelegateMap`, `FilteredKeyMap`) keep their `ClassMeta<T>` 
return types via Java covariant returns (no source changes needed in those 
classes). Five marshalling-side files that used the raw-typed `(Del [...]
   - **Build/test verification.** `python3 scripts/test.py --full` passed clean 
(juneau + juneau-rest + juneau-microservice + examples + utests, ~70k tests). 
`cd juneau-core/juneau-commons && mvn clean compile` passes standalone — 
`juneau-commons` still compiles without depending on `juneau-marshall`. 
`ReadLints` clean on all modified files.
 - [ ] **Step 8b-ii** — Continuation of the bean-runtime move. Remaining work 
to physically relocate the 8 target files (`BeanMap`, `BeanMapEntry`, 
`BeanMeta`, `BeanMetaFiltered`, `BeanPropertyMeta`, `BeanPropertyValue`, 
`BeanPropertyConsumer`, `BeanProxyInvocationHandler`) into `commons.bean`. The 
SPI seams from 8b-i are in place; this step is the field-retype + 
cross-module-leakage cleanup pass:
-  - **`BeanPropertyMeta.rawTypeMeta` / `typeMeta`** — retype from 
`ClassMeta<?>` to `BeanTypeInfo<?>`. `BeanPropertyMeta.getClassMeta()` return 
type widens to `BeanTypeInfo<?>`. The ~70 marshalling-side call sites that call 
`pMeta.getClassMeta().<method>` mostly work unchanged (inherited 
`ClassInfo`/`ClassInfoTyped` methods + new `BeanTypeInfo` abstract methods are 
sufficient). The few sites that call `ClassMeta`-only methods (e.g. 
`getSerializedClassMeta(this)`, `getMarshallingContext() [...]
-  - **`BeanPropertyMeta.swap`** — retype from `ObjectSwap` to `Object`. 
Internal references (`setPropertyValue`'s defensive double-unswap check, 
`properties()` debug method, Javadocs) cast or remove the check (it's 
belt-and-braces — the install-time `writeTransform` already normalizes the 
value before this point).
-  - **`BeanPropertyMeta.Builder.bc`** — retype from `MarshallingContext` to 
`Object`. `Builder.bc.resolveClassMeta(...)`, `Builder.bc.getClassMeta(...)`, 
`Builder.bc.object()` calls inside `validate(...)` lift out to a 
marshalling-side post-processor (or route through a `BeanSession`-style narrow 
SPI). The bean-modeling-side path already short-circuits when `bc == null`, so 
the lift-out is mostly about giving marshalling-side callers a place to install 
type metadata after the commons-sid [...]
-  - **`@MarshalledProp` annotation reads** inside 
`BeanPropertyMeta.Builder.validate(...)` — lift out to a marshalling-side 
post-processor. After `Builder.build()` returns, the marshalling layer reads 
`@MarshalledProp` annotations off the property's getter/setter/field and 
post-processes the just-built `BeanPropertyMeta` to install the swap transforms 
/ dictionary classes / property override list.
-  - **`BeanMeta.beanRegistry`** and **`propertyBeanRegistries`** — retype from 
`BeanRegistry` to `BeanRegistryLookup`. `BeanMeta.getBeanRegistry()` and 
`getPropertyBeanRegistry(...)` return types narrow to `BeanRegistry` via casts 
(only concrete impl in-tree), OR the public getter return types widen to 
`BeanRegistryLookup` and the 3 callers 
(ParserSession/SerializerSession/XmlParserSession) cast at their call sites. 
The `findBeanRegistry()` helper either: (a) stays in `BeanMeta` with `Ob [...]
-  - **`BeanMeta` constructor signatures** — the `protected 
BeanMeta(ClassMeta<T>, BeanFilter, String[], ClassInfo)` constructor takes a 
`ClassMeta` that the bean-modeling layer should not know about. Three options: 
(a) keep this constructor on `BeanMeta` typed as `Object`/`BeanTypeInfo`, 
marshalling-side callers cast; (b) move it to a marshalling-side factory helper 
(e.g. `ClassMeta.findBeanMeta()` was the only original caller) and delete it 
from `BeanMeta`; (c) introduce a marshalling-s [...]
-  - **`BeanMetaFiltered.super(...)`** call — uses the old `(ClassMeta, 
MarshalledFilter, String[], ClassInfo)` BeanMeta constructor. After Step 
8b-ii's constructor refactor, this call site needs to either inline a private 
helper-build path or route through the new commons-side `BeanMeta.of(Class, 
BeanConfigContext)` factory.
-  - **`BeanMap.load(Reader, ReaderParser)`** and **`BeanMap.load(String)`** — 
these use `JsonMap.ofText(...)` / `JsonMap.ofJson(...)` which are 
marshalling-side. Move both methods to a marshalling-side helper class (e.g. 
`BeanMapLoader`); the static methods take a `BeanMap<T>` + `Reader`/`String` 
and call `putAll(JsonMap.ofText(...))`. Updates the JsonMap-aware Javadoc 
references on `BeanMap`.
-  - **`BeanMap.getBean()`** uses `Json5Serializer.DEFAULT.toString(...)` for 
an error message — replace with `Arrays.toString(...)` or simple class-name 
iteration.
-  - **`BeanProxyInvocationHandler.toString()`** uses 
`Json5Serializer.DEFAULT.toString(...)` — replace with `Objects.toString(...)` 
or a hand-rolled "{prop1: val1, prop2: val2}" formatter.
-  - **`JsonMap` / `JsonList` constructions** inside 
`BeanPropertyMeta.add(...)` and `BeanPropertyMeta.setPropertyValue(...)` — 
replace with `LinkedHashMap` / `ArrayList` (the only reason 
`JsonMap`/`JsonList` were used is for the `MarshallingSession`-aware 
member-type conversion; after Step 3 those conversions go through 
`BeanSession.convertToMemberType(...)` directly, so plain JDK collections work).
-  - **`BeanInstantiator.of(Collection.class)/of(Map.class)` calls** inside 
`setPropertyValue` — `BeanInstantiator` is already in `commons.inject`, but the 
`type(rawTypeMeta)` overload may need updating to take `BeanTypeInfo<?>` rather 
than `ClassMeta<?>`.
-  - **`ParseException`** imports — `BeanMap.load(...)` and 
`BeanPropertyMeta.set` throw `ParseException` which lives in 
`org.apache.juneau.parser`. After the load() methods move to a marshalling-side 
helper, the only remaining `ParseException` site is `BeanPropertyMeta.set` 
where it's caught from `session.convertToMemberType(...)` and re-thrown as a 
`BeanRuntimeException` (which is the desired bean-modeling-side exception). The 
catch block already wraps it; just delete the `throws ParseE [...]
-  - **`Surrogate.class`** reference inside 
`BeanPropertyMeta.Builder.swapSwap(...)` — already gated by an 
`unsupportedOp("TODO - Surrogate swaps not yet supported on bean properties.")` 
throw. Either move `Surrogate` to commons (it's a marker class), or skip the 
check entirely on the commons-side path (rely on the marshalling-side 
post-processor to do `@Swap`/`@Surrogate` validation).
-  - **`StringFormatSwap`** instantiation inside 
`BeanPropertyMeta.Builder.marshalledPropSwap(...)` — marshalling-side type. 
Move into the marshalling-side post-processor along with the `@MarshalledProp` 
annotation read (one location).
-  - **Final cleanup** — `git mv` the 8 files into 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`, 
update `package` declarations, repo-wide reference sweep (`import 
org.apache.juneau.BeanMap` → `import org.apache.juneau.commons.bean.BeanMap`, 
etc., plus `{@link …}` references in Javadoc), verify `cd 
juneau-core/juneau-commons && mvn clean compile` standalone passes, run 
`python3 scripts/test.py --full`.
+
+  **Phase A status (uncommitted, working tree) — COMPLETE, build + full test 
green (49,912 tests pass).**
+
+  Lift-out items landed:
+  - **Sub-item 12 (`throws ParseException` declaration on `set`)** — removed 
the `throws ParseException` from the private `setPropertyValue` helper inside 
`BeanPropertyMeta`. `ParseException` is a `RuntimeException` so the declaration 
was documentation-only; the catch in `set(...)` already wraps it via `bex(e2)`.
+  - **Sub-item 9 (`Json5Serializer.DEFAULT.toString(...)`)** — 
`BeanMap.getBean(true)` error message now uses 
`Arrays.toString(getClasses(args))`; `BeanProxyInvocationHandler.toString()` 
now uses `Objects.toString(this.beanProps)`. Both files dropped the `import 
org.apache.juneau.json5.*;`. The two remaining substring-based test assertions 
still match because both still produce a bracketed list of property/class names.
+  - **Sub-item 13 (`Surrogate.class` reference in `swapSwap`)** — moved out of 
`BeanPropertyMeta.Builder` along with the rest of the `@Swap` annotation logic 
(see sub-items 4 + 14 below). The new home is a marshalling-side post-processor 
that retains the `unsupportedOp("TODO - Surrogate swaps not yet supported on 
bean properties.")` guard.
+  - **Sub-items 4 + 14 (`@MarshalledProp` annotation reads + 
`StringFormatSwap` instantiation)** — created new marshalling-side helper 
`MarshalledPropertyPostProcessor` in 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/MarshalledPropertyPostProcessor.java`.
 It owns the `marshalledPropSwap(AnnotationInfo<MarshalledProp>)` and 
`swapSwap(AnnotationInfo<Swap>)` helpers that used to live as `private static` 
methods on `BeanPropertyMeta.Builder`, plus the `forEach(@MarshalledProp [...]
+  - **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 B status (uncommitted, working tree) — COMPLETE, build + full test 
green (49,912 tests pass).**
+
+  Field retypes landed (all use the "type the field against the commons.bean 
SPI seam; keep the public getter returning the marshalling-side type via a 
cast" pattern, so external callers don't need to change):
+
+  - **Sub-item 2 (`BeanPropertyMeta.swap` → `Object`)** — both `Builder.swap` 
and `BeanPropertyMeta.swap` retyped. The single use site inside 
`setPropertyValue` (the defensive double-unswap check) keeps the `nn(swap) && 
value1 != null && 
((ObjectSwap)swap).getSwapClass().isAssignableFrom(value1.getClass())` guard 
with a local cast. Marshalling-side consumers 
(`BeanMeta.installSwapAwareTransforms` reading `Builder.swap`, 
`MarshalledPropertyPostProcessor` reading `b.swap.getSwapClass()`) c [...]
+  - **Sub-item 3 (`BeanPropertyMeta.Builder.bc` → `Object`)** — both 
`Builder.bc` and `BeanPropertyMeta.bc` retyped. The few callers that need 
MarshallingContext-only methods (`Builder.rawMetaType(Class<?>)` calls 
`bc.getClassMeta(value)`, `applyChildPropertiesFilter` calls 
`bc.getBeanMeta(o.getClass())`, the post-constructor body calls 
`bc.getAnnotationProvider()`) cast back to `MarshallingContext` at the use 
site. `Builder.validate(MarshallingContext bc, ...)`'s parameter is unchanged  
[...]
+  - **Sub-item 5 (`BeanMeta.beanRegistry` / `propertyBeanRegistries` → 
`BeanRegistryLookup`)** — `Supplier<BeanRegistry>` → 
`Supplier<BeanRegistryLookup>`, `Map<BeanPropertyMeta,BeanRegistry>` → 
`Map<BeanPropertyMeta,BeanRegistryLookup>`. `findBeanRegistry()` return 
narrowed to `BeanRegistryLookup`. Public getters `getBeanRegistry()` and 
`getPropertyBeanRegistry(BeanPropertyMeta)` keep their `BeanRegistry` return 
types via internal `(BeanRegistry) beanRegistry.get()` / `(BeanRegistry) pr 
[...]
+  - **Sub-item 1 (`BeanPropertyMeta.rawTypeMeta` / `typeMeta` / 
`BeanMeta.classMeta` → `BeanTypeInfo<?>`)** — all three fields retyped (both 
Builder copies and the final `BeanPropertyMeta` instance fields). The public 
getters `BeanPropertyMeta.getClassMeta()` and `BeanMeta.getClassMeta()` keep 
their `ClassMeta<?>` / `ClassMeta<T>` return types via internal `(ClassMeta<?>) 
typeMeta` / `(ClassMeta<T>) classMeta` casts. Internal call sites that pass 
`rawTypeMeta` to a `ClassMeta`-typed para [...]
+  - **Sub-items 6 + 7 (`BeanMeta` constructor refactor)** — picked option (a) 
(typed against `BeanTypeInfo<T>`, marshalling-side body casts back). The 
protected `BeanMeta(ClassMeta<T>, BeanFilter, String[], ClassInfo)` constructor 
signature changed to `BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], 
ClassInfo)`; the body casts `cm` back to `ClassMeta<T>` to call 
`getMarshallingContext()` / `getBeanConfigContext()`. 
`BeanMetaFiltered.super(...)` (passes `innerMeta.getClassMeta()` which st [...]
+  - **Sub-item 11 (`BeanInstantiator.type(rawTypeMeta)`)** — verified 
compatible. `BeanInstantiator.type(ClassInfo)` accepts any `ClassInfo` subtype; 
`BeanTypeInfo<T>` extends `ClassInfoTyped<T>` which extends `ClassInfo`, so 
`BeanInstantiator.of(Collection.class).type(rawTypeMeta).preferZeroArgConstructor().run()`
 compiles unchanged. No `BeanInstantiator` changes needed.
+  - **`MarshallingSession` bridge fix** — `convertToType(Object value, Object 
targetType)` and `convertToMemberType(Object outer, Object value, Object 
targetType)` now treat `targetType == null` as a `(ClassMeta<?>) null` dispatch 
so the typed `ClassMeta`-aware overloads can apply their `object()` fallback. 
Without this fix, the `getElementType()`/`getValueType()`/`getKeyType()` calls 
that legitimately return `null` for non-collection/non-map types throw 
`"Unsupported targetType for conv [...]
+
+  Remaining sub-items (deferred to Phase C):
+
+  - **Phase C — Public API widen.** `BeanPropertyMeta.getClassMeta()` → 
`BeanTypeInfo<?>`, `BeanMeta.getClassMeta()` → `BeanTypeInfo<T>` (or remove 
from the bean-modeling layer entirely), 
`BeanMeta.getBeanRegistry()`/`getPropertyBeanRegistry(...)` → 
`BeanRegistryLookup`. The ~30+ marshalling-side callers that currently use the 
`ClassMeta`-returning getters need an audit — most will just inherit 
`BeanTypeInfo`/`ClassInfo` methods; some will need explicit `(ClassMeta<?>) 
pMeta.getClassMeta [...]
+  - **Phase C — `validate(...)` body lift-out.** 
`Builder.validate(MarshallingContext bc, ...)` reads `bc.resolveClassMeta(...)` 
/ `bc.object()`; those reads need to move to a marshalling-side post-processor 
(or a narrow `BeanSession` SPI) so the bean-modeling-side `validate` becomes 
pure (or split into a commons-side `validate()` and a marshalling-side 
`installTypeMetadata(...)`).
+  - **Phase C — Marshalling-side `BeanMeta` factory helper.** Move the body of 
`BeanMeta(BeanTypeInfo<T>, BeanFilter, String[], ClassInfo)` out to a 
marshalling-side helper that sets `classMeta` and `marshallingContext` via a 
setter on the commons-side `BeanMeta`. Alternatively, fold the marshalling-only 
fields (`classMeta`, `marshallingContext`) into a side-map keyed by `BeanMeta` 
instance.
+  - **Phase C — `JsonList`/`JsonMap` final cleanup (deferred from Phase A 
sub-item 10).** Flip `setPropertyValue`'s `new JsonList(valueList)` to `new 
ArrayList<>(valueList)`. This is test-observable: 
`BeanMap_Test.a05_arrayProperties`, `a06_arrayProperties_usingConfig`, 
`a09_beanPropertyAnnotation` assert that the resulting field's concrete class 
is `JsonList`. Either update the tests to expect `ArrayList` (preferred — the 
original `JsonList` choice was incidental, not contractual), or w [...]
+  - **Phase C sub-item 15 — Final cleanup.** `git mv` the 8 files into 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/`, 
update `package` declarations, repo-wide reference sweep (`import 
org.apache.juneau.BeanMap` → `import org.apache.juneau.commons.bean.BeanMap`, 
etc., plus `{@link …}` references in Javadoc), verify `cd 
juneau-core/juneau-commons && mvn clean compile` standalone passes, run 
`python3 scripts/test.py --full`.
 - [ ] **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.

Reply via email to