You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@juneau.apache.org by ja...@apache.org on 2026/04/04 19:39:23 UTC

(juneau) branch master updated: Add support for setting serializer/parser session properties as HTTP headers and query parameters

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 aa8f207c57 Add support for setting serializer/parser session properties as HTTP headers and query parameters
aa8f207c57 is described below

commit aa8f207c5763e4fc62d0c411143c964a8ff18062
Author: James Bognar <ja...@jamesbog-ltmjqf2.internal.salesforce.com>
AuthorDate: Sat Apr 4 15:39:19 2026 -0400

    Add support for setting serializer/parser session properties as HTTP headers and query parameters
---
 .../juneau/commons/reflect/AnnotationInfo.java     |  21 +++
 .../juneau/commons/utils/CollectionUtils.java      |  80 ++++++++
 .../apache/juneau/commons/utils/StringUtils.java   |   3 +
 .../org/apache/juneau/rest/client/RestClient.java  | 133 ++++++++++++++
 .../org/apache/juneau/rest/client/RestRequest.java | 123 +++++++++++++
 .../apache/juneau/rest/RestSharedConstants.java    |  59 ++++++
 .../apache/juneau/rest/mock/MockRestClient.java    |  48 +++++
 .../apache/juneau/rest/mock/MockRestRequest.java   |  48 +++++
 .../java/org/apache/juneau/rest/RestContext.java   | 155 ++++++++++++++++
 .../java/org/apache/juneau/rest/RestOpContext.java | 104 +++++++++++
 .../java/org/apache/juneau/rest/RestRequest.java   | 202 +++++++++++++++++++++
 .../apache/juneau/rest/RestServerConstants.java    |  53 ++++++
 .../org/apache/juneau/rest/annotation/Rest.java    |  56 ++++++
 .../juneau/rest/annotation/RestAnnotation.java     |  57 ++++++
 .../apache/juneau/rest/annotation/RestDelete.java  |  50 +++++
 .../rest/annotation/RestDeleteAnnotation.java      |  57 ++++++
 .../org/apache/juneau/rest/annotation/RestGet.java |  50 +++++
 .../juneau/rest/annotation/RestGetAnnotation.java  |  57 ++++++
 .../org/apache/juneau/rest/annotation/RestOp.java  |  50 +++++
 .../juneau/rest/annotation/RestOpAnnotation.java   |  57 ++++++
 .../apache/juneau/rest/annotation/RestOptions.java |  50 +++++
 .../rest/annotation/RestOptionsAnnotation.java     |  57 ++++++
 .../apache/juneau/rest/annotation/RestPatch.java   |  50 +++++
 .../rest/annotation/RestPatchAnnotation.java       |  57 ++++++
 .../apache/juneau/rest/annotation/RestPost.java    |  50 +++++
 .../juneau/rest/annotation/RestPostAnnotation.java |  57 ++++++
 .../org/apache/juneau/rest/annotation/RestPut.java |  50 +++++
 .../juneau/rest/annotation/RestPutAnnotation.java  |  57 ++++++
 .../juneau/rest/config/BasicUniversalConfig.java   |  41 +++++
 .../juneau/rest/httppart/RequestContent.java       |   2 +-
 .../rest/processor/SerializedPojoProcessor.java    |   2 +-
 .../juneau/commons/utils/CollectionUtils_Test.java |  22 +++
 .../org/apache/juneau/rest/NoInherit_Test.java     | 168 +++++++++++++++++
 .../rest/RestRequest_SessionProperties_Test.java   | 158 ++++++++++++++++
 .../rest/annotation/RestAnnotation_Test.java       |  16 +-
 .../rest/annotation/RestDeleteAnnotation_Test.java |  16 +-
 .../rest/annotation/RestGetAnnotation_Test.java    |  16 +-
 .../rest/annotation/RestOpAnnotation_Test.java     |  16 +-
 .../rest/annotation/RestPostAnnotation_Test.java   |  16 +-
 .../rest/annotation/RestPutAnnotation_Test.java    |  16 +-
 .../rest/client/RestClient_Headers_Test.java       |  10 +-
 .../juneau/rest/client/RestClient_Query_Test.java  |  10 +
 42 files changed, 2385 insertions(+), 15 deletions(-)

diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
index 59e56cd831..2da9f91b57 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/reflect/AnnotationInfo.java
@@ -127,6 +127,27 @@ public class AnnotationInfo<T extends Annotation> {
 		this.toString = memoize(this::findToString);
 	}
 
+	/**
+	 * Returns the annotatable object (class, method, field, etc.) where this annotation was found.
+	 *
+	 * @return The annotatable object where this annotation was found.
+	 */
+	public Annotatable getAnnotatable() {
+		return annotatable;
+	}
+
+	/**
+	 * Returns the annotatable object cast to the specified type.
+	 *
+	 * @param <A> The annotatable type.
+	 * @param type The class to cast to.
+	 * @return The annotatable element cast to the specified type.
+	 * @throws ClassCastException If the annotatable element is not of the specified type.
+	 */
+	public <A extends Annotatable> A getAnnotatable(Class<A> type) {
+		return type.cast(annotatable);
+	}
+
 	/**
 	 * Returns the annotation type of this annotation.
 	 *
diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
index 92d86c2ee6..2de555694a 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/CollectionUtils.java
@@ -129,6 +129,8 @@ import org.apache.juneau.commons.collections.*;
 public class CollectionUtils {
 
 	// Argument name constants for assertArgNotNull
+	private static final String ARG_comparator = "comparator";
+	private static final String ARG_input = "input";
 	private static final String ARG_value = "value";
 	private static final String ARG_array = "array";
 	private static final String ARG_arrays = "arrays";
@@ -869,6 +871,26 @@ public class CollectionUtils {
 		return value;
 	}
 
+	/**
+	 * Returns <jk>true</jk> if the specified collection is null or empty.
+	 *
+	 * @param c The collection to check.
+	 * @return <jk>true</jk> if the specified collection is null or empty.
+	 */
+	public static boolean isEmpty(Collection<?> c) {
+		return c == null || c.isEmpty();
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified map is null or empty.
+	 *
+	 * @param m The map to check.
+	 * @return <jk>true</jk> if the specified map is null or empty.
+	 */
+	public static boolean isEmpty(Map<?,?> m) {
+		return m == null || m.isEmpty();
+	}
+
 	/**
 	 * Returns <jk>true</jk> if the specified array is null or has a length of zero.
 	 *
@@ -2015,6 +2037,64 @@ public class CollectionUtils {
 		return IntStream.range(0, value.size()).mapToObj(i -> value.get(value.size() - 1 - i));
 	}
 
+	/**
+	 * Removes negation tokens from a list.
+	 *
+	 * <p>
+	 * A negation token is a string starting with <js>'-'</js> followed by at least one character.
+	 * When encountered, it removes the first prior occurrence of the corresponding positive token.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bjava'>
+	 * 	List&lt;String&gt; <jv>result</jv> = <jsm>removeNegations</jsm>(list(<js>"a"</js>, <js>"b"</js>, <js>"-a"</js>, <js>"c"</js>));
+	 * 	<jc>// Produces: ["b", "c"]</jc>
+	 * </p>
+	 *
+	 * @param input The input list. Cannot be <jk>null</jk>.
+	 * @return The list with negation tokens applied, or the original list if no negation tokens were present.
+	 */
+	public static List<String> removeNegations(List<String> input) {
+		assertArgNotNull(ARG_input, input);
+		var hasNegation = false;
+		for (var token : input) {
+			if (token != null && token.length() > 1 && token.charAt(0) == '-') {
+				hasNegation = true;
+				break;
+			}
+		}
+		if (!hasNegation)
+			return input;
+		var out = new ArrayList<String>(input.size());
+		for (var token : input) {
+			if (token != null && token.length() > 1 && token.charAt(0) == '-')
+				out.remove(token.substring(1));
+			else
+				out.add(token);
+		}
+		return out;
+	}
+
+	/**
+	 * Creates a {@link TreeSet} with a custom comparator from a collection of elements.
+	 *
+	 * <p>
+	 * Null elements in the collection are silently skipped.
+	 *
+	 * @param <E> The element type.
+	 * @param comparator The comparator to use for ordering. Cannot be <jk>null</jk>.
+	 * @param elements The initial elements. Can be <jk>null</jk> (treated as empty).
+	 * @return A new {@link TreeSet} containing all non-null elements from the collection.
+	 */
+	public static <E> SortedSet<E> treeSet(Comparator<? super E> comparator, Collection<? extends E> elements) {
+		assertArgNotNull(ARG_comparator, comparator);
+		var s = new TreeSet<E>(comparator);
+		if (elements != null)
+			for (var e : elements)
+				if (e != null)
+					s.add(e);
+		return s;
+	}
+
 	/**
 	 * Shortcut for creating a modifiable set out of an array of values.
 	 *
diff --git a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
index b6620512cf..47cd29bde7 100644
--- a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
+++ b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/utils/StringUtils.java
@@ -77,6 +77,9 @@ public class StringUtils {
 	/** Zero-length string constant. */
 	public static final String EMPTY = "";
 
+	/** Zero-length string array constant. */
+	public static final String[] EMPTY_STRING_ARRAY = new String[0];
+
 	/** Characters allowed at the beginning of a numeric literal. */
 	public static final AsciiSet FIRST_NUMBER_CHARS = AsciiSet.of("+-.#0123456789");
 
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index e5a2a49701..4b2b2496a8 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -29,6 +29,7 @@ import static org.apache.juneau.http.HttpHeaders.*;
 import static org.apache.juneau.http.HttpMethod.*;
 import static org.apache.juneau.http.HttpParts.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
 import static org.apache.juneau.rest.client.RestOperation.*;
 
 import java.io.*;
@@ -2552,6 +2553,72 @@ public class RestClient extends BeanContextable implements HttpClient, Closeable
 			return this;
 		}
 
