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 2021/07/11 16:34:48 UTC

[juneau] branch master updated: Assertions API improvements.

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 4666cdb  Assertions API improvements.
4666cdb is described below

commit 4666cdb0f23d85ef8a92149e92d631ce4dd400ad
Author: JamesBognar <ja...@salesforce.com>
AuthorDate: Sun Jul 11 12:32:49 2021 -0400

    Assertions API improvements.
---
 TODO.txt                                           |  37 +-
 .../main/ConfigurablePropertyCodeGenerator.java    |   7 +-
 .../main/java/org/apache/juneau/BeanContext.java   |   3 +
 .../src/main/java/org/apache/juneau/BeanMap.java   |   6 +-
 .../main/java/org/apache/juneau/BeanSession.java   |   8 +-
 .../java/org/apache/juneau/BeanSessionArgs.java    |   6 +
 .../apache/juneau/ContextPropertiesBuilder.java    |   2 +-
 .../src/main/java/org/apache/juneau/Session.java   |  13 +-
 .../main/java/org/apache/juneau/SessionArgs.java   |  15 +
 .../java/org/apache/juneau/SessionProperties.java  |  18 +-
 .../org/apache/juneau/{internal => }/Version.java  |  76 ++-
 .../apache/juneau/assertions/ArrayAssertion.java   |  31 +-
 .../org/apache/juneau/assertions/Assertion.java    |  12 +
 .../juneau/assertions/AssertionPredicate.java      | 189 ++++++
 .../juneau/assertions/AssertionPredicates.java     | 205 +++++++
 .../org/apache/juneau/assertions/Assertions.java   |  54 +-
 .../apache/juneau/assertions/BeanAssertion.java    |  29 +-
 .../{BeanAssertion.java => BeanListAssertion.java} |  36 +-
 .../apache/juneau/assertions/BooleanAssertion.java |   5 -
 .../juneau/assertions/ByteArrayAssertion.java      |   5 -
 .../juneau/assertions/CollectionAssertion.java     |  28 +-
 .../juneau/assertions/ComparableAssertion.java     |  28 +-
 .../apache/juneau/assertions/DateAssertion.java    |   5 -
 .../juneau/assertions/FluentArrayAssertion.java    | 245 +++++---
 .../apache/juneau/assertions/FluentAssertion.java  |   3 +-
 .../juneau/assertions/FluentBaseAssertion.java     | 484 ---------------
 .../juneau/assertions/FluentBeanAssertion.java     |  73 ++-
 ...Assertion.java => FluentBeanListAssertion.java} |  72 +--
 .../juneau/assertions/FluentBooleanAssertion.java  |   9 +-
 .../assertions/FluentByteArrayAssertion.java       |  13 +-
 .../assertions/FluentCollectionAssertion.java      |  77 +--
 .../assertions/FluentComparableAssertion.java      |  41 +-
 .../juneau/assertions/FluentDateAssertion.java     |  17 +-
 .../juneau/assertions/FluentIntegerAssertion.java  |   5 +-
 .../juneau/assertions/FluentListAssertion.java     | 149 ++++-
 .../juneau/assertions/FluentLongAssertion.java     |   8 +-
 .../juneau/assertions/FluentMapAssertion.java      | 105 ++--
 .../juneau/assertions/FluentObjectAssertion.java   | 651 ++++++++++++++++++++-
 ...ion.java => FluentPrimitiveArrayAssertion.java} | 172 ++++--
 .../juneau/assertions/FluentStringAssertion.java   |  99 ++--
 .../assertions/FluentThrowableAssertion.java       |  46 +-
 .../juneau/assertions/FluentVersionAssertion.java  |  11 +-
 .../assertions/FluentZonedDateTimeAssertion.java   |  18 +-
 .../apache/juneau/assertions/IntegerAssertion.java |   5 -
 .../apache/juneau/assertions/ListAssertion.java    |  28 +-
 .../apache/juneau/assertions/LongAssertion.java    |   5 -
 .../org/apache/juneau/assertions/MapAssertion.java |  29 +-
 .../apache/juneau/assertions/ObjectAssertion.java  |  25 +-
 ...Assertion.java => PrimitiveArrayAssertion.java} |  35 +-
 .../apache/juneau/assertions/StringAssertion.java  |   5 -
 .../juneau/assertions/ThrowableAssertion.java      |  23 +-
 .../apache/juneau/assertions/VersionAssertion.java |   6 +-
 .../juneau/assertions/ZonedDateTimeAssertion.java  |   5 -
 .../java/org/apache/juneau/collections/AList.java  |  31 +-
 .../java/org/apache/juneau/collections/AMap.java   |  10 +-
 .../java/org/apache/juneau/collections/ASet.java   |  10 +-
 .../org/apache/juneau/collections/ASortedMap.java  |  10 +-
 .../org/apache/juneau/collections/ASortedSet.java  |  10 +-
 .../java/org/apache/juneau/collections/OList.java  |   6 +-
 .../java/org/apache/juneau/collections/OMap.java   |   6 +-
 .../java/org/apache/juneau/http/HttpHeaders.java   |   2 +-
 .../juneau/http/header/BasicCsvArrayHeader.java    |   2 +-
 .../apache/juneau/http/header/ClientVersion.java   |   1 +
 .../apache/juneau/http/part/BasicCsvArrayPart.java |   2 +-
 .../org/apache/juneau/internal/ObjectUtils.java    |   2 +-
 .../org/apache/juneau/internal/VersionRange.java   |   2 +
 .../apache/juneau/parser/ParserSessionArgs.java    |   6 +
 .../juneau/serializer/SerializerSessionArgs.java   |   6 +
 .../java/org/apache/juneau/utils/PojoRest.java     |   2 +-
 .../assertion/FluentResponseBodyAssertion.java     |  81 ++-
 .../assertion/FluentResponseHeaderAssertion.java   |  47 +-
 .../FluentResponseStatusLineAssertion.java         |   4 +-
 .../juneau/rest/mock/MockRestClientBuilder.java    |  60 +-
 .../org/apache/juneau/rest/RequestFormParam.java   |   2 +-
 .../org/apache/juneau/rest/RequestPathParam.java   |   2 +-
 .../org/apache/juneau/rest/RequestQueryParam.java  |   2 +-
 .../assertions/FluentProtocolVersionAssertion.java |  11 +-
 .../assertions/FluentRequestBodyAssertion.java     |  28 +-
 .../FluentRequestFormParamAssertion.java           |  33 +-
 .../assertions/FluentRequestHeaderAssertion.java   |  30 +-
 .../assertions/FluentRequestLineAssertion.java     |  11 +-
 .../FluentRequestQueryParamAssertion.java          |  33 +-
 .../java/org/apache/juneau/BeanConfigTest.java     |   2 +-
 .../java/org/apache/juneau/BeanContextTest.java    |   3 +-
 .../test/java/org/apache/juneau/Version_Test.java  | 109 ++++
 .../juneau/assertions/ArrayAssertion_Test.java     |   8 +-
 .../juneau/assertions/BeanAssertion_Test.java      |  10 +-
 .../assertions/CollectionAssertion_Test.java       |   2 +-
 .../assertions/ComparableAssertion_Test.java       |   2 +-
 .../juneau/assertions/ListAssertion_Test.java      |   2 +-
 .../juneau/assertions/MapAssertion_Test.java       |   4 +-
 .../juneau/assertions/ObjectAssertion_Test.java    |  16 +-
 .../juneau/assertions/StringAssertion_Test.java    |   2 +-
 .../juneau/assertions/ThrowableAssertion_Test.java |  10 +-
 .../apache/juneau/http/SerializedHeader_Test.java  |   4 +-
 .../apache/juneau/http/SerializedPart_Test.java    |   4 +-
 .../juneau/http/header/ClientVersion_Test.java     |   1 +
 .../http/remote/Remote_BodyAnnotation_Test.java    |   2 +-
 .../remote/Remote_FormDataAnnotation_Test.java     |   2 +-
 .../http/remote/Remote_HeaderAnnotation_Test.java  |   2 +-
 .../http/remote/Remote_QueryAnnotation_Test.java   |   2 +-
 .../apache/juneau/pojotools/PojoSorterTest.java    |   2 +-
 .../apache/juneau/pojotools/PojoViewerTest.java    |   2 +-
 .../juneau/rest/client/RestClient_Body_Test.java   |   4 +-
 .../rest/client/RestClient_Response_Body_Test.java |   8 +-
 .../rest/client/RestClient_Response_Test.java      |   4 +-
 .../transforms/InputStreamBase64SwapTest.java      |   2 +-
 .../apache/juneau/transforms/LocaleSwapTest.java   |   2 +-
 .../apache/juneau/transforms/ReaderSwapTest.java   |   2 +-
 .../transforms/TemporalCalendarSwapTest.java       |   2 +-
 .../juneau/transforms/TemporalDateSwapTest.java    |   2 +-
 .../apache/juneau/transforms/TemporalSwapTest.java |   2 +-
 .../org/apache/juneau/utils/MethodInvokerTest.java |   6 +-
 113 files changed, 2500 insertions(+), 1459 deletions(-)

diff --git a/TODO.txt b/TODO.txt
index b47edb0..b08f30b 100644
--- a/TODO.txt
+++ b/TODO.txt
@@ -8,12 +8,35 @@ HttpException subclasses can set status, but does it use code?
 HttpException should use list of Headers and have a headers(Header...) method.
 
 JsonSchema should have fluent getters and setters.
-Break out @Response bean handling into it's own handler.
-Can HeaderGroup be used universally?
 
 @ResponseBody and @ResponseHeaders shouldn't be required on HttpResponse objects.
