This is an automated email from the ASF dual-hosted git repository.

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new 7f54e8124a Add support for setting Session properties via setProperty
7f54e8124a is described below

commit 7f54e8124a526ec5868f088128a2a2dc72b498d6
Author: James Bognar <[email protected]>
AuthorDate: Sat Apr 4 14:06:17 2026 -0400

    Add support for setting Session properties via setProperty
---
 RELEASE-NOTES.txt                                  |  36 +++
 .../apache/juneau/jena/RdfSerializerSession.java   |  11 -
 .../juneau/jena/RdfStreamSerializerSession.java    |   4 -
 .../org/apache/juneau/BeanTraverseSession.java     |  26 +-
 .../apache/juneau/bson/BsonSerializerSession.java  |   4 -
 .../apache/juneau/cbor/CborSerializerSession.java  |  10 -
 .../apache/juneau/csv/CsvSerializerSession.java    |  59 +++-
 .../apache/juneau/html/HtmlSerializerSession.java  | 127 +++++++--
 .../org/apache/juneau/json/JsonParserSession.java  |  31 +-
 .../apache/juneau/json/JsonSerializerSession.java  |  41 ++-
 .../juneau/msgpack/MsgPackSerializerSession.java   |   8 +-
 .../org/apache/juneau/parser/ParserSession.java    |  24 +-
 .../juneau/serializer/SerializerSession.java       | 168 ++++++++++-
 .../juneau/serializer/WriterSerializerSession.java |  50 +++-
 .../org/apache/juneau/uon/UonParserSession.java    |  37 ++-
 .../apache/juneau/uon/UonSerializerSession.java    |  78 ++++--
 .../urlencoding/UrlEncodingParserSession.java      |  31 +-
 .../urlencoding/UrlEncodingSerializerSession.java  |  31 +-
 .../org/apache/juneau/xml/XmlParserSession.java    |  52 +++-
 .../apache/juneau/xml/XmlSerializerSession.java    | 123 ++++++--
 .../apache/juneau/yaml/YamlSerializerSession.java  |  15 -
 .../org/apache/juneau/ContextSession_Test.java     | 312 +++++++++++++++++++++
 todo/session-options-files.zip                     | Bin 0 -> 439801 bytes
 todo/session-options-plan.md                       | 288 +++++++++++++++++++
 24 files changed, 1400 insertions(+), 166 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index e3e2a27df9..fbf28c6e4e 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -110,6 +110,42 @@ Release Notes - Juneau - Version 9.2.1 - YYYY-MM-DD
     * New classes: CsvCellParser, CsvCellSerializer, ByteArrayFormat.
     * New builder options: byteArrayFormat(), allowNestedStructures(), 
nullValue().
 
+** New Features - Per-Session Property Overrides
+
+    * Session.Builder.property(String, Object) and properties(Map) now 
dispatch to typed
+      session-level setter methods, enabling per-request override of 
serializer/parser configuration.
+    * Both short names (e.g., "locale") and fully-qualified names (e.g., 
"BeanSession.locale") are
+      accepted as property keys.
+    * New properties exposed as session-level overrides:
+        - ParserSession: trimStrings
+        - BeanTraverseSession: initialDepth
+        - SerializerSession: keepNullProperties, trimStrings, addBeanTypes, 
addRootType,
+          sortCollections, sortMaps, trimEmptyCollections, trimEmptyMaps
+        - WriterSerializerSession: maxIndent, quoteChar
+        - CsvSerializerSession: byteArrayFormat, allowNestedStructures, 
nullValue
+        - JsonSerializerSession: escapeSolidus
+        - JsonParserSession: validateEnd
+        - UonSerializerSession: encoding, paramFormat (also fixes quoteChar 
bypass)
+        - UonParserSession: validateEnd
+        - UrlEncodingSerializerSession: expandedParams
+        - UrlEncodingParserSession: expandedParams
+        - XmlSerializerSession: addNamespaceUrisToRoot, autoDetectNamespaces, 
defaultNamespace,
+          enableNamespaces, textNodeDelimiter
+        - XmlParserSession: preserveRootElement, validating
+        - HtmlSerializerSession: addKeyValueTableHeaders, 
detectLabelParameters,
+          detectLinksInStrings, labelParameter, uriAnchorText
+    * Bug fixes - several serializer sessions were bypassing the session-level 
addBeanTypes field by
+      overriding isAddBeanTypes() to return ctx.isAddBeanTypes() directly. 
These overrides now
+      correctly delegate to the session field via super.isAddBeanTypes(). 
Affected classes:
+      JsonSerializerSession, UonSerializerSession, XmlSerializerSession, 
HtmlSerializerSession,
+      MsgPackSerializerSession, YamlSerializerSession, CborSerializerSession, 
BsonSerializerSession,
+      RdfSerializerSession, RdfStreamSerializerSession. Similarly, 
ParserSession.isTrimStrings()
+      now returns the session-level field instead of delegating to the context.
+    * Example: serializer.createSession().property("locale", "fr-FR").build()
+      is equivalent to 
serializer.createSession().locale(Locale.forLanguageTag("fr-FR")).build()
+    * BasicConverter handles type coercion so string values (e.g., "true", 
"fr-FR") are
+      automatically converted to the expected target types.
+
 ** Changes
 
     * Bump junit.version from a mix of 5.13.4 and 6.0.1 to 6.0.3.
diff --git 
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
 
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
index a6fe3cebb3..1fef41dce7 100644
--- 
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
+++ 
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfSerializerSession.java
@@ -681,17 +681,6 @@ public class RdfSerializerSession extends 
WriterSerializerSession {
                return bpm == null ? XmlBeanPropertyMeta.DEFAULT : 
ctx.getXmlBeanPropertyMeta(bpm);
        }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @see RdfSerializer.Builder#addBeanTypes()
-        * @return
-        *      <jk>true</jk> if <js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
-       @Override
-       protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); 
}
-
        /**
         * Add XSI data types to non-<c>String</c> literals.
         *
diff --git 
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java
 
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java
index b2ce29d271..b7b7466617 100644
--- 
a/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java
+++ 
b/juneau-core/juneau-marshall-rdf/src/main/java/org/apache/juneau/jena/RdfStreamSerializerSession.java
@@ -488,8 +488,4 @@ public class RdfStreamSerializerSession extends 
OutputStreamSerializerSession {
                RDFDataMgr.write(out.getOutputStream(), model, lang);
        }
 
-       @Override
-       protected boolean isAddBeanTypes() {
-               return ctx.isAddBeanTypes();
-       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
index 15ee784d42..5d225c549f 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanTraverseSession.java
@@ -56,6 +56,8 @@ public class BeanTraverseSession extends BeanSession {
        // Property name constants
        private static final String PROP_indent = "indent";
        private static final String PROP_depth = "depth";
+       private static final String PROP_initialDepth = "initialDepth";
+       private static final String PROP_BeanTraverseSession_initialDepth = 
"BeanTraverseSession.initialDepth";
 
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
@@ -117,10 +119,30 @@ public class BeanTraverseSession extends BeanSession {
                        return this;
                }
 
+               /**
+                * Initial depth.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder initialDepth(int value) {
+                       initialDepth = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) {
+                               super.property(key, value);
+                               return this;
+                       }
+                       switch (key) {
+                               case PROP_initialDepth, 
PROP_BeanTraverseSession_initialDepth:
+                                       return initialDepth(cvt(value, 
Integer.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonSerializerSession.java
index e31b813cf4..379fbb6589 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonSerializerSession.java
@@ -310,8 +310,4 @@ public class BsonSerializerSession extends 
OutputStreamSerializerSession {
                bsonOut.writeDocumentTo(out.getOutputStream());
        }
 
-       @Override
-       protected boolean isAddBeanTypes() {
-               return ctx.isAddBeanTypes();
-       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborSerializerSession.java
index ffae0910a1..f49545f628 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/cbor/CborSerializerSession.java
@@ -58,8 +58,6 @@ public class CborSerializerSession extends 
OutputStreamSerializerSession {
         */
        public static class Builder extends 
