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

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

commit e32f1a586793de88bcfd82b6085f18228c886065
Author: James Bognar <[email protected]>
AuthorDate: Thu Jul 23 09:13:08 2026 -0400

    TODO-286: resolve null-effect doc/impl contradictions + enrich ambiguous 
sites
    
    Triage of 36 flagged sites from the TODO-265 null-effect sweep:
    - DOC-FIX (behavior-preserving) where the Javadoc contradicted traced 
reality:
      BidiMap null-handling, BasicMediaTypeHeader "never null", and 
must-not-be-null
      NPE docs on IfRange.isEtag, UrlPathMatch, MethodExecStats.
    - CODE-FIX (10.0.0 behavior change): make the fluent request/response
      header/param/form assertions null-tolerant so asserting on an absent 
value no
      longer NPEs; adds null-value test coverage.
    - Enrich the genuinely implementation-dependent Group 2 sites with the 
TODO-265
      convention pointing at the relevant {@link} target; setContent() reuses 
the
      existing leaf-type-dependent clause.
    - JsonSchemaMap left unchanged (contradiction refuted: ConcurrentHashMap 
rejects
      null keys regardless).
    
    Co-authored-by: Cursor <[email protected]>
---
 .../juneau/commons/bean/BeanMetaInitializer.java   |  6 +--
 .../apache/juneau/commons/collections/BidiMap.java |  9 ++--
 .../org/apache/juneau/marshall/UriResolver.java    |  8 ++--
 .../marshall/httppart/BaseHttpPartParser.java      |  6 +--
 .../marshall/httppart/BaseHttpPartSerializer.java  |  2 +-
 .../marshall/serializer/SerializerWriter.java      |  2 +-
 .../apache/juneau/marshall/yaml/YamlWriter.java    |  2 +-
 .../assertion/FluentResponseHeaderAssertion.java   | 18 ++++----
 .../FluentResponseHeaderAssertion_Test.java        | 51 +++++++++++++++++++++
 .../http/classic/entity/BasicHttpEntity.java       |  2 +-
 .../http/classic/header/BasicMediaTypeHeader.java  |  2 +-
 .../apache/juneau/http/classic/header/IfRange.java |  2 +-
 .../http/classic/header/SerializedHeader.java      |  8 ++--
 .../juneau/http/classic/part/PartBeanMeta.java     |  4 +-
 .../juneau/http/classic/part/SerializedPart.java   |  4 +-
 .../http/classic/resource/BasicResource.java       |  2 +-
 .../http/classic/response/BasicHttpException.java  |  2 +-
 .../http/classic/response/BasicHttpResponse.java   |  2 +-
 .../FluentRequestFormParamAssertion.java           | 18 ++++----
 .../assertions/FluentRequestHeaderAssertion.java   | 18 ++++----
 .../FluentRequestQueryParamAssertion.java          | 18 ++++----
 .../juneau/rest/server/stats/MethodExecStats.java  |  2 +-
 .../juneau/rest/server/util/UrlPathMatch.java      |  2 +-
 .../FluentRequestFormParamAssertion_Test.java      | 52 ++++++++++++++++++++++
 .../FluentRequestHeaderAssertion_Test.java         | 22 +++++++++
 .../FluentRequestQueryParamAssertion_Test.java     | 52 ++++++++++++++++++++++
 26 files changed, 247 insertions(+), 69 deletions(-)

diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMetaInitializer.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMetaInitializer.java
index efcb65256d..c411c95e6e 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMetaInitializer.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/bean/BeanMetaInitializer.java
@@ -130,8 +130,8 @@ public interface BeanMetaInitializer {
         * Builds the bean-level {@link BeanRegistryLookup} for the supplied 
class.  Returns <jk>null</jk> on the
         * commons-side path (no marshalling context).
         *
-        * @param marshallingContext The marshalling-side context (as an opaque 
{@link Object}).  Can be <jk>null</jk>.
-        * @param beanFilter The bean filter applied to this bean meta.  Can be 
<jk>null</jk>.
+        * @param marshallingContext The marshalling-side context (as an opaque 
{@link Object}).  Can be <jk>null</jk>.  The effect is implementation-dependent 
— see {@link org.apache.juneau.marshall.MarshalledBeanMetaInitializer}, the 
sole concrete implementer of this SPI.
+        * @param beanFilter The bean filter applied to this bean meta.  Can be 
<jk>null</jk>.  The effect is implementation-dependent — see {@link 
org.apache.juneau.marshall.MarshalledBeanMetaInitializer}.
         * @param classInfo The class info of the bean.  Must not be 
<jk>null</jk>.
         * @param config The bean-modeling configuration.  Must not be 
<jk>null</jk>.
         * @return The bean-level registry, or <jk>null</jk>.
@@ -152,7 +152,7 @@ public interface BeanMetaInitializer {
         * Walks the parent classes/interfaces of {@code classInfo} and returns 
the first dictionary-name mapping for
         * {@code rawClass} found in any parent's registry, or <jk>null</jk> if 
none.
         *
-        * @param marshallingContext The marshalling-side context.  Can be 
<jk>null</jk>.
+        * @param marshallingContext The marshalling-side context.  Can be 
<jk>null</jk>.  The effect is implementation-dependent — see {@link 
org.apache.juneau.marshall.MarshalledBeanMetaInitializer}.
         * @param classInfo The bean's class info.
         * @param rawClass The raw class to look up.
         * @return The dictionary name found in a parent's registry, or 
<jk>null</jk>.
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
index 2f833b5988..9d2e4ab13d 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/collections/BidiMap.java
@@ -36,7 +36,7 @@ import java.util.*;
  *     <li>Implements the standard {@link Map} interface for forward key→value 
lookups
  *     <li>Provides {@link #getKey(Object)} method for reverse value→key 
lookups
  *     <li>Maintains insertion order using {@link LinkedHashMap} internally
- *     <li>Automatically filters out null keys and values
+ *     <li>Filters out null keys and values when built via the {@link Builder}
  *     <li>Supports both mutable and unmodifiable instances via the builder
  *     <li>Thread-safety: Not thread-safe by default; external synchronization 
required if accessed by multiple threads
  * </ul>
@@ -59,9 +59,10 @@ import java.util.*;
  *
  * <h5 class='section'>Null Handling:</h5>
  * <p>
- * This map automatically filters out entries with null keys or values during 
construction.
- * Attempting to add null keys or values via {@link #put(Object, Object)} or 
{@link #putAll(Map)}
- * after construction will result in them being stored in the forward map but 
not the reverse map.
+ * The {@link Builder#add(Object, Object)}/{@link Builder#build()} 
construction path filters out entries with
+ * null keys or values.
+ * {@link #put(Object, Object)} and {@link #putAll(Map)} perform <b>no</b> 
null filtering after construction —
+ * null keys and values are stored in both the forward and reverse maps 
unconditionally.
  *
  * <h5 class='section'>Unmodifiable Instances:</h5>
  * <p class='bjava'>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/UriResolver.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/UriResolver.java
index aaaa342e61..73561797c6 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/UriResolver.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/UriResolver.java
@@ -130,7 +130,7 @@ public class UriResolver {
         * Same as {@link #resolve(Object)} except appends result to the 
specified appendable.
         *
         * @param a The appendable to append the URL to. Must not be 
<jk>null</jk>.
-        * @param o The URI to convert to absolute form. Can be <jk>null</jk>.
+        * @param o The URI to convert to absolute form. Can be <jk>null</jk>.  
The effect is implementation-dependent, varying with the configured {@link 
UriResolution} and {@link UriRelativity}.
         * @return The same appendable passed in.
         */
        @SuppressWarnings({
@@ -282,8 +282,8 @@ public class UriResolver {
         *      String <jv>relativeUri</jv> = 
<jv>resolver</jv>.relativize(<js>"servlet:/"</js>, 
<js>"/context/servlet/path/foo"</js>);
         * </p>
         *
-        * @param relativeTo The URI to relativize against. Can be 
<jk>null</jk>.
-        * @param uri The URI to relativize. Can be <jk>null</jk>.
+        * @param relativeTo The URI to relativize against. Can be 
<jk>null</jk>.  The effect is implementation-dependent, varying with the 
configured {@link UriResolution} and {@link UriRelativity}.
+        * @param uri The URI to relativize. Can be <jk>null</jk>.  The effect 
is implementation-dependent, varying with the configured {@link UriResolution} 
and {@link UriRelativity}.
         * @return The relativized URI.
         */
        public String relativize(Object relativeTo, Object uri) {
@@ -297,7 +297,7 @@ public class UriResolver {
         *
         * @param uri
         *      The URI to convert to absolute form.
-        *      Can be <jk>null</jk>.
+        *      Can be <jk>null</jk>.  The effect is implementation-dependent, 
varying with the configured {@link UriResolution} and {@link UriRelativity}.
         *      Can be any of the following:
         *      <ul>
         *              <li>{@link URI}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartParser.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartParser.java
index 4006de8088..4b70a5ba4f 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartParser.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartParser.java
@@ -96,7 +96,7 @@ public abstract class BaseHttpPartParser extends 
MarshallingContextable implemen
         *      <br>Can be <jk>null</jk> (will default to {@link 
HttpPartType#OTHER}).
         * @param schema
         *      Schema information about the part.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartParserSession}.
         *      <br>Not all part parsers use the schema information.
         * @param in The input being parsed.
         *      <br>Can be <jk>null</jk> (will return <jk>null</jk> or use 
schema default if available).
@@ -118,7 +118,7 @@ public abstract class BaseHttpPartParser extends 
MarshallingContextable implemen
         *      <br>Can be <jk>null</jk> (will default to {@link 
HttpPartType#OTHER}).
         * @param schema
         *      Schema information about the part.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartParserSession}.
         *      <br>Not all part parsers use the schema information.
         * @param in The input being parsed.
         *      <br>Can be <jk>null</jk> (will return <jk>null</jk> or use 
schema default if available).
@@ -140,7 +140,7 @@ public abstract class BaseHttpPartParser extends 
MarshallingContextable implemen
         *      <br>Can be <jk>null</jk> (will default to {@link 
HttpPartType#OTHER}).
         * @param schema
         *      Schema information about the part.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartParserSession}.
         *      <br>Not all part parsers use the schema information.
         * @param in The input being parsed.
         *      <br>Can be <jk>null</jk> (will return <jk>null</jk> or use 
schema default if available).
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartSerializer.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartSerializer.java
index bd6e738abc..58dd097cf8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartSerializer.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/httppart/BaseHttpPartSerializer.java
@@ -84,7 +84,7 @@ public abstract class BaseHttpPartSerializer extends 
MarshallingContextable impl
         *      <br>Can be <jk>null</jk> (will default to {@link 
HttpPartType#OTHER}).
         * @param schema
         *      Schema information about the part.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartSerializerSession}.
         *      <br>Not all part serializers use the schema information.
         * @param value The value being serialized.
         *      <br>Can be <jk>null</jk> (will return <jk>null</jk>).
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerWriter.java
index 6ffbd1d453..07f93a019a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/serializer/SerializerWriter.java
@@ -235,7 +235,7 @@ public abstract class SerializerWriter<SELF extends 
SerializerWriter<SELF>> exte
         * session.
         *
         * @param value The URI to serialize.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link UriResolver#append(Appendable, Object)}.
         * @return This object.
         */
        public SELF appendUri(Object value) {
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/yaml/YamlWriter.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/yaml/YamlWriter.java
index b677be3c36..668a3efa4a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/yaml/YamlWriter.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/marshall/yaml/YamlWriter.java
@@ -149,7 +149,7 @@ public class YamlWriter extends 
SerializerWriter<YamlWriter> {
         * Resolves the URI and writes it as a double-quoted YAML string.
         *
         * @param uri The URI to append to the output.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link UriResolver#resolve(Object)}.
         * @return This object.
         */
        public YamlWriter uriValue(Object uri) {
diff --git 
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion.java
 
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion.java
index 7f766eec56..be3f2a0beb 100644
--- 
a/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion.java
+++ 
b/juneau-rest/juneau-rest-client-classic/src/main/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion.java
@@ -138,14 +138,14 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         *      <br>Should be <jk>null</jk> if this is the top-level assertion.
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a header with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
         * used on the same assertion.
         */
        public FluentResponseHeaderAssertion(Assertion creator, ResponseHeader 
value, R returns) {
-               super(creator, value.asString().orElse(null), returns);
+               super(creator, value == null ? null : 
value.asString().orElse(null), returns);
                this.value = value;
                setThrowable(BadRequest.class);
        }
@@ -155,7 +155,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         *
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a header with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
@@ -174,7 +174,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws RestCallException If value could not be parsed.
         */
        public <T> FluentAnyAssertion<T,R> as(Class<T> type) throws 
RestCallException {
-               return new FluentAnyAssertion<>(value.as(type).orElse(null), 
returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type).orElse(null), returns());
        }
 
        /**
@@ -189,7 +189,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws RestCallException If value could not be parsed.
         */
        public FluentAnyAssertion<Object,R> as(Type type, Type...args) throws 
RestCallException {
-               return new FluentAnyAssertion<>(value.as(type, 
args).orElse(null), returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type, args).orElse(null), returns());
        }
 
        /**
@@ -199,7 +199,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a boolean.
         */
        public FluentBooleanAssertion<R> asBoolean() {
-               return new FluentBooleanAssertion<>(this, 
value.asBoolean().orElse(null), returns());
+               return new FluentBooleanAssertion<>(this, value == null ? null 
: value.asBoolean().orElse(null), returns());
        }
 
        /**
@@ -209,7 +209,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not an integer.
         */
        public FluentIntegerAssertion<R> asInteger() {
-               return new FluentIntegerAssertion<>(this, 
value.asInteger().orElse(null), returns());
+               return new FluentIntegerAssertion<>(this, value == null ? null 
: value.asInteger().orElse(null), returns());
        }
 
        @Override /* Overridden from FluentStringAssertion */
@@ -225,7 +225,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a long.
         */
        public FluentLongAssertion<R> asLong() {
-               return new FluentLongAssertion<>(this, 
value.asLong().orElse(null), returns());
+               return new FluentLongAssertion<>(this, value == null ? null : 
value.asLong().orElse(null), returns());
        }
 
        /**
@@ -235,7 +235,7 @@ public class FluentResponseHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a zoned-datetime.
         */
        public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-               return new FluentZonedDateTimeAssertion<>(this, 
value.asDateHeader().asZonedDateTime().orElse(null), returns());
+               return new FluentZonedDateTimeAssertion<>(this, value == null ? 
null : value.asDateHeader().asZonedDateTime().orElse(null), returns());
        }
 
        @Override /* Overridden from Assertion */
diff --git 
a/juneau-rest/juneau-rest-client-classic/src/test/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion_Test.java
 
b/juneau-rest/juneau-rest-client-classic/src/test/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion_Test.java
new file mode 100644
index 0000000000..a136f73846
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-client-classic/src/test/java/org/apache/juneau/rest/client/classic/assertion/FluentResponseHeaderAssertion_Test.java
@@ -0,0 +1,51 @@
+/*
+ * 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.client.classic.assertion;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.rest.client.classic.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link FluentResponseHeaderAssertion} tolerates a null {@code 
value} (TODO-286 #9/#10 null-tolerance fix).
+ *
+ * <p>
+ * A null {@link ResponseHeader} used to NPE in both constructors ({@code 
value.asString()}) and in every transform
+ * method ({@code value.asBoolean()}, etc.).
+ */
+class FluentResponseHeaderAssertion_Test {
+
+       @Test void a01_nullValue_chainedConstructor_doesNotThrow() {
+               var a = new FluentResponseHeaderAssertion<>(null, 
(ResponseHeader)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a02_nullValue_directConstructor_doesNotThrow() {
+               var a = new 
FluentResponseHeaderAssertion<>((ResponseHeader)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a03_nullValue_transformMethodsDoNotThrowAndYieldNull() {
+               var a = new 
FluentResponseHeaderAssertion<>((ResponseHeader)null, null);
+               assertDoesNotThrow(() -> a.asBoolean().isNull());
+               assertDoesNotThrow(() -> a.asInteger().isNull());
+               assertDoesNotThrow(() -> a.asLong().isNull());
+               assertDoesNotThrow(() -> a.asZonedDateTime().isNull());
+               assertDoesNotThrow(() -> a.as(String.class).isNull());
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/entity/BasicHttpEntity.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/entity/BasicHttpEntity.java
index 4c6043417d..f6eea6e254 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/entity/BasicHttpEntity.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/entity/BasicHttpEntity.java
@@ -293,7 +293,7 @@ public abstract class BasicHttpEntity<SELF extends 
BasicHttpEntity<SELF>> implem
        /**
         * Sets the content on this entity bean.
         *
-        * @param value The entity content, can be <jk>null</jk>.
+        * @param value The entity content.  Can be <jk>null</jk> (the effect 
depends on the concrete leaf type: e.g. treated as empty for {@link 
StringEntity}/{@link ByteArrayEntity}, or a {@link NullPointerException} on 
read for {@link StreamEntity}/{@link ReaderEntity}/{@link FileEntity}).
         * @return This object.
         */
        public SELF setContent(Object value) {
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/BasicMediaTypeHeader.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/BasicMediaTypeHeader.java
index 490ffcf113..9fa0a42daf 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/BasicMediaTypeHeader.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/BasicMediaTypeHeader.java
@@ -177,7 +177,7 @@ public class BasicMediaTypeHeader extends BasicStringHeader 
{
         * For example, the media type <js>"text/foo+bar"</js> will return a 
list of
         * <code>[<js>'foo'</js>,<js>'bar'</js>]</code>
         *
-        * @return An unmodifiable list of subtype fragments.  Can be 
<jk>null</jk>.
+        * @return An unmodifiable list of subtype fragments.  Never 
<jk>null</jk>.
         */
        public final List<String> getSubTypes() { return 
orElse(MediaType.EMPTY).getSubTypes(); }
 
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/IfRange.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/IfRange.java
index 60fa6cc2c2..71a4678fc9 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/IfRange.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/IfRange.java
@@ -161,7 +161,7 @@ public class IfRange extends BasicDateHeader {
         * @param value
         *      The header value.
         *      <br>Must be an RFC-1123 formated string (e.g. <js>"Sat, 29 Oct 
1994 19:43:31 GMT"</js>).
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Must not be <jk>null</jk> (a <jk>null</jk> argument throws 
{@link NullPointerException}).
         */
        public IfRange(String value) {
                super(NAME, isEtag(value) ? null : value);
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/SerializedHeader.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/SerializedHeader.java
index ffa27d5797..3058832343 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/SerializedHeader.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/header/SerializedHeader.java
@@ -44,7 +44,7 @@ public class SerializedHeader extends BasicHeader {
         * @param name The header name.
         * @param value
         *      The POJO to serialize as the header value.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartSerializerSession} and {@link 
HttpPartSchema}.
         * @return A new header bean.  Never <jk>null</jk>.
         * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
         */
@@ -84,7 +84,7 @@ public class SerializedHeader extends BasicHeader {
         * @param name The header name.
         * @param value
         *      The supplier of the POJO to serialize as the header value.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent — see {@link HttpPartSerializerSession} and {@link 
HttpPartSchema}.
         * @return A new header bean.  Never <jk>null</jk>.
         * @throws IllegalArgumentException If name is <jk>null</jk> or empty.
         */
@@ -128,7 +128,7 @@ public class SerializedHeader extends BasicHeader {
         * Constructor.
         *
         * @param name The HTTP header name name.
-        * @param value The POJO to serialize to the parameter value.  Can be 
<jk>null</jk>.
+        * @param value The POJO to serialize to the parameter value.  Can be 
<jk>null</jk>.  The effect is implementation-dependent — see {@link 
HttpPartSerializerSession} and {@link HttpPartSchema}.
         * @param serializer
         *      The serializer to use for serializing the value to a string 
value.
         *      <br>Can be <jk>null</jk>, in which case the value is converted 
directly to a string instead of being serialized.
@@ -160,7 +160,7 @@ public class SerializedHeader extends BasicHeader {
         * Header value is re-evaluated on each call to {@link #getValue()}.
         *
         * @param name The HTTP header name name.
-        * @param value The supplier of the POJO to serialize to the parameter 
value.  Can be <jk>null</jk>.
+        * @param value The supplier of the POJO to serialize to the parameter 
value.  Can be <jk>null</jk>.  The effect is implementation-dependent — see 
{@link HttpPartSerializerSession} and {@link HttpPartSchema}.
         * @param serializer
         *      The serializer to use for serializing the value to a string 
value.
         *      <br>Can be <jk>null</jk>, in which case the value is converted 
directly to a string instead of being serialized.
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/PartBeanMeta.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/PartBeanMeta.java
index cb76b21c2d..358ed034f1 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/PartBeanMeta.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/PartBeanMeta.java
@@ -121,7 +121,7 @@ public class PartBeanMeta<T> {
         *
         * @param value
         *      The part value.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent, depending on the target bean type's constructor shape 
— see {@link #construct(String, Object)}.
         * @return A newly constructed bean.
         * @throws UnsupportedOperationException If bean could not be 
constructed (e.g. couldn't find a constructor).
         */
@@ -138,7 +138,7 @@ public class PartBeanMeta<T> {
         *      {@link Header#value() @Header(value)} annotations.
         * @param value
         *      The part value.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>.  The effect is 
implementation-dependent, depending on the target bean type's constructor shape 
(e.g. coerced to <c>String</c> for a single-<c>String</c>-arg constructor, 
passed through unchanged otherwise).
         * @return A newly constructed bean.
         * @throws UnsupportedOperationException If bean could not be 
constructed (e.g. couldn't find a constructor).
         */
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/SerializedPart.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/SerializedPart.java
index 390e3c3252..f9ca83d98b 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/SerializedPart.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/part/SerializedPart.java
@@ -89,7 +89,7 @@ public class SerializedPart extends BasicPart {
         *
         * @param name The part name.
         * @param value The POJO to serialize to The part value.
-        * @param type The HTTP part type.  Can be <jk>null</jk>.
+        * @param type The HTTP part type.  Can be <jk>null</jk>.  The effect 
is implementation-dependent — see {@link HttpPartSerializerSession#write}.
         * @param serializer
         *      The serializer to use for serializing the value to a string 
value.
         *      <br>Can be <jk>null</jk>, in which case the value is converted 
using a simple string conversion instead of being serialized.
@@ -232,7 +232,7 @@ public class SerializedPart extends BasicPart {
        /**
         * Sets the HTTP part type.
         *
-        * @param value The new value for this property.  Can be <jk>null</jk>.
+        * @param value The new value for this property.  Can be <jk>null</jk>. 
 The effect is implementation-dependent — see {@link 
HttpPartSerializerSession#write}.
         * @return This object.
         */
        public SerializedPart type(HttpPartType value) {
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/resource/BasicResource.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/resource/BasicResource.java
index 7512fa1997..d1dc9ade40 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/resource/BasicResource.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/resource/BasicResource.java
@@ -304,7 +304,7 @@ public abstract class BasicResource<SELF extends 
BasicResource<SELF>> implements
        /**
         * Sets the content on this entity bean.
         *
-        * @param value The entity content, can be <jk>null</jk>.
+        * @param value The entity content.  Can be <jk>null</jk> (the effect 
depends on the concrete leaf type: e.g. treated as empty for {@link 
StringEntity}/{@link ByteArrayEntity}, or a {@link NullPointerException} on 
read for {@link StreamEntity}/{@link ReaderEntity}/{@link FileEntity}).
         * @return This object.
         */
        public SELF setContent(Object value) {
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpException.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpException.java
index 6021f6b846..6eb78b4b17 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpException.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpException.java
@@ -448,7 +448,7 @@ public class BasicHttpException extends 
BasicRuntimeException implements HttpRes
         * <p>
         * If not specified, uses {@link Locale#getDefault()}.
         *
-        * @param value The new value.  Can be <jk>null</jk>.
+        * @param value The new value.  Can be <jk>null</jk>.  The effect is 
implementation-dependent, depending on the configured {@link 
ReasonPhraseCatalog} (e.g. ignored by {@link EnglishReasonPhraseCatalog}).
         * @return This object.
         */
        public BasicHttpException setLocale2(Locale value) {
diff --git 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpResponse.java
 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpResponse.java
index 47b9b5cf80..60aaab7f5a 100644
--- 
a/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpResponse.java
+++ 
b/juneau-rest/juneau-rest-common-classic/src/main/java/org/apache/juneau/http/classic/response/BasicHttpResponse.java
@@ -302,7 +302,7 @@ public abstract class BasicHttpResponse<SELF extends 
BasicHttpResponse<SELF>> im
         * <p>
         * If not specified, uses {@link Locale#getDefault()}.
         *
-        * @param value The new value.  Can be <jk>null</jk>.
+        * @param value The new value.  Can be <jk>null</jk>.  The effect is 
implementation-dependent, depending on the configured {@link 
ReasonPhraseCatalog} (e.g. ignored by {@link EnglishReasonPhraseCatalog}).
         * @return This object.
         */
        public SELF setLocale2(Locale value) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion.java
index e09dce7844..d0e7e8d30b 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion.java
@@ -137,14 +137,14 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         *      <br>Should be <jk>null</jk> if this is the top-level assertion.
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
         * used on the same assertion.
         */
        public FluentRequestFormParamAssertion(Assertion creator, 
RequestFormParam value, R returns) {
-               super(creator, value.asString().orElse(null), returns);
+               super(creator, value == null ? null : 
value.asString().orElse(null), returns);
                this.value = value;
                setThrowable(BadRequest.class);
        }
@@ -154,7 +154,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         *
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
@@ -172,7 +172,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @return A new fluent assertion object.
         */
        public <T> FluentAnyAssertion<T,R> as(Class<T> type) {
-               return new FluentAnyAssertion<>(value.as(type).orElse(null), 
returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type).orElse(null), returns());
        }
 
        /**
@@ -186,7 +186,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @return A new fluent assertion object.
         */
        public FluentAnyAssertion<Object,R> as(Type type, Type...args) {
-               return new FluentAnyAssertion<>(value.as(type, 
args).orElse(null), returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type, args).orElse(null), returns());
        }
 
        /**
@@ -196,7 +196,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @throws AssertionError If object is not a boolean.
         */
        public FluentBooleanAssertion<R> asBoolean() {
-               return new FluentBooleanAssertion<>(this, 
value.asBoolean().orElse(null), returns());
+               return new FluentBooleanAssertion<>(this, value == null ? null 
: value.asBoolean().orElse(null), returns());
        }
 
        /**
@@ -206,7 +206,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @throws AssertionError If object is not an integer.
         */
        public FluentIntegerAssertion<R> asInteger() {
-               return new FluentIntegerAssertion<>(this, 
value.asInteger().orElse(null), returns());
+               return new FluentIntegerAssertion<>(this, value == null ? null 
: value.asInteger().orElse(null), returns());
        }
 
        @Override /* Overridden from FluentStringAssertion */
@@ -222,7 +222,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @throws AssertionError If object is not a long.
         */
        public FluentLongAssertion<R> asLong() {
-               return new FluentLongAssertion<>(this, 
value.asLong().orElse(null), returns());
+               return new FluentLongAssertion<>(this, value == null ? null : 
value.asLong().orElse(null), returns());
        }
 
        /**
@@ -232,7 +232,7 @@ public class FluentRequestFormParamAssertion<R> extends 
FluentStringAssertion<R>
         * @throws AssertionError If object is not a zoned-datetime.
         */
        public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-               return new FluentZonedDateTimeAssertion<>(this, 
value.asDatePart().asZonedDateTime().orElse(null), returns());
+               return new FluentZonedDateTimeAssertion<>(this, value == null ? 
null : value.asDatePart().asZonedDateTime().orElse(null), returns());
        }
 
        @Override /* Overridden from Assertion */
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
index 8140424327..332a5474a0 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion.java
@@ -136,14 +136,14 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         *      <br>Should be <jk>null</jk> if this is the top-level assertion.
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
         * used on the same assertion.
         */
        public FluentRequestHeaderAssertion(Assertion creator, RequestHeader 
value, R returns) {
-               super(creator, value.asString().orElse(null), returns);
+               super(creator, value == null ? null : 
value.asString().orElse(null), returns);
                this.value = value;
                setThrowable(BadRequest.class);
        }
@@ -153,7 +153,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         *
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
@@ -171,7 +171,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @return A new fluent assertion object.
         */
        public <T> FluentAnyAssertion<T,R> as(Class<T> type) {
-               return new FluentAnyAssertion<>(value.as(type).orElse(null), 
returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type).orElse(null), returns());
        }
 
        /**
@@ -185,7 +185,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @return A new fluent assertion object.
         */
        public FluentAnyAssertion<Object,R> as(Type type, Type...args) {
-               return new FluentAnyAssertion<>(value.as(type, 
args).orElse(null), returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type, args).orElse(null), returns());
        }
 
        /**
@@ -195,7 +195,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a boolean.
         */
        public FluentBooleanAssertion<R> asBoolean() {
-               return new FluentBooleanAssertion<>(this, 
value.asBoolean().orElse(null), returns());
+               return new FluentBooleanAssertion<>(this, value == null ? null 
: value.asBoolean().orElse(null), returns());
        }
 
        /**
@@ -205,7 +205,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not an integer.
         */
        public FluentIntegerAssertion<R> asInteger() {
-               return new FluentIntegerAssertion<>(this, 
value.asInteger().orElse(null), returns());
+               return new FluentIntegerAssertion<>(this, value == null ? null 
: value.asInteger().orElse(null), returns());
        }
 
        @Override /* Overridden from FluentStringAssertion */
@@ -221,7 +221,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a long.
         */
        public FluentLongAssertion<R> asLong() {
-               return new FluentLongAssertion<>(this, 
value.asLong().orElse(null), returns());
+               return new FluentLongAssertion<>(this, value == null ? null : 
value.asLong().orElse(null), returns());
        }
 
        /**
@@ -231,7 +231,7 @@ public class FluentRequestHeaderAssertion<R> extends 
FluentStringAssertion<R> {
         * @throws AssertionError If object is not a zoned-datetime.
         */
        public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-               return new FluentZonedDateTimeAssertion<>(this, 
value.asDateHeader().asZonedDateTime().orElse(null), returns());
+               return new FluentZonedDateTimeAssertion<>(this, value == null ? 
null : value.asDateHeader().asZonedDateTime().orElse(null), returns());
        }
 
        @Override /* Overridden from Assertion */
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion.java
index d9715e6355..cad335c245 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion.java
@@ -137,14 +137,14 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         *      <br>Should be <jk>null</jk> if this is the top-level assertion.
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
         * used on the same assertion.
         */
        public FluentRequestQueryParamAssertion(Assertion creator, 
RequestQueryParam value, R returns) {
-               super(creator, value.asString().orElse(null), returns);
+               super(creator, value == null ? null : 
value.asString().orElse(null), returns);
                this.value = value;
                setThrowable(BadRequest.class);
        }
@@ -154,7 +154,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         *
         * @param value
         *      The object being tested.
-        *      <br>Can be <jk>null</jk>.
+        *      <br>Can be <jk>null</jk>, in which case this assertion behaves 
as if testing a part with no value (e.g. {@link #isNull()} passes, transform 
methods yield <jk>null</jk>-valued assertions).
         * @param returns
         *      The object to return after a test method is called.
         *      <br>If <jk>null</jk>, the test method returns this object 
allowing multiple test method calls to be
@@ -172,7 +172,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @return A new fluent assertion object.
         */
        public <T> FluentAnyAssertion<T,R> as(Class<T> type) {
-               return new FluentAnyAssertion<>(value.as(type).orElse(null), 
returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type).orElse(null), returns());
        }
 
        /**
@@ -186,7 +186,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @return A new fluent assertion object.
         */
        public FluentAnyAssertion<Object,R> as(Type type, Type...args) {
-               return new FluentAnyAssertion<>(value.as(type, 
args).orElse(null), returns());
+               return new FluentAnyAssertion<>(value == null ? null : 
value.as(type, args).orElse(null), returns());
        }
 
        /**
@@ -196,7 +196,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @throws AssertionError If object is not a boolean.
         */
        public FluentBooleanAssertion<R> asBoolean() {
-               return new FluentBooleanAssertion<>(this, 
value.asBoolean().orElse(null), returns());
+               return new FluentBooleanAssertion<>(this, value == null ? null 
: value.asBoolean().orElse(null), returns());
        }
 
        /**
@@ -206,7 +206,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @throws AssertionError If object is not an integer.
         */
        public FluentIntegerAssertion<R> asInteger() {
-               return new FluentIntegerAssertion<>(this, 
value.asInteger().orElse(null), returns());
+               return new FluentIntegerAssertion<>(this, value == null ? null 
: value.asInteger().orElse(null), returns());
        }
 
        @Override /* Overridden from FluentStringAssertion */
@@ -222,7 +222,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @throws AssertionError If object is not a long.
         */
        public FluentLongAssertion<R> asLong() {
-               return new FluentLongAssertion<>(this, 
value.asLong().orElse(null), returns());
+               return new FluentLongAssertion<>(this, value == null ? null : 
value.asLong().orElse(null), returns());
        }
 
        /**
@@ -232,7 +232,7 @@ public class FluentRequestQueryParamAssertion<R> extends 
FluentStringAssertion<R
         * @throws AssertionError If object is not a zoned-datetime.
         */
        public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-               return new FluentZonedDateTimeAssertion<>(this, 
value.asDatePart().asZonedDateTime().orElse(null), returns());
+               return new FluentZonedDateTimeAssertion<>(this, value == null ? 
null : value.asDatePart().asZonedDateTime().orElse(null), returns());
        }
 
        @Override /* Overridden from Assertion */
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/stats/MethodExecStats.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/stats/MethodExecStats.java
index 2e603e2ceb..8c01d4ed2f 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/stats/MethodExecStats.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/stats/MethodExecStats.java
@@ -184,7 +184,7 @@ public class MethodExecStats {
        /**
         * Call when an error occurs.
         *
-        * @param e The exception thrown.  Can be <jk>null</jk>.
+        * @param e The exception thrown.  Must not be <jk>null</jk> (a 
<jk>null</jk> argument throws {@link NullPointerException} via {@link 
ThrownStore#add(Throwable)}).
         * @return This object.
         */
        public MethodExecStats error(Throwable e) {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPathMatch.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPathMatch.java
index b670f461fe..fe10b0b3de 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPathMatch.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/server/util/UrlPathMatch.java
@@ -49,7 +49,7 @@ public class UrlPathMatch {
        /**
         * Constructor.
         *
-        * @param path The path being matched against.  Can be <jk>null</jk>.
+        * @param path The path being matched against.  Must not be 
<jk>null</jk> (a <jk>null</jk> argument causes {@link #getPrefix()}/{@link 
#getSuffix()} to throw {@link NullPointerException}).
         * @param matchedParts The number of parts that were matched against 
the path.
         * @param keys The variable keys.  Can be <jk>null</jk> (no path 
variables are recorded; the variable map resolves to empty).
         * @param values The variable values.  Can be <jk>null</jk> if 
<c>keys</c> is also <jk>null</jk>; otherwise must align positionally with 
<c>keys</c>.
diff --git 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion_Test.java
 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion_Test.java
new file mode 100644
index 0000000000..e74480762d
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestFormParamAssertion_Test.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.assertions;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.server.httppart.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link FluentRequestFormParamAssertion} tolerates a null {@code 
value} (TODO-286 #6 null-tolerance fix).
+ *
+ * <p>
+ * A null {@link RequestFormParam} used to NPE in the constructor ({@code 
value.asString()}) and in every transform
+ * method ({@code value.asBoolean()}, etc.).
+ */
+class FluentRequestFormParamAssertion_Test extends TestBase {
+
+       @Test void a01_nullValue_chainedConstructor_doesNotThrow() {
+               var a = new FluentRequestFormParamAssertion<>(null, 
(RequestFormParam)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a02_nullValue_directConstructor_doesNotThrow() {
+               var a = new 
FluentRequestFormParamAssertion<>((RequestFormParam)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a03_nullValue_transformMethodsDoNotThrowAndYieldNull() {
+               var a = new 
FluentRequestFormParamAssertion<>((RequestFormParam)null, null);
+               assertDoesNotThrow(() -> a.asBoolean().isNull());
+               assertDoesNotThrow(() -> a.asInteger().isNull());
+               assertDoesNotThrow(() -> a.asLong().isNull());
+               assertDoesNotThrow(() -> a.asZonedDateTime().isNull());
+               assertDoesNotThrow(() -> a.as(String.class).isNull());
+       }
+}
diff --git 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
index 0061b9395e..baa71235b2 100644
--- 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
+++ 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestHeaderAssertion_Test.java
@@ -59,4 +59,26 @@ class FluentRequestHeaderAssertion_Test extends TestBase {
                var e = assertThrows(RuntimeException.class, () -> 
h.is("expected"));
                assertFalse(e.getMessage().startsWith("CUSTOM "));
        }
+
+       // TODO-286 #5: a null RequestHeader used to NPE in the constructor 
(value.asString()) and in every
+       // transform method (value.asBoolean(), etc.). Verify null is now 
tolerated end-to-end.
+
+       @Test void a03_nullValue_chainedConstructor_doesNotThrow() {
+               var h = new FluentRequestHeaderAssertion<>(null, 
(RequestHeader)null, null);
+               assertDoesNotThrow(h::isNull);
+       }
+
+       @Test void a04_nullValue_directConstructor_doesNotThrow() {
+               var h = new FluentRequestHeaderAssertion<>((RequestHeader)null, 
null);
+               assertDoesNotThrow(h::isNull);
+       }
+
+       @Test void a05_nullValue_transformMethodsDoNotThrowAndYieldNull() {
+               var h = new FluentRequestHeaderAssertion<>((RequestHeader)null, 
null);
+               assertDoesNotThrow(() -> h.asBoolean().isNull());
+               assertDoesNotThrow(() -> h.asInteger().isNull());
+               assertDoesNotThrow(() -> h.asLong().isNull());
+               assertDoesNotThrow(() -> h.asZonedDateTime().isNull());
+               assertDoesNotThrow(() -> h.as(String.class).isNull());
+       }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion_Test.java
 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion_Test.java
new file mode 100644
index 0000000000..10cefe827a
--- /dev/null
+++ 
b/juneau-rest/juneau-rest-server/src/test/java/org/apache/juneau/rest/server/assertions/FluentRequestQueryParamAssertion_Test.java
@@ -0,0 +1,52 @@
+/*
+ * 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.server.assertions;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.rest.server.httppart.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Validates {@link FluentRequestQueryParamAssertion} tolerates a null {@code 
value} (TODO-286 #4 null-tolerance fix).
+ *
+ * <p>
+ * A null {@link RequestQueryParam} used to NPE in the constructor ({@code 
value.asString()}) and in every transform
+ * method ({@code value.asBoolean()}, etc.).
+ */
+class FluentRequestQueryParamAssertion_Test extends TestBase {
+
+       @Test void a01_nullValue_chainedConstructor_doesNotThrow() {
+               var a = new FluentRequestQueryParamAssertion<>(null, 
(RequestQueryParam)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a02_nullValue_directConstructor_doesNotThrow() {
+               var a = new 
FluentRequestQueryParamAssertion<>((RequestQueryParam)null, null);
+               assertDoesNotThrow(a::isNull);
+       }
+
+       @Test void a03_nullValue_transformMethodsDoNotThrowAndYieldNull() {
+               var a = new 
FluentRequestQueryParamAssertion<>((RequestQueryParam)null, null);
+               assertDoesNotThrow(() -> a.asBoolean().isNull());
+               assertDoesNotThrow(() -> a.asInteger().isNull());
+               assertDoesNotThrow(() -> a.asLong().isNull());
+               assertDoesNotThrow(() -> a.asZonedDateTime().isNull());
+               assertDoesNotThrow(() -> a.as(String.class).isNull());
+       }
+}

Reply via email to