-HttpRuntimeException?
-Add ExceptionHeader
-REST query/formdata/path parts should extend from PartGroup class.
-Eliminate PartSupplier and HeaderSupplier?
-Replace ThrowableUtils.assertFieldNotNull() and other methods.
+
+
+This has to be easier:
+	@Enumerated(STRING)
+	@Schema(description="Routing types that this directive applies to.")
+	@NotEmpty(message="At least one copy type is required")
+	@Fetched(primary=true, fetcher=CopyTypes.class)
+	@SortNatural
+	@Beanp(type=TreeSet.class, params=CopyType.class)
+	protected Set<CopyType> copyTypes;
+
+	public Set<CopyType> getCopyTypes() {
+		return copyTypes;
+	}
+
+	public Directive setCopyTypes(Set<CopyType> value) {
+		this.copyTypes = value;
+		return this;
+	}
+	
+assertBodyMatches should tell you at which position it differs and make it obvious in the error.
+
+
+            .extracting(Directive::getLabel, Directive::getStatus, Directive::getStart, Directive::getEnd)
+            .containsOnly(d.getLabel(), DirectiveStatus.ACTIVE, d.getStart(), d.getEnd());
+            
+        assertThat(releaseService.sortReleases(Arrays.asList(sb0_224, sb0_226, sb0_222))).containsExactly(sb0_222, sb0_224, sb0_226);
+            
+
+Better support for SortedSet properties.
diff --git a/juneau-all/src/java/main/ConfigurablePropertyCodeGenerator.java b/juneau-all/src/java/main/ConfigurablePropertyCodeGenerator.java
index 60c6eeb..276424a 100644
--- a/juneau-all/src/java/main/ConfigurablePropertyCodeGenerator.java
+++ b/juneau-all/src/java/main/ConfigurablePropertyCodeGenerator.java
@@ -56,6 +56,8 @@ import org.apache.juneau.urlencoding.*;
 import org.apache.juneau.xml.*;
 
 public class ConfigurablePropertyCodeGenerator {
+	
+	private final String TODO = "Have this use code scanning.";
 
 	static Class<?>[] classes = new Class<?>[]{
 		AnnotationBuilder.class,
@@ -65,6 +67,7 @@ public class ConfigurablePropertyCodeGenerator {
 		BasicRuntimeExceptionBuilder.class,
 		BasicStatusLineBuilder.class,
 		BeanAssertion.class,
+		BeanListAssertion.class,
 		BeanContextBuilder.class,
 		BeanSessionArgs.class,
 		BeanStoreBuilder.class,
@@ -87,8 +90,8 @@ public class ConfigurablePropertyCodeGenerator {
 		FileFinderBuilder.class,
 		FluentArrayAssertion.class,
 		FluentAssertion.class,
-		FluentBaseAssertion.class,
 		FluentBeanAssertion.class,
+		FluentBeanListAssertion.class,
 		FluentBooleanAssertion.class,
 		FluentByteArrayAssertion.class,
 		FluentCollectionAssertion.class,
@@ -99,6 +102,7 @@ public class ConfigurablePropertyCodeGenerator {
 		FluentLongAssertion.class,
 		FluentMapAssertion.class,
 		FluentObjectAssertion.class,
+		FluentPrimitiveArrayAssertion.class,
 		FluentProtocolVersionAssertion.class,
 		FluentRequestBodyAssertion.class,
 		FluentRequestFormParamAssertion.class,
@@ -147,6 +151,7 @@ public class ConfigurablePropertyCodeGenerator {
 		ParserSessionArgs.class,
 		PlainTextParserBuilder.class,
 		PlainTextSerializerBuilder.class,
+		PrimitiveArrayAssertion.class,
 		RdfParserBuilder.class,
 		RdfSerializerBuilder.class,
 		ReaderParserBuilder.class,
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index 162fb83..fcc0c52 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -2028,6 +2028,9 @@ public class BeanContext extends Context implements MetaProvider {
 	/** Default config.  All default settings except sort bean properties. */
 	public static final BeanContext DEFAULT_SORTED = BeanContext.create().sortProperties().build();
 
+	/** Default reusable unmodifiable session.  Can be used to avoid overhead of creating a session (for creating BeanMaps for example).*/
+	public  static final BeanSession DEFAULT_SESSION = new BeanSession(DEFAULT, DEFAULT.createDefaultBeanSessionArgs().unmodifiable());
+
 	private final boolean
 		beansRequireDefaultConstructor,
 		beansRequireSerializable,
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
index cc3ae3b..7618992 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMap.java
@@ -83,8 +83,8 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T
 	 * @param bean The bean being wrapped.
 	 * @return A new {@link BeanMap} instance wrapping the bean.
 	 */
-	public static <T> BeanMap<T> create(T bean) {
-		return BeanContext.DEFAULT.createBeanSession().toBeanMap(bean);
+	public static <T> BeanMap<T> of(T bean) {
+		return BeanContext.DEFAULT_SESSION.toBeanMap(bean);
 	}
 
 	/**
@@ -460,7 +460,7 @@ public class BeanMap<T> extends AbstractMap<String,Object> implements Delegate<T
 	 * 	A new map with fields as key-value pairs.
 	 * 	<br>Note that modifying the values in this map will also modify the underlying bean.
 	 */
-	public Map<String,Object> getFields(String...fields) {
+	public Map<String,Object> getProperties(String...fields) {
 		return new FilteredMap<>(null, this, fields);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
index 98ff9b6..1747c93 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSession.java
@@ -49,7 +49,7 @@ public class BeanSession extends Session {
 
 	private final BeanContext ctx;
 	private final HttpPartSchema schema;
-	private Stack<StringBuilder> sbStack = new Stack<>();
+	private final Stack<StringBuilder> sbStack;
 
 	/**
 	 * Create a new session using properties specified in the context.
@@ -64,6 +64,7 @@ public class BeanSession extends Session {
 		super(ctx, args);
 		this.ctx = ctx;
 		schema = args.schema;
+		sbStack = args.unmodifiable ? null : new Stack<>();
 	}
 
 	/**
@@ -1108,7 +1109,7 @@ public class BeanSession extends Session {
 	 * @return A new or previously returned string builder.
 	 */
 	protected final StringBuilder getStringBuilder() {
-		if (sbStack.isEmpty())
+		if (sbStack == null || sbStack.isEmpty())
 			return new StringBuilder();
 		return sbStack.pop();
 	}
@@ -1122,7 +1123,8 @@ public class BeanSession extends Session {
 		if (sb == null)
 			return;
 		sb.setLength(0);
-		sbStack.push(sb);
+		if (sbStack != null)
+			sbStack.push(sb);
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSessionArgs.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSessionArgs.java
index 330818e..ac0d232 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSessionArgs.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanSessionArgs.java
@@ -105,6 +105,12 @@ public class BeanSessionArgs extends SessionArgs {
 		return this;
 	}
 
+	@Override /* GENERATED - SessionArgs */
+	public BeanSessionArgs unmodifiable() {
+		super.unmodifiable();
+		return this;
+	}
+
 	// </FluentSetters>
 
 	@Override /* SessionArgs */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextPropertiesBuilder.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextPropertiesBuilder.java
index 6bc5ad4..337c3ec 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextPropertiesBuilder.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/ContextPropertiesBuilder.java
@@ -611,7 +611,7 @@ public class ContextPropertiesBuilder {
 		Object o = peek(key);
 		if (o == null)
 			return null;
-		return BeanContext.DEFAULT.createBeanSession().convertToType(o, c);
+		return BeanContext.DEFAULT_SESSION.convertToType(o, c);
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Session.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Session.java
index 8e03a7b..0a825f5 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Session.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Session.java
@@ -41,6 +41,7 @@ public abstract class Session {
 	private final Locale locale;
 	private final TimeZone timeZone;
 	private final MediaType mediaType;
+	private final boolean unmodifiable;
 
 
 	/**
@@ -52,7 +53,11 @@ public abstract class Session {
 	 */
 	protected Session(Context ctx, SessionArgs args) {
 		this.ctx = ctx;
-		SessionProperties sp = this.properties = args.properties;
+		this.unmodifiable = args.unmodifiable;
+		SessionProperties sp = args.properties;
+		if (args.unmodifiable)
+			sp = sp.unmodifiable();
+		properties = sp;
 		debug = sp.get(CONTEXT_debug, Boolean.class).orElse(ctx.isDebug());
 		locale = sp.get(CONTEXT_locale, Locale.class).orElse(ctx.getDefaultLocale());
 		timeZone = sp.get(CONTEXT_timeZone, TimeZone.class).orElse(ctx.getDefaultTimeZone());
@@ -87,6 +92,8 @@ public abstract class Session {
 	 * @param val The cached object.
 	 */
 	public final void addToCache(String key, Object val) {
+		if (unmodifiable)
+			return;
 		if (cache == null)
 			cache = new TreeMap<>();
 		cache.put(key, val);
@@ -103,6 +110,8 @@ public abstract class Session {
 	 * 	No-op if <jk>null</jk>.
 	 */
 	public final void addToCache(Map<String,Object> cacheObjects) {
+		if (unmodifiable)
+			return;
 		if (cacheObjects != null) {
 			if (cache == null)
 				cache = new TreeMap<>();
@@ -129,6 +138,8 @@ public abstract class Session {
 	 * @param args Optional {@link MessageFormat}-style arguments.
 	 */
 	public void addWarning(String msg, Object... args) {
+		if (unmodifiable)
+			return;
 		if (warnings == null)
 			warnings = new LinkedList<>();
 		warnings.add((warnings.size() + 1) + ": " + format(msg, args));
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionArgs.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionArgs.java
index afe8a3b..d039a6e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionArgs.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionArgs.java
@@ -28,6 +28,7 @@ import org.apache.juneau.json.*;
 public class SessionArgs {
 
 	SessionProperties properties = SessionProperties.create();
+	boolean unmodifiable;
 
 	/**
 	 * Constructor.
@@ -122,6 +123,20 @@ public class SessionArgs {
 	}
 
 	/**
+	 * Create an unmodifiable session.
+	 *
+	 * <p>
+	 * The created Session object will be unmodifiable which makes it suitable for caching and reuse.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	@FluentSetter
+	public SessionArgs unmodifiable() {
+		this.unmodifiable = true;
+		return this;
+	}
+
+	/**
 	 * Session-level properties.
 	 *
 	 * <p>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionProperties.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionProperties.java
index 921758d..b2c5353 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionProperties.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/SessionProperties.java
@@ -50,7 +50,23 @@ public class SessionProperties {
 	 * Constructor.
 	 */
 	private SessionProperties(Map<String,Object> inner) {
-		this.map = inner == null ? new OMap() : inner instanceof OMap ? (OMap)inner : new OMap(inner);
+		this(inner, false);
+	}
+
+	private SessionProperties(Map<String,Object> inner, boolean unmodifiable) {
+		OMap m = inner == null ? new OMap() : inner instanceof OMap ? (OMap)inner : new OMap(inner);
+		if (unmodifiable)
+			 m = m.unmodifiable();
+		this.map = m;
+	}
+
+	/**
+	 * Returns an unmodifiable copy of these properties.
+	 *
+	 * @return An unmodifiable copy of these properties.
+	 */
+	public final SessionProperties unmodifiable() {
+		return new SessionProperties(map, true);
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Version.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Version.java
similarity index 68%
rename from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Version.java
rename to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Version.java
index 194e733..a37b8cd 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/Version.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/Version.java
@@ -10,7 +10,7 @@
 // * "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.internal;
+package org.apache.juneau;
 
 import static org.apache.juneau.internal.StringUtils.*;
 
@@ -26,10 +26,6 @@ import static java.util.Optional.*;
  */
 public class Version implements Comparable<Version> {
 
-	static {
-		boolean NEEDS_TESTING = true;
-	}
-
 	private int[] parts;
 
 	/**
@@ -89,7 +85,7 @@ public class Version implements Comparable<Version> {
 	 * @return The version part, never <jk>null</jk>.
 	 */
 	public Optional<Integer> getPart(int index) {
-		if (parts.length <= index)
+		if (index < 0 || parts.length <= index)
 			return empty();
 		return ofNullable(parts[index]);
 	}
@@ -124,12 +120,29 @@ public class Version implements Comparable<Version> {
 	/**
 	 * Returns <jk>true</jk> if the specified version is at least this version.
 	 *
-	 * <p>
-	 * Note that the following is true:
+	 * <h5 class='section'>Example:</h5>
 	 * <p class='bcode w800'>
-	 * 	boolean b;
-	 * 	b = <jk>new</jk> Version(<js>"1.2"</js>).isAtLeast(<jk>new</jk> Version(<js>"1.2.3"</js>)); <jc>// == true </jc>
-	 * 	b = <jk>new</jk> Version(<js>"1.2.0"</js>).isAtLeast(<jk>new</jk> Version(<js>"1.2.3"</js>)); <jc>// == false</jc>
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1"</js>)));
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"2"</js>)));
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.0"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
+	 * </p>
+	 *
+	 * @param v The version to compare to.
+	 * @return <jk>true</jk> if the specified version is at least this version.
+	 */
+	public boolean isAtLeast(Version v) {
+		return isAtLeast(v, false);
+	}
+
+
+	/**
+	 * Returns <jk>true</jk> if the specified version is at least this version.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>false</jk>)));
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtLeast(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>true</jk>)));
 	 * </p>
 	 *
 	 * @param v The version to compare to.
@@ -153,12 +166,28 @@ public class Version implements Comparable<Version> {
 	/**
 	 * Returns <jk>true</jk> if the specified version is at most this version.
 	 *
-	 * <p>
-	 * Note that the following is true:
+	 * <h5 class='section'>Example:</h5>
 	 * <p class='bcode w800'>
-	 * 	boolean b;
-	 * 	b = <jk>new</jk> Version(<js>"1.2.3"</js>).isAtMost(<jk>new</jk> Version(<js>"1.2"</js>)); <jc>// == true </jc>
-	 * 	b = <jk>new</jk> Version(<js>"1.2.3"</js>).isAtMost(<jk>new</jk> Version(<js>"1.2.0"</js>)); <jc>// == false</jc>
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1"</js>)));
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"2"</js>)));
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2"</js>)));
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.0"</js>)));
+	 * </p>
+	 *
+	 * @param v The version to compare to.
+	 * @return <jk>true</jk> if the specified version is at most this version.
+	 */
+	public boolean isAtMost(Version v) {
+		return isAtMost(v, false);
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the specified version is at most this version.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>false</jk>)));
+	 * 	<jsm>assertFalse</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isAtMost(Version.<jsm>of</jsm>(<js>"1.2.3"</js>, <jk>true</jk>)));
 	 * </p>
 	 *
 	 * @param v The version to compare to.
@@ -175,19 +204,17 @@ public class Version implements Comparable<Version> {
 		}
 		for (int i = parts.length; i < v.parts.length; i++)
 			if (v.parts[i] > 0)
-				return false;
+				return true;
 		return ! exclusive;
 	}
 
 	/**
 	 * Returns <jk>true</jk> if the specified version is equal to this version.
 	 *
-	 * <p>
-	 * Note that the following is true:
+	 * <h5 class='section'>Example:</h5>
 	 * <p class='bcode w800'>
-	 * 	boolean b;
-	 * 	b = <jk>new</jk> Version(<js>"1.2.3"</js>).equals(<jk>new</jk> Version(<js>"1.2"</js>)); <jc>// == true </jc>
-	 * 	b = <jk>new</jk> Version(<js>"1.2"</js>).equals(<jk>new</jk> Version(<js>"1.2.3"</js>)); <jc>// == true</jc>
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2.3"</js>)));
+	 * 	<jsm>assertTrue</jsm>(Version.<jsm>of</jsm>(<js>"1.2.3"</js>).isEqualsTo(Version.<jsm>of</jsm>(<js>"1.2"</js>)));
 	 * </p>
 	 *
 	 * @param v The version to compare to.
@@ -212,9 +239,6 @@ public class Version implements Comparable<Version> {
 			if (c != 0)
 				return c;
 		}
-		for (int i = parts.length; i < v.parts.length; i++)
-			if (v.parts[i] > 0)
-				return 1;
-		return 0;
+		return parts.length - v.parts.length;
 	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java
index 4e67a8e..2a58fa9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java
@@ -21,12 +21,16 @@ import org.apache.juneau.internal.*;
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bcode w800'>
- * 	String[] <jv>array</jv> = <jk>new</jk> String[]{<js>"foo"</js>};
+ * 	<jk>import static</jk> org.apache.juneau.assertions.Assertions.*;
+ * 
+ * 	String[] <jv>array</jv> = {<js>"foo"</js>};
  * 	<jsm>assertArray</jsm>(<jv>array</jv>).exists().isSize(1);
  * </p>
+ *
+ * @param <E> The element type.
  */
-@FluentSetters(returns="ArrayAssertion")
-public class ArrayAssertion extends FluentArrayAssertion<ArrayAssertion> {
+@FluentSetters(returns="ArrayAssertion<E>")
+public class ArrayAssertion<E> extends FluentArrayAssertion<E,ArrayAssertion<E>> {
 
 	/**
 	 * Creator.
@@ -34,8 +38,8 @@ public class ArrayAssertion extends FluentArrayAssertion<ArrayAssertion> {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ArrayAssertion} object.
 	 */
-	public static ArrayAssertion create(Object value) {
-		return new ArrayAssertion(value);
+	public static <E> ArrayAssertion<E> create(E[] value) {
+		return new ArrayAssertion<>(value);
 	}
 
 	/**
@@ -43,43 +47,38 @@ public class ArrayAssertion extends FluentArrayAssertion<ArrayAssertion> {
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ArrayAssertion(Object value) {
+	public ArrayAssertion(E[] value) {
 		super(value, null);
 	}
 
-	@Override
-	protected ArrayAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion msg(String msg, Object...args) {
+	public ArrayAssertion<E> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion out(PrintStream value) {
+	public ArrayAssertion<E> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion silent() {
+	public ArrayAssertion<E> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion stdout() {
+	public ArrayAssertion<E> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public ArrayAssertion<E> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
index b7be160..35b052d 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertion.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.ExceptionUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 
 import java.io.*;
+import java.lang.reflect.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.cp.*;
@@ -152,6 +153,17 @@ public class Assertion {
 	}
 
 	/**
+	 * Convenience method for getting the array class of the specified element type.
+	 *
+	 * @param c The object to get the class name for.
+	 * @return The class name for an object.
+	 */
+	@SuppressWarnings("unchecked")
+	protected static <E> Class<E[]> arrayClass(Class<E> c) {
+		return (Class<E[]>)Array.newInstance(c,0).getClass();
+	}
+
+	/**
 	 * Asserts the specified value is not null.
 	 *
 	 * @param value The value to check.
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicate.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicate.java
new file mode 100644
index 0000000..2678a99
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicate.java
@@ -0,0 +1,189 @@
+// ***************************************************************************************************************************
+// * 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.assertions;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.util.function.*;
+
+/**
+ * Wrapper around a {@link Predicate} that allows for an error message for when the predicate fails.
+ *
+ * @param <T> the type of input being tested.
+ */
+public class AssertionPredicate<T> implements Predicate<T> {
+
+	final Predicate<T> inner;
+	final String message;
+	final ThreadLocal<String> failedMessage = new ThreadLocal<>();
+
+	/**
+	 * Constructor.
+	 *
+	 * @param inner The predicate test.
+	 * @param message
+	 * 	The error message if predicate fails.
+	 * 	<br>Can contain <c>{VALUE}</c> variable.
+	 * @param args Optional message arguments.
+	 */
+	public AssertionPredicate(Predicate<T> inner, String message, Object...args) {
+		this.inner = inner;
+		this.message = format(message, args);
+	}
+
+	AssertionPredicate() {
+		this.inner = null;
+		this.message = null;
+	}
+
+	@Override /* Predicate */
+	public boolean test(T t) {
+		failedMessage.remove();
+		boolean b = inner.test(t);
+		if (! b) {
+			String m = message.replace("{VALUE}", stringify(t));
+			if (inner instanceof AssertionPredicate)
+				m += "\n\t" + ((AssertionPredicate<?>)inner).getFailureMessage();
+			failedMessage.set(m);
+		}
+		return inner.test(t);
+	}
+
+	/**
+	 * Returns the error message from the last call to this assertion.
+	 *
+	 * @return The error message, or <jk>null</jk> if there was no failure.
+	 */
+	public String getFailureMessage() {
+		return failedMessage.get();
+	}
+
+	/**
+	 * Encapsulates multiple predicates into a single AND operation.
+	 *
+	 * <p>
+	 * Similar to <c><jsm>stream</jsm>(<jv>predicates<jv>).reduce(<jv>x</jv>-><jk>true</jk>, Predicate::and)</c> but
+	 * provides for {@link #getFailureMessage()} to return a useful message.
+	 *
+	 * @param <T> the type of input being tested.
+	 */
+	public static class And<T> extends AssertionPredicate<T> {
+
+		private final Predicate<T>[] inner;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param inner The inner predicates to run.
+		 */
+		@SafeVarargs
+		public And(Predicate<T>...inner) {
+			this.inner = inner;
+		}
+
+		@Override /* Predicate */
+		public boolean test(T t) {
+			failedMessage.remove();
+			for (int i = 0; i < inner.length; i++) {
+				Predicate<T> p = inner[i];
+				if (p != null) {
+					boolean b = p.test(t);
+					if (! b) {
+						String m = format("Predicate test #{0} failed.", i);
+						if (p instanceof AssertionPredicate)
+							m += "\n\t" + ((AssertionPredicate<?>)p).getFailureMessage();
+						failedMessage.set(m);
+						return false;
+					}
+				}
+			}
+			return true;
+		}
+	}
+
+	/**
+	 * Encapsulates multiple predicates into a single OR operation.
+	 *
+	 * <p>
+	 * Similar to <c><jsm>stream</jsm>(<jv>predicates<jv>).reduce(<jv>x</jv>-><jk>true</jk>, Predicate::or)</c> but
+	 * provides for {@link #getFailureMessage()} to return a useful message.
+	 *
+	 * @param <T> the type of input being tested.
+	 */
+	public static class Or<T> extends AssertionPredicate<T> {
+
+		private final Predicate<T>[] inner;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param inner The inner predicates to run.
+		 */
+		@SafeVarargs
+		public Or(Predicate<T>...inner) {
+			this.inner = inner;
+		}
+
+		@Override /* Predicate */
+		public boolean test(T t) {
+			failedMessage.remove();
+			for (Predicate<T> p : inner)
+				if (p != null)
+					if (p.test(t))
+						return true;
+			String m = format("No predicate tests passed.");
+			failedMessage.set(m);
+			return false;
+		}
+	}
+
+	/**
+	 * Negates an assertion.
+	 *
+	 * <p>
+	 * Similar to <c><jv>predicate</jv>.negate()</c> but provides for {@link #getFailureMessage()} to return a useful message.
+	 *
+	 * @param <T> the type of input being tested.
+	 */
+	public static class Not<T> extends AssertionPredicate<T> {
+
+		private final Predicate<T> inner;
+
+		/**
+		 * Constructor.
+		 *
+		 * @param inner The inner predicates to run.
+		 */
+		public Not(Predicate<T> inner) {
+			this.inner = inner;
+		}
+
+		@Override /* Predicate */
+		public boolean test(T t) {
+			failedMessage.remove();
+			Predicate<T> p = inner;
+			if (p != null) {
+				boolean b = p.test(t);
+				if (b) {
+					String m = format("Predicate test unexpectedly passed.");
+					if (p instanceof AssertionPredicate)
+						m += "\n\t" + ((AssertionPredicate<?>)p).getFailureMessage();
+					failedMessage.set(m);
+					return false;
+				}
+			}
+			return true;
+		}
+	}
+
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicates.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicates.java
new file mode 100644
index 0000000..3416ae6
--- /dev/null
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/AssertionPredicates.java
@@ -0,0 +1,205 @@
+// ***************************************************************************************************************************
+// * 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.assertions;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.util.*;
+import java.util.function.*;
+import java.util.regex.*;
+
+import org.apache.juneau.internal.*;
+
+/**
+ * Generic predicates that can be run.
+ *
+ * <h5 class='section'>Example:</h5>
+ * <p class='bcode w800'>
+ * 	<jc>// Asserts that a list contains te specified values.</jc>
+ * 	List&lt;Object&gt; <jv>myList</jv> = AList.<jsm>of</jsm>(...);
+ * 	<jsm>assertList</jsm>(<jv>myList</jv>)
+ * 		.passes(<jsm>eq</jsm>(<js>"foo"</js>), <jsm>any</jsm>(), <jsm>match</jsm>(<js>"bar*"</js>));
+ * </p>
+ */
+public class AssertionPredicates {
+
+	/**
+	 * Predicate that always returns <jk>true</jk>.
+	 *
+	 * <p>
+	 * Note that this typically has the same affect as a <jk>null</jk> predicate.
+	 *
+	 * @param <T> The object type being tested.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> any() {
+		return new AssertionPredicate<>(x -> true, null);
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value is not null.
+	 *
+	 * @param <T> The object type being tested.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> notNull() {
+		return new AssertionPredicate<>(x -> x != null, "Value was null.");
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value is null.
+	 *
+	 * @param <T> The object type being tested.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> isNull() {
+		return new AssertionPredicate<>(x -> x == null, "Value was not null.");
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value equals the specified value.
+	 *
+	 * <p>
+	 * Uses standard Java equality for testing.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> eq(Object value) {
+		return new AssertionPredicate<>(x -> Objects.equals(x, value), "Value did not match expected.  Expected=''{0}'', Actual='{VALUE}'.", value);
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string matches the specified value.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> eq(String value) {
+		return new AssertionPredicate<>(x -> Objects.equals(stringify(x), value), "Value did not match expected.  Expected=''{0}'', Actual='{VALUE}'.", value);
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value does not match the specified value.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> ne(Object value) {
+		return new AssertionPredicate<>(x -> ! Objects.equals(x, value), "Value unexpectedly matched.  Value='{VALUE}'.");
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string does not match the specified value.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> ne(String value) {
+		return new AssertionPredicate<>(x -> ! Objects.equals(stringify(x), value), "Value unexpectedly matched.  Value='{VALUE}'.");
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string does not match the specified value ignoring case.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> eqic(String value) {
+		return new AssertionPredicate<>(x -> StringUtils.eqic(stringify(x), value), "Value did not match expected.  Expected=''{0}'', Actual='{VALUE}'.", value);
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value is the specified type.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param type The specified type.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> type(Class<?> type) {
+		return new AssertionPredicate<>(x -> x.getClass().isAssignableFrom(type), "Value was not expected type  Expected=''{0}'', Actual='{VALUE}'.", type);
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string matches the specified match pattern.
+	 *
+	 * <p>
+	 * Match pattern can contain the <js>"*"</js> meta-character.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The specified value.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> match(String value) {
+		return regex(StringUtils.getMatchPattern(value));
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string matches the specified regular expression.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param expression The regular expression to match.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> regex(String expression) {
+		return regex(Pattern.compile(expression));
+	}
+
+	/**
+	 * Predicate that returns <jk>true</jk> if the tested value converted to a string matches the specified regular expression.
+	 *
+	 * @param <T> The object type being tested.
+	 * @param value The regular expression to match.
+	 * @return A new predicate.
+	 */
+	public static final <T> Predicate<T> regex(Pattern value) {
+		return new AssertionPredicate<>(x -> x != null && value.matcher(stringify(x)).matches(), "Value did not match pattern.  Pattern=''{0}'', Actual='{VALUE}'", value.pattern());
+	}
+
+	/**
+	 * Combines the specified predicates into a singled AND'ed predicate.
+	 *
+	 * @param predicates The predicates to combine.
+	 * @return The combined predicates.
+	 */
+	@SafeVarargs
+	public static final <T> Predicate<T> and(Predicate<T>...predicates) {
+		return new AssertionPredicate.And<>(predicates);
+	}
+
+	/**
+	 * Combines the specified predicates into a singled OR'ed predicate.
+	 *
+	 * @param predicates The predicates to combine.
+	 * @return The combined predicates.
+	 */
+	@SafeVarargs
+	public static final <T> Predicate<T> or(Predicate<T>...predicates) {
+		return new AssertionPredicate.Or<>(predicates);
+	}
+
+	/**
+	 * Negates the specified predicate.
+	 *
+	 * @param predicate The predicate to negate.
+	 * @return The combined predicates.
+	 */
+	public static final <T> Predicate<T> not(Predicate<T> predicate) {
+		return new AssertionPredicate.Not<>(predicate);
+	}
+}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
index cca32e6..e728bfb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/Assertions.java
@@ -20,7 +20,6 @@ import java.time.*;
 import java.util.*;
 
 import org.apache.juneau.*;
-import org.apache.juneau.internal.*;
 
 /**
  * Main class for creation of assertions for testing.
@@ -204,7 +203,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link LongAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static final ComparableAssertion assertComparable(Comparable<?> value) {
+	public static final <T extends Comparable<T>> ComparableAssertion<T> assertComparable(T value) {
 		return ComparableAssertion.create(value);
 	}
 
@@ -220,7 +219,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ObjectAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static final <V> ObjectAssertion<V> assertObject(V value) {
+	public static final <T> ObjectAssertion<T> assertObject(T value) {
 		return ObjectAssertion.create(value);
 	}
 
@@ -275,6 +274,24 @@ public class Assertions {
 	}
 
 	/**
+	 * Used for assertion calls against lists of Java beans.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the specified list contains 3 beans with the specified values for the 'foo' property.</jc>
+	 * 	<jsm>assertBeanList</jsm>(<jv>myBeanList</jv>)
+	 * 		.property(<js>"foo"</js>)
+	 * 		.is(<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>);
+	 * </p>
+	 *
+	 * @param value The object being wrapped.
+	 * @return A new {@link BeanListAssertion} object.  Never <jk>null</jk>.
+	 */
+	public static final <E> BeanListAssertion<E> assertBeanList(List<E> value) {
+		return BeanListAssertion.create(value);
+	}
+
+	/**
 	 * Used for assertion calls against string objects.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -357,22 +374,38 @@ public class Assertions {
 	}
 
 	/**
-	 * Used for assertion calls against arrays.
+	 * Used for assertion calls against Java object arrays.
 	 *
 	 * <h5 class='section'>Example:</h5>
 	 * <p class='bcode w800'>
-	 * 	String[] <jv>array</jv> = <jk>new</jk> String[]{<js>"foo"</js>};
+	 * 	String[] <jv>array</jv> = {<js>"foo"</js>};
 	 * 	<jsm>assertArray</jsm>(<jv>array</jv>).isSize(1);
 	 * </p>
 	 *
 	 * @param value The object being wrapped.
 	 * @return A new {@link ArrayAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static final ArrayAssertion assertArray(Object value) {
+	public static final <E> ArrayAssertion<E> assertArray(E[] value) {
 		return ArrayAssertion.create(value);
 	}
 
 	/**
+	 * Used for assertion calls against primitive arrays.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jk>int</jk>[] <jv>array</jv> = {1,2,3};
+	 * 	<jsm>assertPrimitiveArray</jsm>(<jv>array</jv>).isSize(3);
+	 * </p>
+	 *
+	 * @param value The object being wrapped.
+	 * @return A new {@link ArrayAssertion} object.  Never <jk>null</jk>.
+	 */
+	public static final <E> PrimitiveArrayAssertion<E> assertPrimitiveArray(E value) {
+		return PrimitiveArrayAssertion.create(value);
+	}
+
+	/**
 	 * Used for assertion calls against {@link Collection} objects.
 	 *
 	 * <h5 class='section'>Example:</h5>
@@ -384,7 +417,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link CollectionAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static final CollectionAssertion assertCollection(Collection<?> value) {
+	public static final <E> CollectionAssertion<E> assertCollection(Collection<E> value) {
 		return CollectionAssertion.create(value);
 	}
 
@@ -394,13 +427,13 @@ public class Assertions {
 	 * <h5 class='section'>Example:</h5>
 	 * <p class='bcode w800'>
 	 * 	List=&lt;String&gt; <jv>list</jv> = AList.<jsm>of</jsm>(<js>"foo"</js>);
-	 * 	<jsm>assertList</jsm>(<jv>list</jv>).item(0).isEqual(<js>"foo"</js>);
+	 * 	<jsm>assertList</jsm>(<jv>list</jv>).item(0).is(<js>"foo"</js>);
 	 * </p>
 	 *
 	 * @param value The object being wrapped.
 	 * @return A new {@link ListAssertion} object.  Never <jk>null</jk>.
 	 */
-	public static final ListAssertion assertList(List<?> value) {
+	public static final <E> ListAssertion<E> assertList(List<E> value) {
 		return ListAssertion.create(value);
 	}
 
@@ -416,8 +449,7 @@ public class Assertions {
 	 * @param value The object being wrapped.
 	 * @return A new {@link MapAssertion} object.  Never <jk>null</jk>.
 	 */
-	@SuppressWarnings("rawtypes")
-	public static final MapAssertion assertMap(Map value) {
+	public static final <K,V> MapAssertion<K,V> assertMap(Map<K,V> value) {
 		return MapAssertion.create(value);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
index e9a23fd..b0be0ce 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
@@ -22,13 +22,15 @@ import org.apache.juneau.internal.*;
  * <h5 class='section'>Example:</h5>
  * <p class='bcode w800'>
  * 	<jc>// Validates the specified POJO is the specified type and serializes to the specified value.</jc>
- * 	<jsm>assertBean</jsm>(<jv>myBean</jv>).isType(MyBean.<jk>class</jk>).fields(<js>"foo"</js>).asJson().is(<js>"{foo:'bar'}"</js>);
+ * 	<jsm>assertBean</jsm>(<jv>myBean</jv>)
+ * 	.isType(MyBean.<jk>class</jk>)
+ * 	.properties(<js>"foo,bar"</js>).asJson().is(<js>"{foo:1,bar:2}"</js>);
  * </p>
  *
- * @param <V> The bean type.
+ * @param <T> The bean type.
  */
-@FluentSetters(returns="BeanAssertion<V>")
-public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V>> {
+@FluentSetters(returns="BeanAssertion<T>")
+public class BeanAssertion<T> extends FluentBeanAssertion<T,BeanAssertion<T>> {
 
 	/**
 	 * Creator.
@@ -36,7 +38,7 @@ public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V
 	 * @param value The object being wrapped.
 	 * @return A new {@link BeanAssertion} object.
 	 */
-	public static <V> BeanAssertion<V> create(V value) {
+	public static <T> BeanAssertion<T> create(T value) {
 		return new BeanAssertion<>(value);
 	}
 
@@ -45,43 +47,38 @@ public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public BeanAssertion(Object value) {
+	public BeanAssertion(T value) {
 		super(value, null);
 	}
 
-	@Override
-	protected BeanAssertion<V> returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> msg(String msg, Object...args) {
+	public BeanAssertion<T> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> out(PrintStream value) {
+	public BeanAssertion<T> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> silent() {
+	public BeanAssertion<T> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> stdout() {
+	public BeanAssertion<T> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public BeanAssertion<T> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanListAssertion.java
similarity index 71%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanListAssertion.java
index e9a23fd..d2deab4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BeanListAssertion.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.assertions;
 
 import java.io.*;
+import java.util.*;
 
 import org.apache.juneau.internal.*;
 
@@ -21,23 +22,25 @@ import org.apache.juneau.internal.*;
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bcode w800'>
- * 	<jc>// Validates the specified POJO is the specified type and serializes to the specified value.</jc>
- * 	<jsm>assertBean</jsm>(<jv>myBean</jv>).isType(MyBean.<jk>class</jk>).fields(<js>"foo"</js>).asJson().is(<js>"{foo:'bar'}"</js>);
+ * 	<jc>// Validates the specified list contains 3 beans with the specified values for the 'foo' property.</jc>
+ * 	<jsm>assertBeanList</jsm>(<jv>myBeanList</jv>)
+ * 		.property(<js>"foo"</js>)
+ * 		.is(<js>"bar"</js>,<js>"baz"</js>,<js>"qux"</js>);
  * </p>
  *
- * @param <V> The bean type.
+ * @param <E> The bean type.
  */
-@FluentSetters(returns="BeanAssertion<V>")
-public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V>> {
+@FluentSetters(returns="BeanListAssertion<E>")
+public class BeanListAssertion<E> extends FluentBeanListAssertion<E,BeanListAssertion<E>> {
 
 	/**
 	 * Creator.
 	 *
 	 * @param value The object being wrapped.
-	 * @return A new {@link BeanAssertion} object.
+	 * @return A new {@link BeanListAssertion} object.
 	 */
-	public static <V> BeanAssertion<V> create(V value) {
-		return new BeanAssertion<>(value);
+	public static <E> BeanListAssertion<E> create(List<E> value) {
+		return new BeanListAssertion<>(value);
 	}
 
 	/**
@@ -45,43 +48,38 @@ public class BeanAssertion<V> extends FluentBeanAssertion<Object,BeanAssertion<V
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public BeanAssertion(Object value) {
+	public BeanListAssertion(List<E> value) {
 		super(value, null);
 	}
 
-	@Override
-	protected BeanAssertion<V> returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> msg(String msg, Object...args) {
+	public BeanListAssertion<E> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> out(PrintStream value) {
+	public BeanListAssertion<E> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> silent() {
+	public BeanListAssertion<E> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> stdout() {
+	public BeanListAssertion<E> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public BeanAssertion<V> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public BeanListAssertion<E> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BooleanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BooleanAssertion.java
index ef7aff1..7e6d71f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BooleanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/BooleanAssertion.java
@@ -41,11 +41,6 @@ public class BooleanAssertion extends FluentBooleanAssertion<BooleanAssertion> {
 		super(value, null);
 	}
 
-	@Override
-	protected BooleanAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ByteArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ByteArrayAssertion.java
index 37b8c74..e8db6e9 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ByteArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ByteArrayAssertion.java
@@ -47,11 +47,6 @@ public class ByteArrayAssertion extends FluentByteArrayAssertion<ByteArrayAssert
 		super(value, null);
 	}
 
-	@Override
-	protected ByteArrayAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/CollectionAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/CollectionAssertion.java
index 6f36b77..24afcaf 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/CollectionAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/CollectionAssertion.java
@@ -25,10 +25,11 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the specified list is not empty.</jc>
  * 	<jsm>assertCollection</jsm>(<jv>myList</jv>).isNotEmpty();
  * </p>
+ *
+ * @param <E> The element type.
  */
-@FluentSetters(returns="CollectionAssertion")
-@SuppressWarnings("rawtypes")
-public class CollectionAssertion extends FluentCollectionAssertion<CollectionAssertion> {
+@FluentSetters(returns="CollectionAssertion<E>")
+public class CollectionAssertion<E> extends FluentCollectionAssertion<E,CollectionAssertion<E>> {
 
 	/**
 	 * Creator.
@@ -36,8 +37,8 @@ public class CollectionAssertion extends FluentCollectionAssertion<CollectionAss
 	 * @param value The object being wrapped.
 	 * @return A new {@link CollectionAssertion} object.
 	 */
-	public static CollectionAssertion create(Collection value) {
-		return new CollectionAssertion(value);
+	public static <E> CollectionAssertion<E> create(Collection<E> value) {
+		return new CollectionAssertion<>(value);
 	}
 
 	/**
@@ -45,43 +46,38 @@ public class CollectionAssertion extends FluentCollectionAssertion<CollectionAss
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public CollectionAssertion(Collection value) {
+	public CollectionAssertion(Collection<E> value) {
 		super(value, null);
 	}
 
-	@Override
-	protected CollectionAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public CollectionAssertion msg(String msg, Object...args) {
+	public CollectionAssertion<E> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public CollectionAssertion out(PrintStream value) {
+	public CollectionAssertion<E> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public CollectionAssertion silent() {
+	public CollectionAssertion<E> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public CollectionAssertion stdout() {
+	public CollectionAssertion<E> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public CollectionAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public CollectionAssertion<E> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ComparableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ComparableAssertion.java
index e0f93ec..2bc592e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ComparableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ComparableAssertion.java
@@ -18,10 +18,11 @@ import org.apache.juneau.internal.*;
 
 /**
  * Used for assertion calls against comparable objects.
+ *
+ * @param <T> The comparable type.
  */
-@FluentSetters(returns="ComparableAssertion")
-@SuppressWarnings("rawtypes")
-public class ComparableAssertion extends FluentComparableAssertion<Comparable,ComparableAssertion> {
+@FluentSetters(returns="ComparableAssertion<T>")
+public class ComparableAssertion<T extends Comparable<T>> extends FluentComparableAssertion<T,ComparableAssertion<T>> {
 
 	/**
 	 * Creator.
@@ -29,8 +30,8 @@ public class ComparableAssertion extends FluentComparableAssertion<Comparable,Co
 	 * @param value The object being wrapped.
 	 * @return A new {@link ComparableAssertion} object.
 	 */
-	public static ComparableAssertion create(Comparable value) {
-		return new ComparableAssertion(value);
+	public static <T extends Comparable<T>> ComparableAssertion<T> create(T value) {
+		return new ComparableAssertion<>(value);
 	}
 
 	/**
@@ -38,43 +39,38 @@ public class ComparableAssertion extends FluentComparableAssertion<Comparable,Co
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ComparableAssertion(Comparable value) {
+	public ComparableAssertion(T value) {
 		super(value, null);
 	}
 
-	@Override
-	protected ComparableAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ComparableAssertion msg(String msg, Object...args) {
+	public ComparableAssertion<T> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ComparableAssertion out(PrintStream value) {
+	public ComparableAssertion<T> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ComparableAssertion silent() {
+	public ComparableAssertion<T> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ComparableAssertion stdout() {
+	public ComparableAssertion<T> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ComparableAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public ComparableAssertion<T> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/DateAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/DateAssertion.java
index 5da9f76..55c042e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/DateAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/DateAssertion.java
@@ -48,11 +48,6 @@ public class DateAssertion extends FluentDateAssertion<DateAssertion> {
 		super(value, null);
 	}
 
-	@Override
-	protected DateAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
index ff6f276..66d31c4 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
@@ -13,37 +13,24 @@
 package org.apache.juneau.assertions;
 
 import static org.apache.juneau.internal.ObjectUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+import static java.util.Arrays.*;
 
 import java.io.*;
-import java.lang.reflect.*;
 import java.util.*;
 import java.util.function.*;
 
-import org.apache.juneau.*;
+import org.apache.juneau.collections.*;
 import org.apache.juneau.internal.*;
-import org.apache.juneau.marshall.*;
 
 /**
  * Used for fluent assertion calls against array objects.
  *
+ * @param <E> The entry type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentArrayAssertion<R>")
-public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
-
-	private static final Map<Class<?>,Function<Object,String>> STRINGIFIERS = new HashMap<>();
-	static {
-		STRINGIFIERS.put(boolean.class, (x) -> Arrays.toString((boolean[])x));
-		STRINGIFIERS.put(byte.class, (x) -> Arrays.toString((byte[])x));
-		STRINGIFIERS.put(char.class, (x) -> Arrays.toString((char[])x));
-		STRINGIFIERS.put(double.class, (x) -> Arrays.toString((double[])x));
-		STRINGIFIERS.put(float.class, (x) -> Arrays.toString((float[])x));
-		STRINGIFIERS.put(int.class, (x) -> Arrays.toString((int[])x));
-		STRINGIFIERS.put(long.class, (x) -> Arrays.toString((long[])x));
-		STRINGIFIERS.put(short.class, (x) -> Arrays.toString((short[])x));
-	}
-
-	private Object value;
+@FluentSetters(returns="FluentArrayAssertion<E,R>")
+public class FluentArrayAssertion<E,R> extends FluentObjectAssertion<E[],R> {
 
 	/**
 	 * Constructor.
@@ -51,7 +38,7 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentArrayAssertion(Object contents, R returns) {
+	public FluentArrayAssertion(E[] contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -62,19 +49,8 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentArrayAssertion(Assertion creator, Object contents, R returns) {
+	public FluentArrayAssertion(Assertion creator, E[] contents, R returns) {
 		super(creator, contents, returns);
-		if (contents != null && ! contents.getClass().isArray())
-			throw new BasicAssertionError("Object was not an array.  Actual=''{0}''", contents.getClass());
-		this.value = contents;
-	}
-
-	@Override /* FluentBaseAssertion */
-	public FluentStringAssertion<R> asString() {
-		String s = null;
-		if (value != null)
-			s = STRINGIFIERS.getOrDefault( value.getClass().getComponentType(), (x) -> Arrays.toString((Object[])x)).apply(value);
-		return new FluentStringAssertion<>(this, s, returns());
 	}
 
 	/**
@@ -84,8 +60,7 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEmpty() throws AssertionError {
-		exists();
-		if (Array.getLength(value) != 0)
+		if (length() != 0)
 			throw error("Array was not empty.");
 		return returns();
 	}
@@ -97,8 +72,7 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isNotEmpty() throws AssertionError {
-		exists();
-		if (Array.getLength(value) == 0)
+		if (length() == 0)
 			throw error("Array was empty.");
 		return returns();
 	}
@@ -111,39 +85,50 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isSize(int size) throws AssertionError {
-		exists();
-		if (Array.getLength(value) != size)
-			throw error("Array did not have the expected size.  Expect={0}, Actual={1}.", size, Array.getLength(value));
+		if (length() != size)
+			throw error("Array did not have the expected size.  Expect={0}, Actual={1}.", size, length());
 		return returns();
 	}
 
 	/**
 	 * Asserts that the array contains the expected value.
 	 *
-	 * @param value The value to check for.
+	 * @param entry The value to check for.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R contains(Object entry) throws AssertionError {
+		for (int i = 0, j = length(); i < j; i++)
+			if (eq(at(i), entry))
+				return returns();
+		throw error("Array did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", toString(), entry);
+	}
+
+	/**
+	 * Asserts that the array contains the expected value when value is converted to a string.
+	 *
+	 * @param entry The value to check for.
 	 * @return The object to return after the test.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public R contains(Object value) throws AssertionError {
-		exists();
-		for (int i = 0; i < Array.getLength(this.value); i++)
-			if (eq(Array.get(this.value, i), value))
+	public R contains(String entry) throws AssertionError {
+		for (int i = 0, j = length(); i < j; i++)
+			if (eq(stringify(at(i)), entry))
 				return returns();
-		throw error("Array did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+		throw error("Array did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", toString(), entry);
 	}
 
 	/**
 	 * Asserts that the array does not contain the expected value.
 	 *
-	 * @param value The value to check for.
+	 * @param entry The value to check for.
 	 * @return The object to return after the test.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public R doesNotContain(Object value) throws AssertionError {
-		exists();
-		for (int i = 0; i < Array.getLength(this.value); i++)
-			if (eq(Array.get(this.value, i), value))
-				throw error("Array contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+	public R doesNotContain(Object entry) throws AssertionError {
+		for (int i = 0, j = length(); i < j; i++)
+			if (eq(at(i), entry))
+				throw error("Array contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", toString(), entry);
 		return returns();
 	}
 
@@ -157,62 +142,176 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param index The index of the item to retrieve from the array.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<Object,R> item(int index) {
-		return item(Object.class, index);
+	public FluentObjectAssertion<E,R> item(int index) {
+		return new FluentObjectAssertion<>(this, at(index), returns());
 	}
 
 	/**
-	 * Returns an object assertion on the item specified at the specified index.
+	 * Converts this assertion into a {@link FluentBeanListAssertion}.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Extracts the 'foo' property from an array of beans and validates their values.</jc>.
+	 * 	<jsm>assertObject<jsm>(myArrayOfBeans).asBeanList().property(<js>"foo"</js>).sorted().equals(<js>"value1"</js>,<js>"value2"</js>,<js>"value3"</js>);
+	 * </p>
+	 *
+	 * @return A new fluent string assertion.
+	 */
+	public FluentBeanListAssertion<E,R> asBeanList() {
+		return new FluentBeanListAssertion<>(this, toList(), returns());
+	}
+
+	/**
+	 * Sorts the entries in this list.
+	 *
+	 * @return A new list assertion.  The contents of the original list remain unchanged.
+	 */
+	public FluentListAssertion<E,R> sorted() {
+		return new FluentListAssertion<>(this, toSortedList(null), returns());
+	}
+
+	/**
+	 * Sorts the entries in this list using the specified comparator.
+	 *
+	 * @param comparator The comparator to use to sort the list.
+	 * @return A new list assertion.  The contents of the original list remain unchanged.
+	 */
+	public FluentListAssertion<E,R> sorted(Comparator<E> comparator) {
+		return new FluentListAssertion<>(this, toSortedList(comparator), returns());
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R equals(String...entries) throws AssertionError {
+		Predicate<E>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+ 		return passes(p);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
 	 *
 	 * <p>
-	 * If the array is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 * Equivalent to {@link #equals(String...)}
 	 *
-	 * @param type The value type.
-	 * @param index The index of the item to retrieve from the array.
-	 * @return A new assertion.
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R is(String...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SuppressWarnings("unchecked")
+	public R equals(E...entries) throws AssertionError {
+		Predicate<E>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+ 		return passes(p);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values.
+	 *
+	 * <p>
+	 * Equivalent to {@link #equals(String...)}
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
 	 */
-	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
-		Object v = getItem(index);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, v, returns());
-		throw error("Array value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
+	public R is(@SuppressWarnings("unchecked") E...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the contents of this list pass the specified tests.
+	 *
+	 * <p>
+	 * Equivalent to {@link #equals(String...)}
+	 *
+	 * @param tests The tests to run.  <jk>null</jk> entries are ignored.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SafeVarargs
+	public final R passes(Predicate<E>...tests) throws AssertionError {
+		isSize(tests.length);
+		for (int i = 0, j = length(); i < j; i++) {
+			Predicate<E> t = tests[i];
+			if (t != null && ! t.test(at(i)))
+				throw error("Array did not contain expected value at index {0}.\n\t{1}", i, getFailureMessage(t, at(i)));
+		}
+		return returns();
+	}
+
+	@Override /* FluentBaseAssertion */
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(this, toString(), returns());
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private int length() {
+		return value().length;
+	}
+
+	private List<E> toList() {
+		return valueIsNull() ? null : AList.of(value());
+	}
+
+	private List<E> toSortedList(Comparator<E> comparator) {
+		return valueIsNull() ? null : AList.of(value()).sortWith(comparator);
+	}
+
+	private E at(int index) {
+		return valueIsNull() || index >= length() ? null : value()[index];
 	}
 
-	private Object getItem(int index) {
-		if (value != null && Array.getLength(value) > index)
-			return Array.get(value, index);
-		return null;
+	@Override
+	public String toString() {
+		return valueIsNull() ? null : Arrays.toString(value());
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> msg(String msg, Object...args) {
+	public FluentArrayAssertion<E,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> out(PrintStream value) {
+	public FluentArrayAssertion<E,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> silent() {
+	public FluentArrayAssertion<E,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> stdout() {
+	public FluentArrayAssertion<E,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentArrayAssertion<E,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentAssertion.java
index 9f76314..2522c58 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentAssertion.java
@@ -42,8 +42,9 @@ public abstract class FluentAssertion<R> extends Assertion {
 	 *
 	 * @return The response object.
 	 */
+	@SuppressWarnings("unchecked")
 	protected R returns() {
-		return returns;
+		return returns != null ? returns : (R)this;
 	}
 
 	// <FluentSetters>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java
deleted file mode 100644
index 8fc7ac7..0000000
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBaseAssertion.java
+++ /dev/null
@@ -1,484 +0,0 @@
-// ***************************************************************************************************************************
-// * 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.assertions;
-
-import static org.apache.juneau.internal.ExceptionUtils.*;
-import static org.apache.juneau.internal.StringUtils.*;
-
-import java.io.*;
-import java.util.function.*;
-
-import org.apache.juneau.internal.*;
-import org.apache.juneau.json.*;
-import org.apache.juneau.marshall.*;
-import org.apache.juneau.reflect.*;
-import org.apache.juneau.serializer.*;
-
-/**
- * Used for fluent assertion calls against POJOs.
- *
- * @param <V> The object type.
- * @param <R> The return type.
- */
-@FluentSetters(returns="FluentBaseAssertion<V,R>")
-public class FluentBaseAssertion<V,R> extends FluentAssertion<R> {
-
-	private final V value;
-
-	private static JsonSerializer JSON = JsonSerializer.create()
-		.ssq()
-		.build();
-
-	private static JsonSerializer JSON_SORTED = JsonSerializer.create()
-		.ssq()
-		.sortProperties()
-		.sortCollections()
-		.sortMaps()
-		.build();
-
-	/**
-	 * Constructor.
-	 *
-	 * @param value The object being tested.
-	 * @param returns The object to return after the test.
-	 */
-	public FluentBaseAssertion(V value, R returns) {
-		this(null, value, returns);
-	}
-
-	/**
-	 * Constructor.
-	 *
-	 * @param creator The assertion that created this assertion.
-	 * @param value The object being tested.
-	 * @param returns The object to return after the test.
-	 */
-	public FluentBaseAssertion(Assertion creator, V value, R returns) {
-		super(creator, returns);
-		this.value = value;
-	}
-
-	/**
-	 * Asserts that the object is an instance of the specified class.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).isType(MyBean.<jk>class</jk>);
-	 * </p>
-	 *
-	 * @param parent The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isType(Class<?> parent) throws AssertionError {
-		exists();
-		assertNotNull("parent", parent);
-		if (! ClassInfo.of(value).isChildOf(parent))
-			throw error("Unexpected class.\n\tExpect=[{0}]\n\tActual=[{1}]", className(parent), className(value));
-		return returns();
-	}
-
-	/**
-	 * Converts this object to text using the specified serializer and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asString(XmlSerializer.<jsf>DEFAULT</jsf>).is(<js>"&lt;object>&lt;foo>bar&lt;/foo>&lt;baz>qux&lt;/baz>&lt;/object>"</js>);
-	 * </p>
-	 *
-	 * @param ws The serializer to use to convert the object to text.
-	 * @return A new fluent string assertion.
-	 */
-	public FluentStringAssertion<R> asString(WriterSerializer ws) {
-		try {
-			String s = ws.serialize(this.value);
-			return new FluentStringAssertion<>(this, s, returns());
-		} catch (SerializeException e) {
-			throw runtimeException(e);
-		}
-	}
-
-	/**
-	 * Converts this object to a string using {@link Object#toString} and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asString().is(<js>"foobar"</js>);
-	 * </p>
-	 *
-	 * @return A new fluent string assertion.
-	 */
-	public FluentStringAssertion<R> asString() {
-		return new FluentStringAssertion<>(this, value == null ? null : value.toString(), returns());
-	}
-
-	/**
-	 * Converts this object to a string using the specified function and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asString(<jv>x</jv>-><jv>x</jv>.toString()).is(<js>"foobar"</js>);
-	 * </p>
-	 *
-	 * @param function The conversion function.
-	 * @return A new fluent string assertion.
-	 */
-	public FluentStringAssertion<R> asString(Function<Object,String> function) {
-		return new FluentStringAssertion<>(this, function.apply(value), returns());
-	}
-
-	/**
-	 * Converts this object to a string using the specified function and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asString(MyBean.<jk>class</jk>,<jv>x</jv>-><jv>x</jv>.myBeanMethod()).is(<js>"foobar"</js>);
-	 * </p>
-	 *
-	 * @param c The class of the object being converted.
-	 * @param function The conversion function.
-	 * @param <T> The class of the object being converted.
-	 * @return A new fluent string assertion.
-	 */
-	@SuppressWarnings("unchecked")
-	public <T> FluentStringAssertion<R> asString(Class<T> c, Function<T,String> function) {
-		return new FluentStringAssertion<>(this, function.apply((T)value), returns());
-	}
-
-	/**
-	 * Converts this object to simplified JSON and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asJson().is(<js>"{foo:'bar',baz:'qux'}"</js>);
-	 * </p>
-	 *
-	 * @return A new fluent string assertion.
-	 */
-	public FluentStringAssertion<R> asJson() {
-		return asString(JSON);
-	}
-
-	/**
-	 * Converts this object to sorted simplified JSON and returns it as a new assertion.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asJsonSorted().is(<js>"{baz:'qux',foo:'bar'}"</js>);
-	 * </p>
-	 *
-	 * @return A new fluent string assertion.
-	 */
-	public FluentStringAssertion<R> asJsonSorted() {
-		return asString(JSON_SORTED);
-	}
-
-	/**
-	 * Verifies that two objects are equivalent after converting them both to JSON.
-	 *
-	 * @param o The object to compare against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isSameJsonAs(Object o) throws AssertionError {
-		return isSameSerializedAs(o, JSON);
-	}
-
-	/**
-	 * Verifies that two objects are equivalent after converting them both to sorted JSON.
-	 *
-	 * <p>
-	 * Properties, maps, and collections are all sorted on both objects before comparison.
-	 *
-	 * @param o The object to compare against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isSameSortedAs(Object o) {
-		return isSameSerializedAs(o, JSON_SORTED);
-	}
-
-	/**
-	 * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer.
-	 *
-	 * @param o The object to compare against.
-	 * @param serializer The serializer to use to serialize this object.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isSameSerializedAs(Object o, WriterSerializer serializer) {
-		try {
-			String s1 = serializer.serialize(this.value);
-			String s2 = serializer.serialize(o);
-			if (ne(s1, s2))
-				throw error("Unexpected comparison.\n\tExpect=[{0}]\n\tActual=[{1}]", s2, s1);
-		} catch (SerializeException e) {
-			throw runtimeException(e);
-		}
-		return returns();
-	}
-
-	/**
-	 * Asserts that the value equals the specified value.
-	 *
-	 * @param value The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isEqual(Object value) throws AssertionError {
-		if (this.value == value)
-			return returns();
-		exists();
-		if (! this.value.equals(equivalent(value)))
-			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
-		return returns();
-	}
-
-	/**
-	 * Asserts that the value equals the specified value.
-	 *
-	 * <p>
-	 * Equivalent to {@link #isEqual(Object)}.
-	 *
-	 * @param value The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R is(Object value) throws AssertionError {
-		return isEqual(equivalent(value));
-	}
-
-	/**
-	 * Asserts that the value equals the specified value.
-	 *
-	 * @param value The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R doesNotEqual(Object value) throws AssertionError {
-		if (this.value == null && value != null || this.value != null && value == null)
-			return returns();
-		if (this.value == null || this.value.equals(equivalent(value)))
-			throw error("Unexpected value.\n\tExpected not=[{0}]\n\tActual=[{1}]", value, this.value);
-		return returns();
-	}
-
-	/**
-	 * Asserts that the specified object is the same object as this object.
-	 *
-	 * @param value The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isSameObjectAs(Object value) throws AssertionError {
-		if (this.value == value)
-			return returns();
-		throw error("Not the same value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
-	}
-
-	/**
-	 * Asserts that the value passes the specified predicate test.
-	 *
-	 * @param test The predicate to use to test the value.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R passes(Predicate<V> test) throws AssertionError {
-		if (! test.test(value))
-			throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
-		return returns();
-	}
-
-	/**
-	 * Asserts that the object is not null.
-	 *
-	 * <p>
-	 * Equivalent to {@link #isNotNull()}.
-	 *
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R exists() throws AssertionError {
-		return isNotNull();
-	}
-
-	/**
-	 * Asserts that the object is null.
-	 *
-	 * <p>
-	 * Equivalent to {@link #isNotNull()}.
-	 *
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R doesNotExist() throws AssertionError {
-		return isNull();
-	}
-
-	/**
-	 * Asserts that the object is not null.
-	 *
-	 * <p>
-	 * Equivalent to {@link #isNotNull()}.
-	 *
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isNotNull() throws AssertionError {
-		if (value == null)
-			throw error("Value was null.");
-		return returns();
-	}
-
-	/**
-	 * Asserts that the object i null.
-	 *
-	 * <p>
-	 * Equivalent to {@link #isNotNull()}.
-	 *
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isNull() throws AssertionError {
-		if (value != null)
-			throw error("Value was not null.");
-		return returns();
-	}
-
-	/**
-	 * Asserts that the value equals the specified value.
-	 *
-	 * <p>
-	 * Equivalent to {@link #doesNotEqual(Object)}.
-	 *
-	 * @param value The value to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isNot(Object value) throws AssertionError {
-		return doesNotEqual(equivalent(value));
-	}
-
-	/**
-	 * Asserts that the value is one of the specified values.
-	 *
-	 * @param values The values to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isAny(Object...values) throws AssertionError {
-		exists();
-		for (Object v : values)
-			if (this.value.equals(equivalent(v)))
-				return returns();
-		throw error("Expected value not found.\n\tExpect=[{0}]\n\tActual=[{1}]", SimpleJson.DEFAULT.toString(values), value);
-	}
-
-	/**
-	 * Asserts that the value is one of the specified values.
-	 *
-	 * @param values The values to check against.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R isNotAny(Object...values) throws AssertionError {
-		exists();
-		for (Object v : values)
-			if (this.value.equals(equivalent(v)))
-				throw error("Unexpected value found.\n\tUnexpected=[{0}]\n\tActual=[{1}]", v, value);
-		return returns();
-	}
-
-	/**
-	 * Subclasses can override this method to provide special conversions on objects being compared.
-	 *
-	 * @param o The object to cast.
-	 * @return The cast object.
-	 */
-	protected Object equivalent(Object o) {
-		return o;
-	}
-
-	/**
-	 * Converts this object to a string using {@link Object#toString} and runs the {@link FluentStringAssertion#is(String)} on the result.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).is(<js>"foobar"</js>);
-	 * </p>
-	 *
-	 * @param value The expected string value.
-	 * @return This object (for method chaining).
-	 */
-	public R isString(String value) {
-		return asString().is(value);
-	}
-
-	/**
-	 * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result.
-	 *
-	 * <h5 class='section'>Example:</h5>
-	 * <p class='bcode w800'>
-	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
-	 * 	<jsm>assertObject<jsm>(myPojo).asJson().is(<js>"{foo:'bar',baz:'qux'}"</js>);
-	 * </p>
-	 *
-	 * @param value The expected string value.
-	 * @return This object (for method chaining).
-	 */
-	public R isJson(String value) {
-		return asJson().is(value);
-	}
-
-	// <FluentSetters>
-
-	@Override /* GENERATED - Assertion */
-	public FluentBaseAssertion<V,R> msg(String msg, Object...args) {
-		super.msg(msg, args);
-		return this;
-	}
-
-	@Override /* GENERATED - Assertion */
-	public FluentBaseAssertion<V,R> out(PrintStream value) {
-		super.out(value);
-		return this;
-	}
-
-	@Override /* GENERATED - Assertion */
-	public FluentBaseAssertion<V,R> silent() {
-		super.silent();
-		return this;
-	}
-
-	@Override /* GENERATED - Assertion */
-	public FluentBaseAssertion<V,R> stdout() {
-		super.stdout();
-		return this;
-	}
-
-	@Override /* GENERATED - Assertion */
-	public FluentBaseAssertion<V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
-		super.throwable(value);
-		return this;
-	}
-
-	// </FluentSetters>
-}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
index 5742d91..86b3e2f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanAssertion.java
@@ -12,6 +12,10 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static org.apache.juneau.internal.StringUtils.*;
+import static java.util.stream.Collectors.*;
+import static java.util.Arrays.*;
+
 import java.io.*;
 
 import org.apache.juneau.*;
@@ -20,13 +24,11 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against Java beans.
  *
- * @param <V> The bean type.
+ * @param <T> The bean type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentBeanAssertion<V,R>")
-public class FluentBeanAssertion<V,R> extends FluentBaseAssertion<Object,R> {
-
-	private final Object value;
+@FluentSetters(returns="FluentBeanAssertion<T,R>")
+public class FluentBeanAssertion<T,R> extends FluentObjectAssertion<T,R> {
 
 	/**
 	 * Constructor.
@@ -34,7 +36,7 @@ public class FluentBeanAssertion<V,R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentBeanAssertion(V value, R returns) {
+	public FluentBeanAssertion(T value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -45,9 +47,8 @@ public class FluentBeanAssertion<V,R> extends FluentBaseAssertion<Object,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentBeanAssertion(Assertion creator, V value, R returns) {
+	public FluentBeanAssertion(Assertion creator, T value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -57,75 +58,67 @@ public class FluentBeanAssertion<V,R> extends FluentBaseAssertion<Object,R> {
 	 * @param names The fields to extract.  Can also pass in comma-delimited lists.
 	 * @return The response object (for method chaining).
 	 */
-	public FluentMapAssertion<R> fields(String...names) {
-		exists();
-		names = StringUtils.split(names, ',');
-		return new FluentMapAssertion<>(this, BeanMap.create(value).getFields(names), returns());
+	public FluentMapAssertion<String,Object,R> mapOf(String...names) {
+		return new FluentMapAssertion<>(this, toBeanMap().getProperties(split(names, ',')), returns());
 	}
 
 	/**
-	 * Extracts the specified field as an {@link ObjectAssertion}.
+	 * Extracts the specified property as an {@link FluentObjectAssertion}.
 	 *
-	 * @param name The field to extract.  Can also pass in comma-delimited lists.
-	 * @return The response object (for method chaining).
+	 * @param name The property to extract.  Can also pass in comma-delimited lists.
+	 * @return An assertion of the property value.
 	 */
-	public FluentObjectAssertion<Object,R> field(String name) {
-		return field(Object.class, name);
+	public FluentObjectAssertion<Object,R> property(String name) {
+		return new FluentObjectAssertion<>(this, toBeanMap().get(name), returns());
 	}
 
 	/**
-	 * Returns an object assertion on the value specified at the specified key.
+	 * Extracts the specified property as an {@link FluentListAssertion}.
 	 *
-	 * <p>
-	 * If the map is <jk>null</jk> or the map doesn't contain the specified key, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
-	 *
-	 * @param type The value type.
-	 * @param name The bean property name.
-	 * @return A new assertion.
+	 * @param names The names of the properties to extract.  Can also pass in comma-delimited lists.
+	 * @return An assertion of the property values.
 	 */
-	@SuppressWarnings("unchecked")
-	public <E> FluentObjectAssertion<E,R> field(Class<E> type, String name) {
-		Object v = getField(name);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, (E)v, returns());
-		throw error("Bean property value not of expected type for property ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", name, type, v.getClass());
+	public FluentListAssertion<Object,R> properties(String...names) {
+		BeanMap<T> bm = toBeanMap();
+		return new FluentListAssertion<>(stream(names).map(x -> bm.get(x)).collect(toList()), returns());
 	}
 
-	private Object getField(String name) {
-		exists();
-		return BeanMap.create(value).get(name);
-	}
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
 
+	private BeanMap<T> toBeanMap() {
+		return BeanMap.of(value());
+	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<V,R> msg(String msg, Object...args) {
+	public FluentBeanAssertion<T,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<V,R> out(PrintStream value) {
+	public FluentBeanAssertion<T,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<V,R> silent() {
+	public FluentBeanAssertion<T,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<V,R> stdout() {
+	public FluentBeanAssertion<T,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentBeanAssertion<V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentBeanAssertion<T,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanListAssertion.java
similarity index 56%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanListAssertion.java
index d852b39..50ddcef 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBeanListAssertion.java
@@ -12,21 +12,22 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static java.util.stream.Collectors.*;
+
 import java.io.*;
 import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
- * Used for fluent assertion calls against lists.
+ * Used for fluent assertion calls against lists of beans.
  *
+ * @param <E> The bean type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentListAssertion<R>")
-@SuppressWarnings("rawtypes")
-public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
-
-	private List value;
+@FluentSetters(returns="FluentBeanListAssertion<E,R>")
+public class FluentBeanListAssertion<E,R> extends FluentListAssertion<E,R> {
 
 	/**
 	 * Constructor.
@@ -34,7 +35,7 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentListAssertion(List contents, R returns) {
+	public FluentBeanListAssertion(List<E> contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -45,77 +46,68 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentListAssertion(Assertion creator, List contents, R returns) {
+	public FluentBeanListAssertion(Assertion creator, List<E> contents, R returns) {
 		super(creator, contents, returns);
-		this.value = contents;
 	}
 
 	/**
-	 * Returns an object assertion on the item specified at the specified index.
-	 *
-	 * <p>
-	 * If the list is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 * Extracts the specified fields of this bean into a simple map of key/value pairs and returns it as
+	 * a new {@link FluentListAssertion} containing maps.
 	 *
-	 * @param index The index of the item to retrieve from the list.
-	 * @return A new assertion.
+	 * @param names The fields to extract.  Can also pass in comma-delimited lists.
+	 * @return The response object (for method chaining).
 	 */
-	public FluentObjectAssertion<Object,R> item(int index) {
-		return item(Object.class, index);
+	public FluentListAssertion<Map<String,Object>,R> extract(String...names) {
+		String[] n = StringUtils.split(names, ',');
+		return new FluentListAssertion<>(this, value().stream().map(x -> beanMap(x).getProperties(n)).collect(toList()), returns());
 	}
 
 	/**
-	 * Returns an object assertion on the item specified at the specified index.
+	 * Extracts the specified property from each entry in this list and returns it as a {@link FluentListAssertion}.
 	 *
-	 * <p>
-	 * If the list is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
-	 *
-	 * @param type The value type.
-	 * @param index The index of the item to retrieve from the list.
-	 * @return A new assertion.
+	 * @param name The field to extract.
+	 * @return The response object (for method chaining).
 	 */
-	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
-		Object v = getItem(index);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, v, returns());
-		throw error("List value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
+	public FluentListAssertion<Object,R> property(String name) {
+		return new FluentListAssertion<>(this, value().stream().map(x -> beanMap(x).get(name)).collect(toList()), returns());
 	}
 
-	private Object getItem(int index) {
-		if (value != null && value.size() > index)
-			return value.get(index);
-		return null;
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private static BeanMap<?> beanMap(Object o) {
+		return BeanMap.of(o);
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> msg(String msg, Object...args) {
+	public FluentBeanListAssertion<E,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> out(PrintStream value) {
+	public FluentBeanListAssertion<E,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> silent() {
+	public FluentBeanListAssertion<E,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> stdout() {
+	public FluentBeanListAssertion<E,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentBeanListAssertion<E,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBooleanAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBooleanAssertion.java
index 4d8a63e..e82fbb2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBooleanAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentBooleanAssertion.java
@@ -25,8 +25,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentBooleanAssertion<R>")
 public class FluentBooleanAssertion<R> extends FluentComparableAssertion<Boolean,R> {
 
-	private final Boolean value;
-
 	/**
 	 * Constructor.
 	 *
@@ -46,7 +44,6 @@ public class FluentBooleanAssertion<R> extends FluentComparableAssertion<Boolean
 	 */
 	public FluentBooleanAssertion(Assertion creator, Boolean value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -56,8 +53,7 @@ public class FluentBooleanAssertion<R> extends FluentComparableAssertion<Boolean
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isTrue() throws AssertionError {
-		exists();
-		if (value == false)
+		if (value() == false)
 			throw error("Value was false.");
 		return returns();
 	}
@@ -69,8 +65,7 @@ public class FluentBooleanAssertion<R> extends FluentComparableAssertion<Boolean
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isFalse() throws AssertionError {
-		exists();
-		if (value == true)
+		if (value() == true)
 			throw error("Value was true.");
 		return returns();
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentByteArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentByteArrayAssertion.java
index 9d950c1..e7d8c53 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentByteArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentByteArrayAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.internal.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentByteArrayAssertion<R>")
-public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
-
-	private byte[] value;
+public class FluentByteArrayAssertion<R> extends FluentPrimitiveArrayAssertion<byte[],R> {
 
 	/**
 	 * Constructor.
@@ -48,7 +46,6 @@ public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
 	 */
 	public FluentByteArrayAssertion(Assertion creator, byte[] contents, R returns) {
 		super(creator, contents, returns);
-		this.value = contents;
 	}
 
 	/**
@@ -80,7 +77,7 @@ public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
 	 * @return A new fluent string assertion.
 	 */
 	public FluentStringAssertion<R> asString(Charset cs) {
-		return new FluentStringAssertion<>(this, value == null ? null : new String(value, cs), returns());
+		return new FluentStringAssertion<>(this, valueIsNull() ? null : new String(value(), cs), returns());
 	}
 
 	/**
@@ -95,7 +92,7 @@ public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
 	 * @return A new fluent string assertion.
 	 */
 	public FluentStringAssertion<R> asBase64() {
-		return new FluentStringAssertion<>(this, value == null ? null : base64Encode(value), returns());
+		return new FluentStringAssertion<>(this, valueIsNull() ? null : base64Encode(value()), returns());
 	}
 
 	/**
@@ -110,7 +107,7 @@ public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
 	 * @return A new string consisting of hexadecimal characters.
 	 */
 	public FluentStringAssertion<R> asHex() {
-		return new FluentStringAssertion<>(this, value == null ? null : toHex(value), returns());
+		return new FluentStringAssertion<>(this, valueIsNull() ? null : toHex(value()), returns());
 	}
 
 	/**
@@ -125,7 +122,7 @@ public class FluentByteArrayAssertion<R> extends FluentArrayAssertion<R> {
 	 * @return A new string consisting of hexadecimal characters.
 	 */
 	public FluentStringAssertion<R> asSpacedHex() {
-		return new FluentStringAssertion<>(this, value == null ? null : toSpacedHex(value), returns());
+		return new FluentStringAssertion<>(this, valueIsNull() ? null : toSpacedHex(value()), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentCollectionAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentCollectionAssertion.java
index ed1a894..e04f92f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentCollectionAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentCollectionAssertion.java
@@ -18,18 +18,15 @@ import java.io.*;
 import java.util.*;
 
 import org.apache.juneau.internal.*;
-import org.apache.juneau.marshall.*;
 
 /**
  * Used for fluent assertion calls against collections objects.
  *
+ * @param <E> The element type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentCollectionAssertion<R>")
-@SuppressWarnings("rawtypes")
-public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection,R> {
-
-	private Collection value;
+@FluentSetters(returns="FluentCollectionAssertion<E,R>")
+public class FluentCollectionAssertion<E,R> extends FluentObjectAssertion<Collection<E>,R> {
 
 	/**
 	 * Constructor.
@@ -37,7 +34,7 @@ public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentCollectionAssertion(Collection contents, R returns) {
+	public FluentCollectionAssertion(Collection<E> contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -48,20 +45,18 @@ public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentCollectionAssertion(Assertion creator, Collection contents, R returns) {
+	public FluentCollectionAssertion(Assertion creator, Collection<E> contents, R returns) {
 		super(creator, contents, returns);
-		this.value = contents;
 	}
 
 	/**
 	 * Asserts that the collection exists and is empty.
 	 *
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isEmpty() throws AssertionError {
-		exists();
-		if (! value.isEmpty())
+		if (! value().isEmpty())
 			throw error("Collection was not empty.");
 		return returns();
 	}
@@ -69,30 +64,28 @@ public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection
 	/**
 	 * Asserts that the collection contains the expected value.
 	 *
-	 * @param value The value to check for.
+	 * @param entry The value to check for.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
-	public R contains(Object value) throws AssertionError {
-		exists();
-		for (Object o : this.value)
-			if (eq(o, value))
+	public R contains(E entry) throws AssertionError {
+		for (Object v : value())
+			if (eq(v, entry))
 				return returns();
-		throw error("Collection did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+		throw error("Collection did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", value(), entry);
 	}
 
 	/**
 	 * Asserts that the collection contains the expected value.
 	 *
-	 * @param value The value to check for.
+	 * @param entry The value to check for.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
-	public R doesNotContain(Object value) throws AssertionError {
-		exists();
-		for (Object o : this.value)
-			if (eq(o, value))
-				throw error("Collection contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+	public R doesNotContain(E entry) throws AssertionError {
+		for (Object v : value())
+			if (eq(v, entry))
+				throw error("Collection contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", value(), entry);
 		return returns();
 	}
 
@@ -100,11 +93,10 @@ public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection
 	 * Asserts that the collection exists and is not empty.
 	 *
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isNotEmpty() throws AssertionError {
-		exists();
-		if (value.isEmpty())
+		if (value().isEmpty())
 			throw error("Collection was empty.");
 		return returns();
 	}
@@ -114,43 +106,52 @@ public class FluentCollectionAssertion<R> extends FluentBaseAssertion<Collection
 	 *
 	 * @param size The expected size.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isSize(int size) throws AssertionError {
-		exists();
-		if (value.size() != size)
-			throw error("Collection did not have the expected size.  Expect={0}, Actual={1}.", size, value.size());
+		if (size() != size)
+			throw error("Collection did not have the expected size.  Expect={0}, Actual={1}.", size, size());
 		return returns();
 	}
 
+	/**
+	 * Returns the size of this collection if it is not <jk>null</jk>.
+	 *
+	 * @return the size of this collection if it is not <jk>null</jk>.
+	 * @throws AssertionError If value was <jk>null</jk>.
+	 */
+	protected int size() throws AssertionError {
+		return value().size();
+	}
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentCollectionAssertion<R> msg(String msg, Object...args) {
+	public FluentCollectionAssertion<E,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentCollectionAssertion<R> out(PrintStream value) {
+	public FluentCollectionAssertion<E,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentCollectionAssertion<R> silent() {
+	public FluentCollectionAssertion<E,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentCollectionAssertion<R> stdout() {
+	public FluentCollectionAssertion<E,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentCollectionAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentCollectionAssertion<E,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentComparableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentComparableAssertion.java
index cacf9d9..c6b5ddb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentComparableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentComparableAssertion.java
@@ -19,14 +19,12 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against comparable objects.
  *
- * @param <V> The value type
+ * @param <T> The value type
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentComparableAssertion<V,R>")
+@FluentSetters(returns="FluentComparableAssertion<T,R>")
 @SuppressWarnings("rawtypes")
-public class FluentComparableAssertion<V extends Comparable,R> extends FluentBaseAssertion<V,R> {
-
-	private final V value;
+public class FluentComparableAssertion<T extends Comparable,R> extends FluentObjectAssertion<T,R> {
 
 	/**
 	 * Constructor.
@@ -34,7 +32,7 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @param value The value being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentComparableAssertion(V value, R returns) {
+	public FluentComparableAssertion(T value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -45,9 +43,8 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @param value The value being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentComparableAssertion(Assertion creator, V value, R returns) {
+	public FluentComparableAssertion(Assertion creator, T value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -58,10 +55,9 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isGreaterThan(Comparable value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
 		if (compareTo(value) <= 0)
-			throw error("Value was not greater than expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+			throw error("Value was not greater than expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -87,10 +83,9 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isGreaterThanOrEqual(Comparable value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
 		if (compareTo(value) < 0)
-				throw error("Value was not greater than or equals to expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+				throw error("Value was not greater than or equals to expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -116,10 +111,9 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isLessThan(Comparable value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
 		if (compareTo(value) >= 0)
-				throw error("Value was not less than expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+				throw error("Value was not less than expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -145,10 +139,9 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isLessThanOrEqual(Comparable value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
 		if (compareTo(value) > 0)
-				throw error("Value was not less than or equals to expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+				throw error("Value was not less than or equals to expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -188,40 +181,40 @@ public class FluentComparableAssertion<V extends Comparable,R> extends FluentBas
 	 *
 	 * @param value The object to compare against.
 	 * @return The comparison value.
+	 * @throws AssertionError If value was <jk>null</jk>.
 	 */
-	@SuppressWarnings("unchecked")
-	protected int compareTo(Object value) {
-		return this.value.compareTo(equivalent(value));
+	protected int compareTo(Object value) throws AssertionError {
+		return value().compareTo(equivalent(value));
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentComparableAssertion<V,R> msg(String msg, Object...args) {
+	public FluentComparableAssertion<T,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentComparableAssertion<V,R> out(PrintStream value) {
+	public FluentComparableAssertion<T,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentComparableAssertion<V,R> silent() {
+	public FluentComparableAssertion<T,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentComparableAssertion<V,R> stdout() {
+	public FluentComparableAssertion<T,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentComparableAssertion<V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentComparableAssertion<T,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentDateAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentDateAssertion.java
index fd33f0f..28a095a 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentDateAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentDateAssertion.java
@@ -37,8 +37,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentDateAssertion<R>")
 public class FluentDateAssertion<R> extends FluentComparableAssertion<Date,R> {
 
-	private final Date value;
-
 	/**
 	 * Constructor.
 	 *
@@ -58,7 +56,6 @@ public class FluentDateAssertion<R> extends FluentComparableAssertion<Date,R> {
 	 */
 	public FluentDateAssertion(Assertion creator, Date value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -70,8 +67,8 @@ public class FluentDateAssertion<R> extends FluentComparableAssertion<Date,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEqual(Date value, ChronoUnit precision) throws AssertionError {
-		if (ne(this.value, value, (x,y)->x.toInstant().truncatedTo(precision).equals(y.toInstant().truncatedTo(precision))))
-			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		if (ne(value(), value, (x,y)->x.toInstant().truncatedTo(precision).equals(y.toInstant().truncatedTo(precision))))
+			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -83,10 +80,9 @@ public class FluentDateAssertion<R> extends FluentComparableAssertion<Date,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isAfter(Date value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
-		if (! (this.value.after(value)))
-			throw error("Value was not after expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		if (! (value().after(value)))
+			throw error("Value was not after expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -108,10 +104,9 @@ public class FluentDateAssertion<R> extends FluentComparableAssertion<Date,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isBefore(Date value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
-		if (! (this.value.before(value)))
-			throw error("Value was not before expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		if (! (value().before(value)))
+			throw error("Value was not before expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentIntegerAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentIntegerAssertion.java
index 220ba7e..86eab26 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentIntegerAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentIntegerAssertion.java
@@ -34,8 +34,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentIntegerAssertion<R>")
 public class FluentIntegerAssertion<R> extends FluentComparableAssertion<Integer,R> {
 
-	private final Integer value;
-
 	/**
 	 * Constructor.
 	 *
@@ -55,12 +53,11 @@ public class FluentIntegerAssertion<R> extends FluentComparableAssertion<Integer
 	 */
 	public FluentIntegerAssertion(Assertion creator, Integer value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	@Override
 	protected int compareTo(Object value) {
-		return this.value.compareTo(((Number)value).intValue());
+		return value().compareTo(((Number)value).intValue());
 	}
 
 	@Override
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
index d852b39..ae5c2bb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentListAssertion.java
@@ -12,21 +12,23 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static java.util.Arrays.*;
+
 import java.io.*;
 import java.util.*;
+import java.util.function.*;
 
+import org.apache.juneau.collections.*;
 import org.apache.juneau.internal.*;
 
 /**
  * Used for fluent assertion calls against lists.
  *
+ * @param <E> The element type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentListAssertion<R>")
-@SuppressWarnings("rawtypes")
-public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
-
-	private List value;
+@FluentSetters(returns="FluentListAssertion<E,R>")
+public class FluentListAssertion<E,R> extends FluentCollectionAssertion<E,R> {
 
 	/**
 	 * Constructor.
@@ -34,7 +36,7 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentListAssertion(List contents, R returns) {
+	public FluentListAssertion(List<E> contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -45,9 +47,8 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentListAssertion(Assertion creator, List contents, R returns) {
+	public FluentListAssertion(Assertion creator, List<E> contents, R returns) {
 		super(creator, contents, returns);
-		this.value = contents;
 	}
 
 	/**
@@ -60,62 +61,150 @@ public class FluentListAssertion<R> extends FluentCollectionAssertion<R> {
 	 * @param index The index of the item to retrieve from the list.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<Object,R> item(int index) {
-		return item(Object.class, index);
+	public FluentObjectAssertion<E,R> item(int index) {
+		return new FluentObjectAssertion<>(this, at(index), returns());
 	}
 
 	/**
-	 * Returns an object assertion on the item specified at the specified index.
+	 * Sorts the entries in this list.
+	 *
+	 * @return A new list assertion.  The contents of the original list remain unchanged.
+	 */
+	public FluentListAssertion<E,R> sorted() {
+		return new FluentListAssertion<>(this, toSortedList(null), returns());
+	}
+
+	/**
+	 * Sorts the entries in this list using the specified comparator.
+	 *
+	 * @param comparator The comparator to use to sort the list.
+	 * @return A new list assertion.  The contents of the original list remain unchanged.
+	 */
+	public FluentListAssertion<E,R> sorted(Comparator<E> comparator) {
+		return new FluentListAssertion<>(this, toSortedList(comparator), returns());
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R equals(String...entries) throws AssertionError {
+		Predicate<E>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+ 		return passes(p);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
 	 *
 	 * <p>
-	 * If the list is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 * Equivalent to {@link #equals(String...)}
 	 *
-	 * @param type The value type.
-	 * @param index The index of the item to retrieve from the list.
-	 * @return A new assertion.
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R is(String...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values when each entry is converted to a string.
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SuppressWarnings("unchecked")
+	public R equals(E...entries) throws AssertionError {
+		Predicate<E>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+ 		return passes(p);
+	}
+
+	/**
+	 * Asserts that the contents of this list contain the specified values.
+	 *
+	 * <p>
+	 * Equivalent to {@link #equals(String...)}
+	 *
+	 * @param entries The expected entries in this list.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
 	 */
-	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
-		Object v = getItem(index);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, v, returns());
-		throw error("List value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
+	public R is(@SuppressWarnings("unchecked") E...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the contents of this list pass the specified tests.
+	 *
+	 * <p>
+	 * Equivalent to {@link #equals(String...)}
+	 *
+	 * @param tests
+	 * 	The tests to run.
+	 * <jk>null</jk> predicates are ignored.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SafeVarargs
+	public final R passes(Predicate<E>...tests) throws AssertionError {
+		isSize(tests.length);
+		for (int i = 0, j = size(); i < j; i++) {
+			Predicate<E> t = tests[i];
+			if (t != null && ! t.test(at(i)))
+				throw error("List did not contain expected value at index {0}.\n\t{1}", i, getFailureMessage(t, at(i)));
+		}
+		return returns();
+	}
+
+	@Override
+	protected List<E> value() throws AssertionError {
+		return (List<E>)super.value();
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private E at(int index) throws AssertionError {
+		return valueIsNull() || index >= size() ? null : value().get(index);
 	}
 
-	private Object getItem(int index) {
-		if (value != null && value.size() > index)
-			return value.get(index);
-		return null;
+	private List<E> toSortedList(Comparator<E> comparator) {
+		return valueIsNull() ? null : AList.of(value()).sortWith(comparator);
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> msg(String msg, Object...args) {
+	public FluentListAssertion<E,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> out(PrintStream value) {
+	public FluentListAssertion<E,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> silent() {
+	public FluentListAssertion<E,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> stdout() {
+	public FluentListAssertion<E,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentListAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentListAssertion<E,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentLongAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentLongAssertion.java
index b7940f4..fce2175 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentLongAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentLongAssertion.java
@@ -34,8 +34,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentLongAssertion<R>")
 public class FluentLongAssertion<R> extends FluentComparableAssertion<Long,R> {
 
-	private final Long value;
-
 	/**
 	 * Constructor.
 	 *
@@ -55,7 +53,6 @@ public class FluentLongAssertion<R> extends FluentComparableAssertion<Long,R> {
 	 */
 	public FluentLongAssertion(Assertion creator, Long value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -63,13 +60,14 @@ public class FluentLongAssertion<R> extends FluentComparableAssertion<Long,R> {
 	 *
 	 * @return A new assertion.
 	 */
+	@Override
 	public FluentIntegerAssertion<R> asInteger() {
-		return new FluentIntegerAssertion<>(this, value == null ? null : value.intValue(), returns());
+		return new FluentIntegerAssertion<>(this, map(Long::intValue).orElse(null), returns());
 	}
 
 	@Override
 	protected int compareTo(Object value) {
-		return this.value.compareTo(((Number)value).longValue());
+		return value().compareTo(((Number)value).longValue());
 	}
 
 	@Override
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
index da7d053..c514165 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentMapAssertion.java
@@ -12,22 +12,24 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static java.util.Collections.*;
+import static java.util.stream.Collectors.*;
+import static java.util.Arrays.*;
+
 import java.io.*;
 import java.util.*;
 
 import org.apache.juneau.internal.*;
-import org.apache.juneau.marshall.*;
 
 /**
  * Used for fluent assertion calls against maps.
  *
+ * @param <K> The key type.
+ * @param <V> The value type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentMapAssertion<R>")
-@SuppressWarnings("rawtypes")
-public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
-
-	private Map value;
+@FluentSetters(returns="FluentMapAssertion<K,V,R>")
+public class FluentMapAssertion<K,V,R> extends FluentObjectAssertion<Map<K,V>,R>  {
 
 	/**
 	 * Constructor.
@@ -35,7 +37,7 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentMapAssertion(Map contents, R returns) {
+	public FluentMapAssertion(Map<K,V> contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -46,9 +48,8 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentMapAssertion(Assertion creator, Map contents, R returns) {
+	public FluentMapAssertion(Assertion creator, Map<K,V> contents, R returns) {
 		super(creator, contents, returns);
-		this.value = contents;
 	}
 
 	/**
@@ -61,43 +62,31 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	 * @param key The key of the item to retrieve from the map.
 	 * @return A new assertion.
 	 */
-	public FluentObjectAssertion<Object,R> value(String key) {
-		return value(Object.class, key);
+	public FluentObjectAssertion<V,R> value(K key) {
+		return new FluentObjectAssertion<>(this, get(key), returns());
 	}
 
 	/**
-	 * Returns an object assertion on the value specified at the specified key.
+	 * Returns a {@link FluentListAssertion} of the values of the specified keys.
 	 *
-	 * <p>
-	 * If the map is <jk>null</jk> or the map doesn't contain the specified key, the returned assertion is a null assertion
+	 * If the map is <jk>null</jk>, the returned assertion is a null assertion
 	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
 	 *
-	 * @param type The value type.
-	 * @param key The key of the item to retrieve from the map.
+	 * @param keys The keys of the values to retrieve from the map.
 	 * @return A new assertion.
 	 */
-	public <V> FluentObjectAssertion<Object,R> value(Class<V> type, String key) {
-		Object v = getValue(key);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, v, returns());
-		throw error("Map value not of expected type for key ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", key, type, v.getClass());
-	}
-
-	private Object getValue(String key) {
-		if (value != null)
-			return value.get(key);
-		return null;
+	public FluentListAssertion<Object,R> values(@SuppressWarnings("unchecked") K...keys) {
+		return new FluentListAssertion<>(stream(keys).map(x -> get(x)).collect(toList()), returns());
 	}
 
 	/**
 	 * Asserts that the map exists and is empty.
 	 *
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isEmpty() throws AssertionError {
-		exists();
-		if (! value.isEmpty())
+		if (! value().isEmpty())
 			throw error("Map was not empty.");
 		return returns();
 	}
@@ -105,40 +94,37 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	/**
 	 * Asserts that the map contains the expected key.
 	 *
-	 * @param value The value to check for.
+	 * @param name The key name to check for.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
-	public R containsKey(String value) throws AssertionError {
-		exists();
-		if (this.value.containsKey(value))
+	public R containsKey(String name) throws AssertionError {
+		if (value().containsKey(name))
 			return returns();
-		throw error("Map did not contain expected key.\n\tContents: {0}\n\tExpected key: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+		throw error("Map did not contain expected key.\n\tContents: {0}\n\tExpected key: {1}", value(), name);
 	}
 
 	/**
 	 * Asserts that the map contains the expected key.
 	 *
-	 * @param value The value to check for.
+	 * @param name The key name to check for.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
-	public R doesNotContainKey(String value) throws AssertionError {
-		exists();
-		if (! this.value.containsKey(value))
+	public R doesNotContainKey(String name) throws AssertionError {
+		if (! value().containsKey(name))
 			return returns();
-		throw error("Map contained unexpected key.\n\tContents: {0}\n\tUnexpected key: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+		throw error("Map contained unexpected key.\n\tContents: {0}\n\tUnexpected key: {1}", value(), name);
 	}
 
 	/**
 	 * Asserts that the map exists and is not empty.
 	 *
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isNotEmpty() throws AssertionError {
-		exists();
-		if (value.isEmpty())
+		if (value().isEmpty())
 			throw error("Map was empty.");
 		return returns();
 	}
@@ -148,43 +134,54 @@ public class FluentMapAssertion<R> extends FluentBaseAssertion<Map,R>  {
 	 *
 	 * @param size The expected size.
 	 * @return The object to return after the test.
-	 * @throws AssertionError If assertion failed.
+	 * @throws AssertionError If assertion failed or value was <jk>null</jk>.
 	 */
 	public R isSize(int size) throws AssertionError {
-		exists();
-		if (value.size() != size)
-			throw error("Map did not have the expected size.  Expect={0}, Actual={1}.", size, value.size());
+		if (size() != size)
+			throw error("Map did not have the expected size.  Expect={0}, Actual={1}.", size, size());
 		return returns();
 	}
 
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	private V get(K key) {
+		return orElse(emptyMap()).get(key);
+	}
+
+	private int size() {
+		return value().size();
+	}
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentMapAssertion<R> msg(String msg, Object...args) {
+	public FluentMapAssertion<K,V,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentMapAssertion<R> out(PrintStream value) {
+	public FluentMapAssertion<K,V,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentMapAssertion<R> silent() {
+	public FluentMapAssertion<K,V,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentMapAssertion<R> stdout() {
+	public FluentMapAssertion<K,V,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentMapAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentMapAssertion<K,V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
index 8fa6e2b..0ec2372 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentObjectAssertion.java
@@ -12,23 +12,41 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static org.apache.juneau.internal.ExceptionUtils.*;
+import static org.apache.juneau.internal.StringUtils.*;
+
 import java.io.*;
 import java.time.*;
 import java.util.*;
+import java.util.function.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
+import org.apache.juneau.json.*;
+import org.apache.juneau.reflect.*;
+import org.apache.juneau.serializer.*;
 
 /**
  * Used for fluent assertion calls against POJOs.
  *
- * @param <V> The object type.
+ * @param <T> The object type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentObjectAssertion<V,R>")
-public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
+@FluentSetters(returns="FluentObjectAssertion<T,R>")
+public class FluentObjectAssertion<T,R> extends FluentAssertion<R> {
+
+	private final T value;
+
+	private static JsonSerializer JSON = JsonSerializer.create()
+		.ssq()
+		.build();
 
-	private final Object value;
+	private static JsonSerializer JSON_SORTED = JsonSerializer.create()
+		.ssq()
+		.sortProperties()
+		.sortCollections()
+		.sortMaps()
+		.build();
 
 	/**
 	 * Constructor.
@@ -36,7 +54,7 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentObjectAssertion(V value, R returns) {
+	public FluentObjectAssertion(T value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -47,27 +65,395 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	 * @param value The object being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentObjectAssertion(Assertion creator, V value, R returns) {
-		super(creator, value, returns);
+	public FluentObjectAssertion(Assertion creator, T value, R returns) {
+		super(creator, returns);
 		this.value = value;
 	}
 
+	/**
+	 * Asserts that the object is an instance of the specified class.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).isType(MyBean.<jk>class</jk>);
+	 * </p>
+	 *
+	 * @param parent The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isType(Class<?> parent) throws AssertionError {
+		assertNotNull("parent", parent);
+		if (! ClassInfo.of(value()).isChildOf(parent))
+			throw error("Unexpected class.\n\tExpect=[{0}]\n\tActual=[{1}]", className(parent), className(value));
+		return returns();
+	}
+
+	/**
+	 * Converts this object to text using the specified serializer and returns it as a new assertion.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asString(XmlSerializer.<jsf>DEFAULT</jsf>).is(<js>"&lt;object>&lt;foo>bar&lt;/foo>&lt;baz>qux&lt;/baz>&lt;/object>"</js>);
+	 * </p>
+	 *
+	 * @param ws The serializer to use to convert the object to text.
+	 * @return A new fluent string assertion.
+	 */
+	public FluentStringAssertion<R> asString(WriterSerializer ws) {
+		try {
+			return new FluentStringAssertion<>(this, ws.serialize(value), returns());
+		} catch (SerializeException e) {
+			throw runtimeException(e);
+		}
+	}
+
+	/**
+	 * Converts this object to a string using {@link Object#toString} and returns it as a new assertion.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asString().is(<js>"foobar"</js>);
+	 * </p>
+	 *
+	 * @return A new fluent string assertion.
+	 */
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(this, stringify(value), returns());
+	}
+
+	/**
+	 * Converts this object to a string using the specified function and returns it as a new assertion.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asString(<jv>x</jv>-><jv>x</jv>.toString()).is(<js>"foobar"</js>);
+	 * </p>
+	 *
+	 * @param function The conversion function.
+	 * @return A new fluent string assertion.
+	 */
+	public FluentStringAssertion<R> asString(Function<Object,String> function) {
+		return new FluentStringAssertion<>(this, function.apply(value), returns());
+	}
+
+	/**
+	 * Converts this object to simplified JSON and returns it as a new assertion.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asJson().is(<js>"{foo:'bar',baz:'qux'}"</js>);
+	 * </p>
+	 *
+	 * @return A new fluent string assertion.
+	 */
+	public FluentStringAssertion<R> asJson() {
+		return asString(JSON);
+	}
+
+	/**
+	 * Converts this object to sorted simplified JSON and returns it as a new assertion.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asJsonSorted().is(<js>"{baz:'qux',foo:'bar'}"</js>);
+	 * </p>
+	 *
+	 * @return A new fluent string assertion.
+	 */
+	public FluentStringAssertion<R> asJsonSorted() {
+		return asString(JSON_SORTED);
+	}
+
+	/**
+	 * Verifies that two objects are equivalent after converting them both to JSON.
+	 *
+	 * @param o The object to compare against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isSameJsonAs(Object o) throws AssertionError {
+		return isSameSerializedAs(o, JSON);
+	}
+
+	/**
+	 * Verifies that two objects are equivalent after converting them both to sorted JSON.
+	 *
+	 * <p>
+	 * Properties, maps, and collections are all sorted on both objects before comparison.
+	 *
+	 * @param o The object to compare against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isSameSortedAs(Object o) {
+		return isSameSerializedAs(o, JSON_SORTED);
+	}
+
+	/**
+	 * Asserts that the specified object is the same as this object after converting both to strings using the specified serializer.
+	 *
+	 * @param o The object to compare against.
+	 * @param serializer The serializer to use to serialize this object.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isSameSerializedAs(Object o, WriterSerializer serializer) {
+		try {
+			String s1 = serializer.serialize(value);
+			String s2 = serializer.serialize(o);
+			if (ne(s1, s2))
+				throw error("Unexpected comparison.\n\tExpect=[{0}]\n\tActual=[{1}]", s2, s1);
+		} catch (SerializeException e) {
+			throw runtimeException(e);
+		}
+		return returns();
+	}
+
+	/**
+	 * Asserts that the value equals the specified value.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isEqual(Object value) throws AssertionError {
+		if (this.value == value)
+			return returns();
+		if (! value().equals(equivalent(value)))
+			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		return returns();
+	}
+
+	/**
+	 * Asserts that the value equals the specified value.
+	 *
+	 * <p>
+	 * Equivalent to {@link #isEqual(Object)}.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R is(Object value) throws AssertionError {
+		return isEqual(equivalent(value));
+	}
+
+	/**
+	 * Asserts that the value equals the specified value.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R doesNotEqual(Object value) throws AssertionError {
+		if (this.value == null && value != null || this.value != null && value == null)
+			return returns();
+		if (this.value == null || this.value.equals(equivalent(value)))
+			throw error("Unexpected value.\n\tDid not expect=[{0}]\n\tActual=[{1}]", value, orElse(null));
+		return returns();
+	}
+
+	/**
+	 * Asserts that the specified object is the same object as this object.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isSameObjectAs(Object value) throws AssertionError {
+		if (this.value == value)
+			return returns();
+		throw error("Not the same value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+	}
+
+	/**
+	 * Asserts that the value passes the specified predicate test.
+	 *
+	 * @param test The predicate to use to test the value.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R passes(Predicate<T> test) throws AssertionError {
+		if (test != null && ! test.test(value))
+			throw error(getFailureMessage(test, value));
+		return returns();
+	}
+
+	/**
+	 * Asserts that the object is not null.
+	 *
+	 * <p>
+	 * Equivalent to {@link #isNotNull()}.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R exists() throws AssertionError {
+		return isNotNull();
+	}
+
+	/**
+	 * Asserts that the object is null.
+	 *
+	 * <p>
+	 * Equivalent to {@link #isNotNull()}.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R doesNotExist() throws AssertionError {
+		return isNull();
+	}
+
+	/**
+	 * Asserts that the object is not null.
+	 *
+	 * <p>
+	 * Equivalent to {@link #isNotNull()}.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isNotNull() throws AssertionError {
+		if (value == null)
+			throw error("Value was null.");
+		return returns();
+	}
+
+	/**
+	 * Asserts that the object i null.
+	 *
+	 * <p>
+	 * Equivalent to {@link #isNotNull()}.
+	 *
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isNull() throws AssertionError {
+		if (value != null)
+			throw error("Value was not null.");
+		return returns();
+	}
+
+	/**
+	 * Asserts that the value equals the specified value.
+	 *
+	 * <p>
+	 * Equivalent to {@link #doesNotEqual(Object)}.
+	 *
+	 * @param value The value to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isNot(Object value) throws AssertionError {
+		return doesNotEqual(equivalent(value));
+	}
+
+	/**
+	 * Asserts that the value is one of the specified values.
+	 *
+	 * @param values The values to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isAny(Object...values) throws AssertionError {
+		for (Object v : values)
+			if (value().equals(equivalent(v)))
+				return returns();
+		throw error("Expected value not found.\n\tExpect=[{0}]\n\tActual=[{1}]", values, value);
+	}
+
+	/**
+	 * Asserts that the value is one of the specified values.
+	 *
+	 * @param values The values to check against.
+	 * @return The response object (for method chaining).
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R isNotAny(Object...values) throws AssertionError {
+		for (Object v : values)
+			if (value().equals(equivalent(v)))
+				throw error("Unexpected value found.\n\tUnexpected=[{0}]\n\tActual=[{1}]", v, value);
+		return returns();
+	}
+
+	/**
+	 * Subclasses can override this method to provide special conversions on objects being compared.
+	 *
+	 * @param o The object to cast.
+	 * @return The cast object.
+	 */
+	protected Object equivalent(Object o) {
+		return o;
+	}
+
+	/**
+	 * Converts this object to a string using {@link Object#toString} and runs the {@link FluentStringAssertion#is(String)} on the result.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is "foobar" after converting to a string.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).is(<js>"foobar"</js>);
+	 * </p>
+	 *
+	 * @param value The expected string value.
+	 * @return This object (for method chaining).
+	 */
+	public R isString(String value) {
+		return asString().is(value);
+	}
+
+	/**
+	 * Converts this object to simplified JSON and runs the {@link FluentStringAssertion#is(String)} on the result.
+	 *
+	 * <h5 class='section'>Example:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates that the specified object is an instance of MyBean.</jc>
+	 * 	<jsm>assertObject<jsm>(myPojo).asJson().is(<js>"{foo:'bar',baz:'qux'}"</js>);
+	 * </p>
+	 *
+	 * @param value The expected string value.
+	 * @return This object (for method chaining).
+	 */
+	public R isJson(String value) {
+		return asJson().is(value);
+	}
+
 	@SuppressWarnings("unchecked")
-	private <T> T cast(Class<T> c) throws AssertionError {
+	private <T2> T2 cast(Class<T2> c) throws AssertionError {
 		Object o = value;
-		if (value == null || c.isInstance(value))
-			return (T)o;
-		throw new BasicAssertionError("Object was not type ''{0}''.  Actual=''{1}''", c, o.getClass());
+		if (o == null || c.isInstance(o))
+			return (T2)o;
+		throw new BasicAssertionError("Object was not type ''{0}''.  Actual=''{1}''", ClassInfo.of(c).getFullName(), o.getClass());
 	}
 
 	/**
 	 * Converts this object assertion into an array assertion.
 	 *
+	 * @param elementType The element type of the array.
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an array.
 	 */
-	public FluentArrayAssertion<R> asArray() throws AssertionError {
-		return new FluentArrayAssertion<>(this, value, returns());
+	public <E> FluentArrayAssertion<E,R> asArray(Class<E> elementType) throws AssertionError {
+		return new FluentArrayAssertion<>(this, cast(arrayClass(elementType)), returns());
+	}
+
+	/**
+	 * Converts this object assertion into a primitive array assertion.
+	 *
+	 * @param arrayType The array type.
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not an array.
+	 */
+	public <E> FluentPrimitiveArrayAssertion<E,R> asPrimitiveArray(Class<E> arrayType) throws AssertionError {
+		return new FluentPrimitiveArrayAssertion<>(this, cast(arrayType), returns());
 	}
 
 	/**
@@ -91,12 +477,94 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	}
 
 	/**
+	 * Converts this object assertion into a primitive short array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive short array.
+	 */
+	public FluentPrimitiveArrayAssertion<short[],R> asShortArray() {
+		return asPrimitiveArray(short[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive int array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive int array.
+	 */
+	public FluentPrimitiveArrayAssertion<int[],R> asIntArray() {
+		return asPrimitiveArray(int[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive long array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive long array.
+	 */
+	public FluentPrimitiveArrayAssertion<long[],R> asLongArray() {
+		return asPrimitiveArray(long[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive float array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive float array.
+	 */
+	public FluentPrimitiveArrayAssertion<float[],R> asFloatArray() {
+		return asPrimitiveArray(float[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive double array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive double array.
+	 */
+	public FluentPrimitiveArrayAssertion<double[],R> asDoubleArray() {
+		return asPrimitiveArray(double[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive boolean array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive boolean array.
+	 */
+	public FluentPrimitiveArrayAssertion<boolean[],R> asBooleanArray() {
+		return asPrimitiveArray(boolean[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a primitive char array assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a primitive char array.
+	 */
+	public FluentPrimitiveArrayAssertion<char[],R> asCharArray() {
+		return asPrimitiveArray(char[].class);
+	}
+
+	/**
+	 * Converts this object assertion into a collection assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a collection.
+	 */
+	public FluentCollectionAssertion<Object,R> asCollection() {
+		return asCollection(Object.class);
+	}
+
+	/**
 	 * Converts this object assertion into a collection assertion.
 	 *
+	 * @param elementType The element type of the collection.
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a collection.
 	 */
-	public FluentCollectionAssertion<R> asCollection() {
+	@SuppressWarnings("unchecked")
+	public <E> FluentCollectionAssertion<E,R> asCollection(Class<E> elementType) {
 		return new FluentCollectionAssertion<>(this, cast(Collection.class), returns());
 	}
 
@@ -106,8 +574,9 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an instance of {@link Comparable}.
 	 */
-	public FluentComparableAssertion<Comparable<?>,R> asComparable() {
-		return new FluentComparableAssertion<>(this, cast(Comparable.class), returns());
+	@SuppressWarnings("unchecked")
+	public <T2 extends Comparable<T2>> FluentComparableAssertion<T2,R> asComparable() {
+		return new FluentComparableAssertion<>(this, (T2)cast(Comparable.class), returns());
 	}
 
 	/**
@@ -136,7 +605,19 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a list.
 	 */
-	public FluentListAssertion<R> asList() {
+	public FluentListAssertion<Object,R> asList() {
+		return asList(Object.class);
+	}
+
+	/**
+	 * Converts this object assertion into a list assertion.
+	 *
+	 * @param elementType The element type.
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a list.
+	 */
+	@SuppressWarnings("unchecked")
+	public <E> FluentListAssertion<E,R> asList(Class<E> elementType) {
 		return new FluentListAssertion<>(this, cast(List.class), returns());
 	}
 
@@ -156,11 +637,47 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a map.
 	 */
-	public FluentMapAssertion<R> asMap() {
+	public FluentMapAssertion<String,Object,R> asMap() {
+		return asMap(String.class,Object.class);
+	}
+
+	/**
+	 * Converts this object assertion into a map assertion with the specified key and value types.
+	 *
+	 * @param keyType The key type.
+	 * @param valueType The value type.
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a map.
+	 */
+	@SuppressWarnings("unchecked")
+	public <K,V> FluentMapAssertion<K,V,R> asMap(Class<K> keyType, Class<V> valueType) {
 		return new FluentMapAssertion<>(this, cast(Map.class), returns());
 	}
 
 	/**
+	 * Converts this object assertion into a bean assertion.
+	 *
+	 * @param beanType The bean type.
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a bean.
+	 */
+	public <T2> FluentBeanAssertion<T2,R> asBean(Class<T2> beanType) {
+		return new FluentBeanAssertion<>(this, cast(beanType), returns());
+	}
+
+	/**
+	 * Converts this object assertion into a list-of-beans assertion.
+	 *
+	 * @param beanType The bean type.
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a bean.
+	 */
+	@SuppressWarnings("unchecked")
+	public <T2> FluentBeanListAssertion<T2,R> asBeanList(Class<T2> beanType) {
+		return new FluentBeanListAssertion<>(this, cast(List.class), returns());
+	}
+
+	/**
 	 * Converts this object assertion into a zoned-datetime assertion.
 	 *
 	 * @return A new assertion.
@@ -170,34 +687,122 @@ public class FluentObjectAssertion<V,R> extends FluentBaseAssertion<V,R> {
 		return new FluentZonedDateTimeAssertion<>(this, cast(ZonedDateTime.class), returns());
 	}
 
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	/**
+	 * Returns the inner value after asserting it is not <jk>null</jk>.
+	 *
+	 * @return The inner value.
+	 * @throws AssertionError If inner value was <jk>null</jk>.
+	 */
+	protected T value() throws AssertionError {
+		exists();
+		return value;
+	}
+
+	/**
+	 * Returns the inner value or the other value if the value is <jk>null</jk>.
+	 *
+	 * @param other The other value.
+	 * @return The inner value.
+	 */
+	protected T orElse(T other) {
+		return value == null ? other : value;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the inner value is null.
+	 *
+	 * @return <jk>true</jk> if the inner value is null.
+	 */
+	protected boolean valueIsNull() {
+		return value == null;
+	}
+
+	/**
+	 * Returns <jk>true</jk> if the inner value is not null.
+	 *
+	 * @return <jk>true</jk> if the inner value is not null.
+	 */
+	protected boolean valueIsNotNull() {
+		return value != null;
+	}
+
+	/**
+	 * Returns the value wrapped in an {@link Optional}.
+	 *
+	 * @return The value wrapped in an {@link Optional}.
+	 */
+	protected Optional<T> opt() {
+		return Optional.ofNullable(value);
+	}
+
+	/**
+	 * Returns the result of running the specified function against the value and returns the result.
+	 *
+	 * @param mapper The function to run against the value.
+	 * @return The result, never <jk>null</jk>.
+	 */
+	protected <T2> Optional<T2> map(Function<? super T, ? extends T2> mapper) {
+		return opt().map(mapper);
+	}
+
+	/**
+	 * Returns the predicate failure message.
+	 *
+	 * <p>
+	 * If the predicate extends from {@link AssertionPredicate}, then the message comes from {@link AssertionPredicate#getFailureMessage()}.
+	 * Otherwise, returns a generic <js>"Unexpected value: x"</js> message.
+	 *
+	 * @param p The function to run against the value.
+	 * @param value The value that failed the test.
+	 * @return The result, never <jk>null</jk>.
+	 */
+	protected String getFailureMessage(Predicate<?> p, Object value) {
+		if (p instanceof AssertionPredicate)
+			return ((AssertionPredicate<?>)p).getFailureMessage();
+		return format("Unexpected value: ''{0}''", value);
+	}
+
+	/**
+	 * Returns the string form of the inner object.
+	 * Subclasses can override this method to affect the {@link #asString()} method (and related).
+	 */
+	@Override /* Object */
+	public String toString() {
+		return value == null ? null : value.toString();
+	}
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<V,R> msg(String msg, Object...args) {
+	public FluentObjectAssertion<T,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<V,R> out(PrintStream value) {
+	public FluentObjectAssertion<T,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<V,R> silent() {
+	public FluentObjectAssertion<T,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<V,R> stdout() {
+	public FluentObjectAssertion<T,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentObjectAssertion<V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentObjectAssertion<T,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentPrimitiveArrayAssertion.java
similarity index 53%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentPrimitiveArrayAssertion.java
index ff6f276..71d823c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentPrimitiveArrayAssertion.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.assertions;
 
 import static org.apache.juneau.internal.ObjectUtils.*;
+import static java.util.Arrays.*;
 
 import java.io.*;
 import java.lang.reflect.*;
@@ -21,15 +22,15 @@ import java.util.function.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
-import org.apache.juneau.marshall.*;
 
 /**
- * Used for fluent assertion calls against array objects.
+ * Used for fluent assertion calls against primitive array objects (e.g. <c><jk>int</jk>[]</c>).
  *
+ * @param <T> The array type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentArrayAssertion<R>")
-public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
+@FluentSetters(returns="FluentPrimitiveArrayAssertion<T,R>")
+public class FluentPrimitiveArrayAssertion<T,R> extends FluentObjectAssertion<T,R> {
 
 	private static final Map<Class<?>,Function<Object,String>> STRINGIFIERS = new HashMap<>();
 	static {
@@ -43,15 +44,13 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 		STRINGIFIERS.put(short.class, (x) -> Arrays.toString((short[])x));
 	}
 
-	private Object value;
-
 	/**
 	 * Constructor.
 	 *
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentArrayAssertion(Object contents, R returns) {
+	public FluentPrimitiveArrayAssertion(T contents, R returns) {
 		this(null, contents, returns);
 	}
 
@@ -62,19 +61,15 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @param contents The byte array being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentArrayAssertion(Assertion creator, Object contents, R returns) {
+	public FluentPrimitiveArrayAssertion(Assertion creator, T contents, R returns) {
 		super(creator, contents, returns);
 		if (contents != null && ! contents.getClass().isArray())
 			throw new BasicAssertionError("Object was not an array.  Actual=''{0}''", contents.getClass());
-		this.value = contents;
 	}
 
 	@Override /* FluentBaseAssertion */
 	public FluentStringAssertion<R> asString() {
-		String s = null;
-		if (value != null)
-			s = STRINGIFIERS.getOrDefault( value.getClass().getComponentType(), (x) -> Arrays.toString((Object[])x)).apply(value);
-		return new FluentStringAssertion<>(this, s, returns());
+		return new FluentStringAssertion<>(this, toString(), returns());
 	}
 
 	/**
@@ -84,8 +79,7 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEmpty() throws AssertionError {
-		exists();
-		if (Array.getLength(value) != 0)
+		if (length() != 0)
 			throw error("Array was not empty.");
 		return returns();
 	}
@@ -97,8 +91,7 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isNotEmpty() throws AssertionError {
-		exists();
-		if (Array.getLength(value) == 0)
+		if (length() == 0)
 			throw error("Array was empty.");
 		return returns();
 	}
@@ -111,54 +104,109 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isSize(int size) throws AssertionError {
-		exists();
-		if (Array.getLength(value) != size)
-			throw error("Array did not have the expected size.  Expect={0}, Actual={1}.", size, Array.getLength(value));
+		if (length() != size)
+			throw error("Array did not have the expected size.  Expect={0}, Actual={1}.", size, length());
 		return returns();
 	}
 
 	/**
-	 * Asserts that the array contains the expected value.
+	 * Asserts that the array contains the expected entry.
 	 *
-	 * @param value The value to check for.
+	 * @param entry The value to check for.
 	 * @return The object to return after the test.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public R contains(Object value) throws AssertionError {
-		exists();
-		for (int i = 0; i < Array.getLength(this.value); i++)
-			if (eq(Array.get(this.value, i), value))
+	public R contains(Object entry) throws AssertionError {
+		for (int i = 0, j = length(); i < j; i++)
+			if (eq(at(i), entry))
 				return returns();
-		throw error("Array did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
+		throw error("Array did not contain expected value.\n\tContents: {0}\n\tExpected: {1}", value(), entry);
 	}
 
 	/**
-	 * Asserts that the array does not contain the expected value.
+	 * Asserts that the array contains the expected entries.
 	 *
-	 * @param value The value to check for.
+	 * @param entries The values to check for after being converted to strings.
 	 * @return The object to return after the test.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public R doesNotContain(Object value) throws AssertionError {
-		exists();
-		for (int i = 0; i < Array.getLength(this.value); i++)
-			if (eq(Array.get(this.value, i), value))
-				throw error("Array contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", SimpleJson.DEFAULT.toString(this.value), value);
-		return returns();
+	public R equals(String...entries) throws AssertionError {
+		Predicate<T>[] p = stream(entries).map(StringUtils::stringify).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+		return passes(p);
 	}
 
 	/**
-	 * Returns an object assertion on the item specified at the specified index.
+	 * Asserts that the array contains the expected entries.
 	 *
 	 * <p>
-	 * If the array is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
-	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
+	 * Equivalent to {@link #equals(String...)}.
 	 *
-	 * @param index The index of the item to retrieve from the array.
-	 * @return A new assertion.
+	 * @param entries The values to check for after being converted to strings.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
+	 */
+	public R is(String...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the array contains the expected entries.
+	 *
+	 * @param entries The values to check for.  Uses {@link Object#equals(Object)} for equivalency on entries.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SuppressWarnings("unchecked")
+	public R equals(T...entries) throws AssertionError {
+		Predicate<T>[] p = stream(entries).map(AssertionPredicates::eq).toArray(Predicate[]::new);
+		return passes(p);
+	}
+
+	/**
+	 * Asserts that the array contains the expected entries.
+	 *
+	 * <p>
+	 * Equivalent to {@link #equals(T...)}.
+	 *
+	 * @param entries The values to check for.  Uses {@link Object#equals(Object)} for equivalency on entries.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SuppressWarnings("unchecked")
+	public R is(T...entries) throws AssertionError {
+		return equals(entries);
+	}
+
+	/**
+	 * Asserts that the entries in this array pass the specified tests for each entry.
+	 *
+	 * @param tests The tests to run.  <jk>null</jk> values are ignored.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
+	 */
+	@SuppressWarnings("unchecked")
+	public R passes(Predicate<T>...tests) throws AssertionError {
+		isSize(tests.length);
+		for (int i = 0, j = length(); i < j; i++) {
+			Predicate<T> t = tests[i];
+			if (t != null && ! t.test(at(i)))
+				throw error("Array did not contain expected value at index {0}.\n\t{1}", i, getFailureMessage(t, at(i)));
+		}
+		return returns();
+	}
+
+	/**
+	 * Asserts that the array does not contain the expected value.
+	 *
+	 * @param entry The value to check for.
+	 * @return The object to return after the test.
+	 * @throws AssertionError If assertion failed.
 	 */
-	public FluentObjectAssertion<Object,R> item(int index) {
-		return item(Object.class, index);
+	public R doesNotContain(Object entry) throws AssertionError {
+		for (int i = 0; i < length(); i++)
+			if (eq(at(i), entry))
+				throw error("Array contained unexpected value.\n\tContents: {0}\n\tUnexpected: {1}", value(), entry);
+		return returns();
 	}
 
 	/**
@@ -168,51 +216,61 @@ public class FluentArrayAssertion<R> extends FluentBaseAssertion<Object,R> {
 	 * If the array is <jk>null</jk> or the index is out-of-bounds, the returned assertion is a null assertion
 	 * (meaning {@link FluentObjectAssertion#exists()} returns <jk>false</jk>).
 	 *
-	 * @param type The value type.
 	 * @param index The index of the item to retrieve from the array.
 	 * @return A new assertion.
 	 */
-	public <V> FluentObjectAssertion<Object,R> item(Class<V> type, int index) {
-		Object v = getItem(index);
-		if (v == null || type.isInstance(v))
-			return new FluentObjectAssertion<>(this, v, returns());
-		throw error("Array value not of expected type at index ''{0}''.\n\tExpected: {1}.\n\tActual: {2}", index, type, v.getClass());
+	public FluentObjectAssertion<T,R> item(int index) {
+		return new FluentObjectAssertion<>(this, at(index), returns());
+	}
+
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
+
+	@SuppressWarnings("unchecked")
+	private T at(int index) {
+		return valueIsNull() || index >= length() ? null : (T)Array.get(value(), index);
+	}
+
+	private int length() {
+		return Array.getLength(value());
 	}
 
-	private Object getItem(int index) {
-		if (value != null && Array.getLength(value) > index)
-			return Array.get(value, index);
-		return null;
+	@Override
+	public String toString() {
+		if (valueIsNull())
+			return null;
+		return STRINGIFIERS.getOrDefault(value().getClass().getComponentType(), (x) -> x.toString()).apply(value());
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> msg(String msg, Object...args) {
+	public FluentPrimitiveArrayAssertion<T,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> out(PrintStream value) {
+	public FluentPrimitiveArrayAssertion<T,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> silent() {
+	public FluentPrimitiveArrayAssertion<T,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> stdout() {
+	public FluentPrimitiveArrayAssertion<T,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentArrayAssertion<R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentPrimitiveArrayAssertion<T,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
index f9e0ed1..3cf3c94 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentStringAssertion.java
@@ -13,12 +13,13 @@
 package org.apache.juneau.assertions;
 
 import static org.apache.juneau.internal.StringUtils.*;
+import static java.util.stream.Collectors.*;
+import static java.util.Arrays.*;
 
 import java.io.*;
 import java.util.*;
 import java.util.function.*;
 import java.util.regex.*;
-import java.util.stream.*;
 
 import org.apache.juneau.internal.*;
 
@@ -37,9 +38,8 @@ import org.apache.juneau.internal.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentStringAssertion<R>")
-public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
+public class FluentStringAssertion<R> extends FluentObjectAssertion<String,R> {
 
-	private String text;
 	private boolean javaStrings;
 
 	/**
@@ -61,7 +61,6 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public FluentStringAssertion(Assertion creator, String text, R returns) {
 		super(creator, text, returns);
-		this.text = text;
 	}
 
 	/**
@@ -89,7 +88,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	public FluentStringAssertion<R> replaceAll(String regex, String replacement) {
 		assertNotNull("regex", regex);
 		assertNotNull("replacement", replacement);
-		return apply(x -> x == null ? null : text.replaceAll(regex, replacement));
+		return apply(x -> x == null ? null : x.replaceAll(regex, replacement));
 	}
 
 	/**
@@ -102,7 +101,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	public FluentStringAssertion<R> replace(String target, String replacement) {
 		assertNotNull("target", target);
 		assertNotNull("replacement", replacement);
-		return apply(x -> x == null ? null : text.replace(target, replacement));
+		return apply(x -> x == null ? null : x.replace(target, replacement));
 	}
 
 	/**
@@ -111,7 +110,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @return The response object (for method chaining).
 	 */
 	public FluentStringAssertion<R> urlDecode() {
-		return apply(x->StringUtils.urlDecode(x));
+		return apply(StringUtils::urlDecode);
 	}
 
 	/**
@@ -120,7 +119,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @return The response object (for method chaining).
 	 */
 	public FluentStringAssertion<R> sort() {
-		return apply(x->x == null ? null : Arrays.asList(x.trim().split("[\r\n]+")).stream().sorted().collect(Collectors.joining("\n")));
+		return apply(x->x == null ? null : stream(x.trim().split("[\r\n]+")).sorted().collect(joining("\n")));
 	}
 
 	/**
@@ -148,7 +147,7 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @return The response object (for method chaining).
 	 */
 	public FluentStringAssertion<R> apply(Function<String,String> f) {
-		return new FluentStringAssertion<>(this, f.apply(text), returns());
+		return new FluentStringAssertion<>(this, f.apply(orElse(null)), returns());
 	}
 
 	/**
@@ -173,8 +172,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEqualTo(String value) throws AssertionError {
-		if (ne(value, text))
-			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPosition(value, text), fix(value), fix(text));
+		String s = orElse(null);
+		if (ne(value, s))
+			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPosition(value, s), fix(value), fix(s));
 		return returns();
 	}
 
@@ -203,8 +203,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	public R isEqualLinesTo(String...lines) throws AssertionError {
 		assertNotNull("lines", lines);
 		String v = join(lines, '\n');
-		if (ne(v, text))
-			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPosition(v, text), fix(v), fix(text));
+		String s = value();
+		if (ne(v, s))
+			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPosition(v, s), fix(v), fix(s));
 		return returns();
 	}
 
@@ -232,10 +233,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R isEqualSortedLinesTo(String...lines) {
 		assertNotNull("lines", lines);
-		exists();
 
 		// Must work for windows too.
-		String[] e = StringUtils.join(lines, '\n').trim().split("[\r\n]+"), a = this.text.trim().split("[\r\n]+");
+		String[] e = StringUtils.join(lines, '\n').trim().split("[\r\n]+"), a = value().trim().split("[\r\n]+");
 
 		if (e.length != a.length)
 			throw error("Expected text had different numbers of lines.\n\tExpect=[{0}]\n\tActual=[{1}]", e.length, a.length);
@@ -272,8 +272,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R is(String value) throws AssertionError {
-		if (ne(value, text))
-			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", fix(value), fix(text));
+		String s = orElse(null);
+		if (ne(value, s))
+			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", fix(value), fix(s));
 		return isEqualTo(value);
 	}
 
@@ -285,8 +286,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEqualIgnoreCaseTo(String value) throws AssertionError {
-		if (neic(value, text))
-			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPositionIc(value, text), fix(value), fix(text));
+		String s = orElse(null);
+		if (neic(value, s))
+			throw error("Text differed at position {0}.\n\tExpect=[{1}]\n\tActual=[{2}]", diffPositionIc(value, s), fix(value), fix(s));
 		return returns();
 	}
 
@@ -298,8 +300,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R doesNotEqual(String value) throws AssertionError {
-		if (eq(value, text))
-			throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text));
+		String s = orElse(null);
+		if (eq(value, s))
+			throw error("Text equaled unexpected.\n\tText=[{0}]", fix(s));
 		return returns();
 	}
 
@@ -325,8 +328,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R doesNotEqualIc(String value) throws AssertionError {
-		if (eqic(value, text))
-			throw error("Text equaled unexpected.\n\tText=[{0}]", fix(text));
+		String s = orElse(null);
+		if (eqic(value, s))
+			throw error("Text equaled unexpected.\n\tText=[{0}]", fix(s));
 		return returns();
 	}
 
@@ -339,9 +343,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R contains(String...values) throws AssertionError {
 		assertNotNull("values", values);
+		String s = orElse(null);
 		for (String substring : values)
-			if (substring != null && ! StringUtils.contains(text, substring))
-				throw error("Text did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text));
+			if (substring != null && ! StringUtils.contains(s, substring))
+				throw error("Text did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(s));
 		return returns();
 	}
 
@@ -354,9 +359,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R doesNotContain(String...values) throws AssertionError {
 		assertNotNull("values", values);
+		String s = orElse(null);
 		for (String substring : values)
-			if (substring != null && StringUtils.contains(text, substring))
-				throw error("Text contained unexpected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(text));
+			if (substring != null && StringUtils.contains(s, substring))
+				throw error("Text contained unexpected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", fix(substring), fix(s));
 		return returns();
 	}
 
@@ -367,8 +373,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEmpty() throws AssertionError {
-		if (text != null && ! text.isEmpty())
-			throw error("Text was not empty.\n\tText=[{0}]", fix(text));
+		String s = orElse(null);
+		if (s != null && ! s.isEmpty())
+			throw error("Text was not empty.\n\tText=[{0}]", fix(s));
 		return returns();
 	}
 
@@ -379,9 +386,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isNotEmpty() throws AssertionError {
-		if (text == null)
+		String s = orElse(null);
+		if (s == null)
 			throw error("Text was null.");
-		if (text.isEmpty())
+		if (s.isEmpty())
 			throw error("Text was empty.");
 		return returns();
 	}
@@ -434,10 +442,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R matches(String regex, int flags) throws AssertionError {
 		assertNotNull("regex", regex);
-		exists();
 		Pattern p = Pattern.compile(regex, flags);
-		if (! p.matcher(text).matches())
-			throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(regex), fix(text));
+		String s = value();
+		if (! p.matcher(s).matches())
+			throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(regex), fix(s));
 		return returns();
 	}
 
@@ -463,9 +471,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R matches(Pattern pattern) throws AssertionError {
 		assertNotNull("pattern", pattern);
-		exists();
-		if (! pattern.matcher(text).matches())
-			throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text));
+		String s = value();
+		if (! pattern.matcher(s).matches())
+			throw error("Text did not match expected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(s));
 		return returns();
 	}
 
@@ -478,8 +486,9 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 */
 	public R doesNotMatch(Pattern pattern) throws AssertionError {
 		assertNotNull("pattern", pattern);
-		if (text != null && pattern.matcher(text).matches())
-			throw error("Text matched unexpected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(text));
+		String s = orElse(null);
+		if (s != null && pattern.matcher(s).matches())
+			throw error("Text matched unexpected pattern.\n\tPattern=[{0}]\n\tText=[{1}]", fix(pattern.pattern()), fix(s));
 		return returns();
 	}
 
@@ -491,10 +500,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R startsWith(String string) {
-		exists();
 		assertNotNull("string", string);
-		if (! text.startsWith(string))
-			throw error("Text did not start with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text));
+		String s = value();
+		if (! s.startsWith(string))
+			throw error("Text did not start with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(s));
 		return returns();
 	}
 
@@ -506,10 +515,10 @@ public class FluentStringAssertion<R> extends FluentBaseAssertion<String,R> {
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R endsWith(String string) {
-		exists();
 		assertNotNull("string", string);
-		if (! text.endsWith(string))
-			throw error("Text did not end with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(text));
+		String s = value();
+		if (! s.endsWith(string))
+			throw error("Text did not end with expected string.\n\tString=[{0}]\n\tText=[{1}]", fix(string), fix(s));
 		return returns();
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
index 6eae78a..485c491 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentThrowableAssertion.java
@@ -19,13 +19,11 @@ import org.apache.juneau.internal.*;
 /**
  * Used for fluent assertion calls against throwables.
  *
- * @param <V> The throwable type.
+ * @param <T> The throwable type.
  * @param <R> The return type.
  */
-@FluentSetters(returns="FluentThrowableAssertion<V,R>")
-public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseAssertion<V,R> {
-
-	private final Throwable value;
+@FluentSetters(returns="FluentThrowableAssertion<T,R>")
+public class FluentThrowableAssertion<T extends Throwable,R> extends FluentObjectAssertion<T,R> {
 
 	/**
 	 * Constructor.
@@ -33,7 +31,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 * @param value The throwable being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentThrowableAssertion(V value, R returns) {
+	public FluentThrowableAssertion(T value, R returns) {
 		this(null, value, returns);
 	}
 
@@ -44,9 +42,8 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 * @param value The throwable being tested.
 	 * @param returns The object to return after the test.
 	 */
-	public FluentThrowableAssertion(Assertion creator, V value, R returns) {
+	public FluentThrowableAssertion(Assertion creator, T value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -65,8 +62,8 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	@Override
 	public R isType(Class<?> type) {
 		assertNotNull("type", type);
-		if (! type.isInstance(value))
-			throw error("Exception was not expected type.\n\tExpect=[{0}]\n\tActual=[{1}]", className(type), className(value));
+		if (! type.isInstance(value()))
+			throw error("Exception was not expected type.\n\tExpect=[{0}]\n\tActual=[{1}]", className(type), className(value()));
 		return returns();
 	}
 
@@ -84,17 +81,16 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 */
 	public R contains(String...substrings) {
 		assertNotNull("substrings", substrings);
-		exists();
 		for (String substring : substrings) {
 			if (substring != null) {
-				Throwable e2 = value;
+				Throwable e2 = value();
 				boolean found = false;
 				while (e2 != null && ! found) {
 					found |= StringUtils.contains(e2.getMessage(), substring);
 					e2 = e2.getCause();
 				}
 				if (! found) {
-					throw error("Exception message did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", substring, value.getMessage());
+					throw error("Exception message did not contain expected substring.\n\tSubstring=[{0}]\n\tText=[{1}]", substring, value().getMessage());
 				}
 			}
 		}
@@ -130,7 +126,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 */
 	@Override
 	public R exists() {
-		if (value == null)
+		if (valueIsNull())
 			throw error("Exception was not thrown.");
 		return returns();
 	}
@@ -148,7 +144,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 */
 	@Override
 	public R doesNotExist() {
-		if (value != null)
+		if (valueIsNotNull())
 			throw error("Exception was thrown.");
 		return returns();
 	}
@@ -165,7 +161,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 * @return An assertion against the throwable message.  Never <jk>null</jk>.
 	 */
 	public FluentStringAssertion<R> message() {
-		return new FluentStringAssertion<>(this, value == null ? null : value.getMessage(), returns());
+		return new FluentStringAssertion<>(this, map(Throwable::getMessage).orElse(null), returns());
 	}
 
 	/**
@@ -180,7 +176,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 * @return An assertion against the throwable localized message.  Never <jk>null</jk>.
 	 */
 	public FluentStringAssertion<R> localizedMessage() {
-		return new FluentStringAssertion<>(this, value == null ? null : value.getLocalizedMessage(), returns());
+		return new FluentStringAssertion<>(this, map(Throwable::getLocalizedMessage).orElse(null), returns());
 	}
 
 	/**
@@ -195,7 +191,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 * @return An assertion against the throwable stacktrace.  Never <jk>null</jk>.
 	 */
 	public FluentStringAssertion<R> stackTrace() {
-		return new FluentStringAssertion<>(this, value == null ? null : StringUtils.getStackTrace(value), returns());
+		return new FluentStringAssertion<>(this, map(StringUtils::getStackTrace).orElse(null), returns());
 	}
 
 	/**
@@ -227,7 +223,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 */
 	@SuppressWarnings("unchecked")
 	public <E extends Throwable> FluentThrowableAssertion<E,R> causedBy(Class<E> type) {
-		Throwable t = value == null ? null : value.getCause();
+		Throwable t = map(Throwable::getCause).orElse(null);
 		if (t == null || type.isInstance(t))
 			return new FluentThrowableAssertion<>(this, (E)t, returns());
 		throw error("Caused-by exception not of expected type.\n\tExpected: {1}.\n\tActual: {2}", type, t.getClass());
@@ -247,7 +243,7 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	 */
 	@SuppressWarnings("unchecked")
 	public <E extends Throwable> FluentThrowableAssertion<E,R> find(Class<E> throwableClass) {
-		Throwable t = value;
+		Throwable t = orElse(null);
 		while (t != null) {
 			if (throwableClass.isInstance(t))
 				return new FluentThrowableAssertion<>(this, (E)t, returns());
@@ -259,31 +255,31 @@ public class FluentThrowableAssertion<V extends Throwable,R> extends FluentBaseA
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<V,R> msg(String msg, Object...args) {
+	public FluentThrowableAssertion<T,R> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<V,R> out(PrintStream value) {
+	public FluentThrowableAssertion<T,R> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<V,R> silent() {
+	public FluentThrowableAssertion<T,R> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<V,R> stdout() {
+	public FluentThrowableAssertion<T,R> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public FluentThrowableAssertion<V,R> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public FluentThrowableAssertion<T,R> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentVersionAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentVersionAssertion.java
index e21f984..57d6dde 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentVersionAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentVersionAssertion.java
@@ -14,6 +14,7 @@ package org.apache.juneau.assertions;
 
 import java.io.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
@@ -33,12 +34,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentVersionAssertion<R>")
 public class FluentVersionAssertion<R> extends FluentComparableAssertion<Version,R> {
 
-	static {
-		boolean NEEDS_TESTING = true;
-	}
-
-	private final Version value;
-
 	/**
 	 * Constructor.
 	 *
@@ -58,7 +53,6 @@ public class FluentVersionAssertion<R> extends FluentComparableAssertion<Version
 	 */
 	public FluentVersionAssertion(Assertion creator, Version value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -68,8 +62,7 @@ public class FluentVersionAssertion<R> extends FluentComparableAssertion<Version
 	 * @return The response object (for method chaining).
 	 */
 	public FluentIntegerAssertion<R> part(int index) {
-		exists();
-		return new FluentIntegerAssertion<>(this, this.value.getPart(index).orElse(null), returns());
+		return new FluentIntegerAssertion<>(this, value().getPart(index).orElse(null), returns());
 	}
 
 	/**
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentZonedDateTimeAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentZonedDateTimeAssertion.java
index 765e098..bdf0acc 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentZonedDateTimeAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/FluentZonedDateTimeAssertion.java
@@ -37,8 +37,6 @@ import org.apache.juneau.internal.*;
 @FluentSetters(returns="FluentZonedDateTimeAssertion<R>")
 public class FluentZonedDateTimeAssertion<R> extends FluentComparableAssertion<ZonedDateTime,R> {
 
-	private final ZonedDateTime value;
-
 	/**
 	 * Constructor.
 	 *
@@ -58,7 +56,6 @@ public class FluentZonedDateTimeAssertion<R> extends FluentComparableAssertion<Z
 	 */
 	public FluentZonedDateTimeAssertion(Assertion creator, ZonedDateTime value, R returns) {
 		super(creator, value, returns);
-		this.value = value;
 	}
 
 	/**
@@ -70,8 +67,9 @@ public class FluentZonedDateTimeAssertion<R> extends FluentComparableAssertion<Z
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isEqual(ZonedDateTime value, ChronoUnit precision) throws AssertionError {
-		if (ne(this.value, value, (x,y)->x.toInstant().truncatedTo(precision).equals(y.toInstant().truncatedTo(precision))))
-			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		ZonedDateTime v = orElse(null);
+		if (ne(v, value, (x,y)->x.toInstant().truncatedTo(precision).equals(y.toInstant().truncatedTo(precision))))
+			throw error("Unexpected value.\n\tExpect=[{0}]\n\tActual=[{1}]", value, v);
 		return returns();
 	}
 
@@ -83,10 +81,9 @@ public class FluentZonedDateTimeAssertion<R> extends FluentComparableAssertion<Z
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isAfter(ZonedDateTime value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
-		if (! (this.value.isAfter(value)))
-			throw error("Value was not after expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		if (! (value().isAfter(value)))
+			throw error("Value was not after expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
@@ -108,10 +105,9 @@ public class FluentZonedDateTimeAssertion<R> extends FluentComparableAssertion<Z
 	 * @throws AssertionError If assertion failed.
 	 */
 	public R isBefore(ZonedDateTime value) throws AssertionError {
-		exists();
 		assertNotNull("value", value);
-		if (! (this.value.isBefore(value)))
-			throw error("Value was not before expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, this.value);
+		if (! (value().isBefore(value)))
+			throw error("Value was not before expected.\n\tExpect=[{0}]\n\tActual=[{1}]", value, value());
 		return returns();
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/IntegerAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/IntegerAssertion.java
index 85fa84e..c5c753b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/IntegerAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/IntegerAssertion.java
@@ -47,11 +47,6 @@ public class IntegerAssertion extends FluentIntegerAssertion<IntegerAssertion> {
 		super(value, null);
 	}
 
-	@Override
-	protected IntegerAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ListAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ListAssertion.java
index 308c7ff..6e1bca0 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ListAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ListAssertion.java
@@ -25,10 +25,11 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the specified POJO is the specified type.</jc>
  * 	<jsm>assertList</jsm>(<jv>myList</jv>).isNotEmpty();
  * </p>
+ *
+ * @param <E> The element type.
  */
-@FluentSetters(returns="ListAssertion")
-@SuppressWarnings("rawtypes")
-public class ListAssertion extends FluentListAssertion<ListAssertion> {
+@FluentSetters(returns="ListAssertion<E>")
+public class ListAssertion<E> extends FluentListAssertion<E,ListAssertion<E>> {
 
 	/**
 	 * Creator.
@@ -36,8 +37,8 @@ public class ListAssertion extends FluentListAssertion<ListAssertion> {
 	 * @param value The object being wrapped.
 	 * @return A new {@link ListAssertion} object.
 	 */
-	public static ListAssertion create(List value) {
-		return new ListAssertion(value);
+	public static <E> ListAssertion<E> create(List<E> value) {
+		return new ListAssertion<>(value);
 	}
 
 	/**
@@ -45,43 +46,38 @@ public class ListAssertion extends FluentListAssertion<ListAssertion> {
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ListAssertion(List value) {
+	public ListAssertion(List<E> value) {
 		super(value, null);
 	}
 
-	@Override
-	protected ListAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ListAssertion msg(String msg, Object...args) {
+	public ListAssertion<E> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ListAssertion out(PrintStream value) {
+	public ListAssertion<E> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ListAssertion silent() {
+	public ListAssertion<E> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ListAssertion stdout() {
+	public ListAssertion<E> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ListAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public ListAssertion<E> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/LongAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/LongAssertion.java
index e7ac213..aad13dd 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/LongAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/LongAssertion.java
@@ -47,11 +47,6 @@ public class LongAssertion extends FluentLongAssertion<LongAssertion> {
 		super(value, null);
 	}
 
-	@Override
-	protected LongAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/MapAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/MapAssertion.java
index 9b86734..b554eed 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/MapAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/MapAssertion.java
@@ -25,10 +25,12 @@ import org.apache.juneau.internal.*;
  * 	<jc>// Validates the specified POJO is the specified type.</jc>
  * 	<jsm>assertMap</jsm>(<jv>mymap</jv>).isNotEmpty();
  * </p>
+ *
+ * @param <K> The map key type.
+ * @param <V> The map value type.
  */
-@FluentSetters(returns="MapAssertion")
-@SuppressWarnings("rawtypes")
-public class MapAssertion extends FluentMapAssertion<MapAssertion> {
+@FluentSetters(returns="MapAssertion<K,V>")
+public class MapAssertion<K,V> extends FluentMapAssertion<K,V,MapAssertion<K,V>> {
 
 	/**
 	 * Creator.
@@ -36,8 +38,8 @@ public class MapAssertion extends FluentMapAssertion<MapAssertion> {
 	 * @param value The object being wrapped.
 	 * @return A new {@link MapAssertion} object.
 	 */
-	public static MapAssertion create(Map value) {
-		return new MapAssertion(value);
+	public static <K,V> MapAssertion<K,V> create(Map<K,V> value) {
+		return new MapAssertion<>(value);
 	}
 
 	/**
@@ -45,43 +47,38 @@ public class MapAssertion extends FluentMapAssertion<MapAssertion> {
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public MapAssertion(Map value) {
+	public MapAssertion(Map<K,V> value) {
 		super(value, null);
 	}
 
-	@Override
-	protected MapAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public MapAssertion msg(String msg, Object...args) {
+	public MapAssertion<K,V> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public MapAssertion out(PrintStream value) {
+	public MapAssertion<K,V> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public MapAssertion silent() {
+	public MapAssertion<K,V> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public MapAssertion stdout() {
+	public MapAssertion<K,V> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public MapAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public MapAssertion<K,V> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
index 5f6da25..4d4ae0f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ObjectAssertion.java
@@ -25,10 +25,10 @@ import org.apache.juneau.internal.*;
  * 	<jsm>assertObject</jsm>(<jv>myPojo</jv>).isType(MyBean.<jk>class</jk>);
  * </p>
  *
- * @param <V> The object type.
+ * @param <T> The object type.
  */
-@FluentSetters(returns="ObjectAssertion<V>")
-public class ObjectAssertion<V> extends FluentObjectAssertion<V,ObjectAssertion<V>> {
+@FluentSetters(returns="ObjectAssertion<T>")
+public class ObjectAssertion<T> extends FluentObjectAssertion<T,ObjectAssertion<T>> {
 
 	/**
 	 * Creator.
@@ -36,7 +36,7 @@ public class ObjectAssertion<V> extends FluentObjectAssertion<V,ObjectAssertion<
 	 * @param value The object being wrapped.
 	 * @return A new {@link ObjectAssertion} object.
 	 */
-	public static <V> ObjectAssertion<V> create(V value) {
+	public static <T> ObjectAssertion<T> create(T value) {
 		return new ObjectAssertion<>(value);
 	}
 
@@ -45,43 +45,38 @@ public class ObjectAssertion<V> extends FluentObjectAssertion<V,ObjectAssertion<
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ObjectAssertion(V value) {
+	public ObjectAssertion(T value) {
 		super(value, null);
 	}
 
-	@Override
-	protected ObjectAssertion<V> returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion<V> msg(String msg, Object...args) {
+	public ObjectAssertion<T> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion<V> out(PrintStream value) {
+	public ObjectAssertion<T> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion<V> silent() {
+	public ObjectAssertion<T> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion<V> stdout() {
+	public ObjectAssertion<T> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ObjectAssertion<V> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public ObjectAssertion<T> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/PrimitiveArrayAssertion.java
similarity index 70%
copy from juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java
copy to juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/PrimitiveArrayAssertion.java
index 4e67a8e..1dd8f2c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ArrayAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/PrimitiveArrayAssertion.java
@@ -12,6 +12,8 @@
 // ***************************************************************************************************************************
 package org.apache.juneau.assertions;
 
+import static org.apache.juneau.assertions.Assertions.*;
+
 import java.io.*;
 
 import org.apache.juneau.internal.*;
@@ -21,21 +23,23 @@ import org.apache.juneau.internal.*;
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bcode w800'>
- * 	String[] <jv>array</jv> = <jk>new</jk> String[]{<js>"foo"</js>};
+ * 	<jk>int</jk>[] <jv>array</jv> = {1};
  * 	<jsm>assertArray</jsm>(<jv>array</jv>).exists().isSize(1);
  * </p>
+ *
+ * @param <T> The array type.
  */
-@FluentSetters(returns="ArrayAssertion")
-public class ArrayAssertion extends FluentArrayAssertion<ArrayAssertion> {
+@FluentSetters(returns="PrimitiveArrayAssertion<T>")
+public class PrimitiveArrayAssertion<T> extends FluentPrimitiveArrayAssertion<T,PrimitiveArrayAssertion<T>> {
 
 	/**
 	 * Creator.
 	 *
 	 * @param value The object being wrapped.
-	 * @return A new {@link ArrayAssertion} object.
+	 * @return A new {@link PrimitiveArrayAssertion} object.
 	 */
-	public static ArrayAssertion create(Object value) {
-		return new ArrayAssertion(value);
+	public static <T> PrimitiveArrayAssertion<T> create(T value) {
+		return new PrimitiveArrayAssertion<>(value);
 	}
 
 	/**
@@ -43,43 +47,40 @@ public class ArrayAssertion extends FluentArrayAssertion<ArrayAssertion> {
 	 *
 	 * @param value The object being wrapped.
 	 */
-	public ArrayAssertion(Object value) {
+	public PrimitiveArrayAssertion(T value) {
 		super(value, null);
-	}
-
-	@Override
-	protected ArrayAssertion returns() {
-		return this;
+		Class<?> c = value == null ? null : value.getClass();
+		assertArg(c == null || c.isArray() && c.getComponentType().isPrimitive(), "Value wasn't a primitive array.");
 	}
 
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion msg(String msg, Object...args) {
+	public PrimitiveArrayAssertion<T> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion out(PrintStream value) {
+	public PrimitiveArrayAssertion<T> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion silent() {
+	public PrimitiveArrayAssertion<T> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion stdout() {
+	public PrimitiveArrayAssertion<T> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ArrayAssertion throwable(Class<? extends java.lang.RuntimeException> value) {
+	public PrimitiveArrayAssertion<T> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/StringAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/StringAssertion.java
index 26484fa..3a4d246 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/StringAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/StringAssertion.java
@@ -49,11 +49,6 @@ public class StringAssertion extends FluentStringAssertion<StringAssertion> {
 		super(stringify(text), null);
 	}
 
-	@Override
-	protected StringAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
index fae7ab1..9c9891e 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ThrowableAssertion.java
@@ -25,10 +25,10 @@ import org.apache.juneau.internal.*;
  * 	<jsm>assertThrowable</jsm>(<jv>throwable</jv>).contains(<js>"Foobar"</js>);
  * </p>
  *
- * @param <V> The throwable type.
+ * @param <T> The throwable type.
  */
-@FluentSetters(returns="ThrowableAssertion<V>")
-public class ThrowableAssertion<V extends Throwable> extends FluentThrowableAssertion<V,ThrowableAssertion<V>> {
+@FluentSetters(returns="ThrowableAssertion<T>")
+public class ThrowableAssertion<T extends Throwable> extends FluentThrowableAssertion<T,ThrowableAssertion<T>> {
 
 	/**
 	 * Creator.
@@ -45,43 +45,38 @@ public class ThrowableAssertion<V extends Throwable> extends FluentThrowableAsse
 	 *
 	 * @param value The throwable being wrapped.
 	 */
-	public ThrowableAssertion(V value) {
+	public ThrowableAssertion(T value) {
 		super(value, null);
 	}
 
-	@Override
-	protected ThrowableAssertion<V> returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion<V> msg(String msg, Object...args) {
+	public ThrowableAssertion<T> msg(String msg, Object...args) {
 		super.msg(msg, args);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion<V> out(PrintStream value) {
+	public ThrowableAssertion<T> out(PrintStream value) {
 		super.out(value);
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion<V> silent() {
+	public ThrowableAssertion<T> silent() {
 		super.silent();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion<V> stdout() {
+	public ThrowableAssertion<T> stdout() {
 		super.stdout();
 		return this;
 	}
 
 	@Override /* GENERATED - Assertion */
-	public ThrowableAssertion<V> throwable(Class<? extends java.lang.RuntimeException> value) {
+	public ThrowableAssertion<T> throwable(Class<? extends java.lang.RuntimeException> value) {
 		super.throwable(value);
 		return this;
 	}
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/VersionAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/VersionAssertion.java
index e50fe0c..9896e5f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/VersionAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/VersionAssertion.java
@@ -14,6 +14,7 @@ package org.apache.juneau.assertions;
 
 import java.io.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.internal.*;
 
 /**
@@ -47,11 +48,6 @@ public class VersionAssertion extends FluentVersionAssertion<VersionAssertion> {
 		super(value, null);
 	}
 
-	@Override
-	protected VersionAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ZonedDateTimeAssertion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ZonedDateTimeAssertion.java
index 779a67a..c069274 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ZonedDateTimeAssertion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/assertions/ZonedDateTimeAssertion.java
@@ -48,11 +48,6 @@ public class ZonedDateTimeAssertion extends FluentZonedDateTimeAssertion<ZonedDa
 		super(value, null);
 	}
 
-	@Override
-	protected ZonedDateTimeAssertion returns() {
-		return this;
-	}
-
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AList.java
index f22ccd2..efa9a92 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AList.java
@@ -328,6 +328,27 @@ public class AList<T> extends ArrayList<T> {
 		return this;
 	}
 
+	/**
+	 * Sorts the contents of this list using natural ordering.
+	 *
+	 * @return This object (for method chaining).
+	 */
+	public AList<T> sort() {
+		super.sort(null);
+		return this;
+	}
+
+	/**
+	 * Sorts the contents of this list using the specified comparator.
+	 *
+	 * @param c The comparator to use for sorting.  Can be <jk>null</jk>.
+	 * @return This object (for method chaining).
+	 */
+	public AList<T> sortWith(Comparator<? super T> c) {
+		super.sort(c);
+		return this;
+	}
+
 	//------------------------------------------------------------------------------------------------------------------
 	// Other methods.
 	//------------------------------------------------------------------------------------------------------------------
@@ -375,15 +396,7 @@ public class AList<T> extends ArrayList<T> {
 	 *
 	 * @return This collection serialized to a string.
 	 */
-	public String asString() {
+	public String asJson() {
 		return SimpleJsonSerializer.DEFAULT.toString(this);
 	}
-
-	/**
-	 * Convert to Simplified JSON.
-	 */
-	@Override /* Object */
-	public String toString() {
-		return asString(SimpleJsonSerializer.DEFAULT);
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
index 0698f50..de56610 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/AMap.java
@@ -285,15 +285,7 @@ public class AMap<K,V> extends LinkedHashMap<K,V> {
 	 *
 	 * @return This collection serialized to a string.
 	 */
-	public String asString() {
+	public String asJson() {
 		return SimpleJsonSerializer.DEFAULT.toString(this);
 	}
-
-	/**
-	 * Convert to Simplified JSON.
-	 */
-	@Override /* Object */
-	public String toString() {
-		return asString(SimpleJsonSerializer.DEFAULT);
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASet.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASet.java
index 4fd2655..565e0f2 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASet.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASet.java
@@ -308,15 +308,7 @@ public class ASet<T> extends LinkedHashSet<T> {
 	 *
 	 * @return This collection serialized to a string.
 	 */
-	public String asString() {
+	public String asJson() {
 		return SimpleJsonSerializer.DEFAULT.toString(this);
 	}
-
-	/**
-	 * Convert to Simplified JSON.
-	 */
-	@Override /* Object */
-	public String toString() {
-		return asString(SimpleJsonSerializer.DEFAULT);
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedMap.java
index 2bc5c9f..951753b 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedMap.java
@@ -238,15 +238,7 @@ public class ASortedMap<K,V> extends TreeMap<K,V> {
 	 *
 	 * @return This collection serialized to a string.
 	 */
-	public String asString() {
+	public String asJson() {
 		return SimpleJsonSerializer.DEFAULT.toString(this);
 	}
-
-	/**
-	 * Convert to Simplified JSON.
-	 */
-	@Override /* Object */
-	public String toString() {
-		return asString(SimpleJsonSerializer.DEFAULT);
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedSet.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedSet.java
index 0f3099f..021f5df 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedSet.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/ASortedSet.java
@@ -256,15 +256,7 @@ public class ASortedSet<T> extends TreeSet<T> {
 	 *
 	 * @return This collection serialized to a string.
 	 */
-	public String asString() {
+	public String asJson() {
 		return SimpleJsonSerializer.DEFAULT.toString(this);
 	}
-
-	/**
-	 * Convert to Simplified JSON.
-	 */
-	@Override /* Object */
-	public String toString() {
-		return asString(SimpleJsonSerializer.DEFAULT);
-	}
 }
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OList.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OList.java
index aa6fb67..0b2c2c8 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OList.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OList.java
@@ -191,7 +191,7 @@ public class OList extends LinkedList<Object> {
 	 * @throws ParseException Malformed input encountered.
 	 */
 	public OList(CharSequence in, Parser p) throws ParseException {
-		this(p == null ? null : p.createBeanSession());
+		this(p == null ? BeanContext.DEFAULT_SESSION : p.createBeanSession());
 		if (p == null)
 			p = JsonParser.DEFAULT;
 		if (in != null)
@@ -222,7 +222,7 @@ public class OList extends LinkedList<Object> {
 	 * @throws ParseException Malformed input encountered.
 	 */
 	public OList(Reader in, Parser p) throws ParseException {
-		this(p == null ? null : p.createBeanSession());
+		this(p == null ? BeanContext.DEFAULT_SESSION : p.createBeanSession());
 		parse(in, p);
 	}
 
@@ -1035,7 +1035,7 @@ public class OList extends LinkedList<Object> {
 
 	BeanSession bs() {
 		if (session == null)
-			session = BeanContext.DEFAULT.createBeanSession();
+			session = BeanContext.DEFAULT_SESSION;
 		return session;
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OMap.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OMap.java
index a740ea4..f82fbdc 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OMap.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/collections/OMap.java
@@ -193,7 +193,7 @@ public class OMap extends LinkedHashMap<String,Object> {
 	 * @throws ParseException Malformed input encountered.
 	 */
 	public OMap(CharSequence in, Parser p) throws ParseException {
-		this(p == null ? null : p.createBeanSession());
+		this(p == null ? BeanContext.DEFAULT_SESSION : p.createBeanSession());
 		if (p == null)
 			p = JsonParser.DEFAULT;
 		if (! StringUtils.isEmpty(in))
@@ -224,7 +224,7 @@ public class OMap extends LinkedHashMap<String,Object> {
 	 * @throws ParseException Malformed input encountered.
 	 */
 	public OMap(Reader in, Parser p) throws ParseException {
-		this(p == null ? null : p.createBeanSession());
+		this(p == null ? BeanContext.DEFAULT_SESSION : p.createBeanSession());
 		parse(in, p);
 	}
 
@@ -1708,7 +1708,7 @@ public class OMap extends LinkedHashMap<String,Object> {
 
 	private BeanSession bs() {
 		if (session == null)
-			session = BeanContext.DEFAULT.createBeanSession();
+			session = BeanContext.DEFAULT_SESSION;
 		return session;
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HttpHeaders.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HttpHeaders.java
index de84ed2..f79ab11 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HttpHeaders.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/HttpHeaders.java
@@ -22,11 +22,11 @@ import java.util.*;
 import java.util.function.*;
 
 import org.apache.http.*;
+import org.apache.juneau.*;
 import org.apache.juneau.http.header.*;
 import org.apache.juneau.http.header.Date;
 import org.apache.juneau.http.part.*;
 import org.apache.juneau.httppart.*;
-import org.apache.juneau.internal.*;
 import org.apache.juneau.oapi.*;
 import org.apache.juneau.reflect.*;
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicCsvArrayHeader.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicCsvArrayHeader.java
index 45941c2..9b6d4ff 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicCsvArrayHeader.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/BasicCsvArrayHeader.java
@@ -205,7 +205,7 @@ public class BasicCsvArrayHeader extends BasicHeader {
 	 * @return A new fluent assertion object.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public FluentListAssertion<BasicCsvArrayHeader> assertList() {
+	public FluentListAssertion<String,BasicCsvArrayHeader> assertList() {
 		return new FluentListAssertion<>(asList().orElse(null), this);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/ClientVersion.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/ClientVersion.java
index 19ffab7..da754bb 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/ClientVersion.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/header/ClientVersion.java
@@ -19,6 +19,7 @@ import static org.apache.juneau.internal.StringUtils.*;
 import java.util.*;
 import java.util.function.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.assertions.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.internal.*;
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicCsvArrayPart.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicCsvArrayPart.java
index 556405e..ff053fe 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicCsvArrayPart.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/part/BasicCsvArrayPart.java
@@ -143,7 +143,7 @@ public class BasicCsvArrayPart extends BasicPart {
 	 * @return A new fluent assertion object.
 	 * @throws AssertionError If assertion failed.
 	 */
-	public FluentListAssertion<BasicCsvArrayPart> assertList() {
+	public FluentListAssertion<String,BasicCsvArrayPart> assertList() {
 		return new FluentListAssertion<>(getParsedValue(), this);
 	}
 
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
index c7bbb10..f31328f 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ObjectUtils.java
@@ -145,7 +145,7 @@ public class ObjectUtils {
 	 * @param o2 Object 2.
 	 * @return <jk>true</jk> if both objects are equal based on the {@link Object#equals(Object)} method.
 	 */
-	public static boolean eq(Object o1, Object o2) {
+	public static <T> boolean eq(T o1, T o2) {
 		if (isArray(o1) && isArray(o2)) {
 			int l1 = Array.getLength(o1), l2 = Array.getLength(o2);
 			if (l1 != l2)
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/VersionRange.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/VersionRange.java
index e4fc69d..41a707c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/VersionRange.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/VersionRange.java
@@ -14,6 +14,8 @@ package org.apache.juneau.internal;
 
 import static org.apache.juneau.internal.StringUtils.*;
 
+import org.apache.juneau.*;
+
 /**
  * Represents an OSGi-style version range like <js>"1.2"</js> or <js>"[1.0,2.0)"</js>.
  *
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java
index 63f1334..c75f3f3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSessionArgs.java
@@ -161,6 +161,12 @@ public final class ParserSessionArgs extends BeanSessionArgs {
 		return this;
 	}
 
+	@Override /* GENERATED - SessionArgs */
+	public ParserSessionArgs unmodifiable() {
+		super.unmodifiable();
+		return this;
+	}
+
 	@Override /* GENERATED - BeanSessionArgs */
 	public ParserSessionArgs schema(HttpPartSchema value) {
 		super.schema(value);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java
index c148d5e..a72af7c 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSessionArgs.java
@@ -207,6 +207,12 @@ public final class SerializerSessionArgs extends BeanSessionArgs {
 		return this;
 	}
 
+	@Override /* GENERATED - SessionArgs */
+	public SerializerSessionArgs unmodifiable() {
+		super.unmodifiable();
+		return this;
+	}
+
 	@Override /* GENERATED - BeanSessionArgs */
 	public SerializerSessionArgs schema(HttpPartSchema value) {
 		super.schema(value);
diff --git a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/PojoRest.java b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/PojoRest.java
index 2c45aa5..61ea906 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/PojoRest.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/utils/PojoRest.java
@@ -162,10 +162,10 @@ public final class PojoRest {
 	 * @param parser The parser to use for parsing arguments and converting objects to the correct data type.
 	 */
 	public PojoRest(Object o, ReaderParser parser) {
+		this.session = parser == null ? BeanContext.DEFAULT_SESSION : parser.createBeanSession();
 		if (parser == null)
 			parser = JsonParser.DEFAULT;
 		this.parser = parser;
-		this.session = parser.createBeanSession();
 		this.root = new JsonNode(null, null, o, session.object());
 	}
 
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java
index c3a7e7b..e1af9b1 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseBodyAssertion.java
@@ -13,8 +13,9 @@
 package org.apache.juneau.rest.client.assertion;
 
 import java.io.*;
-import java.util.function.*;
+import java.util.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.assertions.*;
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.internal.*;
@@ -26,7 +27,7 @@ import org.apache.juneau.rest.client.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentResponseBodyAssertion<R>")
-public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
+public class FluentResponseBodyAssertion<R> extends FluentObjectAssertion<ResponseBody,R> {
 
 	private final ResponseBody value;
 
@@ -108,6 +109,7 @@ public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
 	 *
 	 * @return A new fluent assertion object.
 	 */
+	@Override
 	public FluentStringAssertion<R> asString() {
 		return new FluentStringAssertion<>(valueAsString(), returns());
 	}
@@ -152,7 +154,7 @@ public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
 	 * 	<jv>client</jv>
 	 * 		.get(<js>"/myBean"</js>)
 	 * 		.run()
-	 * 		.assertBody().asType(MyBean.<jk>class</jk>).json().is(<js>"{foo:'bar'}"</js>);
+	 * 		.assertBody().asObject(List.<jk>class</jk>).passes(<jv>x</jv> -&gt; <jv>x</jv>.size() > 0);
 	 * </p>
 	 *
 	 * <ul class='notes'>
@@ -166,13 +168,59 @@ public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
 	 *
 	 * @param type The object type to create.
 	 * @return A new fluent assertion object.
-	 * @throws RestCallException If REST call failed.
 	 */
-	public <V> FluentObjectAssertion<V,R> asType(Class<V> type) throws RestCallException {
+	public <V> FluentObjectAssertion<V,R> asObject(Class<V> type) {
 		return new FluentObjectAssertion<>(valueAsType(type), returns());
 	}
 
 	/**
+	 * Provides the ability to perform fluent-style assertions on this response body.
+	 *
+	 * <p>
+	 * Converts the body to a generic Java object and then returns the value as an object assertion.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response body bean is the expected value.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<js>"/myBean"</js>)
+	 * 		.run()
+	 * 		.assertBody().asObject().asJson().is(<js>"{foo:'bar'}"</js>);
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
+	 *  <li>
+	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
+	 * 	<li>
+	 * 		The input stream is automatically closed after this call.
+	 * </ul>
+	 *
+	 * @return A new fluent assertion object.
+	 */
+	public FluentObjectAssertion<Object,R> asObject() {
+		return asObject(Object.class);
+	}
+
+	@Override
+	public <V> FluentBeanAssertion<V,R> asBean(Class<V> beanType) {
+		return new FluentBeanAssertion<>(valueAsType(beanType), returns());
+	}
+
+	@Override
+	public <V> FluentBeanListAssertion<V,R> asBeanList(Class<V> beanType) {
+		ClassMeta<List<V>> cm = BeanContext.DEFAULT.getClassMeta(List.class, beanType);
+		return new FluentBeanListAssertion<>(valueAsType(cm), returns());
+	}
+
+	@Override
+	public <E> FluentListAssertion<E,R> asList(Class<E> elementType) {
+		ClassMeta<List<E>> cm = BeanContext.DEFAULT.getClassMeta(List.class, elementType);
+		return new FluentListAssertion<>(valueAsType(cm), returns());
+	}
+
+	/**
 	 * Asserts that the value equals the specified value.
 	 *
 	 * @param value The value to check against.
@@ -236,18 +284,9 @@ public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
 		return asString().isNotEmpty();
 	}
 
-	/**
-	 * Asserts that the value passes the specified predicate test.
-	 *
-	 * @param test The predicate to use to test the value.
-	 * @return The response object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R passes(Predicate<String> test) throws AssertionError {
-		if (! test.test(valueAsString()))
-			throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
-		return returns();
-	}
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
 
 	private String valueAsString() throws AssertionError {
 		try {
@@ -273,6 +312,14 @@ public class FluentResponseBodyAssertion<R> extends FluentAssertion<R> {
 		}
 	}
 
+	private <T> T valueAsType(ClassMeta<T> c) throws AssertionError {
+		try {
+			return value.cache().asType(c);
+		} catch (RestCallException e) {
+			throw error(e, "Exception occurred during call.");
+		}
+	}
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseHeaderAssertion.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseHeaderAssertion.java
index 301da51..f7aabc6 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseHeaderAssertion.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseHeaderAssertion.java
@@ -25,7 +25,7 @@ import org.apache.juneau.rest.client.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentResponseHeaderAssertion<R>")
-public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String,R> {
+public class FluentResponseHeaderAssertion<R> extends FluentObjectAssertion<String,R> {
 
 	private final ResponseHeader value;
 
@@ -47,6 +47,7 @@ public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a boolean.
 	 */
+	@Override
 	public FluentBooleanAssertion<R> asBoolean() {
 		return new FluentBooleanAssertion<>(this, value.asBoolean().orElse(null), returns());
 	}
@@ -57,6 +58,7 @@ public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an integer.
 	 */
+	@Override
 	public FluentIntegerAssertion<R> asInteger() {
 		return new FluentIntegerAssertion<>(this, value.asInteger().orElse(null), returns());
 	}
@@ -67,6 +69,7 @@ public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a long.
 	 */
+	@Override
 	public FluentLongAssertion<R> asLong() {
 		return new FluentLongAssertion<>(this, value.asLong().orElse(null), returns());
 	}
@@ -77,23 +80,24 @@ public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a zoned-datetime.
 	 */
+	@Override
 	public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
 		return new FluentZonedDateTimeAssertion<>(this, value.asDateHeader().asZonedDateTime().orElse(null), returns());
 	}
 
 	/**
-	 * Provides the ability to perform fluent-style assertions on this response body.
+	 * Provides the ability to perform fluent-style assertions on this response header.
 	 *
 	 * <p>
-	 * Converts the body to a type using {@link ResponseBody#asType(Class)} and then returns the value as an object assertion.
+	 * Converts the header to a type using {@link ResponseHeader#asType(Class)} and then returns the value as an object assertion.
 	 *
 	 * <h5 class='section'>Examples:</h5>
 	 * <p class='bcode w800'>
-	 * 	<jc>// Validates the response body bean is the expected value.</jc>
+	 * 	<jc>// Validates the response header exists and is the specified value.</jc>
 	 * 	<jv>client</jv>
 	 * 		.get(<js>"/myBean"</js>)
 	 * 		.run()
-	 * 		.assertBody().asType(MyBean.<jk>class</jk>).json().is(<js>"{foo:'bar'}"</js>);
+	 * 		.assertHeader(<js>"Foo"</js>).asObject(Integer.<jk>class</jk>).exists().passes(<mv>x</mv> -&gt; <mv>x</mv> > 0);
 	 * </p>
 	 *
 	 * <ul class='notes'>
@@ -109,10 +113,41 @@ public class FluentResponseHeaderAssertion<R> extends FluentBaseAssertion<String
 	 * @return A new fluent assertion object.
 	 * @throws RestCallException If REST call failed.
 	 */
-	public <V> FluentObjectAssertion<V,R> asType(Class<V> type) throws RestCallException {
+	public <V> FluentObjectAssertion<V,R> asObject(Class<V> type) throws RestCallException {
 		return new FluentObjectAssertion<>(value.asType(type).orElse(null), returns());
 	}
 
+	/**
+	 * Provides the ability to perform fluent-style assertions on this response header.
+	 *
+	 * <p>
+	 * Converts the header to a generic Java object using {@link ResponseHeader#asType(Class)} and then returns the value as an object assertion.
+	 *
+	 * <h5 class='section'>Examples:</h5>
+	 * <p class='bcode w800'>
+	 * 	<jc>// Validates the response header exists and is the specified value.</jc>
+	 * 	<jv>client</jv>
+	 * 		.get(<js>"/myBean"</js>)
+	 * 		.run()
+	 * 		.assertHeader(<js>"Foo"</js>).asObject().exists().asJson().is(<js>"'foobar'"</js>);
+	 * </p>
+	 *
+	 * <ul class='notes'>
+	 * 	<li>
+	 * 		If no charset was found on the <code>Content-Type</code> response header, <js>"UTF-8"</js> is assumed.
+	 *  <li>
+	 *		When using this method, the body is automatically cached by calling the {@link ResponseBody#cache()}.
+	 * 	<li>
+	 * 		The input stream is automatically closed after this call.
+	 * </ul>
+	 *
+	 * @return A new fluent assertion object.
+	 * @throws RestCallException If REST call failed.
+	 */
+	public FluentObjectAssertion<Object,R> asObject() throws RestCallException {
+		return new FluentObjectAssertion<>(value.asType(Object.class), returns());
+	}
+
 	// <FluentSetters>
 
 	@Override /* GENERATED - Assertion */
diff --git a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseStatusLineAssertion.java b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseStatusLineAssertion.java
index 91a4cd7..b406dc4 100644
--- a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseStatusLineAssertion.java
+++ b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/assertion/FluentResponseStatusLineAssertion.java
@@ -15,6 +15,7 @@ package org.apache.juneau.rest.client.assertion;
 import org.apache.http.*;
 import org.apache.juneau.assertions.*;
 import org.apache.juneau.http.response.*;
+import org.apache.juneau.internal.*;
 
 /**
  * Used for fluent assertion calls against a response {@link StatusLine} object.
@@ -30,7 +31,8 @@ import org.apache.juneau.http.response.*;
  *
  * @param <R> The return type.
  */
-public class FluentResponseStatusLineAssertion<R> extends FluentAssertion<R> {
+@FluentSetters(returns="FluentResponseStatusLineAssertion<R>")
+public class FluentResponseStatusLineAssertion<R> extends FluentObjectAssertion<StatusLine,R> {
 
 	private final StatusLine statusLine;
 
diff --git a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
index 30aac6c..207dcc9 100644
--- a/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
+++ b/juneau-rest/juneau-rest-mock/src/main/java/org/apache/juneau/rest/mock/MockRestClientBuilder.java
@@ -939,8 +939,8 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder formData(NameValuePair value) {
-		super.formData(value);
+	public MockRestClientBuilder formData(NameValuePair part) {
+		super.formData(part);
 		return this;
 	}
 
@@ -981,8 +981,8 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder header(Header value) {
-		super.header(value);
+	public MockRestClientBuilder header(Header part) {
+		super.header(part);
 		return this;
 	}
 
@@ -1005,14 +1005,14 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder headers(Header...headers) {
-		super.headers(headers);
+	public MockRestClientBuilder headers(Header...parts) {
+		super.headers(parts);
 		return this;
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder headers(HeaderList headers) {
-		super.headers(headers);
+	public MockRestClientBuilder headers(HeaderList parts) {
+		super.headers(parts);
 		return this;
 	}
 
@@ -1330,8 +1330,8 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder pathData(NameValuePair value) {
-		super.pathData(value);
+	public MockRestClientBuilder pathData(NameValuePair part) {
+		super.pathData(part);
 		return this;
 	}
 
@@ -1408,8 +1408,8 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
-	public MockRestClientBuilder queryData(NameValuePair value) {
-		super.queryData(value);
+	public MockRestClientBuilder queryData(NameValuePair part) {
+		super.queryData(part);
 		return this;
 	}
 
@@ -1529,6 +1529,42 @@ public class MockRestClientBuilder extends RestClientBuilder {
 	}
 
 	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyFormData() {
+		super.skipEmptyFormData();
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyFormData(boolean value) {
+		super.skipEmptyFormData(value);
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyHeaders() {
+		super.skipEmptyHeaders();
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyHeaders(boolean value) {
+		super.skipEmptyHeaders(value);
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyQueryData() {
+		super.skipEmptyQueryData();
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
+	public MockRestClientBuilder skipEmptyQueryData(boolean value) {
+		super.skipEmptyQueryData(value);
+		return this;
+	}
+
+	@Override /* GENERATED - RestClientBuilder */
 	public MockRestClientBuilder sortCollections() {
 		super.sortCollections();
 		return this;
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormParam.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormParam.java
index 2222825..2a6e252 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormParam.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormParam.java
@@ -294,7 +294,7 @@ public class RequestFormParam extends RequestHttpPart implements NameValuePair {
 	 *
 	 * @return A new fluent assertion object.
 	 */
-	public FluentListAssertion<RequestFormParam> assertCsvArray() {
+	public FluentListAssertion<String,RequestFormParam> assertCsvArray() {
 		return new FluentListAssertion<>(asCsvArrayPart().asList().orElse(null), this);
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
index d22d569..848c054 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPathParam.java
@@ -264,7 +264,7 @@ public class RequestPathParam extends RequestHttpPart implements NameValuePair {
 	 *
 	 * @return A new fluent assertion object.
 	 */
-	public FluentListAssertion<RequestPathParam> assertCsvArray() {
+	public FluentListAssertion<String,RequestPathParam> assertCsvArray() {
 		return new FluentListAssertion<>(asCsvArrayPart().asList().orElse(null), this);
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQueryParam.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQueryParam.java
index 0d55e0f..723bd21 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQueryParam.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQueryParam.java
@@ -273,7 +273,7 @@ public class RequestQueryParam extends RequestHttpPart implements NameValuePair
 	 *
 	 * @return A new fluent assertion object.
 	 */
-	public FluentListAssertion<RequestQueryParam> assertCsvArray() {
+	public FluentListAssertion<String,RequestQueryParam> assertCsvArray() {
 		return new FluentListAssertion<>(asCsvArrayPart().asList().orElse(null), this);
 	}
 
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentProtocolVersionAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentProtocolVersionAssertion.java
index 082ee1f..af0299e 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentProtocolVersionAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentProtocolVersionAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.internal.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentProtocolVersionAssertion<R>")
-public class FluentProtocolVersionAssertion<R> extends FluentAssertion<R> {
-
-	private final ProtocolVersion value;
+public class FluentProtocolVersionAssertion<R> extends FluentObjectAssertion<ProtocolVersion,R> {
 
 	/**
 	 * Constructor.
@@ -37,7 +35,6 @@ public class FluentProtocolVersionAssertion<R> extends FluentAssertion<R> {
 	 */
 	public FluentProtocolVersionAssertion(ProtocolVersion value, R returns) {
 		super(null, returns);
-		this.value = value;
 		throwable(BadRequest.class);
 	}
 
@@ -47,7 +44,7 @@ public class FluentProtocolVersionAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentStringAssertion<R> protocol() {
-		return new FluentStringAssertion<>(value.getProtocol(), returns());
+		return new FluentStringAssertion<>(value().getProtocol(), returns());
 	}
 
 	/**
@@ -56,7 +53,7 @@ public class FluentProtocolVersionAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentIntegerAssertion<R> major() {
-		return new FluentIntegerAssertion<>(value.getMajor(), returns());
+		return new FluentIntegerAssertion<>(value().getMajor(), returns());
 	}
 
 	/**
@@ -65,7 +62,7 @@ public class FluentProtocolVersionAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentIntegerAssertion<R> minor() {
-		return new FluentIntegerAssertion<>(value.getMinor(), returns());
+		return new FluentIntegerAssertion<>(value().getMinor(), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestBodyAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestBodyAssertion.java
index 32f60b3..19ee774 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestBodyAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestBodyAssertion.java
@@ -13,7 +13,6 @@
 package org.apache.juneau.rest.assertions;
 
 import java.io.*;
-import java.util.function.*;
 
 import org.apache.juneau.assertions.*;
 import org.apache.juneau.http.response.*;
@@ -26,9 +25,7 @@ import org.apache.juneau.rest.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentRequestBodyAssertion<R>")
-public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
-
-	private final RequestBody value;
+public class FluentRequestBodyAssertion<R> extends FluentObjectAssertion<RequestBody,R> {
 
 	/**
 	 * Constructor.
@@ -38,7 +35,6 @@ public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
 	 */
 	public FluentRequestBodyAssertion(RequestBody value, R returns) {
 		super(null, returns);
-		this.value = value;
 		throwable(BadRequest.class);
 	}
 
@@ -73,6 +69,7 @@ public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
 	 *
 	 * @return A new fluent assertion object.
 	 */
+	@Override
 	public FluentStringAssertion<R> asString() {
 		return new FluentStringAssertion<>(valueAsString(), returns());
 	}
@@ -195,22 +192,13 @@ public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
 		return asString().isNotEmpty();
 	}
 
-	/**
-	 * Asserts that the value passes the specified predicate test.
-	 *
-	 * @param test The predicate to use to test the value.
-	 * @return The request object (for method chaining).
-	 * @throws AssertionError If assertion failed.
-	 */
-	public R passes(Predicate<String> test) throws AssertionError {
-		if (! test.test(valueAsString()))
-			throw error("Value did not pass predicate test.\n\tValue=[{0}]", value);
-		return returns();
-	}
+	//-----------------------------------------------------------------------------------------------------------------
+	// Helper methods.
+	//-----------------------------------------------------------------------------------------------------------------
 
 	private String valueAsString() throws AssertionError {
 		try {
-			return value.cache().asString();
+			return value().cache().asString();
 		} catch (IOException e) {
 			throw error(e, "Exception occurred during call.");
 		}
@@ -218,7 +206,7 @@ public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
 
 	private byte[] valueAsBytes() throws AssertionError {
 		try {
-			return value.cache().asBytes();
+			return value().cache().asBytes();
 		} catch (IOException e) {
 			throw error(e, "Exception occurred during call.");
 		}
@@ -226,7 +214,7 @@ public class FluentRequestBodyAssertion<R> extends FluentAssertion<R> {
 
 	private <T> T valueAsType(Class<T> c) throws AssertionError {
 		try {
-			return value.cache().asType(c);
+			return value().cache().asType(c);
 		} catch (IOException e) {
 			throw error(e, "Exception occurred during call.");
 		}
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestFormParamAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestFormParamAssertion.java
index f1ffedb..8c0fbc2 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestFormParamAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestFormParamAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.rest.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentRequestFormParamAssertion<R>")
-public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<String,R> {
-
-	private final RequestFormParam value;
+public class FluentRequestFormParamAssertion<R> extends FluentObjectAssertion<RequestFormParam,R> {
 
 	/**
 	 * Constructor.
@@ -48,8 +46,18 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @param returns The object to return after the test.
 	 */
 	public FluentRequestFormParamAssertion(Assertion creator, RequestFormParam value, R returns) {
-		super(creator, value.getValue(), returns);
-		this.value = value;
+		super(creator, value, returns);
+	}
+
+	/**
+	 * Converts this object assertion into a string assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a string.
+	 */
+	@Override
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(this, value().asString().orElse(null), returns());
 	}
 
 	/**
@@ -58,8 +66,9 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a boolean.
 	 */
+	@Override
 	public FluentBooleanAssertion<R> asBoolean() {
-		return new FluentBooleanAssertion<>(this, value.asBoolean().orElse(null), returns());
+		return new FluentBooleanAssertion<>(this, value().asBoolean().orElse(null), returns());
 	}
 
 	/**
@@ -68,8 +77,9 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a date.
 	 */
+	@Override
 	public FluentDateAssertion<R> asDate() {
-		return new FluentDateAssertion<>(this, value.asDatePart().asDate().orElse(null), returns());
+		return new FluentDateAssertion<>(this, value().asDatePart().asDate().orElse(null), returns());
 	}
 
 	/**
@@ -78,8 +88,9 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an integer.
 	 */
+	@Override
 	public FluentIntegerAssertion<R> asInteger() {
-		return new FluentIntegerAssertion<>(this, value.asInteger().orElse(null), returns());
+		return new FluentIntegerAssertion<>(this, value().asInteger().orElse(null), returns());
 	}
 
 	/**
@@ -88,8 +99,9 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a long.
 	 */
+	@Override
 	public FluentLongAssertion<R> asLong() {
-		return new FluentLongAssertion<>(this, value.asLong().orElse(null), returns());
+		return new FluentLongAssertion<>(this, value().asLong().orElse(null), returns());
 	}
 
 	/**
@@ -98,8 +110,9 @@ public class FluentRequestFormParamAssertion<R> extends FluentBaseAssertion<Stri
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a zoned-datetime.
 	 */
+	@Override
 	public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-		return new FluentZonedDateTimeAssertion<>(this, value.asDatePart().asZonedDateTime().orElse(null), returns());
+		return new FluentZonedDateTimeAssertion<>(this, value().asDatePart().asZonedDateTime().orElse(null), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestHeaderAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestHeaderAssertion.java
index 4997901..e077d05 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestHeaderAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestHeaderAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.rest.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentRequestHeaderAssertion<R>")
-public class FluentRequestHeaderAssertion<R> extends FluentBaseAssertion<String,R> {
-
-	private final RequestHeader value;
+public class FluentRequestHeaderAssertion<R> extends FluentObjectAssertion<RequestHeader,R> {
 
 	/**
 	 * Constructor.
@@ -47,19 +45,30 @@ public class FluentRequestHeaderAssertion<R> extends FluentBaseAssertion<String,
 	 * @param returns The object to return after the test.
 	 */
 	public FluentRequestHeaderAssertion(Assertion creator, RequestHeader value, R returns) {
-		super(creator, value.getValue(), returns);
-		this.value = value;
+		super(creator, value, returns);
 		throwable(BadRequest.class);
 	}
 
 	/**
+	 * Converts this object assertion into a string assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a string.
+	 */
+	@Override
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(this, value().asString().orElse(null), returns());
+	}
+
+	/**
 	 * Converts this object assertion into a boolean assertion.
 	 *
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a boolean.
 	 */
+	@Override
 	public FluentBooleanAssertion<R> asBoolean() {
-		return new FluentBooleanAssertion<>(this, value.asBoolean().orElse(null), returns());
+		return new FluentBooleanAssertion<>(this, value().asBoolean().orElse(null), returns());
 	}
 
 	/**
@@ -68,8 +77,9 @@ public class FluentRequestHeaderAssertion<R> extends FluentBaseAssertion<String,
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an integer.
 	 */
+	@Override
 	public FluentIntegerAssertion<R> asInteger() {
-		return new FluentIntegerAssertion<>(this, value.asInteger().orElse(null), returns());
+		return new FluentIntegerAssertion<>(this, value().asInteger().orElse(null), returns());
 	}
 
 	/**
@@ -78,8 +88,9 @@ public class FluentRequestHeaderAssertion<R> extends FluentBaseAssertion<String,
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a long.
 	 */
+	@Override
 	public FluentLongAssertion<R> asLong() {
-		return new FluentLongAssertion<>(this, value.asLong().orElse(null), returns());
+		return new FluentLongAssertion<>(this, value().asLong().orElse(null), returns());
 	}
 
 	/**
@@ -88,8 +99,9 @@ public class FluentRequestHeaderAssertion<R> extends FluentBaseAssertion<String,
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a zoned-datetime.
 	 */
+	@Override
 	public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-		return new FluentZonedDateTimeAssertion<>(this, value.asDateHeader().asZonedDateTime().orElse(null), returns());
+		return new FluentZonedDateTimeAssertion<>(this, value().asDateHeader().asZonedDateTime().orElse(null), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestLineAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestLineAssertion.java
index 577f8f7..5c54575 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestLineAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestLineAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.internal.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentRequestLineAssertion<R>")
-public class FluentRequestLineAssertion<R> extends FluentAssertion<R> {
-
-	private final RequestLine value;
+public class FluentRequestLineAssertion<R> extends FluentObjectAssertion<RequestLine,R> {
 
 	/**
 	 * Constructor.
@@ -37,7 +35,6 @@ public class FluentRequestLineAssertion<R> extends FluentAssertion<R> {
 	 */
 	public FluentRequestLineAssertion(RequestLine value, R returns) {
 		super(null, returns);
-		this.value = value;
 		throwable(BadRequest.class);
 	}
 
@@ -47,7 +44,7 @@ public class FluentRequestLineAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentStringAssertion<R> method() {
-		return new FluentStringAssertion<>(value.getMethod(), returns());
+		return new FluentStringAssertion<>(value().getMethod(), returns());
 	}
 
 	/**
@@ -56,7 +53,7 @@ public class FluentRequestLineAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentStringAssertion<R> uri() {
-		return new FluentStringAssertion<>(value.getUri(), returns());
+		return new FluentStringAssertion<>(value().getUri(), returns());
 	}
 
 	/**
@@ -65,7 +62,7 @@ public class FluentRequestLineAssertion<R> extends FluentAssertion<R> {
 	 * @return A new assertion.
 	 */
 	public FluentProtocolVersionAssertion<R> protocolVersion() {
-		return new FluentProtocolVersionAssertion<>(value.getProtocolVersion(), returns());
+		return new FluentProtocolVersionAssertion<>(value().getProtocolVersion(), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestQueryParamAssertion.java b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestQueryParamAssertion.java
index 667213d..599b2b9 100644
--- a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestQueryParamAssertion.java
+++ b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/assertions/FluentRequestQueryParamAssertion.java
@@ -25,9 +25,7 @@ import org.apache.juneau.rest.*;
  * @param <R> The return type.
  */
 @FluentSetters(returns="FluentRequestQueryParamAssertion<R>")
-public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<String,R> {
-
-	private final RequestQueryParam value;
+public class FluentRequestQueryParamAssertion<R> extends FluentObjectAssertion<RequestQueryParam,R> {
 
 	/**
 	 * Constructor.
@@ -48,8 +46,18 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @param returns The object to return after the test.
 	 */
 	public FluentRequestQueryParamAssertion(Assertion creator, RequestQueryParam value, R returns) {
-		super(creator, value.getValue(), returns);
-		this.value = value;
+		super(creator, value, returns);
+	}
+
+	/**
+	 * Converts this object assertion into a string assertion.
+	 *
+	 * @return A new assertion.
+	 * @throws AssertionError If object is not a string.
+	 */
+	@Override
+	public FluentStringAssertion<R> asString() {
+		return new FluentStringAssertion<>(this, value().asString().orElse(null), returns());
 	}
 
 	/**
@@ -58,8 +66,9 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a boolean.
 	 */
+	@Override
 	public FluentBooleanAssertion<R> asBoolean() {
-		return new FluentBooleanAssertion<>(this, value.asBoolean().orElse(null), returns());
+		return new FluentBooleanAssertion<>(this, value().asBoolean().orElse(null), returns());
 	}
 
 	/**
@@ -68,8 +77,9 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a date.
 	 */
+	@Override
 	public FluentDateAssertion<R> asDate() {
-		return new FluentDateAssertion<>(this, value.asDatePart().asDate().orElse(null), returns());
+		return new FluentDateAssertion<>(this, value().asDatePart().asDate().orElse(null), returns());
 	}
 
 	/**
@@ -78,8 +88,9 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not an integer.
 	 */
+	@Override
 	public FluentIntegerAssertion<R> asInteger() {
-		return new FluentIntegerAssertion<>(this, value.asInteger().orElse(null), returns());
+		return new FluentIntegerAssertion<>(this, value().asInteger().orElse(null), returns());
 	}
 
 	/**
@@ -88,8 +99,9 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a long.
 	 */
+	@Override
 	public FluentLongAssertion<R> asLong() {
-		return new FluentLongAssertion<>(this, value.asLong().orElse(null), returns());
+		return new FluentLongAssertion<>(this, value().asLong().orElse(null), returns());
 	}
 
 	/**
@@ -98,8 +110,9 @@ public class FluentRequestQueryParamAssertion<R> extends FluentBaseAssertion<Str
 	 * @return A new assertion.
 	 * @throws AssertionError If object is not a zoned-datetime.
 	 */
+	@Override
 	public FluentZonedDateTimeAssertion<R> asZonedDateTime() {
-		return new FluentZonedDateTimeAssertion<>(this, value.asDatePart().asZonedDateTime().orElse(null), returns());
+		return new FluentZonedDateTimeAssertion<>(this, value().asDatePart().asZonedDateTime().orElse(null), returns());
 	}
 
 	// <FluentSetters>
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanConfigTest.java b/juneau-utest/src/test/java/org/apache/juneau/BeanConfigTest.java
index 33ef8dd..587e93e 100755
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanConfigTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanConfigTest.java
@@ -504,7 +504,7 @@ public class BeanConfigTest {
 	//====================================================================================================
 	@Test
 	public void testProxyHandler() throws Exception {
-		BeanSession session = BeanContext.DEFAULT.createBeanSession();
+		BeanSession session = BeanContext.DEFAULT_SESSION;
 
 		A f1 = (A) Proxy.newProxyInstance(this.getClass()
 				.getClassLoader(), new Class[] { A.class },
diff --git a/juneau-utest/src/test/java/org/apache/juneau/BeanContextTest.java b/juneau-utest/src/test/java/org/apache/juneau/BeanContextTest.java
index 6edb811..85a2d6b 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/BeanContextTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanContextTest.java
@@ -22,6 +22,7 @@ import org.junit.*;
 public class BeanContextTest {
 
 	BeanContext bc = BeanContext.DEFAULT;
+	BeanSession bs = BeanContext.DEFAULT_SESSION;
 
 	public static interface A1 {
 		public int getF1();
@@ -48,7 +49,7 @@ public class BeanContextTest {
 
 	@Test
 	public void proxiesNotCached() throws ExecutableException {
-		A1 a1 = bc.createBeanSession().getBeanMeta(A1.class).newBean(null);
+		A1 a1 = bs.getBeanMeta(A1.class).newBean(null);
 		ClassMeta cm1 = bc.getClassMeta(a1.getClass()), cm2 = bc.getClassMeta(a1.getClass());
 		assertTrue(cm1 != cm2);
 	}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/Version_Test.java b/juneau-utest/src/test/java/org/apache/juneau/Version_Test.java
new file mode 100644
index 0000000..12adb50
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/Version_Test.java
@@ -0,0 +1,109 @@
+// ***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                                                              *
+// *                                                                                                                         *
+// *  http://www.apache.org/licenses/LICENSE-2.0                                                                             *
+// *                                                                                                                         *
+// * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the License.                                              *
+// ***************************************************************************************************************************
+package org.apache.juneau;
+
+import static org.apache.juneau.assertions.Assertions.*;
+import static org.junit.Assert.*;
+import static org.junit.runners.MethodSorters.*;
+
+import java.util.*;
+
+import org.apache.juneau.collections.*;
+
+import static org.apache.juneau.Version.*;
+
+import org.junit.*;
+
+@FixMethodOrder(NAME_ASCENDING)
+public class Version_Test {
+
+	@Test
+	public void a01_basic() {
+		assertObject(of(null)).isNull();
+		assertObject(of("")).asString().is("0");
+
+		Version x = of("1.2.3");
+		assertInteger(x.getMajor()).is(1);
+		assertInteger(x.getMinor()).is(2);
+		assertInteger(x.getMaintenance()).is(3);
+		assertInteger(x.getPart(0)).is(1);
+		assertInteger(x.getPart(1)).is(2);
+		assertInteger(x.getPart(2)).is(3);
+		assertInteger(x.getPart(-1)).isNull();
+		assertInteger(x.getPart(3)).isNull();
+
+		x = of("1..x");
+		assertString(x).is("1.0.2147483647");
+	}
+
+	@Test
+	public void a02_isAtLeast() {
+		Version x = of("1.2.3");
+
+		assertTrue(x.isAtLeast(of("1.2.2")));
+		assertTrue(x.isAtLeast(of("1.2.3")));
+		assertFalse(x.isAtLeast(of("1.2.4")));
+		assertTrue(x.isAtLeast(of("1.2.2"), true));
+		assertFalse(x.isAtLeast(of("1.2.3"), true));
+		assertFalse(x.isAtLeast(of("1.2.4"), true));
+		assertTrue(x.isAtLeast(of("1.2")));
+		assertFalse(x.isAtLeast(of("1.3")));
+		assertTrue(x.isAtLeast(of("1.1.3.1")));
+		assertFalse(x.isAtLeast(of("1.2.3.1")));
+		assertTrue(x.isAtLeast(of("1.2.3.0")));
+		assertFalse(x.isAtLeast(of("1.3.0.1")));
+	}
+
+	@Test
+	public void a03_isAtMost() {
+		Version x = of("1.2.3");
+
+		assertFalse(x.isAtMost(of("1.2.2")));
+		assertTrue(x.isAtMost(of("1.2.3")));
+		assertTrue(x.isAtMost(of("1.2.4")));
+		assertFalse(x.isAtMost(of("1.2.2"), true));
+		assertFalse(x.isAtMost(of("1.2.3"), true));
+		assertTrue(x.isAtMost(of("1.2.4"), true));
+		assertTrue(x.isAtMost(of("1.2")));
+		assertTrue(x.isAtMost(of("1.3")));
+		assertFalse(x.isAtMost(of("1.1.3.1")));
+		assertTrue(x.isAtMost(of("1.2.3.1")));
+		assertTrue(x.isAtMost(of("1.2.3.0")));
+		assertTrue(x.isAtMost(of("1.3.0.1")));
+	}
+
+	@Test
+	public void a04_isEqualsTo() {
+		Version x = of("1.2.3");
+
+		assertTrue(x.isEqualsTo(of("1.2.3")));
+		assertTrue(x.isEqualsTo(of("1.2")));
+		assertTrue(x.isEqualsTo(of("1.2.3.4")));
+		assertFalse(x.isEqualsTo(of("1.2.4")));
+	}
+
+	@Test
+	public void a05_compareTo() {
+		List<Version> l = AList.of(
+			of("1.2.3"),
+			of("1.2"),
+			of(""),
+			of("1.2.3.4"),
+			of("2.0"),
+			of("2")
+		);
+		assertList(l).sorted().asString().is("[0, 1.2, 1.2.3, 1.2.3.4, 2, 2.0]");
+		Collections.reverse(l);
+		assertList(l).sorted().asString().is("[0, 1.2, 1.2.3, 1.2.3.4, 2, 2.0]");
+	}
+}
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ArrayAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ArrayAssertion_Test.java
index 6130ea7..caddd01 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ArrayAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ArrayAssertion_Test.java
@@ -20,7 +20,7 @@ import org.junit.*;
 @FixMethodOrder(NAME_ASCENDING)
 public class ArrayAssertion_Test {
 
-	private ArrayAssertion test(Object value) {
+	private <E> ArrayAssertion<E> test(E[] value) {
 		return assertArray(value).silent();
 	}
 
@@ -53,11 +53,11 @@ public class ArrayAssertion_Test {
 		test(x2).item(0).exists();
 
 		test(x2).contains("foo");
-		assertThrown(()->test(x2).contains("z")).is("Array did not contain expected value.\n\tContents: ['foo','bar']\n\tExpected: z");
+		assertThrown(()->test(x2).contains("z")).is("Array did not contain expected value.\n\tContents: [foo, bar]\n\tExpected: z");
 
 		test(x1).doesNotContain("foo");
-		assertThrown(()->test(x2).doesNotContain("foo")).is("Array contained unexpected value.\n\tContents: ['foo','bar']\n\tUnexpected: foo");
-		assertThrown(()->test(x2).doesNotContain("bar")).is("Array contained unexpected value.\n\tContents: ['foo','bar']\n\tUnexpected: bar");
+		assertThrown(()->test(x2).doesNotContain("foo")).is("Array contained unexpected value.\n\tContents: [foo, bar]\n\tUnexpected: foo");
+		assertThrown(()->test(x2).doesNotContain("bar")).is("Array contained unexpected value.\n\tContents: [foo, bar]\n\tUnexpected: bar");
 	}
 
 	@Test
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/BeanAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/BeanAssertion_Test.java
index 38de897..db30047 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/BeanAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/BeanAssertion_Test.java
@@ -38,12 +38,12 @@ public class BeanAssertion_Test {
 		assertCollection(null).doesNotExist();
 		assertThrown(()->test(a).doesNotExist()).is("Value was not null.");
 
-		test(a).field("f1").asInteger().is(1);
-		test(a).field("x").asInteger().isNull();
-		assertThrown(()->test((Object)null).field("x")).is("Value was null.");
+		test(a).property("f1").asInteger().is(1);
+		test(a).property("x").asInteger().isNull();
+		assertThrown(()->test((Object)null).property("x")).is("Value was null.");
 
-		test(a).fields("f2,f1").asJson().is("{f2:2,f1:1}");
-		test(a).fields("x").asJson().is("{}");
+		test(a).mapOf("f2,f1").asJson().is("{f2:2,f1:1}");
+		test(a).mapOf("x").asJson().is("{}");
 	}
 
 	@Test
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/CollectionAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/CollectionAssertion_Test.java
index 8e5ac11..a280ca8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/CollectionAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/CollectionAssertion_Test.java
@@ -23,7 +23,7 @@ import org.junit.*;
 @FixMethodOrder(NAME_ASCENDING)
 public class CollectionAssertion_Test {
 
-	private CollectionAssertion test(Collection<?> value) {
+	private <T> CollectionAssertion<T> test(Collection<T> value) {
 		return assertCollection(value).silent();
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ComparableAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ComparableAssertion_Test.java
index fa84f7e..01dd6de 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ComparableAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ComparableAssertion_Test.java
@@ -20,7 +20,7 @@ import org.junit.*;
 @FixMethodOrder(NAME_ASCENDING)
 public class ComparableAssertion_Test {
 
-	private ComparableAssertion test(Comparable<?> value) {
+	private <T extends Comparable<T>> ComparableAssertion<T> test(T value) {
 		return assertComparable(value).silent();
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ListAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ListAssertion_Test.java
index 3205965..263a395 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ListAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ListAssertion_Test.java
@@ -23,7 +23,7 @@ import org.junit.*;
 @FixMethodOrder(NAME_ASCENDING)
 public class ListAssertion_Test {
 
-	private ListAssertion test(List<?> value) {
+	private <E> ListAssertion<E> test(List<E> value) {
 		return assertList(value).silent();
 	}
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/MapAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/MapAssertion_Test.java
index 27b733d..0be48e3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/MapAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/MapAssertion_Test.java
@@ -23,7 +23,7 @@ import org.junit.*;
 @FixMethodOrder(NAME_ASCENDING)
 public class MapAssertion_Test {
 
-	private MapAssertion test(Map<?,?> value) {
+	private <K,V> MapAssertion<K,V> test(Map<K,V> value) {
 		return assertMap(value).silent();
 	}
 
@@ -53,7 +53,7 @@ public class MapAssertion_Test {
 
 		test(x2).value("a").asInteger().is(1);
 		test(x2).value("z").asInteger().isNull();
-		test((Map<?,?>)null).value("a").asInteger().isNull();
+		test((Map<String,Object>)null).value("a").asInteger().isNull();
 
 		test(x2).containsKey("a");
 		assertThrown(()->test(x2).containsKey("x")).is("Map did not contain expected key.\n\tContents: {a:1,b:2}\n\tExpected key: x");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
index 01ac6ac..6ff00f3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ObjectAssertion_Test.java
@@ -56,7 +56,7 @@ public class ObjectAssertion_Test {
 		test(1).exists();
 		test(of(1)).exists();
 
-		assertThrown(()->test(empty()).isType(null)).is("Value was null.");
+		assertThrown(()->test(empty()).isType(String.class)).is("Value was null.");
 		assertThrown(()->test("foo").isType(null)).is("Parameter 'parent' cannot be null.");
 		test("foo").isType(String.class);
 		test("foo").isType(CharSequence.class);
@@ -91,14 +91,15 @@ public class ObjectAssertion_Test {
 		test(x1).doesNotEqual(null);
 		test(empty()).doesNotEqual(x1);
 		test(x1).doesNotEqual(x2);
-		assertThrown(()->test(empty()).doesNotEqual(null)).is("Unexpected value.\n\tExpected not=[null]\n\tActual=[null]");
-		assertThrown(()->test(x1).doesNotEqual(x1)).is("Unexpected value.\n\tExpected not=[[1,2]]\n\tActual=[[1,2]]");
+
+		assertThrown(()->test(empty()).doesNotEqual(null)).is("Unexpected value.\n\tDid not expect=[null]\n\tActual=[null]");
+		assertThrown(()->test(x1).doesNotEqual(x1)).is("Unexpected value.\n\tDid not expect=[[1,2]]\n\tActual=[[1,2]]");
 
 		test(x1).passes(x->x != null);
-		assertThrown(()->test(x1).passes(x->x == null)).is("Value did not pass predicate test.\n\tValue=[[1,2]]");
+		assertThrown(()->test(x1).passes(x->x == null)).is("Unexpected value: '[1,2]'");
 
 		test(x1).passes(x->x[0] == 1);
-		assertThrown(()->test(x1).passes(x->x[0]==2)).is("Value did not pass predicate test.\n\tValue=[[1,2]]");
+		assertThrown(()->test(x1).passes(x->x[0]==2)).is("Unexpected value: '[1,2]'");
 
 		test(x1).isNot(null);
 
@@ -115,7 +116,6 @@ public class ObjectAssertion_Test {
 		test((Object)null).asString().isNull();
 
 		test(123).asString(x -> x.toString()).is("123");
-		test(123).asString(Integer.class, x -> String.valueOf(x.intValue())).is("123");
 	}
 
 	@Test
@@ -126,8 +126,8 @@ public class ObjectAssertion_Test {
 
 	@Test
 	public void a03_conversions() throws Exception {
-		test(new String[]{"foo"}).asArray().item(0).is("foo");
-		assertThrown(()->test("foo").asArray()).contains("Object was not an array");
+		test(new String[]{"foo"}).asArray(String.class).item(0).is("foo");
+		assertThrown(()->test("foo").asArray(String.class)).contains("Object was not type 'java.lang.String[]'.  Actual='java.lang.String'");
 
 		test(true).asBoolean().isTrue();
 		assertThrown(()->test("foo").asBoolean()).contains("Object was not type 'java.lang.Boolean'.  Actual='java.lang.String'");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/StringAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/StringAssertion_Test.java
index ebad9f5..da82bd7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/StringAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/StringAssertion_Test.java
@@ -63,7 +63,7 @@ public class StringAssertion_Test {
 
 		test("foo\nbar\nbaz").isEqualLinesTo("foo","bar","baz");
 		assertThrown(()->test(empty()).isEqualLinesTo((String[])null)).is("Parameter 'lines' cannot be null.");
-		assertThrown(()->test(empty()).isEqualLinesTo((String)null)).is("Text differed at position -1.\n\tExpect=[]\n\tActual=[null]");
+		assertThrown(()->test(empty()).isEqualLinesTo((String)null)).is("Value was null.");
 		assertThrown(()->test("foo\nbar\nbaz").javaStrings().isEqualLinesTo("foo","bar","bar")).is("Text differed at position 10.\n\tExpect=[foo\\nbar\\nbar]\n\tActual=[foo\\nbar\\nbaz]");
 
 		test("foo\nbar\nbaz").isEqualSortedLinesTo("bar","foo","baz");
diff --git a/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
index be1bd1a..1062e75 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/assertions/ThrowableAssertion_Test.java
@@ -32,7 +32,7 @@ public class ThrowableAssertion_Test {
 
 		test(x1).isType(Exception.class).isType(RuntimeException.class);
 		assertThrown(()->test(x1).isType(IOException.class)).is("Exception was not expected type.\n\tExpect=[java.io.IOException]\n\tActual=[java.lang.RuntimeException]");
-		assertThrown(()->test(null).isType(IOException.class)).is("Exception was not expected type.\n\tExpect=[java.io.IOException]\n\tActual=[null]");
+		assertThrown(()->test(null).isType(IOException.class)).is("Exception was not thrown.");
 		assertThrown(()->test(x1).isType(null)).is("Parameter 'type' cannot be null.");
 
 		test(x1).contains("foo");
@@ -45,16 +45,16 @@ public class ThrowableAssertion_Test {
 		assertThrown(()->test(x1).doesNotExist()).is("Exception was thrown.");
 
 		test(x1).passes(x->x.getMessage().equals("foo"));
-		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
+		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Unexpected value: 'java.lang.RuntimeException: foo'");
 
 		test(x1).passes(x->x.getMessage().equals("foo"));
-		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
+		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Unexpected value: 'java.lang.RuntimeException: foo'");
 
 		test(x1).passes(x->x.getMessage().equals("foo"));
-		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
+		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Unexpected value: 'java.lang.RuntimeException: foo'");
 
 		test(x1).passes(x->x.getMessage().equals("foo"));
-		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Value did not pass predicate test.\n\tValue=[java.lang.RuntimeException: foo]");
+		assertThrown(()->test(x1).passes(x->x.getMessage().equals("bar"))).is("Unexpected value: 'java.lang.RuntimeException: foo'");
 
 		test(x1).message().is("foo");
 		test(new RuntimeException()).message().doesNotExist();
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/SerializedHeader_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/SerializedHeader_Test.java
index a29a36f..f24ae8c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/SerializedHeader_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/SerializedHeader_Test.java
@@ -50,11 +50,11 @@ public class SerializedHeader_Test {
 	@Test
 	public void a03_serializer() throws Exception {
 		SerializedHeader x1 = serializedHeader("Foo",list("bar","baz")).serializer((HttpPartSerializer)null);
-		assertString(x1.getValue()).is("['bar','baz']");
+		assertString(x1.getValue()).is("[bar, baz]");
 		SerializedHeader x2 = serializedHeader("Foo",list("bar","baz")).serializer((HttpPartSerializer)null).serializer(OAPI_SERIALIZER);
 		assertString(x2.getValue()).is("bar,baz");
 		SerializedHeader x3 = serializedHeader("Foo",list("bar","baz")).serializer(OAPI_SERIALIZER).serializer((HttpPartSerializerSession)null);
-		assertString(x3.getValue()).is("['bar','baz']");
+		assertString(x3.getValue()).is("[bar, baz]");
 		SerializedHeader x4 = serializedHeader("Foo",list("bar","baz")).serializer(OAPI_SERIALIZER).copyWith(null,null);
 		assertString(x4.getValue()).is("bar,baz");
 		SerializedHeader x5 = serializedHeader("Foo",list("bar","baz")).copyWith(OAPI_SERIALIZER.createPartSession(null),null);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/SerializedPart_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/SerializedPart_Test.java
index e21bd34..e8daf68 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/SerializedPart_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/SerializedPart_Test.java
@@ -51,11 +51,11 @@ public class SerializedPart_Test {
 	@Test
 	public void a03_serializer() throws Exception {
 		SerializedPart x1 = serializedPart("Foo",list("bar","baz")).serializer((HttpPartSerializer)null);
-		assertString(x1.getValue()).is("['bar','baz']");
+		assertString(x1.getValue()).is("[bar, baz]");
 		SerializedPart x2 = serializedPart("Foo",list("bar","baz")).serializer((HttpPartSerializer)null).serializer(OAPI_SERIALIZER);
 		assertString(x2.getValue()).is("bar,baz");
 		SerializedPart x3 = serializedPart("Foo",list("bar","baz")).serializer(OAPI_SERIALIZER).serializer((HttpPartSerializerSession)null);
-		assertString(x3.getValue()).is("['bar','baz']");
+		assertString(x3.getValue()).is("[bar, baz]");
 		SerializedPart x4 = serializedPart("Foo",list("bar","baz")).serializer(OAPI_SERIALIZER).copyWith(null,null);
 		assertString(x4.getValue()).is("bar,baz");
 		SerializedPart x5 = serializedPart("Foo",list("bar","baz")).copyWith(OAPI_SERIALIZER.createPartSession(null),null);
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/header/ClientVersion_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/header/ClientVersion_Test.java
index 60a5544..b796ee7 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/header/ClientVersion_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/header/ClientVersion_Test.java
@@ -18,6 +18,7 @@ import static org.junit.runners.MethodSorters.*;
 import java.io.*;
 import java.util.function.*;
 
+import org.apache.juneau.*;
 import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.rest.annotation.*;
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_BodyAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_BodyAnnotation_Test.java
index c2d3bfd..8b3b370 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_BodyAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_BodyAnnotation_Test.java
@@ -320,7 +320,7 @@ public class Remote_BodyAnnotation_Test {
 		assertEquals("1.0",x.postX2(1f));
 		assertEquals("{f:1}",x.postX3(Bean.create()));
 		assertEquals("[{f:1}]",x.postX5(AList.of(Bean.create())));
-		assertEquals("{k1:{f:1}}",x.postX6(AMap.of("k1",Bean.create())));
+		assertEquals("{k1={f:1}}",x.postX6(AMap.of("k1",Bean.create())));
 		assertEquals("xxx",x.postX7(new StringReader("xxx")));
 		assertEquals("xxx",x.postX8(new StringInputStream("xxx")));
 		assertEquals("xxx",x.postX9(new StringEntity("xxx",org.apache.http.entity.ContentType.create("text/plain"))));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
index 1c2c638..1c269c2 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_FormDataAnnotation_Test.java
@@ -816,7 +816,7 @@ public class Remote_FormDataAnnotation_Test {
 		K2 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K2.class);
 		assertEquals("{a:'a1=v1,a2=123,a3=null,a4=',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",x1.postX1(new K2a()));
 		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",x2.postX1(new K2a()));
-		assertEquals("{a:'x{a1:\\'v1\\',a2:123,a3:null,a4:\\'\\'}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.postX2(new K2a()));
+		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.postX2(new K2a()));
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_HeaderAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_HeaderAnnotation_Test.java
index c1723c9..023b671 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_HeaderAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_HeaderAnnotation_Test.java
@@ -779,7 +779,7 @@ public class Remote_HeaderAnnotation_Test {
 		K2 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K2.class);
 		assertEquals("{a:'a1=v1,a2=123,a3=null,a4=',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",x1.getX1(new K2a()));
 		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",x2.getX1(new K2a()));
-		assertEquals("{a:'x{a1:\\'v1\\',a2:123,a3:null,a4:\\'\\'}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.getX2(new K2a()));
+		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.getX2(new K2a()));
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
index f7fd2e5..2cd1be4 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/http/remote/Remote_QueryAnnotation_Test.java
@@ -781,7 +781,7 @@ public class Remote_QueryAnnotation_Test {
 		K2 x2 = client(K.class).partSerializer(UonSerializer.class).build().getRemote(K2.class);
 		assertEquals("{a:'a1=v1,a2=123,a3=null,a4=',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",x1.getX1(new K2a()));
 		assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",x2.getX1(new K2a()));
-		assertEquals("{a:'x{a1:\\'v1\\',a2:123,a3:null,a4:\\'\\'}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.getX2(new K2a()));
+		assertEquals("{a:'x{a1=v1, a2=123, a3=null, a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",x2.getX2(new K2a()));
 	}
 
 	//-----------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoSorterTest.java b/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoSorterTest.java
index dc1d941..64a4de6 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoSorterTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoSorterTest.java
@@ -27,7 +27,7 @@ import org.junit.*;
 public class PojoSorterTest {
 
 	PojoSorter p = new PojoSorter();
-	BeanSession bs = BeanContext.DEFAULT.createBeanSession();
+	BeanSession bs = BeanContext.DEFAULT_SESSION;
 
 	//-----------------------------------------------------------------------------------------------------------------
 	// Null input
diff --git a/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoViewerTest.java b/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoViewerTest.java
index c182ed4..ebad5ed 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoViewerTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/pojotools/PojoViewerTest.java
@@ -27,7 +27,7 @@ import org.junit.*;
 public class PojoViewerTest {
 
 	PojoViewer p = new PojoViewer();
-	BeanSession bs = BeanContext.DEFAULT.createBeanSession();
+	BeanSession bs = BeanContext.DEFAULT_SESSION;
 
 	//-----------------------------------------------------------------------------------------------------------------
 	// Null input
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
index 3f68ea7..bd06e82 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Body_Test.java
@@ -171,14 +171,14 @@ public class RestClient_Body_Test {
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").is("application/json")
-			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asObject(ABean.class).asJson().is("{a:1,b:'foo'}");
 
 		SerializedEntity x3 = serializedEntity(()->ABean.get(),js,null).build();
 		client().build().post("/",x3).run()
 			.assertHeader("X-Content-Length").doesNotExist()
 			.assertHeader("X-Content-Encoding").doesNotExist()
 			.assertHeader("X-Content-Type").is("application/json")
-			.assertBody().asType(ABean.class).asJson().is("{a:1,b:'foo'}");
+			.assertBody().asObject(ABean.class).asJson().is("{a:1,b:'foo'}");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
index 569fcfc..4f498ec 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Body_Test.java
@@ -93,7 +93,7 @@ public class RestClient_Response_Body_Test {
 
 	@Test
 	public void a01_basic() throws Exception {
-		client().build().post("/echo",bean).run().assertBody().asType(ABean.class).asJson().is("{f:1}");
+		client().build().post("/echo",bean).run().assertBody().asObject(ABean.class).asJson().is("{f:1}");
 		client().build().post("/echo",bean).run().assertBody().asBytes().asString().is("{f:1}");
 	}
 
@@ -103,7 +103,7 @@ public class RestClient_Response_Body_Test {
 		ABean b = x.post("/echo",bean).run().getBody().parser(JsonParser.DEFAULT).asType(ABean.class);
 		assertObject(b).asJson().is("{f:1}");
 		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).asType(ABean.class)).contains("ParseError at [row,col]:[1,1]");
-		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).assertValue().asType(ABean.class)).contains("ParseError at [row,col]:[1,1]");
+		assertThrown(()->x.post("/echo",bean).run().getBody().parser(XmlParser.DEFAULT).assertValue().asObject(ABean.class)).contains("ParseError at [row,col]:[1,1]");
 	}
 
 	@Test
@@ -221,8 +221,8 @@ public class RestClient_Response_Body_Test {
 		HttpEntity x6 = testClient().entity(stringEntity("{f:1}")).get().run().getBody().asType(HttpEntity.class);
 		assertTrue(x6 instanceof ResponseBody);
 
-		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asType(A7a.class).passes(x->((A7a)x).x.equals("foo"));
-		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asType(A7b.class).passes(x->((A7b)x).x.equals("foo"));
+		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asObject(A7a.class).passes(x->x.x.equals("foo"));
+		plainTestClient().entity(stringEntity("foo")).get().run().assertBody().asObject(A7b.class).passes(x->x.x.equals("foo"));
 		assertThrown(()->plainTestClient().entity(stringEntity("foo")).headers(header("Content-Type","foo")).get().run().getBody().asType(A7c.class)).exists().contains("Unsupported media-type","'foo'");
 		assertThrown(()->testClient().entity(stringEntity("")).get().run().getBody().asType(A7c.class)).contains("foo");
 
diff --git a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
index 78b463a..e1c387f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/rest/client/RestClient_Response_Test.java
@@ -232,14 +232,14 @@ public class RestClient_Response_Test {
 
 	@Test
 	public void d01_response_assertBody() throws Exception {
-		client(D.class).build().post("/bean",bean).run().assertBody().asType(ABean.class).asJson().is("{f:1}");
+		client(D.class).build().post("/bean",bean).run().assertBody().asObject(ABean.class).asJson().is("{f:1}");
 	}
 
 	@Test
 	public void d02_response_setEntity() throws Exception {
 		RestResponse x = client(D.class).build().post("/bean",bean).run();
 		x.setEntity(new StringEntity("{f:2}"));
-		x.assertBody().asType(ABean.class).asJson().is("{f:2}");
+		x.assertBody().asObject(ABean.class).asJson().is("{f:2}");
 	}
 
 	//------------------------------------------------------------------------------------------------------------------
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/InputStreamBase64SwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/InputStreamBase64SwapTest.java
index 7290106..9e059a0 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/InputStreamBase64SwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/InputStreamBase64SwapTest.java
@@ -28,7 +28,7 @@ public class InputStreamBase64SwapTest extends OneWayStringSwapTest<InputStream>
 	// Setup
 	//------------------------------------------------------------------------------------------------------------------
 
-	private static BeanSession BS = BeanContext.DEFAULT.createBeanSession();
+	private static BeanSession BS = BeanContext.DEFAULT_SESSION;
 	private static InputStreamSwap SWAP = new InputStreamSwap.Base64();
 
 	public InputStreamBase64SwapTest(String label, InputStream o, StringSwap<InputStream> s, String r, BeanSession bs) throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/LocaleSwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/LocaleSwapTest.java
index 250da50..8b15ee6 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/LocaleSwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/LocaleSwapTest.java
@@ -26,7 +26,7 @@ public class LocaleSwapTest extends RoundTripStringSwapTest<Locale> {
 	// Setup
 	//------------------------------------------------------------------------------------------------------------------
 
-	private static BeanSession BS = BeanContext.DEFAULT.createBeanSession();
+	private static BeanSession BS = BeanContext.DEFAULT_SESSION;
 	private static LocaleSwap SWAP = new LocaleSwap();
 
 	public LocaleSwapTest(String label, Locale o, StringSwap<Locale> s, String r, BeanSession bs) throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/ReaderSwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/ReaderSwapTest.java
index 18bcc58..8374a2f 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/ReaderSwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/ReaderSwapTest.java
@@ -27,7 +27,7 @@ public class ReaderSwapTest extends OneWayStringSwapTest<Reader> {
 	// Setup
 	//------------------------------------------------------------------------------------------------------------------
 
-	private static BeanSession BS = BeanContext.DEFAULT.createBeanSession();
+	private static BeanSession BS = BeanContext.DEFAULT_SESSION;
 	private static ReaderSwap SWAP = new ReaderSwap();
 
 	public ReaderSwapTest(String label, Reader o, StringSwap<Reader> s, String r, BeanSession bs) throws Exception {
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalCalendarSwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalCalendarSwapTest.java
index 608ddff..8eb28d3 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalCalendarSwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalCalendarSwapTest.java
@@ -41,7 +41,7 @@ public class TemporalCalendarSwapTest extends RoundTripStringSwapTest<Calendar>
 	}
 
 	private static BeanSession
-		BS_DEFAULT = BeanContext.DEFAULT.createBeanSession(),
+		BS_DEFAULT = BeanContext.DEFAULT_SESSION,
 		BS_PST = BeanContext.DEFAULT.createBeanSession(BeanSessionArgs.create().timeZone(TimeZone.getTimeZone("PST")));
 
 	private static GregorianCalendar T_Calendar = GregorianCalendar.from(ZonedDateTime.from(DateTimeFormatter.ISO_ZONED_DATE_TIME.parse("2012-12-21T12:34:56Z")));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalDateSwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalDateSwapTest.java
index 9986cfc..e734cad 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalDateSwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalDateSwapTest.java
@@ -41,7 +41,7 @@ public class TemporalDateSwapTest extends RoundTripStringSwapTest<Date> {
 	}
 
 	private static BeanSession
-		BS_DEFAULT = BeanContext.DEFAULT.createBeanSession(),
+		BS_DEFAULT = BeanContext.DEFAULT_SESSION,
 		BS_PST = BeanContext.DEFAULT.createBeanSession(BeanSessionArgs.create().timeZone(TimeZone.getTimeZone("PST")));
 
 	private static Date T_Date = Date.from(Instant.from(DateTimeFormatter.ISO_INSTANT.parse("2012-12-21T12:34:56Z")));
diff --git a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalSwapTest.java b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalSwapTest.java
index 6812521..26363f8 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalSwapTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/transforms/TemporalSwapTest.java
@@ -42,7 +42,7 @@ public class TemporalSwapTest extends RoundTripStringSwapTest<Temporal> {
 	}
 
 	private static BeanSession
-		BS_DEFAULT = BeanContext.DEFAULT.createBeanSession(),
+		BS_DEFAULT = BeanContext.DEFAULT_SESSION,
 		BS_PST = BeanContext.DEFAULT.createBeanSession(BeanSessionArgs.create().timeZone(TimeZone.getTimeZone("PST")));
 
 	private static Temporal
diff --git a/juneau-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java b/juneau-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java
index 27cfe8b..746f42c 100644
--- a/juneau-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java
+++ b/juneau-utest/src/test/java/org/apache/juneau/utils/MethodInvokerTest.java
@@ -52,7 +52,7 @@ public class MethodInvokerTest {
 		mi.invoke(a);
 		mi.invoke(a);
 
-		assertBean(mi.getStats()).fields("runs","errors").asJson().is("{runs:3,errors:0}");
+		assertBean(mi.getStats()).mapOf("runs","errors").asJson().is("{runs:3,errors:0}");
 	}
 
 	@Test
@@ -66,7 +66,7 @@ public class MethodInvokerTest {
 		assertThrown(()->mi.invoke(a)).exists();
 		assertThrown(()->mi.invoke(a)).exists();
 
-		assertBean(mi.getStats()).fields("runs","errors").asJson().is("{runs:3,errors:3}");
+		assertBean(mi.getStats()).mapOf("runs","errors").asJson().is("{runs:3,errors:3}");
 	}
 
 	@Test
@@ -80,7 +80,7 @@ public class MethodInvokerTest {
 		assertThrown(()->mi.invoke(a)).exists();
 		assertThrown(()->mi.invoke(a, 1, "x")).exists();
 
-		assertBean(mi.getStats()).fields("runs","errors").asJson().is("{runs:3,errors:3}");
+		assertBean(mi.getStats()).mapOf("runs","errors").asJson().is("{runs:3,errors:3}");
 	}
 
 	@Test