OutputStreamSerializerSession.Builder {
 
-               private CborSerializer ctx;
-
                /**
                 * Constructor
                 *
@@ -68,7 +66,6 @@ public class CborSerializerSession extends 
OutputStreamSerializerSession {
                 */
                protected Builder(CborSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
-                       this.ctx = ctx;
                }
 
                @Override /* Overridden from Builder */
@@ -200,8 +197,6 @@ public class CborSerializerSession extends 
OutputStreamSerializerSession {
                return os;
        }
 
-       private final CborSerializer ctx;
-
        /**
         * Constructor.
         *
@@ -209,7 +204,6 @@ public class CborSerializerSession extends 
OutputStreamSerializerSession {
         */
        protected CborSerializerSession(Builder builder) {
                super(builder);
-               ctx = builder.ctx;
        }
 
        /*
@@ -380,8 +374,4 @@ public class CborSerializerSession extends 
OutputStreamSerializerSession {
                serializeAnything(getCborOutputStream(out), o, 
getExpectedRootType(o), "root", null);
        }
 
-       @Override
-       protected boolean isAddBeanTypes() {
-               return ctx.isAddBeanTypes();
-       }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
index cdf4948748..d4e83e004b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/csv/CsvSerializerSession.java
@@ -56,6 +56,14 @@ import org.apache.juneau.utils.*;
 })
 public class CsvSerializerSession extends WriterSerializerSession {
 
+       // Property name constants
+       private static final String PROP_byteArrayFormat = "byteArrayFormat";
+       private static final String PROP_allowNestedStructures = 
"allowNestedStructures";
+       private static final String PROP_nullValue = "nullValue";
+       private static final String PROP_CsvSerializerSession_byteArrayFormat = 
"CsvSerializerSession.byteArrayFormat";
+       private static final String 
PROP_CsvSerializerSession_allowNestedStructures = 
"CsvSerializerSession.allowNestedStructures";
+       private static final String PROP_CsvSerializerSession_nullValue = 
"CsvSerializerSession.nullValue";
+
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
 
@@ -88,6 +96,17 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
                        nullValue = ctx.getNullValue();
                }
 
+               /**
+                * Allow nested structures.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder allowNestedStructures(boolean value) {
+                       allowNestedStructures = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public <T> Builder apply(Class<T> type, Consumer<T> apply) {
                        super.apply(type, apply);
@@ -99,6 +118,18 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
                        return new CsvSerializerSession(this);
                }
 
+               /**
+                * Byte array format.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder byteArrayFormat(ByteArrayFormat value) {
+                       if (nn(value))
+                               byteArrayFormat = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder debug(Boolean value) {
                        super.debug(value);
@@ -135,6 +166,17 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
                        return this;
                }
 
+               /**
+                * Null value string.
+                *
+                * @param value The new value for this property. Can be 
<jk>null</jk> (resets to default).
+                * @return This object.
+                */
+               public Builder nullValue(String value) {
+                       nullValue = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder properties(Map<String,Object> value) {
                        super.properties(value);
@@ -143,8 +185,21 @@ public class CsvSerializerSession extends 
WriterSerializerSession {
 
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) {
+                               super.property(key, value);
+                               return this;
+                       }
+                       switch (key) {
+                               case PROP_byteArrayFormat, 
PROP_CsvSerializerSession_byteArrayFormat:
+                                       return byteArrayFormat(cvt(value, 
ByteArrayFormat.class));
+                               case PROP_allowNestedStructures, 
PROP_CsvSerializerSession_allowNestedStructures:
+                                       return allowNestedStructures(cvt(value, 
Boolean.class));
+                               case PROP_nullValue, 
PROP_CsvSerializerSession_nullValue:
+                                       return nullValue(cvt(value, 
String.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
index 310011baac..98f7603495 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/html/HtmlSerializerSession.java
@@ -62,6 +62,18 @@ import org.apache.juneau.xml.annotation.*;
 })
 public class HtmlSerializerSession extends XmlSerializerSession {
 
+       // Property name constants
+       private static final String PROP_addKeyValueTableHeaders = 
"addKeyValueTableHeaders";
+       private static final String PROP_detectLabelParameters = 
"detectLabelParameters";
+       private static final String PROP_detectLinksInStrings = 
"detectLinksInStrings";
+       private static final String PROP_labelParameter = "labelParameter";
+       private static final String PROP_uriAnchorText = "uriAnchorText";
+       private static final String 
PROP_HtmlSerializerSession_addKeyValueTableHeaders = 
"HtmlSerializerSession.addKeyValueTableHeaders";
+       private static final String 
PROP_HtmlSerializerSession_detectLabelParameters = 
"HtmlSerializerSession.detectLabelParameters";
+       private static final String 
PROP_HtmlSerializerSession_detectLinksInStrings = 
"HtmlSerializerSession.detectLinksInStrings";
+       private static final String PROP_HtmlSerializerSession_labelParameter = 
"HtmlSerializerSession.labelParameter";
+       private static final String PROP_HtmlSerializerSession_uriAnchorText = 
"HtmlSerializerSession.uriAnchorText";
+
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
 
@@ -76,6 +88,11 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         */
        public static class Builder extends XmlSerializerSession.Builder {
 
+               private boolean addKeyValueTableHeaders;
+               private boolean detectLabelParameters;
+               private boolean detectLinksInStrings;
+               private String labelParameter;
+               private AnchorText uriAnchorText;
                private HtmlSerializer ctx;
 
                /**
@@ -87,6 +104,11 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
                protected Builder(HtmlSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
                        this.ctx = ctx;
+                       addKeyValueTableHeaders = 
ctx.isAddKeyValueTableHeaders();
+                       detectLabelParameters = ctx.isDetectLabelParameters();
+                       detectLinksInStrings = ctx.isDetectLinksInStrings();
+                       labelParameter = ctx.getLabelParameter();
+                       uriAnchorText = ctx.getUriAnchorText();
                }
 
                @Override /* Overridden from Builder */
@@ -142,10 +164,79 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
                        return this;
                }
 
+               /**
+                * Add key/value headers on bean/map tables.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder addKeyValueTableHeaders(boolean value) {
+                       addKeyValueTableHeaders = value;
+                       return this;
+               }
+
+               /**
+                * Look for link labels in URIs.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder detectLabelParameters(boolean value) {
+                       detectLabelParameters = value;
+                       return this;
+               }
+
+               /**
+                * Look for URLs in Strings.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder detectLinksInStrings(boolean value) {
+                       detectLinksInStrings = value;
+                       return this;
+               }
+
+               /**
+                * The label parameter name.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder labelParameter(String value) {
+                       labelParameter = value;
+                       return this;
+               }
+
+               /**
+                * The anchor text to use for URL objects.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder uriAnchorText(AnchorText value) {
+                       uriAnchorText = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_addKeyValueTableHeaders, 
PROP_HtmlSerializerSession_addKeyValueTableHeaders:
+                                       return 
addKeyValueTableHeaders(cvt(value, Boolean.class));
+                               case PROP_detectLabelParameters, 
PROP_HtmlSerializerSession_detectLabelParameters:
+                                       return detectLabelParameters(cvt(value, 
Boolean.class));
+                               case PROP_detectLinksInStrings, 
PROP_HtmlSerializerSession_detectLinksInStrings:
+                                       return detectLinksInStrings(cvt(value, 
Boolean.class));
+                               case PROP_labelParameter, 
PROP_HtmlSerializerSession_labelParameter:
+                                       return labelParameter(cvt(value, 
String.class));
+                               case PROP_uriAnchorText, 
PROP_HtmlSerializerSession_uriAnchorText:
+                                       return uriAnchorText(cvt(value, 
AnchorText.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -215,6 +306,11 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
        }
 
        private final HtmlSerializer ctx;
+       private final boolean addKeyValueTableHeaders;
+       private final boolean detectLabelParameters;
+       private final boolean detectLinksInStrings;
+       private final String labelParameter;
+       private final AnchorText uriAnchorText;
        private final Pattern urlPattern = Pattern.compile("https?\\:\\/\\/.*");
        private final Pattern labelPattern;
 
@@ -226,7 +322,12 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
        protected HtmlSerializerSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
-               labelPattern = Pattern.compile("[\\?\\&]" + 
Pattern.quote(ctx.getLabelParameter()) + "=([^\\&]*)");
+               addKeyValueTableHeaders = builder.addKeyValueTableHeaders;
+               detectLabelParameters = builder.detectLabelParameters;
+               detectLinksInStrings = builder.detectLinksInStrings;
+               labelParameter = builder.labelParameter;
+               uriAnchorText = builder.uriAnchorText;
+               labelPattern = Pattern.compile("[\\?\\&]" + 
Pattern.quote(labelParameter) + "=([^\\&]*)");
        }
 
        /**
@@ -772,7 +873,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         * @return
         *      The parameter name to look for when resolving link labels.
         */
-       protected final String getLabelParameter() { return 
ctx.getLabelParameter(); }
+       protected final String getLabelParameter() { return labelParameter; }
 
        /**
         * Anchor text source.
@@ -782,18 +883,10 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         *      When creating anchor tags (e.g. <code><xt>&lt;a</xt> 
<xa>href</xa>=<xs>'...'</xs>
         *      <xt>&gt;</xt>text<xt>&lt;/a&gt;</xt></code>) in HTML, this 
setting defines what to set the inner text to.
         */
-       protected final AnchorText getUriAnchorText() { return 
ctx.getUriAnchorText(); }
+       protected final AnchorText getUriAnchorText() { return uriAnchorText; }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @see HtmlSerializer.Builder#addBeanTypesHtml()
-        * @return
-        *      <jk>true</jk> if <js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
        @Override
-       protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); 
}
+       public final boolean isAddBeanTypes() { return super.isAddBeanTypes(); }
 
        /**
         * Add key/value headers on bean/map tables.
@@ -802,7 +895,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         * @return
         *      <jk>true</jk> if <bc>key</bc> and <bc>value</bc> column headers 
are added to tables.
         */
-       protected final boolean isAddKeyValueTableHeaders() { return 
ctx.isAddKeyValueTableHeaders(); }
+       protected final boolean isAddKeyValueTableHeaders() { return 
addKeyValueTableHeaders; }
 
        /**
         * Look for link labels in URIs.
@@ -811,7 +904,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         * @return
         *      <jk>true</jk> if we should ook for URL label parameters (e.g. 
<js>"?label=foobar"</js>).
         */
-       protected final boolean isDetectLabelParameters() { return 
ctx.isDetectLabelParameters(); }
+       protected final boolean isDetectLabelParameters() { return 
detectLabelParameters; }
 
        /**
         * Look for URLs in {@link String Strings}.
@@ -820,7 +913,7 @@ public class HtmlSerializerSession extends 
XmlSerializerSession {
         * @return
         *      <jk>true</jk> if we should automatically convert strings to 
URLs if they look like a URL.
         */
-       protected final boolean isDetectLinksInStrings() { return 
ctx.isDetectLinksInStrings(); }
+       protected final boolean isDetectLinksInStrings() { return 
detectLinksInStrings; }
 
        /**
         * Serialize the specified object to the specified writer.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
index e4e06483b8..7b43efa0af 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonParserSession.java
@@ -58,11 +58,16 @@ import org.apache.juneau.utils.Iso8601Utils;
 })
 public class JsonParserSession extends ReaderParserSession {
 
+       // Property name constants
+       private static final String PROP_validateEnd = "validateEnd";
+       private static final String PROP_JsonParserSession_validateEnd = 
"JsonParserSession.validateEnd";
+
        /**
         * Builder class.
         */
        public static class Builder extends ReaderParserSession.Builder {
 
+               private boolean validateEnd;
                private JsonParser ctx;
 
                /**
@@ -73,6 +78,7 @@ public class JsonParserSession extends ReaderParserSession {
                protected Builder(JsonParser ctx) {
                        super(ctx);
                        this.ctx = ctx;
+                       validateEnd = ctx.isValidateEnd();
                }
 
                @Override /* Overridden from Builder */
@@ -134,10 +140,27 @@ public class JsonParserSession extends 
ReaderParserSession {
                        return this;
                }
 
+               /**
+                * Validate end.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder validateEnd(boolean value) {
+                       validateEnd = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_validateEnd, 
PROP_JsonParserSession_validateEnd:
+                                       return validateEnd(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -190,6 +213,7 @@ public class JsonParserSession extends ReaderParserSession {
        }
 
        private final JsonParser ctx;
+       private final boolean validateEnd;
 
        /**
         * Constructor.
@@ -199,6 +223,7 @@ public class JsonParserSession extends ReaderParserSession {
        protected JsonParserSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
+               validateEnd = builder.validateEnd;
        }
 
        @SuppressWarnings({
@@ -905,7 +930,7 @@ public class JsonParserSession extends ReaderParserSession {
         *      <jk>true</jk> if after parsing a POJO from the input, verifies 
that the remaining input in
         *      the stream consists of only comments or whitespace.
         */
-       protected boolean isValidateEnd() { return ctx.isValidateEnd(); }
+       protected boolean isValidateEnd() { return validateEnd; }
 
        /**
         * Returns <jk>true</jk> if the specified character is whitespace.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
index c50eda5873..51dcf9bca4 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java
@@ -51,6 +51,10 @@ import org.apache.juneau.utils.*;
 })
 public class JsonSerializerSession extends WriterSerializerSession {
 
+       // Property name constants
+       private static final String PROP_escapeSolidus = "escapeSolidus";
+       private static final String PROP_JsonSerializerSession_escapeSolidus = 
"JsonSerializerSession.escapeSolidus";
+
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
 
@@ -59,6 +63,7 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
         */
        public static class Builder extends WriterSerializerSession.Builder {
 
+               private boolean escapeSolidus;
                private JsonSerializer ctx;
 
                /**
@@ -70,6 +75,7 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
                protected Builder(JsonSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
                        this.ctx = ctx;
+                       escapeSolidus = ctx.isEscapeSolidus();
                }
 
                @Override /* Overridden from Builder */
@@ -125,10 +131,27 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
                        return this;
                }
 
+               /**
+                * Prefix solidus characters with escapes.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder escapeSolidus(boolean value) {
+                       escapeSolidus = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_escapeSolidus, 
PROP_JsonSerializerSession_escapeSolidus:
+                                       return escapeSolidus(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -198,6 +221,7 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
        }
 
        private final JsonSerializer ctx;
+       private final boolean escapeSolidus;
 
        /**
         * Constructor.
@@ -207,6 +231,7 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
        protected JsonSerializerSession(Builder builder) {
                super(builder);
                this.ctx = builder.ctx;
+               escapeSolidus = builder.escapeSolidus;
        }
 
        protected SerializerWriter serializeBeanMap(JsonWriter out, BeanMap<?> 
m, String typeName) throws SerializeException {
@@ -335,16 +360,8 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
                return w;
        }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @see JsonSerializer.Builder#addBeanTypesJson()
-        * @return
-        *      <jk>true</jk> if <js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
        @Override
-       protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); 
}
+       public final boolean isAddBeanTypes() { return super.isAddBeanTypes(); }
 
        /**
         * Prefix solidus <js>'/'</js> characters with escapes.
@@ -353,7 +370,7 @@ public class JsonSerializerSession extends 
WriterSerializerSession {
         * @return
         *      <jk>true</jk> if solidus (e.g. slash) characters should be 
escaped.
         */
-       protected final boolean isEscapeSolidus() { return 
ctx.isEscapeSolidus(); }
+       protected final boolean isEscapeSolidus() { return escapeSolidus; }
 
        /**
         * Workhorse method.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
index 1aa5dcbb91..b1e680b234 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/msgpack/MsgPackSerializerSession.java
@@ -59,8 +59,6 @@ public class MsgPackSerializerSession extends 
OutputStreamSerializerSession {
         */
        public static class Builder extends 
OutputStreamSerializerSession.Builder {
 
-               private MsgPackSerializer ctx;
-
                /**
                 * Constructor
                 *
@@ -69,7 +67,6 @@ public class MsgPackSerializerSession extends 
OutputStreamSerializerSession {
                 */
                protected Builder(MsgPackSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
-                       this.ctx = ctx;
                }
 
                @Override /* Overridden from Builder */
@@ -201,8 +198,6 @@ public class MsgPackSerializerSession extends 
OutputStreamSerializerSession {
                return os;
        }
 
-       private final MsgPackSerializer ctx;
-
        /**
         * Constructor.
         *
@@ -210,7 +205,6 @@ public class MsgPackSerializerSession extends 
OutputStreamSerializerSession {
         */
        protected MsgPackSerializerSession(Builder builder) {
                super(builder);
-               ctx = builder.ctx;
        }
 
        /*
@@ -391,5 +385,5 @@ public class MsgPackSerializerSession extends 
OutputStreamSerializerSession {
        }
 
        @Override
-       protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
+       public boolean isAddBeanTypes() { return super.isAddBeanTypes(); }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
index d9056bbbba..da76aec9fb 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
@@ -62,9 +62,11 @@ public class ParserSession extends BeanSession {
        private static final String PROP_listener = "listener";
        private static final String PROP_outer = "outer";
        private static final String PROP_schema = "schema";
+       private static final String PROP_trimStrings = "trimStrings";
        private static final String PROP_ParserSession_javaMethod = 
"ParserSession.javaMethod";
        private static final String PROP_ParserSession_outer = 
"ParserSession.outer";
        private static final String PROP_ParserSession_schema = 
"ParserSession.schema";
+       private static final String PROP_ParserSession_trimStrings = 
"ParserSession.trimStrings";
 
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
@@ -77,6 +79,7 @@ public class ParserSession extends BeanSession {
                private HttpPartSchema schema;
                private Method javaMethod;
                private Object outer;
+               private boolean trimStrings;
                private Parser ctx;
 
                /**
@@ -89,6 +92,7 @@ public class ParserSession extends BeanSession {
                        super(assertArgNotNull(ARG_ctx, ctx).getBeanContext());
                        this.ctx = ctx;
                        mediaTypeDefault(ctx.getPrimaryMediaType());
+                       trimStrings = ctx.isTrimStrings();
                }
 
                @Override /* Overridden from Builder */
@@ -172,12 +176,25 @@ public class ParserSession extends BeanSession {
                                        return outer(value);
                                case PROP_schema, PROP_ParserSession_schema:
                                        return schema(cvt(value, 
HttpPartSchema.class));
+                               case PROP_trimStrings, 
PROP_ParserSession_trimStrings:
+                                       return trimStrings(cvt(value, 
Boolean.class));
                                default:
                                        super.property(key, value);
                                        return this;
                        }
                }
 
+               /**
+                * Trim parsed strings.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder trimStrings(boolean value) {
+                       trimStrings = value;
+                       return this;
+               }
+
                /**
                 * HTTP-part schema.
                 *
@@ -283,6 +300,7 @@ public class ParserSession extends BeanSession {
        private final HttpPartSchema schema;
        private final Method javaMethod;
        private final Object outer;
+       private final boolean trimStrings;
        private final Parser ctx;
        private final ParserListener listener;
        private final Deque<StringBuilder> sbStack;
@@ -302,6 +320,7 @@ public class ParserSession extends BeanSession {
                javaMethod = builder.javaMethod;
                outer = builder.outer;
                schema = builder.schema;
+               trimStrings = builder.trimStrings;
                listener = 
BeanCreator.of(ParserListener.class).type(ctx.getListener()).orElse(null);
                sbStack = new ArrayDeque<>();
        }
@@ -1065,7 +1084,7 @@ public class ParserSession extends BeanSession {
         *      <jk>true</jk> if string values will be trimmed of whitespace 
using {@link String#trim()} before being added to
         *      the POJO.
         */
-       protected final boolean isTrimStrings() { return ctx.isTrimStrings(); }
+       protected final boolean isTrimStrings() { return trimStrings; }
 
        /**
         * Unbuffered.
@@ -1126,7 +1145,8 @@ public class ParserSession extends BeanSession {
                return super.properties()
                        .a(PROP_javaMethod, javaMethod)
                        .a(PROP_listener, listener)
-                       .a(PROP_outer, outer);
+                       .a(PROP_outer, outer)
+                       .a(PROP_trimStrings, trimStrings);
        }
 
        /**
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
index b37102ce25..a479fbd8da 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
@@ -75,10 +75,26 @@ public class SerializerSession extends BeanTraverseSession {
        private static final String PROP_schema = "schema";
        private static final String PROP_uriContext = "uriContext";
        private static final String PROP_uriResolver = "uriResolver";
+       private static final String PROP_keepNullProperties = 
"keepNullProperties";
+       private static final String PROP_trimStrings = "trimStrings";
+       private static final String PROP_addBeanTypes = "addBeanTypes";
+       private static final String PROP_addRootType = "addRootType";
+       private static final String PROP_sortCollections = "sortCollections";
+       private static final String PROP_sortMaps = "sortMaps";
+       private static final String PROP_trimEmptyCollections = 
"trimEmptyCollections";
+       private static final String PROP_trimEmptyMaps = "trimEmptyMaps";
        private static final String PROP_SerializerSession_javaMethod = 
"SerializerSession.javaMethod";
        private static final String PROP_SerializerSession_resolver = 
"SerializerSession.resolver";
        private static final String PROP_SerializerSession_schema = 
"SerializerSession.schema";
        private static final String PROP_SerializerSession_uriContext = 
"SerializerSession.uriContext";
+       private static final String PROP_SerializerSession_keepNullProperties = 
"SerializerSession.keepNullProperties";
+       private static final String PROP_SerializerSession_trimStrings = 
"SerializerSession.trimStrings";
+       private static final String PROP_SerializerSession_addBeanTypes = 
"SerializerSession.addBeanTypes";
+       private static final String PROP_SerializerSession_addRootType = 
"SerializerSession.addRootType";
+       private static final String PROP_SerializerSession_sortCollections = 
"SerializerSession.sortCollections";
+       private static final String PROP_SerializerSession_sortMaps = 
"SerializerSession.sortMaps";
+       private static final String PROP_SerializerSession_trimEmptyCollections 
= "SerializerSession.trimEmptyCollections";
+       private static final String PROP_SerializerSession_trimEmptyMaps = 
"SerializerSession.trimEmptyMaps";
 
        /**
         * Builder class.
@@ -92,6 +108,14 @@ public class SerializerSession extends BeanTraverseSession {
                private Serializer ctx;
                private UriContext uriContext;
                private VarResolverSession resolver;
+               private boolean keepNullProperties;
+               private boolean trimStrings;
+               private boolean addBeanTypes;
+               private boolean addRootType;
+               private boolean sortCollections;
+               private boolean sortMaps;
+               private boolean trimEmptyCollections;
+               private boolean trimEmptyMaps;
 
                /**
                 * Constructor
@@ -104,6 +128,14 @@ public class SerializerSession extends BeanTraverseSession 
{
                        this.ctx = ctx;
                        mediaTypeDefault(ctx.getResponseContentType());
                        uriContext = ctx.getUriContext();
+                       keepNullProperties = ctx.isKeepNullProperties();
+                       trimStrings = ctx.isTrimStrings();
+                       addBeanTypes = ctx.isAddBeanTypes();
+                       addRootType = ctx.isAddRootType();
+                       sortCollections = ctx.isSortCollections();
+                       sortMaps = ctx.isSortMaps();
+                       trimEmptyCollections = ctx.isTrimEmptyCollections();
+                       trimEmptyMaps = ctx.isTrimEmptyMaps();
                }
 
                @Override /* Overridden from Builder */
@@ -176,6 +208,22 @@ public class SerializerSession extends BeanTraverseSession 
{
                                        return schema(cvt(value, 
HttpPartSchema.class));
                                case PROP_uriContext, 
PROP_SerializerSession_uriContext:
                                        return uriContext(cvt(value, 
UriContext.class));
+                               case PROP_keepNullProperties, 
PROP_SerializerSession_keepNullProperties:
+                                       return keepNullProperties(cvt(value, 
Boolean.class));
+                               case PROP_trimStrings, 
PROP_SerializerSession_trimStrings:
+                                       return trimStrings(cvt(value, 
Boolean.class));
+                               case PROP_addBeanTypes, 
PROP_SerializerSession_addBeanTypes:
+                                       return addBeanTypes(cvt(value, 
Boolean.class));
+                               case PROP_addRootType, 
PROP_SerializerSession_addRootType:
+                                       return addRootType(cvt(value, 
Boolean.class));
+                               case PROP_sortCollections, 
PROP_SerializerSession_sortCollections:
+                                       return sortCollections(cvt(value, 
Boolean.class));
+                               case PROP_sortMaps, 
PROP_SerializerSession_sortMaps:
+                                       return sortMaps(cvt(value, 
Boolean.class));
+                               case PROP_trimEmptyCollections, 
PROP_SerializerSession_trimEmptyCollections:
+                                       return trimEmptyCollections(cvt(value, 
Boolean.class));
+                               case PROP_trimEmptyMaps, 
PROP_SerializerSession_trimEmptyMaps:
+                                       return trimEmptyMaps(cvt(value, 
Boolean.class));
                                default:
                                        super.property(key, value);
                                        return this;
@@ -267,6 +315,94 @@ public class SerializerSession extends BeanTraverseSession 
{
                                uriContext = value;
                        return this;
                }
+
+               /**
+                * Keep null bean property values.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder keepNullProperties(boolean value) {
+                       keepNullProperties = value;
+                       return this;
+               }
+
+               /**
+                * Trim strings.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder trimStrings(boolean value) {
+                       trimStrings = value;
+                       return this;
+               }
+
+               /**
+                * Add bean types.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder addBeanTypes(boolean value) {
+                       addBeanTypes = value;
+                       return this;
+               }
+
+               /**
+                * Add root type.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder addRootType(boolean value) {
+                       addRootType = value;
+                       return this;
+               }
+
+               /**
+                * Sort collections.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder sortCollections(boolean value) {
+                       sortCollections = value;
+                       return this;
+               }
+
+               /**
+                * Sort maps.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder sortMaps(boolean value) {
+                       sortMaps = value;
+                       return this;
+               }
+
+               /**
+                * Trim empty collections.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder trimEmptyCollections(boolean value) {
+                       trimEmptyCollections = value;
+                       return this;
+               }
+
+               /**
+                * Trim empty maps.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder trimEmptyMaps(boolean value) {
+                       trimEmptyMaps = value;
+                       return this;
+               }
        }
 
        /**
@@ -344,6 +480,14 @@ public class SerializerSession extends BeanTraverseSession 
{
        private final SerializerListener listener;
        private final UriResolver uriResolver;
        private VarResolverSession vrs;
+       private final boolean keepNullProperties;
+       private final boolean trimStrings;
+       private final boolean addBeanTypes;
+       private final boolean addRootType;
+       private final boolean sortCollections;
+       private final boolean sortMaps;
+       private final boolean trimEmptyCollections;
+       private final boolean trimEmptyMaps;
 
        /**
         * Constructor.
@@ -359,6 +503,14 @@ public class SerializerSession extends BeanTraverseSession 
{
                uriResolver = UriResolver.of(ctx.getUriResolution(), 
ctx.getUriRelativity(), uriContext);
                vrs = builder.resolver;
                listener = 
BeanCreator.of(SerializerListener.class).type(ctx.getListener()).orElse(null);
+               keepNullProperties = builder.keepNullProperties;
+               trimStrings = builder.trimStrings;
+               addBeanTypes = builder.addBeanTypes;
+               addRootType = builder.addRootType;
+               sortCollections = builder.sortCollections;
+               sortMaps = builder.sortMaps;
+               trimEmptyCollections = builder.trimEmptyCollections;
+               trimEmptyMaps = builder.trimEmptyMaps;
        }
 
        /**
@@ -973,7 +1125,7 @@ public class SerializerSession extends BeanTraverseSession 
{
         *      <jk>true</jk> if <js>"_type"</js> properties added to beans if 
their type cannot be inferred
         *      through reflection.
         */
-       protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
+       protected boolean isAddBeanTypes() { return addBeanTypes; }
 
        /**
         * Add type attribute to root nodes.
@@ -982,7 +1134,7 @@ public class SerializerSession extends BeanTraverseSession 
{
         * @return
         *      <jk>true</jk> if type property should be added to root node.
         */
-       protected final boolean isAddRootType() { return ctx.isAddRootType(); }
+       protected final boolean isAddRootType() { return addRootType; }
 
        /**
         * Don't trim null bean property values.
@@ -991,7 +1143,7 @@ public class SerializerSession extends BeanTraverseSession 
{
         * @return
         *      <jk>true</jk> if null bean values are serialized to the output.
         */
-       protected final boolean isKeepNullProperties() { return 
ctx.isKeepNullProperties(); }
+       protected final boolean isKeepNullProperties() { return 
keepNullProperties; }
 
        /**
         * Sort arrays and collections alphabetically.
@@ -1000,7 +1152,7 @@ public class SerializerSession extends 
BeanTraverseSession {
         * @return
         *      <jk>true</jk> if arrays and collections are copied and sorted 
before serialization.
         */
-       protected final boolean isSortCollections() { return 
ctx.isSortCollections(); }
+       protected final boolean isSortCollections() { return sortCollections; }
 
        /**
         * Sort maps alphabetically.
@@ -1009,7 +1161,7 @@ public class SerializerSession extends 
BeanTraverseSession {
         * @return
         *      <jk>true</jk> if maps are copied and sorted before 
serialization.
         */
-       protected final boolean isSortMaps() { return ctx.isSortMaps(); }
+       protected final boolean isSortMaps() { return sortMaps; }
 
        /**
         * Trim empty lists and arrays.
@@ -1018,7 +1170,7 @@ public class SerializerSession extends 
BeanTraverseSession {
         * @return
         *      <jk>true</jk> if empty lists and arrays are not serialized to 
the output.
         */
-       protected final boolean isTrimEmptyCollections() { return 
ctx.isTrimEmptyCollections(); }
+       protected final boolean isTrimEmptyCollections() { return 
trimEmptyCollections; }
 
        /**
         * Trim empty maps.
@@ -1027,7 +1179,7 @@ public class SerializerSession extends 
BeanTraverseSession {
         * @return
         *      <jk>true</jk> if empty map values are not serialized to the 
output.
         */
-       protected final boolean isTrimEmptyMaps() { return 
ctx.isTrimEmptyMaps(); }
+       protected final boolean isTrimEmptyMaps() { return trimEmptyMaps; }
 
        /**
         * Trim strings.
@@ -1036,7 +1188,7 @@ public class SerializerSession extends 
BeanTraverseSession {
         * @return
         *      <jk>true</jk> if string values will be trimmed of whitespace 
using {@link String#trim()} before being serialized.
         */
-       protected boolean isTrimStrings() { return ctx.isTrimStrings(); }
+       protected boolean isTrimStrings() { return trimStrings; }
 
        /**
         * Specialized warning when an exception is thrown while executing a 
bean getter.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java
index 4c44251c62..d653401534 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/WriterSerializerSession.java
@@ -59,9 +59,13 @@ public class WriterSerializerSession extends 
SerializerSession {
        private static final String PROP_fileCharset = "fileCharset";
        private static final String PROP_streamCharset = "streamCharset";
        private static final String PROP_useWhitespace = "useWhitespace";
+       private static final String PROP_maxIndent = "maxIndent";
+       private static final String PROP_quoteChar = "quoteChar";
        private static final String PROP_WriterSerializerSession_fileCharset = 
"WriterSerializerSession.fileCharset";
        private static final String PROP_WriterSerializerSession_streamCharset 
= "WriterSerializerSession.streamCharset";
        private static final String PROP_WriterSerializerSession_useWhitespace 
= "WriterSerializerSession.useWhitespace";
+       private static final String PROP_WriterSerializerSession_maxIndent = 
"WriterSerializerSession.maxIndent";
+       private static final String PROP_WriterSerializerSession_quoteChar = 
"WriterSerializerSession.quoteChar";
 
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
@@ -74,7 +78,8 @@ public class WriterSerializerSession extends 
SerializerSession {
                private boolean useWhitespace;
                private Charset fileCharset;
                private Charset streamCharset;
-               private WriterSerializer ctx;
+               private int maxIndent;
+               private char quoteChar;
 
                /**
                 * Constructor
@@ -84,10 +89,11 @@ public class WriterSerializerSession extends 
SerializerSession {
                 */
                protected Builder(WriterSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
-                       this.ctx = ctx;
                        fileCharset = ctx.getFileCharset();
                        streamCharset = ctx.getStreamCharset();
                        useWhitespace = ctx.useWhitespace;
+                       maxIndent = ctx.getMaxIndent();
+                       quoteChar = ctx.getQuoteChar();
                }
 
                @Override /* Overridden from Builder */
@@ -160,6 +166,17 @@ public class WriterSerializerSession extends 
SerializerSession {
                        return this;
                }
 
+               /**
+                * Maximum indentation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder maxIndent(int value) {
+                       maxIndent = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
                        if (key == null) {
@@ -173,12 +190,27 @@ public class WriterSerializerSession extends 
SerializerSession {
                                        return streamCharset(cvt(value, 
Charset.class));
                                case PROP_useWhitespace, 
PROP_WriterSerializerSession_useWhitespace:
                                        return useWhitespace(cvt(value, 
Boolean.class));
+                               case PROP_maxIndent, 
PROP_WriterSerializerSession_maxIndent:
+                                       return maxIndent(cvt(value, 
Integer.class));
+                               case PROP_quoteChar, 
PROP_WriterSerializerSession_quoteChar:
+                                       return quoteChar(cvt(value, 
Character.class));
                                default:
                                        super.property(key, value);
                                        return this;
                        }
                }
 
+               /**
+                * Quote character.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder quoteChar(char value) {
+                       quoteChar = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder resolver(VarResolverSession value) {
                        super.resolver(value);
@@ -273,10 +305,11 @@ public class WriterSerializerSession extends 
SerializerSession {
                return new Builder(assertArgNotNull(ARG_ctx, ctx));
        }
 
-       private final WriterSerializer ctx;
        private final boolean useWhitespace;
        private final Charset fileCharset;
        private final Charset streamCharset;
+       private final int maxIndent;
+       private final char quoteChar;
 
        /**
         * Constructor.
@@ -285,10 +318,11 @@ public class WriterSerializerSession extends 
SerializerSession {
         */
        protected WriterSerializerSession(Builder builder) {
                super(builder);
-               ctx = builder.ctx;
                fileCharset = builder.fileCharset;
                streamCharset = builder.streamCharset;
                useWhitespace = builder.useWhitespace;
+               maxIndent = builder.maxIndent;
+               quoteChar = builder.quoteChar;
        }
 
        /**
@@ -343,7 +377,7 @@ public class WriterSerializerSession extends 
SerializerSession {
         * @return
         *      The maximum indentation level in the serialized document.
         */
-       protected final int getMaxIndent() { return ctx.getMaxIndent(); }
+       protected final int getMaxIndent() { return maxIndent; }
 
        /**
         * Quote character.
@@ -352,7 +386,7 @@ public class WriterSerializerSession extends 
SerializerSession {
         * @return
         *      The character used for quoting attributes and values.
         */
-       protected char getQuoteChar() { return ctx.getQuoteChar(); }
+       protected char getQuoteChar() { return quoteChar; }
 
        /**
         * Use whitespace.
@@ -368,6 +402,8 @@ public class WriterSerializerSession extends 
SerializerSession {
                return super.properties()
                        .a(PROP_fileCharset, fileCharset)
                        .a(PROP_streamCharset, streamCharset)
-                       .a(PROP_useWhitespace, useWhitespace);
+                       .a(PROP_useWhitespace, useWhitespace)
+                       .a(PROP_maxIndent, maxIndent)
+                       .a(PROP_quoteChar, quoteChar);
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
index 39c986642c..a00fb876bb 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonParserSession.java
@@ -61,6 +61,8 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
 
        // Property name constants
        private static final String PROP_decoding = "decoding";
+       private static final String PROP_validateEnd = "validateEnd";
+       private static final String PROP_UonParserSession_validateEnd = 
"UonParserSession.validateEnd";
 
        /**
         * Builder class.
@@ -68,7 +70,7 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
        public static class Builder extends ReaderParserSession.Builder {
 
                private boolean decoding;
-               private UonParser ctx;
+               private boolean validateEnd;
 
                /**
                 * Constructor
@@ -77,8 +79,8 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
                 */
                protected Builder(UonParser ctx) {
                        super(ctx);
-                       this.ctx = ctx;
                        decoding = ctx.decoding;
+                       validateEnd = ctx.isValidateEnd();
                }
 
                @Override /* Overridden from Builder */
@@ -153,7 +155,26 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
 
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_decoding:
+                                       return decoding(cvt(value, 
Boolean.class));
+                               case PROP_validateEnd, 
PROP_UonParserSession_validateEnd:
+                                       return validateEnd(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
+               }
+
+               /**
+                * Validate end.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder validateEnd(boolean value) {
+                       validateEnd = value;
                        return this;
                }
 
@@ -236,9 +257,8 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
                }
        }
 
-       private final UonParser ctx;
-
        private final boolean decoding;
+       private final boolean validateEnd;
 
        /**
         * Constructor.
@@ -247,8 +267,8 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
         */
        protected UonParserSession(Builder builder) {
                super(builder);
-               ctx = builder.ctx;
                decoding = builder.decoding;
+               validateEnd = builder.validateEnd;
        }
 
        /**
@@ -870,7 +890,7 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
         *      <jk>true</jk> if after parsing a POJO from the input, verifies 
that the remaining input in
         *      the stream consists of only comments or whitespace.
         */
-       protected final boolean isValidateEnd() { return ctx.isValidateEnd(); }
+       protected final boolean isValidateEnd() { return validateEnd; }
 
        /**
         * Convenience method for parsing an attribute from the specified 
parser.
@@ -992,6 +1012,7 @@ public class UonParserSession extends ReaderParserSession 
implements HttpPartPar
        @Override /* Overridden from ReaderParserSession */
        protected FluentMap<String,Object> properties() {
                return super.properties()
-                       .a(PROP_decoding, decoding);
+                       .a(PROP_decoding, decoding)
+                       .a(PROP_validateEnd, validateEnd);
        }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
index d8d6b879e3..89a3df235d 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/uon/UonSerializerSession.java
@@ -54,6 +54,12 @@ import org.apache.juneau.utils.*;
 })
 public class UonSerializerSession extends WriterSerializerSession implements 
HttpPartSerializerSession {
 
+       // Property name constants
+       private static final String PROP_encoding = "encoding";
+       private static final String PROP_paramFormat = "paramFormat";
+       private static final String PROP_UonSerializerSession_encoding = 
"UonSerializerSession.encoding";
+       private static final String PROP_UonSerializerSession_paramFormat = 
"UonSerializerSession.paramFormat";
+
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
 
@@ -62,7 +68,8 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
         */
        public static class Builder extends WriterSerializerSession.Builder {
 
-               private UonSerializer ctx;
+               private boolean encoding;
+               private ParamFormat paramFormat;
 
                /**
                 * Constructor
@@ -72,7 +79,8 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
                 */
                protected Builder(UonSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
-                       this.ctx = ctx;
+                       encoding = ctx.isEncoding();
+                       paramFormat = ctx.getParamFormat();
                }
 
                @Override /* Overridden from Builder */
@@ -128,10 +136,40 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
                        return this;
                }
 
+               /**
+                * Encode non-valid URI characters.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder encoding(boolean value) {
+                       encoding = value;
+                       return this;
+               }
+
+               /**
+                * Format to use for query/form-data/header values.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder paramFormat(ParamFormat value) {
+                       paramFormat = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_encoding, 
PROP_UonSerializerSession_encoding:
+                                       return encoding(cvt(value, 
Boolean.class));
+                               case PROP_paramFormat, 
PROP_UonSerializerSession_paramFormat:
+                                       return paramFormat(cvt(value, 
ParamFormat.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -200,7 +238,8 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
                return new Builder(assertArgNotNull(ARG_ctx, ctx));
        }
 
-       private final UonSerializer ctx;
+       private final boolean encoding;
+       private final ParamFormat paramFormat;
        private final boolean plainTextParams;
 
        /**
@@ -210,8 +249,9 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
         */
        public UonSerializerSession(Builder builder) {
                super(builder);
-               ctx = builder.ctx;
-               plainTextParams = ctx.getParamFormat() == ParamFormat.PLAINTEXT;
+               encoding = builder.encoding;
+               paramFormat = builder.paramFormat;
+               plainTextParams = paramFormat == ParamFormat.PLAINTEXT;
        }
 
        @Override /* Overridden from HttpPartSerializer */
@@ -365,17 +405,7 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
         * @return
         *      Specifies the format to use for URL GET parameter keys and 
values.
         */
-       protected final ParamFormat getParamFormat() { return 
ctx.getParamFormat(); }
-
-       /**
-        * Quote character.
-        *
-        * @see UonSerializer.Builder#quoteCharUon(char)
-        * @return
-        *      The character used for quoting attributes and values.
-        */
-       @Override
-       protected final char getQuoteChar() { return ctx.getQuoteChar(); }
+       protected final ParamFormat getParamFormat() { return paramFormat; }
 
        /**
         * Converts the specified output target object to an {@link UonWriter}.
@@ -393,16 +423,8 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
                return w;
        }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @see UonSerializer.Builder#addBeanTypesUon()
-        * @return
-        *      <jk>true</jk> if <js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
        @Override
-       protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); 
}
+       public final boolean isAddBeanTypes() { return super.isAddBeanTypes(); }
 
        /**
         * Encode non-valid URI characters.
@@ -411,7 +433,7 @@ public class UonSerializerSession extends 
WriterSerializerSession implements Htt
         * @return
         *      <jk>true</jk> if non-valid URI characters should be encoded 
with <js>"%xx"</js> constructs.
         */
-       protected final boolean isEncoding() { return ctx.isEncoding(); }
+       protected final boolean isEncoding() { return encoding; }
 
        /**
         * Workhorse method.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
index 231532f529..972fef9485 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingParserSession.java
@@ -56,11 +56,16 @@ public class UrlEncodingParserSession extends 
UonParserSession {
 
        private static final String CONST_value = "_value";
 
+       // Property name constants
+       private static final String PROP_expandedParams = "expandedParams";
+       private static final String 
PROP_UrlEncodingParserSession_expandedParams = 
"UrlEncodingParserSession.expandedParams";
+
        /**
         * Builder class.
         */
        public static class Builder extends UonParserSession.Builder {
 
+               private boolean expandedParams;
                private UrlEncodingParser ctx;
 
                /**
@@ -71,6 +76,7 @@ public class UrlEncodingParserSession extends 
UonParserSession {
                protected Builder(UrlEncodingParser ctx) {
                        super(ctx);
                        this.ctx = ctx;
+                       expandedParams = ctx.isExpandedParams();
                }
 
                @Override /* Overridden from Builder */
@@ -138,10 +144,27 @@ public class UrlEncodingParserSession extends 
UonParserSession {
                        return this;
                }
 
+               /**
+                * Serialize bean property collections/arrays as separate 
key/value pairs.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder expandedParams(boolean value) {
+                       expandedParams = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_expandedParams, 
PROP_UrlEncodingParserSession_expandedParams:
+                                       return expandedParams(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -192,6 +215,7 @@ public class UrlEncodingParserSession extends 
UonParserSession {
        }
 
        private final UrlEncodingParser ctx;
+       private final boolean expandedParams;
 
        /**
         * Constructor.
@@ -201,6 +225,7 @@ public class UrlEncodingParserSession extends 
UonParserSession {
        public UrlEncodingParserSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
+               expandedParams = builder.expandedParams;
        }
 
        /**
@@ -572,5 +597,5 @@ public class UrlEncodingParserSession extends 
UonParserSession {
         * <jk>false</jk> if serializing the array <c>[1,2,3]</c> results in 
<c>?key=$a(1,2,3)</c>.
         * <br><jk>true</jk> if serializing the same array results in 
<c>?key=1&amp;key=2&amp;key=3</c>.
         */
-       protected final boolean isExpandedParams() { return 
ctx.isExpandedParams(); }
+       protected final boolean isExpandedParams() { return expandedParams; }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
index fd89a59fec..fe7b9c37b1 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/urlencoding/UrlEncodingSerializerSession.java
@@ -51,11 +51,16 @@ import org.apache.juneau.uon.*;
 })
 public class UrlEncodingSerializerSession extends UonSerializerSession {
 
+       // Property name constants
+       private static final String PROP_expandedParams = "expandedParams";
+       private static final String 
PROP_UrlEncodingSerializerSession_expandedParams = 
"UrlEncodingSerializerSession.expandedParams";
+
        /**
         * Builder class.
         */
        public static class Builder extends UonSerializerSession.Builder {
 
+               private boolean expandedParams;
                private UrlEncodingSerializer ctx;
 
                /**
@@ -66,6 +71,7 @@ public class UrlEncodingSerializerSession extends 
UonSerializerSession {
                protected Builder(UrlEncodingSerializer ctx) {
                        super(ctx);
                        this.ctx = ctx;
+                       expandedParams = ctx.isExpandedParams();
                }
 
                @Override /* Overridden from Builder */
@@ -121,10 +127,27 @@ public class UrlEncodingSerializerSession extends 
UonSerializerSession {
                        return this;
                }
 
+               /**
+                * Serialize bean property collections/arrays as separate 
key/value pairs.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder expandedParams(boolean value) {
+                       expandedParams = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_expandedParams, 
PROP_UrlEncodingSerializerSession_expandedParams:
+                                       return expandedParams(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -213,6 +236,7 @@ public class UrlEncodingSerializerSession extends 
UonSerializerSession {
        }
 
        private final UrlEncodingSerializer ctx;
+       private final boolean expandedParams;
 
        /**
         * Constructor.
@@ -222,6 +246,7 @@ public class UrlEncodingSerializerSession extends 
UonSerializerSession {
        protected UrlEncodingSerializerSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
+               expandedParams = builder.expandedParams;
        }
 
        /*
@@ -441,5 +466,5 @@ public class UrlEncodingSerializerSession extends 
UonSerializerSession {
         *      <jk>false</jk> if serializing the array <c>[1,2,3]</c> results 
in <c>?key=$a(1,2,3)</c>.
         *      <br><jk>true</jk> if serializing the same array results in 
<c>?key=1&amp;key=2&amp;key=3</c>.
         */
-       protected final boolean isExpandedParams() { return 
ctx.isExpandedParams(); }
+       protected final boolean isExpandedParams() { return expandedParams; }
 }
\ No newline at end of file
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
index 25d1f0ada0..d9f583e21a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlParserSession.java
@@ -58,11 +58,19 @@ import org.apache.juneau.utils.Iso8601Utils;
 })
 public class XmlParserSession extends ReaderParserSession {
 
+       // Property name constants
+       private static final String PROP_preserveRootElement = 
"preserveRootElement";
+       private static final String PROP_validating = "validating";
+       private static final String PROP_XmlParserSession_preserveRootElement = 
"XmlParserSession.preserveRootElement";
+       private static final String PROP_XmlParserSession_validating = 
"XmlParserSession.validating";
+
        /**
         * Builder class.
         */
        public static class Builder extends ReaderParserSession.Builder {
 
+               private boolean preserveRootElement;
+               private boolean validating;
                private XmlParser ctx;
 
                /**
@@ -73,6 +81,8 @@ public class XmlParserSession extends ReaderParserSession {
                protected Builder(XmlParser ctx) {
                        super(ctx);
                        this.ctx = ctx;
+                       preserveRootElement = ctx.isPreserveRootElement();
+                       validating = ctx.isValidating();
                }
 
                @Override /* Overridden from Builder */
@@ -134,10 +144,40 @@ public class XmlParserSession extends ReaderParserSession 
{
                        return this;
                }
 
+               /**
+                * Preserve root element during XML parsing.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder preserveRootElement(boolean value) {
+                       preserveRootElement = value;
+                       return this;
+               }
+
+               /**
+                * Validate XML during parsing.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder validating(boolean value) {
+                       validating = value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_preserveRootElement, 
PROP_XmlParserSession_preserveRootElement:
+                                       return preserveRootElement(cvt(value, 
Boolean.class));
+                               case PROP_validating, 
PROP_XmlParserSession_validating:
+                                       return validating(cvt(value, 
Boolean.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -217,6 +257,8 @@ public class XmlParserSession extends ReaderParserSession {
        }
 
        private final XmlParser ctx;
+       private final boolean preserveRootElement;
+       private final boolean validating;
 
        private final StringBuilder rsb = new StringBuilder();  // Reusable 
string builder used in this class.
 
@@ -228,6 +270,8 @@ public class XmlParserSession extends ReaderParserSession {
        protected XmlParserSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
+               preserveRootElement = builder.preserveRootElement;
+               validating = builder.validating;
        }
 
        /*
@@ -744,7 +788,7 @@ public class XmlParserSession extends ReaderParserSession {
         *      <jk>true</jk> if when parsing into a generic {@link JsonMap}, 
the map will contain a single entry whose key
         *      is the root element name.
         */
-       protected final boolean isPreserveRootElement() { return 
ctx.isPreserveRootElement(); }
+       protected final boolean isPreserveRootElement() { return 
preserveRootElement; }
 
        /**
         * Enable validation.
@@ -753,7 +797,7 @@ public class XmlParserSession extends ReaderParserSession {
         * @return
         *      <jk>true</jk> if XML document will be validated.
         */
-       protected final boolean isValidating() { return ctx.isValidating(); }
+       protected final boolean isValidating() { return validating; }
 
        /**
         * Returns <jk>true</jk> if the current element is a whitespace element.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
index aefab5e43a..d6c5894dec 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/xml/XmlSerializerSession.java
@@ -57,6 +57,18 @@ import org.apache.juneau.xml.annotation.*;
 })
 public class XmlSerializerSession extends WriterSerializerSession {
 
+       // Property name constants
+       private static final String PROP_addNamespaceUrisToRoot = 
"addNamespaceUrisToRoot";
+       private static final String PROP_autoDetectNamespaces = 
"autoDetectNamespaces";
+       private static final String PROP_defaultNamespace = "defaultNamespace";
+       private static final String PROP_enableNamespaces = "enableNamespaces";
+       private static final String PROP_textNodeDelimiter = 
"textNodeDelimiter";
+       private static final String 
PROP_XmlSerializerSession_addNamespaceUrisToRoot = 
"XmlSerializerSession.addNamespaceUrisToRoot";
+       private static final String 
PROP_XmlSerializerSession_autoDetectNamespaces = 
"XmlSerializerSession.autoDetectNamespaces";
+       private static final String PROP_XmlSerializerSession_defaultNamespace 
= "XmlSerializerSession.defaultNamespace";
+       private static final String PROP_XmlSerializerSession_enableNamespaces 
= "XmlSerializerSession.enableNamespaces";
+       private static final String PROP_XmlSerializerSession_textNodeDelimiter 
= "XmlSerializerSession.textNodeDelimiter";
+
        // Argument name constants for assertArgNotNull
        private static final String ARG_ctx = "ctx";
 
@@ -65,6 +77,11 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
         */
        public static class Builder extends WriterSerializerSession.Builder {
 
+               private boolean addNamespaceUrisToRoot;
+               private boolean autoDetectNamespaces;
+               private String defaultNamespace;
+               private boolean enableNamespaces;
+               private String textNodeDelimiter;
                private XmlSerializer ctx;
 
                /**
@@ -76,6 +93,11 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
                protected Builder(XmlSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
                        this.ctx = ctx;
+                       addNamespaceUrisToRoot = ctx.isAddNamespaceUrlsToRoot();
+                       autoDetectNamespaces = ctx.isAutoDetectNamespaces();
+                       defaultNamespace = ctx.getDefaultNamespace() == null ? 
null : ctx.getDefaultNamespace().toString();
+                       enableNamespaces = ctx.isEnableNamespaces();
+                       textNodeDelimiter = ctx.getTextNodeDelimiter();
                }
 
                @Override /* Overridden from Builder */
@@ -131,10 +153,79 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
                        return this;
                }
 
+               /**
+                * Add namespace URLs to the root element.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder addNamespaceUrisToRoot(boolean value) {
+                       addNamespaceUrisToRoot = value;
+                       return this;
+               }
+
+               /**
+                * Auto-detect namespace usage.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder autoDetectNamespaces(boolean value) {
+                       autoDetectNamespaces = value;
+                       return this;
+               }
+
+               /**
+                * Default namespace.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder defaultNamespace(String value) {
+                       defaultNamespace = value;
+                       return this;
+               }
+
+               /**
+                * Enable support for XML namespaces.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder enableNamespaces(boolean value) {
+                       enableNamespaces = value;
+                       return this;
+               }
+
+               /**
+                * Text node delimiter.
+                *
+                * @param value The new value for this setting.
+                * @return This object.
+                */
+               public Builder textNodeDelimiter(String value) {
+                       textNodeDelimiter = value == null ? "" : value;
+                       return this;
+               }
+
                @Override /* Overridden from Builder */
                public Builder property(String key, Object value) {
-                       super.property(key, value);
-                       return this;
+                       if (key == null) { super.property(key, value); return 
this; }
+                       switch (key) {
+                               case PROP_addNamespaceUrisToRoot, 
PROP_XmlSerializerSession_addNamespaceUrisToRoot:
+                                       return 
addNamespaceUrisToRoot(cvt(value, Boolean.class));
+                               case PROP_autoDetectNamespaces, 
PROP_XmlSerializerSession_autoDetectNamespaces:
+                                       return autoDetectNamespaces(cvt(value, 
Boolean.class));
+                               case PROP_defaultNamespace, 
PROP_XmlSerializerSession_defaultNamespace:
+                                       return defaultNamespace(cvt(value, 
String.class));
+                               case PROP_enableNamespaces, 
PROP_XmlSerializerSession_enableNamespaces:
+                                       return enableNamespaces(cvt(value, 
Boolean.class));
+                               case PROP_textNodeDelimiter, 
PROP_XmlSerializerSession_textNodeDelimiter:
+                                       return textNodeDelimiter(cvt(value, 
String.class));
+                               default:
+                                       super.property(key, value);
+                                       return this;
+                       }
                }
 
                @Override /* Overridden from Builder */
@@ -245,6 +336,9 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
        }
 
        private final String textNodeDelimiter;
+       private final boolean addNamespaceUrisToRoot;
+       private final boolean autoDetectNamespaces;
+       private final boolean enableNamespaces;
        private final XmlSerializer ctx;
        private Namespace defaultNamespace;
        private List<Namespace> namespaces = new ArrayList<>();
@@ -257,10 +351,15 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
        protected XmlSerializerSession(Builder builder) {
                super(builder);
                ctx = builder.ctx;
-               defaultNamespace = 
findDefaultNamespace(ctx.getDefaultNamespace());
+               addNamespaceUrisToRoot = builder.addNamespaceUrisToRoot;
+               autoDetectNamespaces = builder.autoDetectNamespaces;
+               enableNamespaces = builder.enableNamespaces;
+               textNodeDelimiter = builder.textNodeDelimiter;
+               defaultNamespace = findDefaultNamespace(
+                       builder.defaultNamespace != null ? 
Namespace.create(builder.defaultNamespace) : ctx.getDefaultNamespace()
+               );
                var ctxNamespaces = ctx.getNamespaces();
                namespaces = ctxNamespaces == null ? new ArrayList<>() : new 
ArrayList<>(ctxNamespaces);
-               textNodeDelimiter = ctx.getTextNodeDelimiter();
        }
 
        /**
@@ -726,16 +825,8 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
         */
        protected final List<Namespace> getNamespaces() { return namespaces == 
null ? l() : namespaces; }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @see XmlSerializer.Builder#addBeanTypesXml()
-        * @return
-        *      <jk>true</jk> if<js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
        @Override
-       protected boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); }
+       public boolean isAddBeanTypes() { return super.isAddBeanTypes(); }
 
        /**
         * Add JSON type tags.
@@ -753,7 +844,7 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
         * @return
         *      <jk>true</jk> if {@code xmlns:x} attributes are added to the 
root element for the default and all mapped namespaces.
         */
-       protected final boolean isAddNamespaceUrisToRoot() { return 
ctx.isAddNamespaceUrlsToRoot(); }
+       protected final boolean isAddNamespaceUrisToRoot() { return 
addNamespaceUrisToRoot; }
 
        /**
         * Auto-detect namespace usage.
@@ -762,7 +853,7 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
         * @return
         *      <jk>true</jk> if namespace usage is detected before 
serialization.
         */
-       protected final boolean isAutoDetectNamespaces() { return 
ctx.isAutoDetectNamespaces(); }
+       protected final boolean isAutoDetectNamespaces() { return 
autoDetectNamespaces; }
 
        /**
         * Enable support for XML namespaces.
@@ -771,7 +862,7 @@ public class XmlSerializerSession extends 
WriterSerializerSession {
         * @return
         *      <jk>false</jk> if XML output will not contain any namespaces 
regardless of any other settings.
         */
-       protected final boolean isEnableNamespaces() { return 
ctx.isEnableNamespaces(); }
+       protected final boolean isEnableNamespaces() { return enableNamespaces; 
}
 
        /**
         * Returns <jk>true</jk> if we're serializing HTML.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializerSession.java
index bd9ea06ae7..ab141aa620 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/yaml/YamlSerializerSession.java
@@ -58,8 +58,6 @@ public class YamlSerializerSession extends 
WriterSerializerSession {
         */
        public static class Builder extends WriterSerializerSession.Builder {
 
-               private YamlSerializer ctx;
-
                /**
                 * Constructor
                 *
@@ -68,7 +66,6 @@ public class YamlSerializerSession extends 
WriterSerializerSession {
                 */
                protected Builder(YamlSerializer ctx) {
                        super(assertArgNotNull(ARG_ctx, ctx));
-                       this.ctx = ctx;
                }
 
                @Override /* Overridden from Builder */
@@ -196,8 +193,6 @@ public class YamlSerializerSession extends 
WriterSerializerSession {
                return new Builder(assertArgNotNull(ARG_ctx, ctx));
        }
 
-       private final YamlSerializer ctx;
-
        /**
         * Constructor.
         *
@@ -205,7 +200,6 @@ public class YamlSerializerSession extends 
WriterSerializerSession {
         */
        protected YamlSerializerSession(Builder builder) {
                super(builder);
-               this.ctx = builder.ctx;
        }
 
        @Override /* Overridden from SerializerSession */
@@ -441,13 +435,4 @@ public class YamlSerializerSession extends 
WriterSerializerSession {
                return out;
        }
 
-       /**
-        * Add <js>"_type"</js> properties when needed.
-        *
-        * @return
-        *      <jk>true</jk> if <js>"_type"</js> properties will be added to 
beans if their type cannot be inferred
-        *      through reflection.
-        */
-       @Override
-       protected final boolean isAddBeanTypes() { return ctx.isAddBeanTypes(); 
}
 }
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/ContextSession_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/ContextSession_Test.java
index d6dfeb45c5..71c4cf0d0c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/ContextSession_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/ContextSession_Test.java
@@ -22,9 +22,15 @@ import java.nio.charset.*;
 import java.util.*;
 
 import org.apache.juneau.collections.*;
+import org.apache.juneau.csv.*;
+import org.apache.juneau.html.*;
 import org.apache.juneau.json.*;
+import org.apache.juneau.json5.*;
 import org.apache.juneau.parser.*;
 import org.apache.juneau.serializer.*;
+import org.apache.juneau.uon.*;
+import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.xml.*;
 import org.junit.jupiter.api.*;
 
 class ContextSession_Test extends TestBase {
@@ -140,6 +146,21 @@ class ContextSession_Test extends TestBase {
 
        @Nested class E_propertyDispatch extends TestBase {
 
+               public static class BeanWithNullField {
+                       public String a = null;
+                       public int b = 1;
+               }
+
+               public static class BeanWithEmptyCollection {
+                       public String[] a = new String[0];
+                       public int b = 1;
+               }
+
+               public static class BeanWithEmptyMap {
+                       public Map<String,Object> a = new LinkedHashMap<>();
+                       public int b = 1;
+               }
+
                // -- ContextSession: debug --
 
                @Test void e01_debug_shortForm() {
@@ -296,6 +317,297 @@ class ContextSession_Test extends TestBase {
                                .build();
                        assertEquals(Locale.forLanguageTag("ja-JP"), 
session.getLocale());
                }
+
+               // -- BeanTraverseSession: initialDepth --
+
+               @Test void e20_beanTraverse_initialDepth_shortForm() {
+                       var session = (BeanTraverseSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("initialDepth", 3)
+                               .build();
+                       assertEquals(3, session.indent);
+               }
+
+               @Test void e21_beanTraverse_initialDepth_qualifiedForm() {
+                       var session = (BeanTraverseSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("BeanTraverseSession.initialDepth", 
"5")
+                               .build();
+                       assertEquals(5, session.indent);
+               }
+
+               // -- SerializerSession: trimStrings --
+
+               @Test void e22_serializer_trimStrings_shortForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("trimStrings", true)
+                               .build();
+                       assertEquals("\"hello\"", session.serialize("  hello  
"));
+               }
+
+               @Test void e23_serializer_trimStrings_qualifiedForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("SerializerSession.trimStrings", 
"true")
+                               .build();
+                       assertEquals("\"hello\"", session.serialize("  hello  
"));
+               }
+
+               // -- SerializerSession: keepNullProperties --
+
+               @Test void e24_serializer_keepNullProperties_true() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("keepNullProperties", true)
+                               .build();
+                       var result = session.serialize(new BeanWithNullField());
+                       assertTrue(result.contains("\"a\""));
+               }
+
+               @Test void e25_serializer_keepNullProperties_false() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               
.property("SerializerSession.keepNullProperties", "false")
+                               .build();
+                       var result = session.serialize(new BeanWithNullField());
+                       assertFalse(result.contains("\"a\""));
+               }
+
+               // -- SerializerSession: sortCollections --
+
+               @Test void e26_serializer_sortCollections() throws Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("sortCollections", true)
+                               .build();
+                       var result = session.serialize(new String[]{"c", "a", 
"b"});
+                       assertEquals("[\"a\",\"b\",\"c\"]", result);
+               }
+
+               // -- SerializerSession: sortMaps --
+
+               @Test void e27_serializer_sortMaps() throws Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("SerializerSession.sortMaps", "true")
+                               .build();
+                       var m = new LinkedHashMap<String,Integer>();
+                       m.put("c", 3);
+                       m.put("a", 1);
+                       m.put("b", 2);
+                       var result = session.serialize(m);
+                       assertEquals("{\"a\":1,\"b\":2,\"c\":3}", result);
+               }
+
+               // -- SerializerSession: trimEmptyCollections --
+
+               @Test void e28_serializer_trimEmptyCollections() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("trimEmptyCollections", true)
+                               .build();
+                       var result = session.serialize(new 
BeanWithEmptyCollection());
+                       assertFalse(result.contains("\"a\""));
+                       assertTrue(result.contains("\"b\""));
+               }
+
+               // -- SerializerSession: trimEmptyMaps --
+
+               @Test void e29_serializer_trimEmptyMaps() throws Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("trimEmptyMaps", true)
+                               .build();
+                       var result = session.serialize(new BeanWithEmptyMap());
+                       assertFalse(result.contains("\"a\""));
+                       assertTrue(result.contains("\"b\""));
+               }
+
+               // -- WriterSerializerSession: maxIndent --
+
+               @Test void e30_writerSerializer_maxIndent_shortForm() throws 
Exception {
+                       var ctx = 
JsonSerializer.create().useWhitespace().build();
+                       var noLimitSession = (WriterSerializerSession) 
ctx.createSession().build();
+                       var limitedSession = (WriterSerializerSession) 
ctx.createSession()
+                               .property("maxIndent", 0)
+                               .build();
+                       // With maxIndent=0, output should have no leading 
spaces on values
+                       var unlimited = 
noLimitSession.serialize(JsonMap.of("a", 1));
+                       var limited = limitedSession.serialize(JsonMap.of("a", 
1));
+                       assertTrue(unlimited.contains("\t"));
+                       assertFalse(limited.contains("\t"));
+               }
+
+               @Test void e31_writerSerializer_maxIndent_qualifiedForm() 
throws Exception {
+                       var ctx = 
JsonSerializer.create().useWhitespace().build();
+                       var session = (WriterSerializerSession) 
ctx.createSession()
+                               .property("WriterSerializerSession.maxIndent", 
"0")
+                               .build();
+                       var result = session.serialize(JsonMap.of("a", 1));
+                       assertFalse(result.contains("\t"));
+               }
+
+               // -- WriterSerializerSession: quoteChar --
+
+               @Test void e32_writerSerializer_quoteChar_shortForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
Json5Serializer.DEFAULT.createSession()
+                               .property("quoteChar", '"')
+                               .build();
+                       var result = session.serialize("hello");
+                       assertEquals("\"hello\"", result);
+               }
+
+               @Test void e33_writerSerializer_quoteChar_qualifiedForm() 
throws Exception {
+                       var session = (WriterSerializerSession) 
Json5Serializer.DEFAULT.createSession()
+                               .property("WriterSerializerSession.quoteChar", 
"\"")
+                               .build();
+                       var result = session.serialize("hello");
+                       assertEquals("\"hello\"", result);
+               }
+
+               // -- CsvSerializerSession: nullValue --
+
+               @Test void e34_csv_nullValue_shortForm() throws Exception {
+                       var session = CsvSerializer.DEFAULT.createSession()
+                               .property("nullValue", "N/A")
+                               .build();
+                       var result = session.serialize(JsonMap.of("a", null));
+                       assertTrue(result.contains("N/A"));
+               }
+
+               @Test void e35_csv_nullValue_qualifiedForm() throws Exception {
+                       var session = CsvSerializer.DEFAULT.createSession()
+                               .property("CsvSerializerSession.nullValue", 
"EMPTY")
+                               .build();
+                       var result = session.serialize(JsonMap.of("a", null));
+                       assertTrue(result.contains("EMPTY"));
+               }
+
+               // -- CsvSerializerSession: byteArrayFormat --
+
+               @Test void e36_csv_byteArrayFormat_shortForm() throws Exception 
{
+                       var session = CsvSerializer.DEFAULT.createSession()
+                               .property("byteArrayFormat", 
ByteArrayFormat.SEMICOLON_DELIMITED)
+                               .build();
+                       var result = session.serialize(new byte[]{1, 2, 3});
+                       assertTrue(result.contains("1;2;3"));
+               }
+
+               // -- ParserSession: trimStrings --
+
+               @Test void e37_parser_trimStrings_shortForm() throws Exception {
+                       var session = JsonParser.DEFAULT.createSession()
+                               .property("trimStrings", true)
+                               .build();
+                       var result = session.parse("\" hello \"", String.class);
+                       assertEquals("hello", result);
+               }
+
+               @Test void e38_parser_trimStrings_qualifiedForm() throws 
Exception {
+                       var session = JsonParser.DEFAULT.createSession()
+                               .property("ParserSession.trimStrings", "true")
+                               .build();
+                       var result = session.parse("\" hello \"", String.class);
+                       assertEquals("hello", result);
+               }
+
+               // -- JsonSerializerSession: escapeSolidus --
+
+               @Test void e39_json_escapeSolidus_shortForm() throws Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               .property("escapeSolidus", true)
+                               .build();
+                       var result = session.serialize("http://example.com";);
+                       assertTrue(result.contains("\\/"));
+               }
+
+               @Test void e40_json_escapeSolidus_qualifiedForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
JsonSerializer.DEFAULT.createSession()
+                               
.property("JsonSerializerSession.escapeSolidus", "true")
+                               .build();
+                       var result = session.serialize("http://example.com";);
+                       assertTrue(result.contains("\\/"));
+               }
+
+               // -- JsonParserSession: validateEnd --
+
+               @Test void e41_json_validateEnd_shortForm() throws Exception {
+                       var session = JsonParser.DEFAULT.createSession()
+                               .property("validateEnd", true)
+                               .build();
+                       // With validateEnd=true, trailing content should throw 
an error
+                       assertThrows(Exception.class, () -> 
session.parse("{\"a\":1}extra", JsonMap.class));
+               }
+
+               // -- UonParserSession: validateEnd --
+
+               @Test void e42_uon_validateEnd_shortForm() throws Exception {
+                       var session = UonParser.DEFAULT.createSession()
+                               .property("validateEnd", true)
+                               .build();
+                       assertThrows(Exception.class, () -> 
session.parse("(a=1)extra", JsonMap.class));
+               }
+
+               // -- UonSerializerSession: encoding --
+
+               @Test void e43_uon_encoding_shortForm() throws Exception {
+                       var session = (WriterSerializerSession) 
UonSerializer.DEFAULT.createSession()
+                               .property("encoding", true)
+                               .build();
+                       var result = session.serialize("hello world");
+                       assertTrue(result.contains("%20") || 
result.contains("+"));
+               }
+
+               // -- UonSerializerSession: paramFormat --
+
+               @Test void e44_uon_paramFormat_shortForm() throws Exception {
+                       var session = (WriterSerializerSession) 
UonSerializer.DEFAULT.createSession()
+                               .property("paramFormat", ParamFormat.PLAINTEXT)
+                               .build();
+                       var result = session.serialize("hello");
+                       assertEquals("hello", result);
+               }
+
+               // -- UrlEncodingSerializerSession: expandedParams --
+
+               @Test void e45_urlEncoding_expandedParams_shortForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
UrlEncodingSerializer.DEFAULT.createSession()
+                               .property("expandedParams", true)
+                               .build();
+                       var result = session.serialize(JsonMap.of("a", new 
int[]{1, 2, 3}));
+                       assertTrue(result.contains("a=1") && 
result.contains("a=2") && result.contains("a=3"));
+               }
+
+               // -- XmlSerializerSession: enableNamespaces --
+
+               @Test void e46_xml_enableNamespaces_shortForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
XmlSerializer.DEFAULT.createSession()
+                               .property("enableNamespaces", false)
+                               .build();
+                       var result = session.serialize(JsonMap.of("a", 1));
+                       assertFalse(result.contains("xmlns"));
+               }
+
+               // -- XmlParserSession: preserveRootElement --
+
+               @Test void e47_xml_preserveRootElement_shortForm() throws 
Exception {
+                       var session = XmlParser.DEFAULT.createSession()
+                               .property("preserveRootElement", true)
+                               .build();
+                       var result = session.parse("<root><a>1</a></root>", 
JsonMap.class);
+                       assertTrue(result.containsKey("root"));
+               }
+
+               // -- HtmlSerializerSession: detectLinksInStrings --
+
+               @Test void e48_html_detectLinksInStrings_shortForm() throws 
Exception {
+                       var session = (WriterSerializerSession) 
HtmlSerializer.DEFAULT.createSession()
+                               .property("detectLinksInStrings", false)
+                               .build();
+                       var result = session.serialize("http://example.com";);
+                       assertFalse(result.contains("<a "));
+               }
+
+               // -- HtmlSerializerSession: labelParameter --
+
+               @Test void e49_html_labelParameter_shortForm() throws Exception 
{
+                       var session = (WriterSerializerSession) 
HtmlSerializer.DEFAULT.createSession()
+                               .property("labelParameter", "customlabel")
+                               .build();
+                       var result = 
session.serialize("http://example.com?customlabel=MyLink";);
+                       assertTrue(result.contains("MyLink"));
+               }
        }
 }
 