+		/**
+		 * Sets a default request header with serializer session options (JSON5 map) for all requests.
+		 *
+		 * <p>
+		 * This is a shortcut for <c>headersDefault(basicHeader({@link org.apache.juneau.rest.RestSharedConstants#HEADER_JuneauSerializerOptions}, <jv>json5</jv>))</c>.
+		 *
+		 * @param json5
+		 * 	Serializer session options in JSON5 form.
+		 * 	<br>Can be <jk>null</jk> (default is not set by this method).
+		 * @return This object.
+		 */
+		public Builder serializerSessionOptionsHeader(String json5) {
+			if (json5 != null)
+				headersDefault(basicHeader(HEADER_JuneauSerializerOptions, json5));
+			return this;
+		}
+
+		/**
+		 * Sets a default request header with parser session options (JSON5 map) for all requests.
+		 *
+		 * <p>
+		 * This is a shortcut for <c>headersDefault(basicHeader({@link org.apache.juneau.rest.RestSharedConstants#HEADER_JuneauParserOptions}, <jv>json5</jv>))</c>.
+		 *
+		 * @param json5
+		 * 	Parser session options in JSON5 form.
+		 * 	<br>Can be <jk>null</jk> (default is not set by this method).
+		 * @return This object.
+		 */
+		public Builder parserSessionOptionsHeader(String json5) {
+			if (json5 != null)
+				headersDefault(basicHeader(HEADER_JuneauParserOptions, json5));
+			return this;
+		}
+
+		/**
+		 * Convenience for {@link #serializerSessionOptionsHeader(String)} using a map serialized with JSON5.
+		 *
+		 * @param properties
+		 * 	Property names and values for serializer session options.
+		 * 	<br>Can be <jk>null</jk> (treated as an empty map).
+		 * @return This object.
+		 */
+		public Builder serializerSessionOptionsHeader(Map<String,?> properties) {
+			try {
+				return serializerSessionOptionsHeader(isEmpty(properties) ? null : Json5.of(properties));
+			} catch (SerializeException e) {
+				throw rex(e, "Could not serialize serializer session options header");
+			}
+		}
+
+		/**
+		 * Convenience for {@link #parserSessionOptionsHeader(String)} using a map serialized with JSON5.
+		 *
+		 * @param properties
+		 * 	Property names and values for parser session options.
+		 * 	<br>Can be <jk>null</jk> (treated as an empty map).
+		 * @return This object.
+		 */
+		public Builder parserSessionOptionsHeader(Map<String,?> properties) {
+			try {
+				return parserSessionOptionsHeader(isEmpty(properties) ? null : Json5.of(properties));
+			} catch (SerializeException e) {
+				throw rex(e, "Could not serialize parser session options header");
+			}
+		}
+
 		/**
 		 * Convenience method for specifying HTML as the marshalling transmission media type.
 		 *
@@ -4639,6 +4706,72 @@ public class RestClient extends BeanContextable implements HttpClient, Closeable
 			return this;
 		}
 
+		/**
+		 * Sets a default query parameter with serializer session options (UON map) for all requests.
+		 *
+		 * <p>
+		 * This is a shortcut for <c>queryDataDefault(stringPart({@link org.apache.juneau.rest.RestSharedConstants#QUERY_juneauSerializerOptions}, <jv>uon</jv>))</c>.
+		 *
+		 * @param uon
+		 * 	Serializer session options in UON form.
+		 * 	<br>Can be <jk>null</jk> (default is not set by this method).
+		 * @return This object.
+		 */
+		public Builder serializerSessionOptionsQueryDefault(String uon) {
+			if (uon != null)
+				queryDataDefault(stringPart(QUERY_juneauSerializerOptions, uon));
+			return this;
+		}
+
+		/**
+		 * Sets a default query parameter with parser session options (UON map) for all requests.
+		 *
+		 * <p>
+		 * This is a shortcut for <c>queryDataDefault(stringPart({@link org.apache.juneau.rest.RestSharedConstants#QUERY_juneauParserOptions}, <jv>uon</jv>))</c>.
+		 *
+		 * @param uon
+		 * 	Parser session options in UON form.
+		 * 	<br>Can be <jk>null</jk> (default is not set by this method).
+		 * @return This object.
+		 */
+		public Builder parserSessionOptionsQueryDefault(String uon) {
+			if (uon != null)
+				queryDataDefault(stringPart(QUERY_juneauParserOptions, uon));
+			return this;
+		}
+
+		/**
+		 * Convenience for {@link #serializerSessionOptionsQueryDefault(String)} using {@link org.apache.juneau.marshaller.Uon#of(Object)}.
+		 *
+		 * @param properties
+		 * 	Property names and values for serializer session options.
+		 * 	<br>Can be <jk>null</jk> (treated as an empty map).
+		 * @return This object.
+		 */
+		public Builder serializerSessionOptionsQueryDefault(Map<String,?> properties) {
+			try {
+				return serializerSessionOptionsQueryDefault(isEmpty(properties) ? null : Uon.of(properties));
+			} catch (SerializeException e) {
+				throw rex(e, "Could not serialize serializer session options query default");
+			}
+		}
+
+		/**
+		 * Convenience for {@link #parserSessionOptionsQueryDefault(String)} using {@link org.apache.juneau.marshaller.Uon#of(Object)}.
+		 *
+		 * @param properties
+		 * 	Property names and values for parser session options.
+		 * 	<br>Can be <jk>null</jk> (treated as an empty map).
+		 * @return This object.
+		 */
+		public Builder parserSessionOptionsQueryDefault(Map<String,?> properties) {
+			try {
+				return parserSessionOptionsQueryDefault(isEmpty(properties) ? null : Uon.of(properties));
+			} catch (SerializeException e) {
+				throw rex(e, "Could not serialize parser session options query default");
+			}
+		}
+
 		/**
 		 * <i><l>WriterSerializer</l> configuration property:&emsp;</i>  Quote character.
 		 *
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
index f1d5799057..1d5bff26a2 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestRequest.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest.client;
 
 import static org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import org.apache.juneau.rest.RestSharedConstants;
 import static org.apache.juneau.commons.utils.IoUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
@@ -1692,6 +1693,128 @@ public class RestRequest extends BeanSession implements HttpUriRequest, Configur
 		return serializer(PlainTextSerializer.class).parser(PlainTextParser.class);
 	}
 
+	/**
+	 * Sets the {@code X-Juneau-Serializer-Options} header for this request.
+	 *
+	 * <p>
+	 * The value is a JSON5 object whose keys are serializer session property names and values are the overrides.
+	 * Example: <js>"{escapeSolidus:true,maxIndent:4}"</js>.
+	 *
+	 * @param json5
+	 * 	Serializer session options as JSON5. Can be <jk>null</jk> (header is not set).
+	 * @return This object.
+	 */
+	public RestRequest serializerSessionOptionsHeader(String json5) {
+		return header(RestSharedConstants.HEADER_JuneauSerializerOptions, json5);
+	}
+
+	/**
+	 * Sets the {@code X-Juneau-Parser-Options} header for this request.
+	 *
+	 * <p>
+	 * The value is a JSON5 object whose keys are parser session property names and values are the overrides.
+	 *
+	 * @param json5
+	 * 	Parser session options as JSON5. Can be <jk>null</jk> (header is not set).
+	 * @return This object.
+	 */
+	public RestRequest parserSessionOptionsHeader(String json5) {
+		return header(RestSharedConstants.HEADER_JuneauParserOptions, json5);
+	}
+
+	/**
+	 * Convenience for {@link #serializerSessionOptionsHeader(String)} using a map serialized with {@link org.apache.juneau.marshaller.Json5#of(Object)}.
+	 *
+	 * @param properties
+	 * 	Property map for serializer session options. Can be <jk>null</jk> (treated as empty).
+	 * @return This object.
+	 */
+	public RestRequest serializerSessionOptionsHeader(Map<String,?> properties) {
+		if (isEmpty(properties))
+			return this;
+		try {
+			return header(RestSharedConstants.HEADER_JuneauSerializerOptions, org.apache.juneau.marshaller.Json5.of(properties));
+		} catch (org.apache.juneau.serializer.SerializeException e) {
+			throw new RuntimeException("Could not serialize serializer session options header", e);
+		}
+	}
+
+	/**
+	 * Convenience for {@link #parserSessionOptionsHeader(String)} using a map serialized with {@link org.apache.juneau.marshaller.Json5#of(Object)}.
+	 *
+	 * @param properties
+	 * 	Property map for parser session options. Can be <jk>null</jk> (treated as empty).
+	 * @return This object.
+	 */
+	public RestRequest parserSessionOptionsHeader(Map<String,?> properties) {
+		if (isEmpty(properties))
+			return this;
+		try {
+			return header(RestSharedConstants.HEADER_JuneauParserOptions, org.apache.juneau.marshaller.Json5.of(properties));
+		} catch (org.apache.juneau.serializer.SerializeException e) {
+			throw new RuntimeException("Could not serialize parser session options header", e);
+		}
+	}
+
+	/**
+	 * Sets the {@code juneauSerializerOptions} query parameter for this request (UON-encoded map).
+	 *
+	 * <p>
+	 * Example: <js>"(escapeSolidus=true,maxIndent=4)"</js>.
+	 *
+	 * @param uon
+	 * 	Serializer session options in UON form. Can be <jk>null</jk> (parameter is not set).
+	 * @return This object.
+	 */
+	public RestRequest serializerSessionOptionsQuery(String uon) {
+		return queryData(RestSharedConstants.QUERY_juneauSerializerOptions, uon);
+	}
+
+	/**
+	 * Sets the {@code juneauParserOptions} query parameter for this request (UON-encoded map).
+	 *
+	 * @param uon
+	 * 	Parser session options in UON form. Can be <jk>null</jk> (parameter is not set).
+	 * @return This object.
+	 */
+	public RestRequest parserSessionOptionsQuery(String uon) {
+		return queryData(RestSharedConstants.QUERY_juneauParserOptions, uon);
+	}
+
+	/**
+	 * Convenience for {@link #serializerSessionOptionsQuery(String)} using {@link org.apache.juneau.marshaller.Uon#of(Object)}.
+	 *
+	 * @param properties
+	 * 	Property map for serializer session options. Can be <jk>null</jk> (treated as empty).
+	 * @return This object.
+	 */
+	public RestRequest serializerSessionOptionsQuery(Map<String,?> properties) {
+		if (isEmpty(properties))
+			return this;
+		try {
+			return serializerSessionOptionsQuery(org.apache.juneau.marshaller.Uon.of(properties));
+		} catch (org.apache.juneau.serializer.SerializeException e) {
+			throw new RuntimeException("Could not serialize serializer session options query", e);
+		}
+	}
+
+	/**
+	 * Convenience for {@link #parserSessionOptionsQuery(String)} using {@link org.apache.juneau.marshaller.Uon#of(Object)}.
+	 *
+	 * @param properties
+	 * 	Property map for parser session options. Can be <jk>null</jk> (treated as empty).
+	 * @return This object.
+	 */
+	public RestRequest parserSessionOptionsQuery(Map<String,?> properties) {
+		if (isEmpty(properties))
+			return this;
+		try {
+			return parserSessionOptionsQuery(org.apache.juneau.marshaller.Uon.of(properties));
+		} catch (org.apache.juneau.serializer.SerializeException e) {
+			throw new RuntimeException("Could not serialize parser session options query", e);
+		}
+	}
+
 	/**
 	 * Sets the protocol version for this request.
 	 *
diff --git a/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java
new file mode 100644
index 0000000000..c6a7fc1c47
--- /dev/null
+++ b/juneau-rest/juneau-rest-common/src/main/java/org/apache/juneau/rest/RestSharedConstants.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+/**
+ * Static constants shared across Juneau REST modules (for example {@code juneau-rest-server},
+ * {@code juneau-rest-client}, and {@code juneau-rest-mock}) so wire names and other cross-tier
+ * strings stay aligned.
+ *
+ * <p>
+ * Add more {@code public static final} fields here when server and client (or other REST modules)
+ * must agree on the same literal values. Server-only constants belong in
+ * {@code RestServerConstants} in {@code juneau-rest-server}.
+ */
+@SuppressWarnings({
+	"java:S115", // Names use HEADER_/QUERY_ + camelCase to mirror wire identifiers; not strict UPPER_SNAKE_CASE
+})
+public final class RestSharedConstants {
+
+	private RestSharedConstants() {}
+
+	/**
+	 * Serializer session options request header (JSON5 object format).
+	 *
+	 * <p>
+	 * HTTP header names are case-insensitive; use this spelling in OpenAPI and examples.
+	 * Example value: <js>{escapeSolidus:true,maxIndent:4}</js>
+	 */
+	public static final String HEADER_JuneauSerializerOptions = "X-Juneau-Serializer-Options";
+
+	/**
+	 * Parser session options request header (JSON5 object format).
+	 *
+	 * <p>
+	 * HTTP header names are case-insensitive; use this spelling in OpenAPI and examples.
+	 * Example value: <js>{trimStrings:true}</js>
+	 */
+	public static final String HEADER_JuneauParserOptions = "X-Juneau-Parser-Options";
+
+	/** Serializer session options query parameter (UON-encoded map format). Example: <js>(maxIndent=4,sortMaps=true)</js> */
+	public static final String QUERY_juneauSerializerOptions = "juneauSerializerOptions";
+
+	/** Parser session options query parameter (UON-encoded map format). Example: <js>(trimStrings=true)</js> */
+	public static final String QUERY_juneauParserOptions = "juneauParserOptions";
+}
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
index ee3204f257..62dea237a7 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClient.java
@@ -320,6 +320,54 @@ public class MockRestClient extends RestClient implements HttpClientConnection {
 			return this;
 		}
 
+		@Override /* Overridden from Builder */
+		public Builder serializerSessionOptionsHeader(String json5) {
+			super.serializerSessionOptionsHeader(json5);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder serializerSessionOptionsHeader(java.util.Map<String,?> properties) {
+			super.serializerSessionOptionsHeader(properties);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder parserSessionOptionsHeader(String json5) {
+			super.parserSessionOptionsHeader(json5);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder parserSessionOptionsHeader(java.util.Map<String,?> properties) {
+			super.parserSessionOptionsHeader(properties);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder serializerSessionOptionsQueryDefault(String uon) {
+			super.serializerSessionOptionsQueryDefault(uon);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder serializerSessionOptionsQueryDefault(java.util.Map<String,?> properties) {
+			super.serializerSessionOptionsQueryDefault(properties);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder parserSessionOptionsQueryDefault(String uon) {
+			super.parserSessionOptionsQueryDefault(uon);
+			return this;
+		}
+
+		@Override /* Overridden from Builder */
+		public Builder parserSessionOptionsQueryDefault(java.util.Map<String,?> properties) {
+			super.parserSessionOptionsQueryDefault(properties);
+			return this;
+		}
+
 		@Override /* Overridden from Builder */
 		public Builder annotations(Annotation...values) {
 			super.annotations(values);
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
index 118465356f..5f750e067b 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestRequest.java
@@ -806,6 +806,54 @@ public class MockRestRequest extends org.apache.juneau.rest.client.RestRequest {
 		return this;
 	}
 
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest serializerSessionOptionsHeader(String json5) {
+		super.serializerSessionOptionsHeader(json5);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest serializerSessionOptionsHeader(Map<String,?> properties) {
+		super.serializerSessionOptionsHeader(properties);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest serializerSessionOptionsQuery(String uon) {
+		super.serializerSessionOptionsQuery(uon);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest serializerSessionOptionsQuery(Map<String,?> properties) {
+		super.serializerSessionOptionsQuery(properties);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest parserSessionOptionsHeader(String json5) {
+		super.parserSessionOptionsHeader(json5);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest parserSessionOptionsHeader(Map<String,?> properties) {
+		super.parserSessionOptionsHeader(properties);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest parserSessionOptionsQuery(String uon) {
+		super.parserSessionOptionsQuery(uon);
+		return this;
+	}
+
+	@Override /* Overridden from RestRequest */
+	public MockRestRequest parserSessionOptionsQuery(Map<String,?> properties) {
+		super.parserSessionOptionsQuery(properties);
+		return this;
+	}
+
 	@Override /* Overridden from RestRequest */
 	public MockRestRequest protocolVersion(ProtocolVersion version) {
 		super.protocolVersion(version);
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 96640c41a9..c681f7cf71 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -23,6 +23,7 @@ import static org.apache.juneau.commons.utils.AssertionUtils.*;
 import static org.apache.juneau.commons.utils.ClassUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.PredicateUtils.*;
+import static org.apache.juneau.rest.RestServerConstants.*;
 import static org.apache.juneau.commons.utils.IoUtils.*;
 import static org.apache.juneau.commons.utils.StringUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
@@ -49,6 +50,7 @@ import org.apache.juneau.*;
 import org.apache.juneau.bean.swagger.Swagger;
 import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.collections.FluentMap;
+import org.apache.juneau.commons.function.Memoizer;
 import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.commons.logging.Logger;
 import org.apache.juneau.commons.reflect.*;
@@ -5184,6 +5186,159 @@ public class RestContext extends Context {
 		return RestSession.create(this);
 	}
 
+	//---------------------------------------------------------------------------------------------
+	// Memoized allowlist fields
+	//---------------------------------------------------------------------------------------------
+
+	/**
+	 * Memoized value of the {@code noInherit} annotation attribute from the nearest {@code @Rest} annotation.
+	 *
+	 * <p>
+	 * {@code noInherit} itself is never inherited; it only applies to the {@code @Rest} that declares it.
+	 */
+	private final Memoizer<SortedSet<String>> noInherit = memoizer(() ->
+		getRestAnnotation()
+			.map(x -> x.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
+			.map(x -> treeSet(String.CASE_INSENSITIVE_ORDER, resolveCdl(x).toList()))
+			.orElseGet(Collections::emptySortedSet)
+	);
+
+	/**
+	 * Memoized list of every {@link Rest} annotation on the resource class and its supertypes, in child-to-parent order.
+	 */
+	private final Memoizer<List<AnnotationInfo<Rest>>> restAnnotations = memoizer(() ->
+		getAnnotationProvider().find(Rest.class, ClassInfo.of(getResourceClass()))
+	);
+
+	/**
+	 * Memoized parser session-option keys from {@code @Rest(allowedParserOptions)}, after SVL resolution and comma expansion.
+	 *
+	 * <p>
+	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
+	 * Leading-hyphen tokens (e.g. {@code -foo}) remove earlier positive tokens.
+	 */
+	private final Memoizer<SortedSet<String>> allowedParserOptions = memoizer(this::findAllowedParserOptions);
+
+	private SortedSet<String> findAllowedParserOptions() {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedParserOptions;
+		if (isInherited(p) && parentContext != null)
+			l.addAll(parentContext.getAllowedParserOptions());
+		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
+		return Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, removeNegations(l)));
+	}
+
+	/**
+	 * Memoized serializer session-option keys from {@code @Rest(allowedSerializerOptions)}, after SVL resolution and comma expansion.
+	 *
+	 * <p>
+	 * When inheritance is not blocked, keys from {@link #parentContext} are included first, then this resource's own tokens.
+	 * Leading-hyphen tokens remove earlier positive tokens.
+	 */
+	private final Memoizer<SortedSet<String>> allowedSerializerOptions = memoizer(this::findAllowedSerializerOptions);
+
+	private SortedSet<String> findAllowedSerializerOptions() {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedSerializerOptions;
+		if (isInherited(p) && parentContext != null)
+			l.addAll(parentContext.getAllowedSerializerOptions());
+		getRestAnnotationsForProperty(p).forEach(x -> resolveCdl(x.getStringArray(p)).forEach(l::add));
+		return Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, removeNegations(l)));
+	}
+
+	private Stream<AnnotationInfo<Rest>> getRestAnnotationsForProperty(String name) {
+		var annotations = getRestAnnotations();
+		var cutoff = annotations.size();
+		for (var i = 0; i < annotations.size(); i++) {
+			if (resolveCdl(annotations.get(i).getStringArray(PROPERTY_noInherit)).anyMatch(name::equalsIgnoreCase)) {
+				cutoff = i + 1;
+				break;
+			}
+		}
+		return rstream(annotations.subList(0, cutoff));
+	}
+
+	/**
+	 * Returns all {@link Rest} annotations on the resource class hierarchy, in child-to-parent order.
+	 *
+	 * @return An unmodifiable list of {@link AnnotationInfo} for {@link Rest}, never {@code null}.
+	 */
+	protected List<AnnotationInfo<Rest>> getRestAnnotations() {
+		return restAnnotations.get();
+	}
+
+	/**
+	 * Returns the nearest {@link Rest} annotation on this resource.
+	 *
+	 * @return An {@link Optional} containing the first (most-derived) {@link Rest} {@link AnnotationInfo}.
+	 */
+	protected Optional<AnnotationInfo<Rest>> getRestAnnotation() {
+		return getRestAnnotations().stream().findFirst();
+	}
+
+	/**
+	 * Returns {@code true} if values for the given annotation attribute may be inherited from {@link #parentContext}.
+	 *
+	 * <p>
+	 * Inheritance is blocked when the nearest {@code @Rest(noInherit)} lists the property name (case-insensitive).
+	 *
+	 * @param property The annotation attribute name (e.g. {@code "allowedSerializerOptions"}).
+	 * @return {@code true} if parent values should be included.
+	 */
+	protected boolean isInherited(String property) {
+		return RestContext.this.parentContext != null && !noInherit.get().contains(property);
+	}
+
+	/**
+	 * Resolves comma-delimited annotation values with SVL variable substitution.
+	 *
+	 * @param values Raw annotation attribute values.
+	 * @return A stream of trimmed, non-blank tokens.
+	 */
+	private Stream<String> resolveCdl(String...values) {
+		if (values == null || values.length == 0)
+			return Stream.empty();
+		return Arrays.stream(values)
+			.filter(Objects::nonNull)
+			.map(this::resolve)
+			.map(StringUtils::split)
+			.flatMap(Collection::stream)
+			.map(String::trim)
+			.filter(StringUtils::isNotBlank);
+	}
+
+	private Stream<String> resolveCdl(Optional<String[]> values) {
+		return values.isEmpty() ? Stream.empty() : resolveCdl(values.get());
+	}
+
+	/**
+	 * Resolves SVL variables in the given string.
+	 *
+	 * @param s The raw string. Can be {@code null}.
+	 * @return The resolved string.
+	 */
+	protected String resolve(String s) {
+		return getVarResolver().resolve(s);
+	}
+
+	/**
+	 * Returns the parser session-option keys allowed for this resource.
+	 *
+	 * @return An unmodifiable case-insensitive sorted set, never {@code null}.
+	 */
+	public SortedSet<String> getAllowedParserOptions() {
+		return allowedParserOptions.get();
+	}
+
+	/**
+	 * Returns the serializer session-option keys allowed for this resource.
+	 *
+	 * @return An unmodifiable case-insensitive sorted set, never {@code null}.
+	 */
+	public SortedSet<String> getAllowedSerializerOptions() {
+		return allowedSerializerOptions.get();
+	}
+
 	/**
 	 * Called during servlet destruction to invoke all {@link RestDestroy} methods.
 	 */
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
index 2c0fabf8bb..5ba6e9b103 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestOpContext.java
@@ -22,6 +22,7 @@ import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.commons.utils.StringUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
 import static org.apache.juneau.commons.utils.Utils.*;
+import static org.apache.juneau.rest.RestServerConstants.*;
 import static org.apache.juneau.http.HttpHeaders.*;
 import static org.apache.juneau.http.HttpParts.*;
 import static org.apache.juneau.httppart.HttpPartType.*;
@@ -32,12 +33,14 @@ import java.lang.reflect.Method;
 import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.*;
+import java.util.stream.*;
 import java.util.function.*;
 import org.apache.http.*;
 import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.collections.FluentMap;
+import org.apache.juneau.commons.function.Memoizer;
 import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.utils.*;
@@ -2179,6 +2182,107 @@ public class RestOpContext extends Context implements Comparable<RestOpContext>
 	protected final String httpMethod;
 	protected final UrlPathMatcher[] pathMatchers;
 
+	//-----------------------------------------------------------------------------------------------------------------
+	// Memoized allowlist fields
+	//-----------------------------------------------------------------------------------------------------------------
+
+	/** Memoized all {@link RestOp}-group annotations on this method, child-to-parent order. */
+	private final Memoizer<List<AnnotationInfo<?>>> restOpAnnotations = memoizer(this::findRestOpAnnotations);
+
+	private List<AnnotationInfo<?>> findRestOpAnnotations() {
+		return context.getAnnotationProvider().find(mi, SELF, MATCHING_METHODS).stream()
+			.filter(ai -> ai.isInGroup(RestOp.class))
+			.toList();
+	}
+
+	/** Memoized aggregated {@code noInherit} keys from all RestOp-group annotations on this operation. */
+	private final Memoizer<SortedSet<String>> noInheritOp = memoizer(this::findNoInheritOp);
+
+	private SortedSet<String> findNoInheritOp() {
+		var l = getRestOpAnnotations().stream()
+			.map(ai -> ai.getStringArray("noInherit").orElse(StringUtils.EMPTY_STRING_ARRAY))
+			.flatMap(arr -> resolveCdl(arr))
+			.toList();
+		return Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, l));
+	}
+
+	/** Memoized effective allowed parser option keys for this operation. */
+	private final Memoizer<SortedSet<String>> allowedParserOptions = memoizer(this::findAllowedParserOptions);
+
+	private SortedSet<String> findAllowedParserOptions() {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedParserOptions;
+		if (isInherited(p))
+			l.addAll(context.getAllowedParserOptions());
+		getRestOpAnnotations().stream()
+			.flatMap(ai -> resolveCdl(ai.getStringArray(p).orElse(new String[0])))
+			.forEach(l::add);
+		return Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, removeNegations(l)));
+	}
+
+	/** Memoized effective allowed serializer option keys for this operation. */
+	private final Memoizer<SortedSet<String>> allowedSerializerOptions = memoizer(this::findAllowedSerializerOptions);
+
+	private SortedSet<String> findAllowedSerializerOptions() {
+		var l = new ArrayList<String>();
+		var p = PROPERTY_allowedSerializerOptions;
+		if (isInherited(p))
+			l.addAll(context.getAllowedSerializerOptions());
+		getRestOpAnnotations().stream()
+			.flatMap(ai -> resolveCdl(ai.getStringArray(p).orElse(new String[0])))
+			.forEach(l::add);
+		return Collections.unmodifiableSortedSet(treeSet(String.CASE_INSENSITIVE_ORDER, removeNegations(l)));
+	}
+
+	private Stream<String> resolveCdl(String...values) {
+		if (values == null || values.length == 0)
+			return Stream.empty();
+		return Arrays.stream(values)
+			.filter(Objects::nonNull)
+			.map(s -> RestOpContext.this.context.getVarResolver().resolve(s))
+			.map(StringUtils::split)
+			.flatMap(Collection::stream)
+			.map(String::trim)
+			.filter(StringUtils::isNotBlank);
+	}
+
+	/**
+	 * Returns all {@link RestOp}-group annotations on this operation method, in child-to-parent order.
+	 *
+	 * @return An unmodifiable list, never {@code null}.
+	 */
+	public List<AnnotationInfo<?>> getRestOpAnnotations() {
+		return restOpAnnotations.get();
+	}
+
+	/**
+	 * Returns {@code true} if context-level values for the given property should be merged.
+	 *
+	 * @param property The annotation attribute name.
+	 * @return {@code true} if {@code noInherit} does not contain this property.
+	 */
+	protected boolean isInherited(String property) {
+		return !noInheritOp.get().contains(property);
+	}
+
+	/**
+	 * Returns the parser session-option keys allowed for this operation.
+	 *
+	 * @return An unmodifiable case-insensitive sorted set, never {@code null}.
+	 */
+	public SortedSet<String> getAllowedParserOptions() {
+		return allowedParserOptions.get();
+	}
+
+	/**
+	 * Returns the serializer session-option keys allowed for this operation.
+	 *
+	 * @return An unmodifiable case-insensitive sorted set, never {@code null}.
+	 */
+	public SortedSet<String> getAllowedSerializerOptions() {
+		return allowedSerializerOptions.get();
+	}
+
 	/**
 	 * Context constructor.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 16feb2c4d5..05a9011442 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -18,6 +18,7 @@ package org.apache.juneau.rest;
 
 import static java.util.Optional.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
 import static org.apache.juneau.commons.utils.IoUtils.*;
 import static org.apache.juneau.commons.utils.StringUtils.*;
 import static org.apache.juneau.commons.utils.ThrowableUtils.*;
@@ -31,6 +32,8 @@ import java.net.*;
 import java.nio.charset.*;
 import java.text.*;
 import java.util.*;
+import java.util.function.*;
+import java.util.stream.*;
 
 import org.apache.http.*;
 import org.apache.http.message.*;
@@ -57,6 +60,9 @@ import org.apache.juneau.rest.staticfile.*;
 import org.apache.juneau.rest.swagger.*;
 import org.apache.juneau.rest.util.*;
 import org.apache.juneau.svl.*;
+import org.apache.juneau.collections.*;
+import org.apache.juneau.marshaller.*;
+import org.apache.juneau.parser.ParseException;
 import org.apache.juneau.uon.*;
 
 import jakarta.servlet.*;
@@ -212,6 +218,8 @@ public class RestRequest extends HttpServletRequestWrapper {
 	private Swagger swagger;
 
 	private Charset charset;
+	private Map<String,Object> serializerSessionProperties;
+	private Map<String,Object> parserSessionProperties;
 
 	/**
 	 * Constructor.
@@ -1427,6 +1435,200 @@ public class RestRequest extends HttpServletRequestWrapper {
 		attrs.set(name, value);
 	}
 
+	/**
+	 * Sets a serializer session property programmatically.
+	 *
+	 * <p>
+	 * Session properties override context-level properties and can be used to customize serializer behavior for this request only.
+	 *
+	 * @param name The property name (e.g. {@code "escapeSolidus"}).
+	 * @param value The property value.
+	 * @return This object.
+	 */
+	public RestRequest setSerializerSessionProperty(String name, Object value) {
+		if (serializerSessionProperties == null)
+			serializerSessionProperties = new LinkedHashMap<>();
+		serializerSessionProperties.put(name, value);
+		return this;
+	}
+
+	/**
+	 * Sets a parser session property programmatically.
+	 *
+	 * <p>
+	 * Session properties override context-level properties and can be used to customize parser behavior for this request only.
+	 *
+	 * @param name The property name (e.g. {@code "trimStrings"}).
+	 * @param value The property value.
+	 * @return This object.
+	 */
+	public RestRequest setParserSessionProperty(String name, Object value) {
+		if (parserSessionProperties == null)
+			parserSessionProperties = new LinkedHashMap<>();
+		parserSessionProperties.put(name, value);
+		return this;
+	}
+
+	/**
+	 * Sets multiple serializer session properties programmatically.
+	 *
+	 * @param values The properties to set. Can be {@code null}.
+	 * @return This object.
+	 */
+	public RestRequest setSerializerSessionProperties(Map<String,Object> values) {
+		if (values != null && !values.isEmpty()) {
+			if (serializerSessionProperties == null)
+				serializerSessionProperties = new LinkedHashMap<>();
+			serializerSessionProperties.putAll(values);
+		}
+		return this;
+	}
+
+	/**
+	 * Sets multiple parser session properties programmatically.
+	 *
+	 * @param values The properties to set. Can be {@code null}.
+	 * @return This object.
+	 */
+	public RestRequest setParserSessionProperties(Map<String,Object> values) {
+		if (values != null && !values.isEmpty()) {
+			if (parserSessionProperties == null)
+				parserSessionProperties = new LinkedHashMap<>();
+			parserSessionProperties.putAll(values);
+		}
+		return this;
+	}
+
+	/**
+	 * Returns the merged serializer session property map for this request.
+	 *
+	 * <p>
+	 * Merges (in order of increasing priority):
+	 * <ol>
+	 * 	<li>Request attributes (from {@link #getAttributes()}, e.g. {@code defaultRequestAttributes})
+	 * 	<li>UON-encoded {@code juneauSerializerOptions} query parameter
+	 * 	<li>JSON5-encoded {@code X-Juneau-Serializer-Options} header
+	 * 	<li>Programmatically-set properties via {@link #setSerializerSessionProperty}
+	 * </ol>
+	 *
+	 * <p>
+	 * Client-supplied keys are validated against the allowlist from {@link RestOpContext#getAllowedSerializerOptions()}.
+	 * Keys not in the allowlist cause a {@code 400 Bad Request} response.
+	 *
+	 * @return An unmodifiable map of session properties; may be empty but never {@code null}.
+	 */
+	@SuppressWarnings({
+		"java:S3776" // cognitive complexity acceptable; sequential null-checks for 4 sources
+	})
+	public Map<String,Object> getSerializerSessionPropertyMap() {
+		var allowlist = opContext.getAllowedSerializerOptions();
+		Map<String,Object> m1 = null, m2 = null;
+
+		var q = getQueryParams().get(QUERY_juneauSerializerOptions).asString().orElse(null);
+		if (q != null && !q.isBlank()) {
+			m1 = parseUonMap(q, e -> badRequest(e, "Could not parse UON session options from query parameter ''{0}''.", QUERY_juneauSerializerOptions));
+			var invalid = m1.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
+			if (!invalid.isEmpty())
+				badRequest("Invalid session options in query parameter ''{0}'': ''{1}''", QUERY_juneauSerializerOptions, invalid);
+		}
+
+		var h = getHeaderParam(HEADER_JuneauSerializerOptions).asString().orElse(null);
+		if (h != null && !h.isBlank()) {
+			m2 = parseJsonMap(h, e -> badRequest(e, "Could not parse JSON5 session options from header ''{0}''.", HEADER_JuneauSerializerOptions));
+			var invalid = m2.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
+			if (!invalid.isEmpty())
+				badRequest("Invalid session options in header ''{0}'': ''{1}''", HEADER_JuneauSerializerOptions, invalid);
+		}
+
+		return mergeSessionMaps(getAttributes().asMap(), m1, m2, serializerSessionProperties);
+	}
+
+	/**
+	 * Returns the merged parser session property map for this request.
+	 *
+	 * <p>
+	 * Merges (in order of increasing priority):
+	 * <ol>
+	 * 	<li>Request attributes (from {@link #getAttributes()}, e.g. {@code defaultRequestAttributes})
+	 * 	<li>UON-encoded {@code juneauParserOptions} query parameter
+	 * 	<li>JSON5-encoded {@code X-Juneau-Parser-Options} header
+	 * 	<li>Programmatically-set properties via {@link #setParserSessionProperty}
+	 * </ol>
+	 *
+	 * <p>
+	 * Client-supplied keys are validated against the allowlist from {@link RestOpContext#getAllowedParserOptions()}.
+	 * Keys not in the allowlist cause a {@code 400 Bad Request} response.
+	 *
+	 * @return An unmodifiable map of session properties; may be empty but never {@code null}.
+	 */
+	@SuppressWarnings({
+		"java:S3776" // cognitive complexity acceptable; sequential null-checks for 4 sources
+	})
+	public Map<String,Object> getParserSessionPropertyMap() {
+		var allowlist = opContext.getAllowedParserOptions();
+		Map<String,Object> m1 = null, m2 = null;
+
+		var q = getQueryParams().get(QUERY_juneauParserOptions).asString().orElse(null);
+		if (q != null && !q.isBlank()) {
+			m1 = parseUonMap(q, e -> badRequest(e, "Could not parse UON session options from query parameter ''{0}''.", QUERY_juneauParserOptions));
+			var invalid = m1.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
+			if (!invalid.isEmpty())
+				badRequest("Invalid session options in query parameter ''{0}'': ''{1}''", QUERY_juneauParserOptions, invalid);
+		}
+
+		var h = getHeaderParam(HEADER_JuneauParserOptions).asString().orElse(null);
+		if (h != null && !h.isBlank()) {
+			m2 = parseJsonMap(h, e -> badRequest(e, "Could not parse JSON5 session options from header ''{0}''.", HEADER_JuneauParserOptions));
+			var invalid = m2.keySet().stream().filter(k -> !allowlist.contains(k)).collect(Collectors.joining(","));
+			if (!invalid.isEmpty())
+				badRequest("Invalid session options in header ''{0}'': ''{1}''", HEADER_JuneauParserOptions, invalid);
+		}
+
+		return mergeSessionMaps(getAttributes().asMap(), m1, m2, parserSessionProperties);
+	}
+
+	@SafeVarargs
+	private static Map<String,Object> mergeSessionMaps(Map<String,Object>...maps) {
+		Map<String,Object> result = null;
+		for (var map : maps) {
+			if (map != null && !map.isEmpty()) {
+				if (result == null)
+					result = new LinkedHashMap<>(map);
+				else
+					result.putAll(map);
+			}
+		}
+		return result != null ? Collections.unmodifiableMap(result) : Collections.emptyMap();
+	}
+
+	private static void badRequest(String msg, Object...args) {
+		throw new BadRequest(msg, args);
+	}
+
+	private static void badRequest(Exception causedBy, String msg, Object...args) {
+		throw new BadRequest(causedBy, msg, args);
+	}
+
+	private static Map<String,Object> parseUonMap(String input, Consumer<ParseException> onError) {
+		try {
+			var m = Uon.DEFAULT.read(input, JsonMap.class);
+			return m != null ? m : Collections.emptyMap();
+		} catch (ParseException e) {
+			onError.accept(e);
+			return Collections.emptyMap();
+		}
+	}
+
+	private static Map<String,Object> parseJsonMap(String input, Consumer<ParseException> onError) {
+		try {
+			var m = Json5.DEFAULT.read(input, JsonMap.class);
+			return m != null ? m : Collections.emptyMap();
+		} catch (ParseException e) {
+			onError.accept(e);
+			return Collections.emptyMap();
+		}
+	}
+
 	/**
 	 * Sets the charset to expect on the request content.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java
new file mode 100644
index 0000000000..bd9d96afa6
--- /dev/null
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestServerConstants.java
@@ -0,0 +1,53 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+/**
+ * Static literals for {@code juneau-rest-server}: annotation attribute name constants used for
+ * allowlist inheritance checks and other module-internal logic.
+ *
+ * <p>
+ * HTTP wire names shared with clients belong on {@link RestSharedConstants} instead.
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ * 	<li class='jc'>{@link RestSharedConstants}
+ * </ul>
+ */
+@SuppressWarnings({
+	"java:S115", // PROPERTY_ + camelCase property name mirrors annotation attribute name; not strict UPPER_SNAKE_CASE
+})
+public final class RestServerConstants {
+
+	private RestServerConstants() {}
+
+	/**
+	 * The {@code "allowedParserOptions"} annotation attribute name — used in {@code noInherit} matching.
+	 *
+	 * @see org.apache.juneau.rest.annotation.Rest#allowedParserOptions()
+	 */
+	public static final String PROPERTY_allowedParserOptions = "allowedParserOptions";
+
+	/**
+	 * The {@code "allowedSerializerOptions"} annotation attribute name — used in {@code noInherit} matching.
+	 *
+	 * @see org.apache.juneau.rest.annotation.Rest#allowedSerializerOptions()
+	 */
+	public static final String PROPERTY_allowedSerializerOptions = "allowedSerializerOptions";
+
+	/** The {@code "noInherit"} annotation attribute name. */
+	public static final String PROPERTY_noInherit = "noInherit";
+}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
index 8b5419ce2a..7368140180 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/Rest.java
@@ -287,6 +287,62 @@ public @interface Rest {
 	 */
 	String[] consumes() default {};
 
+	/**
+	 * Allowed serializer session option keys for this resource (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys (e.g. <js>"escapeSolidus,maxIndent"</js>) that clients may
+	 * send via the <js>"X-Juneau-Serializer-Options"</js> header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Keys not in the effective allowlist cause a {@code 400 Bad Request} response.
+	 *
+	 * <p>
+	 * Entries are merged in application order. A leading hyphen removes a previously added key: <js>"-escapeSolidus"</js>.
+	 * Method-level {@link org.apache.juneau.rest.annotation.RestGet#allowedSerializerOptions()} values are always merged on top.
+	 * Use {@link #noInherit()} to prevent inheriting less-derived contributions.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this resource (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter. Parser options are ignored for operations without
+	 * a request-body parser. Keys not in the effective allowlist cause a {@code 400 Bad Request} response.
+	 *
+	 * <p>
+	 * Use {@link #noInherit()} to prevent inheriting less-derived contributions.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Each entry is SVL-resolved then comma-split. Prevents the named property from inheriting values from
+	 * parent {@code @Rest} annotations (router hierarchy). The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Class-level response converters.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
index 9040454277..bacf7e3f99 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestAnnotation.java
@@ -108,6 +108,9 @@ public class RestAnnotation {
 		private String uriContext = "";
 		private String uriRelativity = "";
 		private String uriResolution = "";
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] consumes = {};
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
@@ -245,6 +248,39 @@ public class RestAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link Rest#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link Rest#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link Rest#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link Rest#converters()} property on this annotation.
 		 *
@@ -802,6 +838,9 @@ public class RestAnnotation {
 		private final String uriContext;
 		private final String uriRelativity;
 		private final String uriResolution;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] consumes;
 		private final String[] produces;
 		private final String[] defaultRequestAttributes;
@@ -825,6 +864,9 @@ public class RestAnnotation {
 			children = copyOf(b.children);
 			clientVersionHeader = b.clientVersionHeader;
 			config = b.config;
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			consumes = copyOf(b.consumes);
 			converters = copyOf(b.converters);
 			debug = b.debug;
@@ -907,6 +949,21 @@ public class RestAnnotation {
 			return config;
 		}
 
+		@Override /* Overridden from Rest */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from Rest */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from Rest */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from Rest */
 		public String[] consumes() {
 			return consumes;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
index e279038bec..35d886bd34 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDelete.java
@@ -452,6 +452,56 @@ public @interface RestDelete {
 	 */
 	String[] path() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
index 7dffbb0001..fa9e2b6d60 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation.java
@@ -77,6 +77,9 @@ public class RestDeleteAnnotation {
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 
 		/**
@@ -241,6 +244,39 @@ public class RestDeleteAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestDelete#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestDelete#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestDelete#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestDelete#roleGuard()} property on this annotation.
 		 *
@@ -374,6 +410,9 @@ public class RestDeleteAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 
 		Object(RestDeleteAnnotation.Builder b) {
@@ -390,6 +429,9 @@ public class RestDeleteAnnotation {
 			encoders = copyOf(b.encoders);
 			guards = copyOf(b.guards);
 			matchers = copyOf(b.matchers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			roleGuard = b.roleGuard;
 			rolesDeclared = b.rolesDeclared;
@@ -453,6 +495,21 @@ public class RestDeleteAnnotation {
 			return matchers;
 		}
 
+		@Override /* Overridden from RestDelete */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestDelete */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestDelete */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestDelete */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
index 3e7fd50769..7ec04ef758 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGet.java
@@ -493,6 +493,56 @@ public @interface RestGet {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
index 7ec55bf2cc..b3ab32cded 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestGetAnnotation.java
@@ -81,6 +81,9 @@ public class RestGetAnnotation {
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 		private String[] produces = {};
 
@@ -269,6 +272,39 @@ public class RestGetAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestGet#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestGet#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestGet#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestGet#roleGuard()} property on this annotation.
 		 *
@@ -419,6 +455,9 @@ public class RestGetAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 		private final String[] produces;
 
@@ -437,6 +476,9 @@ public class RestGetAnnotation {
 			encoders = copyOf(b.encoders);
 			guards = copyOf(b.guards);
 			matchers = copyOf(b.matchers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
 			roleGuard = b.roleGuard;
@@ -507,6 +549,21 @@ public class RestGetAnnotation {
 			return matchers;
 		}
 
+		@Override /* Overridden from RestGet */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestGet */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestGet */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestGet */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
index 3d1b0d0fa6..83f64572bf 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOp.java
@@ -726,6 +726,56 @@ public @interface RestOp {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
index 1ebfe22ae4..b6ee11ddf8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOpAnnotation.java
@@ -92,6 +92,9 @@ public class RestOpAnnotation {
 		private String[] defaultResponseHeaders = {};
 		private String[] path = {};
 		private String[] produces = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] allowedParserOptions = {};
+		private String[] noInherit = {};
 
 		/**
 		 * Constructor.
@@ -344,6 +347,39 @@ public class RestOpAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestOp#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestOp#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestOp#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestOp#roleGuard()} property on this annotation.
 		 *
@@ -517,6 +553,9 @@ public class RestOpAnnotation {
 		private final String[] defaultResponseHeaders;
 		private final String[] path;
 		private final String[] produces;
+		private final String[] allowedSerializerOptions;
+		private final String[] allowedParserOptions;
+		private final String[] noInherit;
 
 		Object(RestOpAnnotation.Builder b) {
 			super(b);
@@ -541,6 +580,9 @@ public class RestOpAnnotation {
 			parsers = copyOf(b.parsers);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			noInherit = copyOf(b.noInherit);
 			roleGuard = b.roleGuard;
 			rolesDeclared = b.rolesDeclared;
 			serializers = copyOf(b.serializers);
@@ -649,6 +691,21 @@ public class RestOpAnnotation {
 			return produces;
 		}
 
+		@Override /* Overridden from RestOp */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestOp */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestOp */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestOp */
 		public String roleGuard() {
 			return roleGuard;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
index 789c500487..f6746461a8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptions.java
@@ -493,6 +493,56 @@ public @interface RestOptions {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
index a66d111333..3df2f33cbd 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestOptionsAnnotation.java
@@ -81,6 +81,9 @@ public class RestOptionsAnnotation {
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 		private String[] produces = {};
 
@@ -269,6 +272,39 @@ public class RestOptionsAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestOptions#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestOptions#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestOptions#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestOptions#roleGuard()} property on this annotation.
 		 *
@@ -419,6 +455,9 @@ public class RestOptionsAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 		private final String[] produces;
 
@@ -437,6 +476,9 @@ public class RestOptionsAnnotation {
 			encoders = copyOf(b.encoders);
 			guards = copyOf(b.guards);
 			matchers = copyOf(b.matchers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
 			roleGuard = b.roleGuard;
@@ -507,6 +549,21 @@ public class RestOptionsAnnotation {
 			return matchers;
 		}
 
+		@Override /* Overridden from RestOptions */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestOptions */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestOptions */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestOptions */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
index 1d3e45ec71..d2c325dd6c 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatch.java
@@ -649,6 +649,56 @@ public @interface RestPatch {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
index 00b1d1f060..a444822bc8 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPatchAnnotation.java
@@ -86,6 +86,9 @@ public class RestPatchAnnotation {
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
 		private String[] description = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 		private String[] produces = {};
 
@@ -329,6 +332,39 @@ public class RestPatchAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestPatch#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPatch#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPatch#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestPatch#roleGuard()} property on this annotation.
 		 *
@@ -489,6 +525,9 @@ public class RestPatchAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 		private final String[] produces;
 
@@ -512,6 +551,9 @@ public class RestPatchAnnotation {
 			matchers = copyOf(b.matchers);
 			maxInput = b.maxInput;
 			parsers = copyOf(b.parsers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
 			roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPatchAnnotation {
 			return parsers;
 		}
 
+		@Override /* Overridden from RestPatch */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestPatch */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestPatch */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestPatch */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
index e4189c1910..2e40b26efc 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPost.java
@@ -649,6 +649,56 @@ public @interface RestPost {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
index 0623c6e7bf..b415dbcf6b 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPostAnnotation.java
@@ -86,6 +86,9 @@ public class RestPostAnnotation {
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 		private String[] produces = {};
 
@@ -329,6 +332,39 @@ public class RestPostAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestPost#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPost#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPost#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestPost#roleGuard()} property on this annotation.
 		 *
@@ -489,6 +525,9 @@ public class RestPostAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 		private final String[] produces;
 
@@ -512,6 +551,9 @@ public class RestPostAnnotation {
 			matchers = copyOf(b.matchers);
 			maxInput = b.maxInput;
 			parsers = copyOf(b.parsers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
 			roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPostAnnotation {
 			return parsers;
 		}
 
+		@Override /* Overridden from RestPost */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestPost */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestPost */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestPost */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
index 5ab32819a5..c34f2713e0 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPut.java
@@ -649,6 +649,56 @@ public @interface RestPut {
 	 */
 	String[] produces() default {};
 
+	/**
+	 * Allowed serializer session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Serializer-Options"</js>
+	 * header or <js>"juneauSerializerOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedSerializerOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedSerializerOptions() default {};
+
+	/**
+	 * Allowed parser session option keys for this operation (ordered merge, prefix {@code -key} removes a key).
+	 *
+	 * <p>
+	 * Comma-delimited list of session property keys that clients may send via the <js>"X-Juneau-Parser-Options"</js>
+	 * header or <js>"juneauParserOptions"</js> query parameter.
+	 * Merged on top of resource-level {@link Rest#allowedParserOptions()} values.
+	 * Use {@link #noInherit()} to replace rather than extend the resource-level list.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions#safe-properties">Session Options - Safe Properties</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] allowedParserOptions() default {};
+
+	/**
+	 * Property names for which less-derived contributions are NOT inherited.
+	 *
+	 * <p>
+	 * Accepted values: {@code "allowedSerializerOptions"}, {@code "allowedParserOptions"}.
+	 * Prevents the named property from inheriting values from the enclosing {@code @Rest} annotation.
+	 * The {@code noInherit} attribute itself is never inherited.
+	 *
+	 * <h5 class='section'>See Also:</h5><ul>
+	 * 	<li class='link'><a class="doclink" href="https://juneau.apache.org/docs/topics/SessionOptions">Session Options</a>
+	 * </ul>
+	 *
+	 * @return The annotation value.
+	 */
+	String[] noInherit() default {};
+
 	/**
 	 * Role guard.
 	 *
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
index dbaff9eb03..c6b41c7b58 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/annotation/RestPutAnnotation.java
@@ -86,6 +86,9 @@ public class RestPutAnnotation {
 		private String[] defaultRequestAttributes = {};
 		private String[] defaultRequestHeaders = {};
 		private String[] defaultResponseHeaders = {};
+		private String[] allowedParserOptions = {};
+		private String[] allowedSerializerOptions = {};
+		private String[] noInherit = {};
 		private String[] path = {};
 		private String[] produces = {};
 
@@ -329,6 +332,39 @@ public class RestPutAnnotation {
 			return this;
 		}
 
+		/**
+		 * Sets the {@link RestPut#allowedSerializerOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedSerializerOptions(String...value) {
+			allowedSerializerOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPut#allowedParserOptions()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder allowedParserOptions(String...value) {
+			allowedParserOptions = value;
+			return this;
+		}
+
+		/**
+		 * Sets the {@link RestPut#noInherit()} property on this annotation.
+		 *
+		 * @param value The new value for this property.
+		 * @return This object.
+		 */
+		public Builder noInherit(String...value) {
+			noInherit = value;
+			return this;
+		}
+
 		/**
 		 * Sets the {@link RestPut#roleGuard()} property on this annotation.
 		 *
@@ -489,6 +525,9 @@ public class RestPutAnnotation {
 		private final String[] defaultRequestAttributes;
 		private final String[] defaultRequestHeaders;
 		private final String[] defaultResponseHeaders;
+		private final String[] allowedParserOptions;
+		private final String[] allowedSerializerOptions;
+		private final String[] noInherit;
 		private final String[] path;
 		private final String[] produces;
 
@@ -512,6 +551,9 @@ public class RestPutAnnotation {
 			matchers = copyOf(b.matchers);
 			maxInput = b.maxInput;
 			parsers = copyOf(b.parsers);
+			allowedParserOptions = copyOf(b.allowedParserOptions);
+			allowedSerializerOptions = copyOf(b.allowedSerializerOptions);
+			noInherit = copyOf(b.noInherit);
 			path = copyOf(b.path);
 			produces = copyOf(b.produces);
 			roleGuard = b.roleGuard;
@@ -607,6 +649,21 @@ public class RestPutAnnotation {
 			return parsers;
 		}
 
+		@Override /* Overridden from RestPut */
+		public String[] allowedParserOptions() {
+			return allowedParserOptions;
+		}
+
+		@Override /* Overridden from RestPut */
+		public String[] allowedSerializerOptions() {
+			return allowedSerializerOptions;
+		}
+
+		@Override /* Overridden from RestPut */
+		public String[] noInherit() {
+			return noInherit;
+		}
+
 		@Override /* Overridden from RestPut */
 		public String[] path() {
 			return path;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
index 5a98c49109..8b97435d97 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/config/BasicUniversalConfig.java
@@ -196,6 +196,47 @@ import org.apache.juneau.xml.*;
 		ParquetParser.class,
 		ProtoParser.class,
 		MarkdownParser.class
+	},
+
+	// Serializer session properties that clients are allowed to override via HTTP headers/query parameters.
+	allowedSerializerOptions={
+		"useWhitespace",          // WriterSerializer - enable/disable whitespace
+		"maxIndent",              // WriterSerializer - max indentation level
+		"quoteChar",              // WriterSerializer - quote character
+		"keepNullProperties",     // SerializerSession - include null properties
+		"trimStrings",            // SerializerSession - trim string values
+		"addBeanTypes",           // SerializerSession - add bean type annotations
+		"addRootType",            // SerializerSession - add root type annotation
+		"sortCollections",        // SerializerSession - sort collections
+		"sortMaps",               // SerializerSession - sort maps
+		"trimEmptyCollections",   // SerializerSession - trim empty collections
+		"trimEmptyMaps",          // SerializerSession - trim empty maps
+		"binaryFormat",           // OutputStreamSerializer - binary encoding format
+		"escapeSolidus",          // JsonSerializerSession - escape forward slashes
+		"encoding",               // UonSerializerSession - URL-encode characters
+		"paramFormat",            // UonSerializerSession - parameter format style
+		"addNamespaceUrisToRoot", // XmlSerializerSession - add namespace URIs to root
+		"autoDetectNamespaces",   // XmlSerializerSession - auto-detect namespaces
+		"enableNamespaces",       // XmlSerializerSession - enable namespace support
+		"textNodeDelimiter",      // XmlSerializerSession - text node delimiter
+		"addKeyValueTableHeaders",// HtmlSerializerSession - add key/value table headers
+		"detectLabelParameters",  // HtmlSerializerSession - detect label parameters
+		"detectLinksInStrings",   // HtmlSerializerSession - detect links in strings
+		"labelParameter",         // HtmlSerializerSession - label parameter name
+		"uriAnchorText",          // HtmlSerializerSession - URI anchor text strategy
+		"byteArrayFormat",        // CsvSerializerSession - byte array encoding format
+		"allowNestedStructures"   // CsvSerializerSession - allow nested structures
+	},
+
+	// Parser session properties that clients are allowed to override via HTTP headers/query parameters.
+	allowedParserOptions={
+		"trimStrings",            // ParserSession - trim string values
+		"binaryFormat",           // InputStreamParser - binary decoding format
+		"validateEnd",            // JsonParserSession/UonParserSession - validate end of input
+		"decoding",               // UonParserSession - URL-decode characters
+		"expandedParams",         // UrlEncodingParserSession - expanded parameters
+		"preserveRootElement",    // XmlParserSession - preserve root element
+		"validating"              // XmlParserSession - validate XML
 	}
 )
 public interface BasicUniversalConfig extends DefaultConfig, DefaultHtmlConfig {}
\ No newline at end of file
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
index 2b95e4c0e0..1de14c5d2f 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/httppart/RequestContent.java
@@ -551,7 +551,7 @@ public class RequestContent {
 			// @formatter:off
 			var session = p
 				.createSession()
-				.properties(req.getAttributes().asMap())
+				.properties(req.getParserSessionPropertyMap())
 				.javaMethod(req.getOpContext().getJavaMethod())
 				.locale(locale)
 				.timeZone(timeZone.orElse(null))
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
index a00bf9a02f..e10b0eb419 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/processor/SerializedPojoProcessor.java
@@ -72,7 +72,7 @@ public class SerializedPojoProcessor implements ResponseProcessor {
 				// @formatter:off
 				SerializerSession session = s
 					.createSession()
-					.properties(req.getAttributes().asMap())
+					.properties(req.getSerializerSessionPropertyMap())
 					.javaMethod(req.getOpContext().getJavaMethod())
 					.locale(req.getLocale())
 					.timeZone(req.getTimeZone().orElse(null))
diff --git a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
index b3e1234657..45c999ae3f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/commons/utils/CollectionUtils_Test.java
@@ -1659,4 +1659,26 @@ class CollectionUtils_Test extends TestBase {
 		assertEquals(1, intSet.first());
 		assertEquals(3, intSet.last());
 	}
+
+	//====================================================================================================
+	// isEmpty(Collection<?>)
+	//====================================================================================================
+	@Test
+	void a096_isEmpty_collection() {
+		assertTrue(isEmpty((java.util.Collection<?>)null));
+		assertTrue(isEmpty(list()));
+		assertFalse(isEmpty(list("a")));
+		assertFalse(isEmpty(list("a", "b")));
+	}
+
+	//====================================================================================================
+	// isEmpty(Map<?,?>)
+	//====================================================================================================
+	@Test
+	void a097_isEmpty_map() {
+		assertTrue(isEmpty((java.util.Map<?,?>)null));
+		assertTrue(isEmpty(map()));
+		assertFalse(isEmpty(map("k", "v")));
+		assertFalse(isEmpty(map("k1", "v1", "k2", "v2")));
+	}
 }
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
new file mode 100644
index 0000000000..1aa0c2d4f8
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/NoInherit_Test.java
@@ -0,0 +1,168 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.TestBase;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.servlet.BasicRestObject;
+import org.junit.jupiter.api.*;
+
+class NoInherit_Test extends TestBase {
+
+	private static RestContext restContext(Class<? extends BasicRestObject> c) throws Exception {
+		var o = c.getDeclaredConstructor().newInstance();
+		
+		// Check if class has a parent REST resource (not BasicRestObject)
+		RestContext parentContext = null;
+		var superClass = c.getSuperclass();
+		if (superClass != null && superClass != BasicRestObject.class && BasicRestObject.class.isAssignableFrom(superClass)) {
+			@SuppressWarnings("unchecked")
+			var parentClass = (Class<? extends BasicRestObject>) superClass;
+			parentContext = restContext(parentClass);
+		}
+		
+		return RestContext.create(c, parentContext, null).init(() -> o).build().postInit().postInitChildFirst();
+	}
+
+	@Rest(allowedSerializerOptions = "parentSer")
+	public static class ParentSer extends BasicRestObject {}
+
+	@Rest(allowedSerializerOptions = "childSer", noInherit = "allowedSerializerOptions")
+	public static class ChildSer extends ParentSer {}
+
+	@Test
+	void a01_classNoInherit_skipsParentSerializerAllowlist() throws Exception {
+		var ctx = restContext(ChildSer.class);
+		var keys = ctx.getAllowedSerializerOptions();
+
+		assertTrue(keys.contains("childSer"));
+		assertFalse(keys.contains("parentSer"));
+	}
+
+	@Rest(allowedSerializerOptions = "pBoth")
+	public static class ParentBoth extends BasicRestObject {}
+
+	@Rest(allowedSerializerOptions = "cBoth")
+	public static class ChildBoth extends ParentBoth {}
+
+	@Test
+	void a02_withoutNoInherit_mergesParentSerializerAllowlist() throws Exception {
+		var ctx = restContext(ChildBoth.class);
+		var keys = ctx.getAllowedSerializerOptions();
+
+		assertTrue(keys.contains("pBoth"));
+		assertTrue(keys.contains("cBoth"));
+	}
+
+	public static class ParentM extends BasicRestObject {
+		@RestGet(allowedSerializerOptions = "parentM")
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	public static class ChildM extends ParentM {
+		@RestGet(allowedSerializerOptions = "childM", noInherit = "allowedSerializerOptions")
+		@Override
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	@Test
+	void a03_methodNoInherit_stillInheritsParentMethodSerializerAllowlist() throws Exception {
+		var ctx = restContext(ChildM.class);
+		var op = ctx.getRestOperations().getOpContexts().stream()
+			.filter(o -> ChildM.class.equals(o.getJavaMethod().getDeclaringClass()) && "get".equals(o.getJavaMethod().getName()))
+			.findFirst()
+			.orElseThrow();
+		var keys = op.getAllowedSerializerOptions();
+
+		// Method annotations ALWAYS inherit from parent methods, regardless of noInherit
+		// noInherit only blocks inheritance from the REST class context
+		assertTrue(keys.contains("childM"));
+		assertTrue(keys.contains("parentM"));
+	}
+	
+	// Test case for aggregated noInherit: parent has noInherit="prop1", child has noInherit="prop2"
+	// The aggregated noInherit should be {"prop1", "prop2"}, but allowedSerializerOptions should
+	// include values from both parent and child since neither blocks allowedSerializerOptions
+	public static class ParentAggregated extends BasicRestObject {
+		@RestGet(allowedSerializerOptions = "parentOpt", noInherit = "prop1")
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	public static class ChildAggregated extends ParentAggregated {
+		@RestGet(allowedSerializerOptions = "childOpt", noInherit = "prop2")
+		@Override
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	@Test
+	void a04_aggregatedNoInherit_includesBothParentAndChild() throws Exception {
+		var ctx = restContext(ChildAggregated.class);
+		var op = ctx.getRestOperations().getOpContexts().stream()
+			.filter(o -> ChildAggregated.class.equals(o.getJavaMethod().getDeclaringClass()) && "get".equals(o.getJavaMethod().getName()))
+			.findFirst()
+			.orElseThrow();
+		var keys = op.getAllowedSerializerOptions();
+
+		// Both parent and child options should be present since neither has allowedSerializerOptions in noInherit
+		assertTrue(keys.contains("childOpt"));
+		assertTrue(keys.contains("parentOpt"));
+	}
+
+	// Test that noInherit blocks REST class context inheritance but NOT parent method inheritance
+	@Rest(allowedSerializerOptions = "classLevel")
+	public static class ParentWithClassLevel extends BasicRestObject {
+		@RestGet(allowedSerializerOptions = "parentMethod")
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	public static class ChildBlocksClassInheritance extends ParentWithClassLevel {
+		@RestGet(allowedSerializerOptions = "childMethod", noInherit = "allowedSerializerOptions")
+		@Override
+		public void get() {
+			// Intentionally empty - method only used for annotation metadata testing
+		}
+	}
+
+	@Test
+	void a05_methodNoInherit_blocksClassLevelButNotParentMethod() throws Exception {
+		var ctx = restContext(ChildBlocksClassInheritance.class);
+		var op = ctx.getRestOperations().getOpContexts().stream()
+			.filter(o -> ChildBlocksClassInheritance.class.equals(o.getJavaMethod().getDeclaringClass()) && "get".equals(o.getJavaMethod().getName()))
+			.findFirst()
+			.orElseThrow();
+		var keys = op.getAllowedSerializerOptions();
+
+		// Should include method-level keys from both child and parent methods
+		assertTrue(keys.contains("childMethod"));
+		assertTrue(keys.contains("parentMethod"));
+		
+		// Should NOT include class-level key because of noInherit
+		assertFalse(keys.contains("classLevel"));
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java
new file mode 100644
index 0000000000..4fcc05674d
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/RestRequest_SessionProperties_Test.java
@@ -0,0 +1,158 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau.rest;
+
+import java.util.*;
+
+import org.apache.juneau.TestBase;
+import org.apache.juneau.json.*;
+import org.apache.juneau.rest.annotation.*;
+import org.apache.juneau.rest.mock.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Tests for {@link RestRequest} session property methods.
+ */
+class RestRequest_SessionProperties_Test extends TestBase {
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Basic setter tests
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class A {
+		@RestGet(path="/test")
+		public String test(RestRequest req) {
+			req.setSerializerSessionProperty("key1", "value1");
+			var map = req.getSerializerSessionPropertyMap();
+			return map.get("key1") != null ? "ok" : "fail";
+		}
+	}
+
+	@Test
+	void a01_setSerializerSessionProperty_single() throws Exception {
+		MockRestClient.create(A.class).plainText().build()
+			.get("/test")
+			.run()
+			.assertContent("ok");
+	}
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class B {
+		@RestPost(path="/test")
+		public String test(RestRequest req) {
+			req.setParserSessionProperty("key1", "value1");
+			var map = req.getParserSessionPropertyMap();
+			return map.get("key1") != null ? "ok" : "fail";
+		}
+	}
+
+	@Test
+	void a02_setParserSessionProperty_single() throws Exception {
+		MockRestClient.create(B.class).plainText().build()
+			.post("/test", "")
+			.run()
+			.assertContent("ok");
+	}
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class C {
+		@RestGet(path="/test")
+		public String test(RestRequest req) {
+			var props = Map.<String,Object>of("key1", "value1", "key2", "value2");
+			req.setSerializerSessionProperties(props);
+			var map = req.getSerializerSessionPropertyMap();
+			return map.get("key1") != null && map.get("key2") != null ? "ok" : "fail";
+		}
+	}
+
+	@Test
+	void a03_setSerializerSessionProperties_bulk() throws Exception {
+		MockRestClient.create(C.class).plainText().build()
+			.get("/test")
+			.run()
+			.assertContent("ok");
+	}
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class D {
+		@RestPost(path="/test")
+		public String test(RestRequest req) {
+			var props = Map.<String,Object>of("key1", "value1", "key2", "value2");
+			req.setParserSessionProperties(props);
+			var map = req.getParserSessionPropertyMap();
+			return map.get("key1") != null && map.get("key2") != null ? "ok" : "fail";
+		}
+	}
+
+	@Test
+	void a04_setParserSessionProperties_bulk() throws Exception {
+		MockRestClient.create(D.class).plainText().build()
+			.post("/test", "")
+			.run()
+			.assertContent("ok");
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Method chaining
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class E {
+		@RestGet(path="/test")
+		public String test(RestRequest req) {
+			req.setSerializerSessionProperty("key1", "value1")
+				.setSerializerSessionProperty("key2", "value2");
+			var map = req.getSerializerSessionPropertyMap();
+			return map.get("key1") != null && map.get("key2") != null ? "ok" : "fail";
+		}
+	}
+
+	@Test
+	void b01_methodChaining_serializer() throws Exception {
+		MockRestClient.create(E.class).plainText().build()
+			.get("/test")
+			.run()
+			.assertContent("ok");
+	}
+
+	//------------------------------------------------------------------------------------------------------------------
+	// Integration with HTTP options (query/header) and programmatic override
+	//------------------------------------------------------------------------------------------------------------------
+
+	@Rest(serializers=JsonSerializer.class, parsers=JsonParser.class)
+	public static class G {
+		@RestGet(path="/test", allowedSerializerOptions="useWhitespace,customKey", allowedParserOptions="strict,customKey")
+		public String test(RestRequest req) {
+			req.setSerializerSessionProperty("customKey", "customValue");
+			req.setParserSessionProperty("customKey", "customValue");
+			
+			var smap = req.getSerializerSessionPropertyMap();
+			var pmap = req.getParserSessionPropertyMap();
+			
+			return "s:" + smap.get("customKey") + ",p:" + pmap.get("customKey");
+		}
+	}
+
+	@Test
+	void c01_integration_programmatic_override() throws Exception {
+		MockRestClient.create(G.class).plainText().build()
+			.get("/test")
+			.run()
+			.assertContent("s:customValue,p:customValue");
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
index afc4ef9fd1..e4eefba02a 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestAnnotation_Test.java
@@ -49,6 +49,9 @@ class RestAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	Rest a1 = RestAnnotation.create()
+		.allowedParserOptions("e1")
+		.allowedSerializerOptions("e2")
+		.noInherit("e3")
 		.disableContentParam("a")
 		.allowedHeaderParams("b")
 		.allowedMethodHeaders("c")
@@ -100,6 +103,9 @@ class RestAnnotation_Test extends TestBase {
 		.build();
 
 	Rest a2 = RestAnnotation.create()
+		.allowedParserOptions("e1")
+		.allowedSerializerOptions("e2")
+		.noInherit("e3")
 		.disableContentParam("a")
 		.allowedHeaderParams("b")
 		.allowedMethodHeaders("c")
@@ -152,8 +158,8 @@ class RestAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,beanStore,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,on,onClass,parsers,partParser,partSerializer,path,produces,renderResponseStackTraces,responseProcessors,restChildrenClass,restOpArgs,rol [...]
-			"b,c,d,BasicBeanStore,CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[s],[RestAnnotation_Test],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],RestChildren,[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
+			"allowedHeaderParams,allowedMethodHeaders,allowedMethodParams,allowedParserOptions,allowedSerializerOptions,beanStore,callLogger,children,clientVersionHeader,config,consumes,converters,debug,debugEnablement,debugOn,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestHeaders,defaultResponseHeaders,description,disableContentParam,encoders,guards,maxInput,messages,noInherit,on,onClass,parsers,partParser,partSerializer,path,produces,renderResponseStackTr [...]
+			"b,c,d,[e1],[e2],BasicBeanStore,CallLogger,[RestAnnotation_Test],e,f,[g],[RestConverter],h,DebugEnablement,i,j,k,l,[m],[n],[o],[p],a,[Encoder],[RestGuard],q,r,[e3],[s],[RestAnnotation_Test],[Parser],HttpPartParser,HttpPartSerializer,t,[u],v,[ResponseProcessor],RestChildren,[RestOpArg],w,x,[Serializer],y,StaticFiles,{{[],,,},[],{[],},{[],,},[],[],[],[],},BasicSwaggerProvider,[z],aa,bb,cc,dd");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -198,6 +204,9 @@ class RestAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	@Rest(
+		allowedParserOptions="e1",
+		allowedSerializerOptions="e2",
+		noInherit="e3",
 		disableContentParam="a",
 		allowedHeaderParams="b",
 		allowedMethodHeaders="c",
@@ -251,6 +260,9 @@ class RestAnnotation_Test extends TestBase {
 	Rest d1 = D1.class.getAnnotationsByType(Rest.class)[0];
 
 	@Rest(
+		allowedParserOptions="e1",
+		allowedSerializerOptions="e2",
+		noInherit="e3",
 		disableContentParam="a",
 		allowedHeaderParams="b",
 		allowedMethodHeaders="c",
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
index c8d9f9e4d6..41b07ac84d 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestDeleteAnnotation_Test.java
@@ -38,6 +38,9 @@ class RestDeleteAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	RestDelete a1 = RestDeleteAnnotation.create()
+		.allowedParserOptions("p1")
+		.allowedSerializerOptions("p2")
+		.noInherit("p3")
 		.clientVersion("a")
 		.debug("b")
 		.defaultAccept("c")
@@ -60,6 +63,9 @@ class RestDeleteAnnotation_Test extends TestBase {
 		.build();
 
 	RestDelete a2 = RestDeleteAnnotation.create()
+		.allowedParserOptions("p1")
+		.allowedSerializerOptions("p2")
+		.noInherit("p3")
 		.clientVersion("a")
 		.debug("b")
 		.defaultAccept("c")
@@ -83,8 +89,8 @@ class RestDeleteAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"clientVersion,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,on,path,roleGuard,rolesDeclared,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-			"a,b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[j],[k],l,m,n,{[],,[],{[],},,[],[],[],[],[],[],[]},o");
+			"allowedParserOptions,allowedSerializerOptions,clientVersion,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,noInherit,on,path,roleGuard,rolesDeclared,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
+			"[p1],[p2],a,b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[p3],[j],[k],l,m,n,{[],,[],{[],},,[],[],[],[],[],[],[]},o");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -129,6 +135,9 @@ class RestDeleteAnnotation_Test extends TestBase {
 	public interface D1 {
 
 		@RestDelete(
+			allowedParserOptions="p1",
+			allowedSerializerOptions="p2",
+			noInherit="p3",
 			clientVersion="a",
 			debug="b",
 			defaultAccept="c",
@@ -152,6 +161,9 @@ class RestDeleteAnnotation_Test extends TestBase {
 		void m1();
 
 		@RestDelete(
+			allowedParserOptions="p1",
+			allowedSerializerOptions="p2",
+			noInherit="p3",
 			clientVersion="a",
 			debug="b",
 			defaultAccept="c",
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
index 39007815c3..98fc623d72 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestGetAnnotation_Test.java
@@ -40,6 +40,9 @@ class RestGetAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	RestGet a1 = RestGetAnnotation.create()
+		.allowedParserOptions("q1")
+		.allowedSerializerOptions("q2")
+		.noInherit("q3")
 		.clientVersion("a")
 		.converters(RestConverter.class)
 		.debug("b")
@@ -65,6 +68,9 @@ class RestGetAnnotation_Test extends TestBase {
 		.build();
 
 	RestGet a2 = RestGetAnnotation.create()
+		.allowedParserOptions("q1")
+		.allowedSerializerOptions("q2")
+		.noInherit("q3")
 		.clientVersion("a")
 		.converters(RestConverter.class)
 		.debug("b")
@@ -91,8 +97,8 @@ class RestGetAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"clientVersion,converters,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,on,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-			"a,[RestConverter],b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[j],[k],[l],m,n,[Serializer],o,{[],,[],{[],},,[],[],[],[],[],[],[]},p");
+			"allowedParserOptions,allowedSerializerOptions,clientVersion,converters,debug,defaultAccept,defaultCharset,defaultRequestAttributes,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,noInherit,on,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
+			"[q1],[q2],a,[RestConverter],b,c,d,[f],[g],[e],[h],[i],[Encoder],[RestGuard],[RestMatcher],[q3],[j],[k],[l],m,n,[Serializer],o,{[],,[],{[],},,[],[],[],[],[],[],[]},p");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -137,6 +143,9 @@ class RestGetAnnotation_Test extends TestBase {
 	public interface D1 {
 
 		@RestGet(
+			allowedParserOptions="q1",
+			allowedSerializerOptions="q2",
+			noInherit="q3",
 			clientVersion="a",
 			converters=RestConverter.class,
 			debug="b",
@@ -163,6 +172,9 @@ class RestGetAnnotation_Test extends TestBase {
 		void m1();
 
 		@RestGet(
+			allowedParserOptions="q1",
+			allowedSerializerOptions="q2",
+			noInherit="q3",
 			clientVersion="a",
 			converters=RestConverter.class,
 			debug="b",
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
index e44ed91c05..2fec081b9b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestOpAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestOpAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	RestOp a1 = RestOpAnnotation.create()
+		.allowedParserOptions("v1")
+		.allowedSerializerOptions("v2")
+		.noInherit("v3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -72,6 +75,9 @@ class RestOpAnnotation_Test extends TestBase {
 		.build();
 
 	RestOp a2 = RestOpAnnotation.create()
+		.allowedParserOptions("v1")
+		.allowedSerializerOptions("v2")
+		.noInherit("v3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -104,8 +110,8 @@ class RestOpAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-			"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[o],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
+			"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,method,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,res [...]
+			"[v1],[v2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,n,[v3],[o],[Parser],[p],[q],r,s,[Serializer],t,{[],,[],{[],},,[],[],[],[],[],[],[]},u");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -150,6 +156,9 @@ class RestOpAnnotation_Test extends TestBase {
 	public interface D1 {
 
 		@RestOp(
+			allowedParserOptions="v1",
+			allowedSerializerOptions="v2",
+			noInherit="v3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
@@ -182,6 +191,9 @@ class RestOpAnnotation_Test extends TestBase {
 		void m1();
 
 		@RestOp(
+			allowedParserOptions="v1",
+			allowedSerializerOptions="v2",
+			noInherit="v3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
index 8ed6188fd7..54b27b2e05 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPostAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestPostAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	RestPost a1 = RestPostAnnotation.create()
+		.allowedParserOptions("u1")
+		.allowedSerializerOptions("u2")
+		.noInherit("u3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -71,6 +74,9 @@ class RestPostAnnotation_Test extends TestBase {
 		.build();
 
 	RestPost a2 = RestPostAnnotation.create()
+		.allowedParserOptions("u1")
+		.allowedSerializerOptions("u2")
+		.noInherit("u3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -102,8 +108,8 @@ class RestPostAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-			"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
+			"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses, [...]
+			"[u1],[u2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[u3],[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -148,6 +154,9 @@ class RestPostAnnotation_Test extends TestBase {
 	public interface D1 {
 
 		@RestPost(
+			allowedParserOptions="u1",
+			allowedSerializerOptions="u2",
+			noInherit="u3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
@@ -179,6 +188,9 @@ class RestPostAnnotation_Test extends TestBase {
 		void m1();
 
 		@RestPost(
+			allowedParserOptions="u1",
+			allowedSerializerOptions="u2",
+			noInherit="u3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
index 97bafdd1a2..b6f1a433b2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/annotation/RestPutAnnotation_Test.java
@@ -41,6 +41,9 @@ class RestPutAnnotation_Test extends TestBase {
 	//------------------------------------------------------------------------------------------------------------------
 
 	RestPut a1 = RestPutAnnotation.create()
+		.allowedParserOptions("u1")
+		.allowedSerializerOptions("u2")
+		.noInherit("u3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -71,6 +74,9 @@ class RestPutAnnotation_Test extends TestBase {
 		.build();
 
 	RestPut a2 = RestPutAnnotation.create()
+		.allowedParserOptions("u1")
+		.allowedSerializerOptions("u2")
+		.noInherit("u3")
 		.clientVersion("a")
 		.consumes("b")
 		.converters(RestConverter.class)
@@ -102,8 +108,8 @@ class RestPutAnnotation_Test extends TestBase {
 
 	@Test void a01_basic() {
 		assertBean(a1,
-			"clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses,schemes,summary,tags,value},value",
-			"a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
+			"allowedParserOptions,allowedSerializerOptions,clientVersion,consumes,converters,debug,defaultAccept,defaultCharset,defaultContentType,defaultRequestAttributes,defaultRequestFormData,defaultRequestHeaders,defaultRequestQueryData,defaultResponseHeaders,description,encoders,guards,matchers,maxInput,noInherit,on,parsers,path,produces,roleGuard,rolesDeclared,serializers,summary,swagger{consumes,deprecated,description,externalDocs{description,url},operationId,parameters,produces,responses, [...]
+			"[u1],[u2],a,[b],[RestConverter],c,d,e,f,[i],[g],[j],[h],[k],[l],[Encoder],[RestGuard],[RestMatcher],m,[u3],[n],[Parser],[o],[p],q,r,[Serializer],s,{[],,[],{[],},,[],[],[],[],[],[],[]},t");
 	}
 
 	@Test void a02_testEquivalency() {
@@ -148,6 +154,9 @@ class RestPutAnnotation_Test extends TestBase {
 	public interface D1 {
 
 		@RestPut(
+			allowedParserOptions="u1",
+			allowedSerializerOptions="u2",
+			noInherit="u3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
@@ -179,6 +188,9 @@ class RestPutAnnotation_Test extends TestBase {
 		void m1();
 
 		@RestPut(
+			allowedParserOptions="u1",
+			allowedSerializerOptions="u2",
+			noInherit="u3",
 			clientVersion="a",
 			consumes="b",
 			converters=RestConverter.class,
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
index d8280b9edf..99e8caed29 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Headers_Test.java
@@ -22,8 +22,10 @@ import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.http.HttpHeaders.*;
 import static org.apache.juneau.httppart.HttpPartSchema.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
 
 import java.time.*;
+import java.util.Map;
 
 import org.apache.juneau.*;
 import org.apache.juneau.http.header.*;
@@ -69,7 +71,7 @@ public class RestClient_Headers_Test extends TestBase {
 
 	@Rest(callLogger=CaptureLogger.class)
 	public static class A extends BasicRestObject {
-		@RestGet
+		@RestGet(allowedSerializerOptions="simple", allowedParserOptions="addBeanTypes")
 		public String[] headers(org.apache.juneau.rest.RestRequest req) {
 			return req.getHeaders().getAll(req.getHeaderParam("Check").orElse(null)).stream().map(RequestHeader::getValue).toArray(String[]::new);
 		}
@@ -82,6 +84,12 @@ public class RestClient_Headers_Test extends TestBase {
 	// Method tests
 	//------------------------------------------------------------------------------------------------------------------
 
+	@Test void a00_sessionOptionHeaders() throws Exception {
+		checkFooClient().serializerSessionOptionsHeader("{simple:true}").build().get("/headers").header("Check", HEADER_JuneauSerializerOptions).run().assertContent("[\"{simple:true}\"]");
+		checkFooClient().parserSessionOptionsHeader("{addBeanTypes:false}").build().get("/headers").header("Check", HEADER_JuneauParserOptions).run().assertContent("[\"{addBeanTypes:false}\"]");
+		checkFooClient().build().get("/headers").serializerSessionOptionsHeader(Map.of("simple", true)).header("Check", HEADER_JuneauSerializerOptions).run().assertContent().asString().isContains("simple");
+	}
+
 	@Test void a01_header_String_Object() throws Exception {
 		checkFooClient().header("Foo","bar").build().get("/headers").run().assertContent("[\"bar\"]");
 		checkFooClient().build().get("/headers").header("Foo","baz").run().assertContent("[\"baz\"]");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
index a8b5fc2964..ef3de9484b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Query_Test.java
@@ -20,6 +20,7 @@ import static org.apache.juneau.TestUtils.*;
 import static org.apache.juneau.commons.utils.CollectionUtils.*;
 import static org.apache.juneau.http.HttpParts.*;
 import static org.apache.juneau.httppart.HttpPartSchema.*;
+import static org.apache.juneau.rest.RestSharedConstants.*;
 
 import java.io.*;
 
@@ -50,6 +51,15 @@ class RestClient_Query_Test extends TestBase {
 	// Method tests
 	//------------------------------------------------------------------------------------------------------------------
 
+	@Test void a00_sessionOptionQuery() throws Exception {
+		client().serializerSessionOptionsQueryDefault("(simple=true)").build().get("/query").run().assertContent().asString().asUrlDecode()
+			.isContains(QUERY_juneauSerializerOptions + "=(simple=true)");
+		client().parserSessionOptionsQueryDefault("(strict=true)").build().get("/query").run().assertContent().asString().asUrlDecode()
+			.isContains(QUERY_juneauParserOptions + "=(strict=true)");
+		client().build().get("/query").serializerSessionOptionsQuery("(a=1)").run().assertContent().asString().asUrlDecode()
+			.isContains(QUERY_juneauSerializerOptions + "=(a=1)");
+	}
+
 	@Test void a01_query_String_Object() throws Exception {
 		client().queryData("foo","bar").queryData(part("foo",new StringBuilder("baz"),null)).build().get("/query").run().assertContent("foo=bar&foo=baz");
 		client().build().get("/query").queryData("foo","bar").run().assertContent().isContains("foo=bar");