diff --git a/todo/session-options-files.zip b/todo/session-options-files.zip
new file mode 100644
index 0000000000..20fc5685f3
Binary files /dev/null and b/todo/session-options-files.zip differ
diff --git a/todo/session-options-plan.md b/todo/session-options-plan.md
new file mode 100644
index 0000000000..6ecc6d98f1
--- /dev/null
+++ b/todo/session-options-plan.md
@@ -0,0 +1,288 @@
+# Session Options & Allowlists Refactoring Plan
+
+This document describes all changes made to implement serializer/parser 
session options, allowlists, `noInherit`, and the separation of request 
attributes from session properties.
+
+---
+
+## Overview
+
+The changes fall into these major categories:
+
+1. **New constants classes** for shared wire names and server-side settings
+2. **New annotation properties** (`allowedSerializerOptions`, 
`allowedParserOptions`, `noInherit`) on `@Rest` and all `@RestOp`-group 
annotations
+3. **Session option allowlists** computed via memoized fields on `RestContext` 
and `RestOpContext`, with inheritance and negation support
+4. **Separation of request attributes from session properties** — 
`defaultRequestAttributes` no longer auto-merged into session properties
+5. **Programmatic session property setters** on `RestRequest`
+6. **REST client wire helpers** for passing session options via headers/query 
params
+7. **Swagger documentation** for session option parameters
+8. **Test updates** to accommodate the new behavior
+9. **Release notes** documenting the changes
+
+---
+
+## 1. New Files (Added)
+
+### 
`juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java`
+Central home for static literals shared across REST modules (server, client, 
mock). Contains:
+- `HEADER_JuneauSerializerOptions` = `"X-Juneau-Serializer-Options"`
+- `HEADER_JuneauParserOptions` = `"X-Juneau-Parser-Options"`
+- `QUERY_juneauSerializerOptions` = `"juneauSerializerOptions"`
+- `QUERY_juneauParserOptions` = `"juneauParserOptions"`
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java`
+Server-side static literals:
+- `PROPERTY_allowedSerializerOptions` = `"allowedSerializerOptions"`
+- `PROPERTY_allowedParserOptions` = `"allowedParserOptions"`
+- `PROPERTY_noInherit` = `"noInherit"`
+- Various `SETTING_sessionOptions_*` fields
+- Various `PROP_*` fields for properties output
+
+### `juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java`
+Tests for `noInherit` behavior:
+- `a01_classNoInherit_skipsParentSerializerAllowlist` — class-level 
`noInherit` blocks parent context
+- `a02_withoutNoInherit_mergesParentSerializerAllowlist` — no `noInherit` 
merges parent+child
+- `a03_methodNoInherit_stillInheritsParentMethodSerializerAllowlist` — method 
`noInherit` does NOT block parent method annotations (only blocks context)
+- `a04_aggregatedNoInherit_includesBothParentAndChild` — aggregated 
`noInherit` for unrelated props doesn't block `allowedSerializerOptions`
+- `a05_methodNoInherit_blocksClassLevelButNotParentMethod` — method 
`noInherit="allowedSerializerOptions"` blocks class-level keys but still 
inherits parent method keys
+
+### `juneau-utest/src/test/java/org/apache/juneau/rest/RestOpContext_Test.java`
+Tests for `RestOpContext` allowlist computation, `noInherit` aggregation, and 
annotation discovery.
+
+### 
`juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java`
+Tests for `RestRequest.setSerializerSessionProperty()`, 
`setParserSessionProperty()`, `setSerializerSessionProperties()`, 
`setParserSessionProperties()`.
+
+---
+
+## 2. Annotation Changes
+
+### New properties on `@Rest` (`Rest.java`, `RestAnnotation.java`):
+- `String[] allowedSerializerOptions() default {};` — CDL of allowed 
serializer session option keys
+- `String[] allowedParserOptions() default {};` — CDL of allowed parser 
session option keys  
+- `String[] noInherit() default {};` — CDL of property names to NOT inherit 
from parent class hierarchy
+
+### New properties on all `@RestOp`-group annotations:
+Same three properties (`allowedSerializerOptions`, `allowedParserOptions`, 
`noInherit`) added to:
+- `RestOp.java` / `RestOpAnnotation.java`
+- `RestGet.java` / `RestGetAnnotation.java`
+- `RestPost.java` / `RestPostAnnotation.java`
+- `RestPut.java` / `RestPutAnnotation.java`
+- `RestPatch.java` / `RestPatchAnnotation.java`
+- `RestDelete.java` / `RestDeleteAnnotation.java`
+- `RestOptions.java` / `RestOptionsAnnotation.java`
+
+Each `*Annotation.java` file also has updated `Builder` with fields, getters, 
setters, and `hashKey()` updates.
+
+Full Javadocs duplicated on each annotation (not cross-referenced to 
`@RestOp`).
+
+---
+
+## 3. Core Library Changes
+
+### 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java`
+- Added `public static final String[] EMPTY_STRING_ARRAY = new String[0];`
+
+### 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java`
+- Added `removeNegations(List<String>)` — removes entries paired with `-entry` 
tokens
+- Added `treeSet(Comparator, Collection)` — creates TreeSet with comparator 
from collection
+- Added `rstream(List)` — reverse stream of a list
+
+### 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java`
+- Added `getStringArray(String name)` — returns `Optional<String[]>` for 
annotation attribute
+- Added `getAnnotatable()` — returns the `Annotatable` where the annotation 
was found
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextSession.java`
+- Added `getSessionProperties()` method returning the session property map
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java`
+- Minor changes for session property support
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/OutputStreamSerializerSession.java`
+- Minor changes for session property support
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/json/JsonSerializerSession.java`
+- Minor changes
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/bson/BsonParserSession.java`
+- Minor changes
+
+### 
`juneau-core/juneau-marshall/src/main/java/org/apache/juneau/objecttools/ObjectRest.java`
+- Minor changes
+
+---
+
+## 4. RestContext Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java`
+
+Major additions:
+- **`allowedSerializerOptionsKeys`** memoizer — computes allowed serializer 
session option keys from `@Rest` annotations:
+  1. Inherits from parent context (router hierarchy) unless `noInherit` blocks 
it
+  2. Pre-pass walks nearest-first to find `noInherit` cut-off point
+  3. Collects tokens in parent-to-child order (reversed) via `rstream()` so 
child negation tokens (`-key`) override parent additions
+  4. Calls `removeNegations()` for deferred negation resolution
+- **`allowedParserOptionsKeys`** memoizer — same pattern for parser options
+- **`getRestAnnotationsForProperty(String name)`** helper — finds cut-off via 
`noInherit`, returns reversed stream
+- **`noInherit`** memoizer — aggregates `noInherit` tokens from nearest `@Rest`
+- **`isInherited(String property)`** — checks if a property is blocked by 
`noInherit` (for parent context inheritance)
+- **`getRestAnnotations()`** — returns all `@Rest` annotations on the class 
hierarchy
+- **`getRestAnnotation()`** — returns the nearest (first) `@Rest` annotation
+- **`resolveCdl(String...)`** — resolves comma-delimited annotation values 
with SVL variable resolution
+- **`reset()`** — clears memoized allowlists and cascades to op contexts
+- Removed `EMPTY_STRING_ARRAY` local constant (now in `StringUtils`)
+- Added `import static` for `StringUtils.EMPTY_STRING_ARRAY`
+
+---
+
+## 5. RestOpContext Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java`
+
+Major additions:
+- **`allowedSerializerOptionsKeys`** memoizer:
+  1. Adds context-level keys from 
`RestContext.getAllowedSerializerOptionsKeys()` if not blocked by aggregated 
`noInherit`
+  2. Always inherits method-level keys from the full method override chain 
(child + parent methods) — `noInherit` does NOT cut off method-chain climbing
+- **`allowedParserOptionsKeys`** memoizer — same pattern
+- **`noInherit`** memoizer — aggregates `noInherit` tokens from ALL 
`@RestOp`-group annotations on the method override chain
+- **`isInherited(String property)`** — checks aggregated `noInherit` for 
context-level inheritance
+- **`getRestOpAnnotations()`** — returns all `@RestOp`-group annotations 
(including `@RestGet`, etc.) on the method and parent methods, child-to-parent 
order, using `AnnotationInfo.isInGroup(RestOp.class)`
+- **`resolveCdl(String...)`** — resolves comma-delimited annotation values
+- **`reset()`** — clears memoized allowlists
+- Removed `EMPTY_STRING_ARRAY` local constant (now in `StringUtils`)
+
+### Inheritance semantics (two types):
+1. **Context inheritance** (from `RestContext`) — controlled by `noInherit`. 
When `noInherit="allowedSerializerOptions"`, blocks values from the `@Rest` 
class annotation
+2. **Method annotation inheritance** (from parent method `@RestOp` 
annotations) — ALWAYS inherited, regardless of `noInherit`
+
+---
+
+## 6. RestRequest Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java`
+
+Major additions:
+- **`setSerializerSessionProperty(String name, Object value)`** — sets a 
single serializer session property
+- **`setParserSessionProperty(String name, Object value)`** — sets a single 
parser session property
+- **`setSerializerSessionProperties(Map<String,Object> values)`** — sets bulk 
serializer session properties
+- **`setParserSessionProperties(Map<String,Object> values)`** — sets bulk 
parser session properties
+- **`getSerializerSessionPropertyMap()`** — computes effective serializer 
session properties by merging:
+  1. Query parameter `juneauSerializerOptions` (UON format)
+  2. Header `X-Juneau-Serializer-Options` (JSON5 format)
+  3. Programmatic `serializerSessionProperties` set via setters
+  - Validates all keys against the allowlist; throws `BadRequest` for 
disallowed keys
+- **`getParserSessionPropertyMap()`** — same for parser options
+- **`isPlainText()`** — checks for `plainText: true` header
+- **Private helpers**: `merge(Map...)`, `badRequest(...)`, `parseUonMap(...)`, 
`parseJsonMap(...)` — all static
+- **Removed**: Request attributes (`getAttributes().asMap()`) are no longer 
merged into session property maps
+
+---
+
+## 7. REST Client Changes
+
+### 
`juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java`
+- Added default session option header/query parameter methods on 
`RestClient.Builder`
+- Uses `RestSharedConstants` wire names
+
+### 
`juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java`
+- Added per-request session option header/query parameter methods
+- Uses `RestSharedConstants` wire names
+
+### 
`juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java`
+- Overrides session option methods for fluent typing
+
+### 
`juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java`
+- Overrides session option methods for fluent typing
+
+---
+
+## 8. Serialized POJO Processor Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java`
+- Now calls `req.getSerializerSessionPropertyMap()` to get session properties 
and passes them to serializer via `.properties()`
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java`
+- Now calls `req.getParserSessionPropertyMap()` to get parser session 
properties
+
+---
+
+## 9. Swagger Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/swagger/BasicSwaggerProviderSession.java`
+- Generated Swagger now includes optional documentation parameters for session 
options:
+  - Headers: `X-Juneau-Serializer-Options`, `X-Juneau-Parser-Options`
+  - Query params: `juneauSerializerOptions`, `juneauParserOptions`
+
+---
+
+## 10. Config Changes
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java`
+- Added `allowedSerializerOptions="binaryFormat"` and 
`allowedParserOptions="binaryFormat"` to `@Rest`
+
+### 
`juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/DefaultConfig.java`
+- Minor changes to allowed header params
+
+---
+
+## 11. Test Updates
+
+### Tests updated to use new programmatic API (separated attributes from 
session properties):
+
+**`Nls_Test.java`**:
+- Removed `defaultRequestAttributes` for session properties from 
`@Rest`/`@RestGet`
+- Methods now accept `RestRequest req` and call 
`req.setSerializerSessionProperty("TestProperty", req.getMessage("key1"))` 
explicitly
+- Removed fully-qualified `org.apache.juneau.rest.RestRequest` references
+
+**`Rest_DefaultRequestAttributes_Test.java`**:
+- Test A (parser): Added `req.setParserSessionProperties(attrs.asMap())` in 
`@RestPreCall` and in REST method `b()`
+- Test B (serializer): Added 
`req.setSerializerSessionProperties(attrs.asMap())` in `@RestPostCall` and in 
REST method `b()`
+
+**`Rest_RVars_Test.java`**:
+- Added `RestRequest req` parameter to method `a()`
+- Added `req.setSerializerSessionProperties(req.getAttributes().asMap())` to 
bridge attributes to session properties
+
+**`RestClient_Headers_Test.java`**:
+- Added `allowedSerializerOptions="simple"` and 
`allowedParserOptions="addBeanTypes"` to `@RestGet` annotation on `headers()` 
method in class `A`
+
+**`ContentComboTestBase.java`**:
+- MsgPack client: added `queryData("juneauSerializerOptions", 
"(binaryFormat=BASE64)")` to send session options
+
+### Annotation test updates:
+- `RestAnnotation_Test.java` — tests for new `allowedSerializerOptions`, 
`allowedParserOptions`, `noInherit` properties
+- `RestOpAnnotation_Test.java` — same
+- `RestGetAnnotation_Test.java` — same
+- `RestPostAnnotation_Test.java` — same
+- `RestPutAnnotation_Test.java` — same
+- `RestDeleteAnnotation_Test.java` — same
+
+### Other test updates:
+- `CollectionUtils_Test.java` — tests for `removeNegations()`, 
`treeSet(Comparator, Collection)`, `rstream()`
+- `AnnotationInfo_Test.java` — tests for `getStringArray()`, `getAnnotatable()`
+- `RestClient_Query_Test.java` — tests for session option query parameters
+
+---
+
+## 12. Release Notes
+
+### `/docs/pages/release-notes/9.2.1.md` (docs repo, untracked)
+Added sections:
+- **Request Attributes and Session Properties Separation** — breaking change, 
new API, migration guidance with code examples
+- **`noInherit` Inheritance Semantics** — clarified class-level vs 
method-level inheritance, with code example showing `noInherit` blocks 
class-level but not parent method-level keys
+
+---
+
+## Key Design Decisions
+
+### `noInherit` semantics:
+1. **On `@Rest`**: Controls two things:
+   - Blocks inheritance from parent context (router hierarchy)
+   - Stops climbing the class hierarchy annotations (the for-loop breaks)
+2. **On `@RestOp`-group annotations**: Only blocks inheritance from 
`RestContext` (class-level); does NOT cut off method override chain climbing — 
parent method annotations are always inherited
+
+### Annotation hierarchy walking in `RestContext`:
+- Pre-pass walks nearest-first to find `noInherit` cut-off
+- Collection pass walks parent-to-child (reversed via `rstream()`) so child 
negation tokens override parent values
+- `removeNegations()` does deferred resolution at the end
+
+### Annotation hierarchy walking in `RestOpContext`:
+- Always iterates ALL method annotations (no cut-off for method chain)
+- `isInherited()` only controls whether to include `RestContext`-level keys

Reply via email to