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 48a5b03893 Unit tests
48a5b03893 is described below

commit 48a5b038939b18626dc2d53cb64283f1adbf2f9c
Author: James Bognar <[email protected]>
AuthorDate: Mon Dec 1 09:21:12 2025 -0800

    Unit tests
---
 BCT_CONVERTER_OVERRIDE_FEASIBILITY.md              | 439 ------------
 TODO.md                                            |   2 +-
 .../org/apache/juneau/junit/bct/AssertionArgs.java | 265 --------
 .../org/apache/juneau/junit/bct/BctAssertions.java | 735 +++++++++++++--------
 .../org/apache/juneau/junit/bct/package-info.java  |  79 ++-
 .../docs/topics/07.01.00.JuneauBctBasics.md        |  40 +-
 juneau-docs/docs/topics/07.01.01.Stringifiers.md   |  16 +-
 juneau-docs/docs/topics/07.01.02.Listifiers.md     |  32 +-
 juneau-docs/docs/topics/07.01.03.Swappers.md       |  34 +-
 .../docs/topics/07.01.04.PropertyExtractors.md     |  74 ++-
 .../docs/topics/07.01.05.CustomErrorMessages.md    | 117 ++--
 .../juneau/junit/bct/AssertionArgs_Test.java       | 526 ---------------
 .../juneau/junit/bct/BctAssertions_Test.java       | 105 +--
 13 files changed, 731 insertions(+), 1733 deletions(-)

diff --git a/BCT_CONVERTER_OVERRIDE_FEASIBILITY.md 
b/BCT_CONVERTER_OVERRIDE_FEASIBILITY.md
deleted file mode 100644
index 3531379792..0000000000
--- a/BCT_CONVERTER_OVERRIDE_FEASIBILITY.md
+++ /dev/null
@@ -1,439 +0,0 @@
-# Feasibility Analysis: Overriding BctAssertions.DEFAULT_CONVERTER
-
-## Overview
-
-This document analyzes the feasibility of making 
`BctAssertions.DEFAULT_CONVERTER` a resettable, memoized thread-local field 
that can be overridden during test setup.
-
-## Current State
-
-- `DEFAULT_CONVERTER` is currently a `static final` field: 
`BasicBeanConverter.DEFAULT`
-- Used throughout `BctAssertions` via: 
`args.getBeanConverter().orElse(DEFAULT_CONVERTER)`
-- Custom converters can be provided per-assertion via 
`AssertionArgs.setBeanConverter()`
-- TODO-88 exists to eliminate the need for `AssertionArgs` by making the 
default converter resettable
-
-## Proposed Solution
-
-### Core Implementation
-
-```java
-public class BctAssertions {
-    // Thread-local with memoized default
-    private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
DEFAULT_CONVERTER = 
-        ThreadLocal.withInitial(() -> memoizeResettable(() -> 
BasicBeanConverter.DEFAULT));
-    
-    // Get the current converter for this thread
-    private static BeanConverter getDefaultConverter() {
-        return DEFAULT_CONVERTER.get().get();
-    }
-    
-    // Override the converter for this thread
-    public static void setDefaultConverter(BeanConverter converter) {
-        DEFAULT_CONVERTER.get().reset();
-        // Need to set a new value - see implementation options below
-    }
-    
-    // Reset to default for this thread
-    public static void resetDefaultConverter() {
-        DEFAULT_CONVERTER.get().reset();
-    }
-}
-```
-
-## JUnit 5 Parallel Execution Considerations
-
-### How JUnit 5 Parallelization Works
-
-1. **Test Method Parallelization**: When 
`junit.jupiter.execution.parallel.mode.default = concurrent`, each test method 
can run in its own thread
-2. **Test Class Parallelization**: When 
`junit.jupiter.execution.parallel.mode.classes.default = concurrent`, different 
test classes run in parallel
-3. **Thread Isolation**: Each test method thread has its own `ThreadLocal` 
storage
-
-### Thread-Local Behavior
-
-✅ **Advantages:**
-- Each parallel test method gets its own converter instance
-- No cross-thread interference
-- Thread-safe by design
-
-⚠️ **Challenges:**
-- Tests in the same class running in parallel will have **separate** converter 
instances
-- Cannot easily share a converter across all tests in a class when running in 
parallel
-
-## Options for Class-Level Converter Sharing
-
-### Option 1: Thread-Local Only (Method-Level Isolation)
-
-**Behavior:**
-- Each test method gets its own converter instance
-- Tests in the same class running in parallel use different converters
-- `@BeforeEach` can set converter per test method
-- `@AfterEach` can reset converter per test method
-
-**Pros:**
-- Simple implementation
-- No cross-test interference
-- Works perfectly with parallel execution
-
-**Cons:**
-- Cannot share converter across all tests in a class when running in parallel
-- Requires setting converter in each test method if you want customization
-
-**Use Case:**
-```java
-@BeforeEach
-void setUp() {
-    var customConverter = BasicBeanConverter.builder()
-        .defaultSettings()
-        .addStringifier(MyType.class, obj -> obj.customFormat())
-        .build();
-    BctAssertions.setDefaultConverter(customConverter);
-}
-
-@AfterEach
-void tearDown() {
-    BctAssertions.resetDefaultConverter();
-}
-```
-
-### Option 2: Class-Level Thread-Local with Synchronization
-
-**Behavior:**
-- Use a `ConcurrentHashMap<Class<?>, BeanConverter>` to store class-level 
converters
-- Thread-local checks class-level first, then falls back to thread-local
-- Requires synchronization or atomic operations
-
-**Implementation Sketch:**
-```java
-private static final ConcurrentHashMap<Class<?>, BeanConverter> 
CLASS_CONVERTERS = new ConcurrentHashMap<>();
-private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
THREAD_CONVERTER = 
-    ThreadLocal.withInitial(() -> memoizeResettable(() -> {
-        // Check class-level first
-        Class<?> testClass = findTestClass(); // Need to detect current test 
class
-        BeanConverter classConverter = CLASS_CONVERTERS.get(testClass);
-        return classConverter != null ? classConverter : 
BasicBeanConverter.DEFAULT;
-    }));
-
-public static void setDefaultConverterForClass(Class<?> testClass, 
BeanConverter converter) {
-    CLASS_CONVERTERS.put(testClass, converter);
-    // Invalidate thread-local cache for all threads running this class
-    THREAD_CONVERTER.get().reset();
-}
-```
-
-**Pros:**
-- Can share converter across all tests in a class
-- Still thread-safe
-
-**Cons:**
-- Complex implementation
-- Requires detecting current test class (reflection/stack trace)
-- Thread-local cache invalidation is tricky
-- May not work well with nested test classes
-
-### Option 3: Hybrid Approach with Test Class Detection
-
-**Behavior:**
-- Use `ThreadLocal` with a `ResettableSupplier` that checks for class-level 
overrides
-- Detect test class from stack trace or via explicit registration
-- Cache converter per thread, but check class-level map on cache miss
-
-**Implementation Sketch:**
-```java
-private static final ConcurrentHashMap<Class<?>, BeanConverter> 
CLASS_CONVERTERS = new ConcurrentHashMap<>();
-private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
THREAD_CONVERTER = 
-    ThreadLocal.withInitial(() -> memoizeResettable(() -> {
-        Class<?> testClass = findTestClassFromStack();
-        return CLASS_CONVERTERS.getOrDefault(testClass, 
BasicBeanConverter.DEFAULT);
-    }));
-
-private static Class<?> findTestClassFromStack() {
-    StackTraceElement[] stack = Thread.currentThread().getStackTrace();
-    for (StackTraceElement element : stack) {
-        try {
-            Class<?> clazz = Class.forName(element.getClassName());
-            if (clazz.getName().endsWith("_Test") || 
clazz.isAnnotationPresent(TestClass.class)) {
-                return clazz;
-            }
-        } catch (ClassNotFoundException e) {
-            // Continue
-        }
-    }
-    return null;
-}
-```
-
-**Pros:**
-- Automatic class detection
-- Supports both class-level and method-level overrides
-- Thread-safe
-
-**Cons:**
-- Stack trace inspection is expensive (but memoized)
-- May incorrectly detect class in some scenarios
-- Complex implementation
-
-### Option 4: Explicit Test Class Registration (Recommended)
-
-**Behavior:**
-- Provide explicit methods for class-level and method-level overrides
-- Use `@BeforeAll` to set class-level converter
-- Use `@BeforeEach` to set method-level converter
-- Thread-local stores the active converter
-
-**Implementation:**
-```java
-public class BctAssertions {
-    // Class-level converters (shared across all threads for a class)
-    private static final ConcurrentHashMap<Class<?>, BeanConverter> 
CLASS_CONVERTERS = new ConcurrentHashMap<>();
-    
-    // Thread-local converter (method-level override)
-    private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
THREAD_CONVERTER = 
-        ThreadLocal.withInitial(() -> memoizeResettable(() -> {
-            // Check if current thread has a class-level converter
-            Class<?> testClass = getTestClassForThread();
-            if (testClass != null) {
-                BeanConverter classConverter = CLASS_CONVERTERS.get(testClass);
-                if (classConverter != null) {
-                    return classConverter;
-                }
-            }
-            return BasicBeanConverter.DEFAULT;
-        }));
-    
-    // Set converter for all tests in a class
-    public static void setDefaultConverterForClass(Class<?> testClass, 
BeanConverter converter) {
-        CLASS_CONVERTERS.put(testClass, converter);
-        // Note: Existing threads will continue using their cached value
-        // New threads will pick up the class-level converter
-    }
-    
-    // Set converter for current thread (method-level)
-    public static void setDefaultConverter(BeanConverter converter) {
-        // Store in a separate thread-local for method-level override
-        METHOD_CONVERTER.set(converter);
-        THREAD_CONVERTER.get().reset(); // Force recomputation
-    }
-    
-    private static BeanConverter getDefaultConverter() {
-        // Check method-level first, then class-level, then default
-        BeanConverter methodConverter = METHOD_CONVERTER.get();
-        if (methodConverter != null) {
-            return methodConverter;
-        }
-        return THREAD_CONVERTER.get().get();
-    }
-}
-```
-
-**Usage:**
-```java
-public class MyTest extends TestBase {
-    @BeforeAll
-    static void setUpClass() {
-        var classConverter = BasicBeanConverter.builder()
-            .defaultSettings()
-            .addStringifier(MyType.class, obj -> obj.customFormat())
-            .build();
-        BctAssertions.setDefaultConverterForClass(MyTest.class, 
classConverter);
-    }
-    
-    @AfterAll
-    static void tearDownClass() {
-        BctAssertions.clearDefaultConverterForClass(MyTest.class);
-    }
-    
-    @BeforeEach
-    void setUp() {
-        // Optional: Override for this specific test method
-        // BctAssertions.setDefaultConverter(customConverter);
-    }
-    
-    @AfterEach
-    void tearDown() {
-        // Optional: Reset method-level override
-        // BctAssertions.resetDefaultConverter();
-    }
-}
-```
-
-**Pros:**
-- Clear and explicit API
-- Supports both class-level and method-level overrides
-- Works with parallel execution
-- No stack trace inspection needed
-- Easy to understand and maintain
-
-**Cons:**
-- Requires explicit class registration in `@BeforeAll`
-- Class-level converters persist until explicitly cleared (but that's usually 
desired)
-
-## Recommended Implementation
-
-I recommend **Option 4 (Explicit Test Class Registration)** because:
-
-1. **Clarity**: Explicit is better than implicit - developers know exactly 
what's happening
-2. **Performance**: No stack trace inspection overhead
-3. **Flexibility**: Supports both class-level and method-level overrides
-4. **Parallel-Safe**: Works correctly with JUnit 5 parallel execution
-5. **Maintainability**: Simple to understand and debug
-
-### Implementation Details
-
-```java
-public class BctAssertions {
-    // Class-level converters (shared across threads for a class)
-    private static final ConcurrentHashMap<Class<?>, BeanConverter> 
CLASS_CONVERTERS = new ConcurrentHashMap<>();
-    
-    // Method-level converter override (thread-local)
-    private static final ThreadLocal<BeanConverter> METHOD_CONVERTER = new 
ThreadLocal<>();
-    
-    // Thread-local memoized supplier for class-level or default converter
-    private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
THREAD_CONVERTER = 
-        ThreadLocal.withInitial(() -> memoizeResettable(() -> {
-            // Find the test class for this thread
-            Class<?> testClass = findTestClassForThread();
-            if (testClass != null) {
-                BeanConverter classConverter = CLASS_CONVERTERS.get(testClass);
-                if (classConverter != null) {
-                    return classConverter;
-                }
-            }
-            return BasicBeanConverter.DEFAULT;
-        }));
-    
-    // Internal: Get converter (checks method-level first, then 
class-level/default)
-    private static BeanConverter getDefaultConverter() {
-        BeanConverter methodConverter = METHOD_CONVERTER.get();
-        if (methodConverter != null) {
-            return methodConverter;
-        }
-        return THREAD_CONVERTER.get().get();
-    }
-    
-    // Public API: Set converter for all tests in a class
-    public static void setDefaultConverterForClass(Class<?> testClass, 
BeanConverter converter) {
-        assertArgNotNull("testClass", testClass);
-        assertArgNotNull("converter", converter);
-        CLASS_CONVERTERS.put(testClass, converter);
-        // Invalidate thread-local cache for this class's threads
-        // Note: This is best-effort; existing threads may continue with 
cached value
-        // until they call getDefaultConverter() again
-    }
-    
-    // Public API: Clear converter for a class
-    public static void clearDefaultConverterForClass(Class<?> testClass) {
-        assertArgNotNull("testClass", testClass);
-        CLASS_CONVERTERS.remove(testClass);
-    }
-    
-    // Public API: Set converter for current thread (method-level override)
-    public static void setDefaultConverter(BeanConverter converter) {
-        assertArgNotNull("converter", converter);
-        METHOD_CONVERTER.set(converter);
-    }
-    
-    // Public API: Reset converter for current thread (clears method-level 
override)
-    public static void resetDefaultConverter() {
-        METHOD_CONVERTER.remove();
-        THREAD_CONVERTER.get().reset(); // Also reset class-level cache
-    }
-    
-    // Internal: Find test class for current thread (simplified - may need 
refinement)
-    private static Class<?> findTestClassForThread() {
-        // This is a simplified version - you may want to cache this per thread
-        // or use a more sophisticated detection mechanism
-        StackTraceElement[] stack = Thread.currentThread().getStackTrace();
-        for (StackTraceElement element : stack) {
-            String className = element.getClassName();
-            if (className.endsWith("_Test") || className.contains("Test")) {
-                try {
-                    return Class.forName(className);
-                } catch (ClassNotFoundException e) {
-                    // Continue
-                }
-            }
-        }
-        return null;
-    }
-    
-    // Update all methods to use getDefaultConverter() instead of 
DEFAULT_CONVERTER
-    // Example:
-    public static void assertBean(Object actual, String fields, String 
expected) {
-        assertBean(args(), actual, fields, expected);
-    }
-    
-    public static void assertBean(AssertionArgs args, Object actual, String 
fields, String expected) {
-        // ... existing code ...
-        var converter = args.getBeanConverter().orElse(getDefaultConverter());
-        // ... rest of method ...
-    }
-}
-```
-
-## Alternative: Simpler Thread-Local Only Approach
-
-If class-level sharing is not a requirement, a simpler implementation is:
-
-```java
-public class BctAssertions {
-    private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
DEFAULT_CONVERTER = 
-        ThreadLocal.withInitial(() -> memoizeResettable(() -> 
BasicBeanConverter.DEFAULT));
-    
-    private static BeanConverter getDefaultConverter() {
-        return DEFAULT_CONVERTER.get().get();
-    }
-    
-    public static void setDefaultConverter(BeanConverter converter) {
-        assertArgNotNull("converter", converter);
-        // Store override in a separate thread-local
-        CONVERTER_OVERRIDE.set(converter);
-        DEFAULT_CONVERTER.get().reset(); // Invalidate cache
-    }
-    
-    public static void resetDefaultConverter() {
-        CONVERTER_OVERRIDE.remove();
-        DEFAULT_CONVERTER.get().reset();
-    }
-    
-    private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE = new 
ThreadLocal<>();
-    
-    private static BeanConverter getDefaultConverter() {
-        BeanConverter override = CONVERTER_OVERRIDE.get();
-        if (override != null) {
-            return override;
-        }
-        return DEFAULT_CONVERTER.get().get();
-    }
-}
-```
-
-This simpler approach:
-- ✅ Works perfectly with parallel execution
-- ✅ Each test method can set its own converter
-- ✅ No class-level sharing (each test is independent)
-- ✅ Much simpler implementation
-
-## Questions to Answer
-
-1. **Do you need class-level converter sharing?**
-   - If YES → Use Option 4 (Explicit Registration)
-   - If NO → Use simpler thread-local only approach
-
-2. **How should converter be set in test setup?**
-   - `@BeforeAll` for class-level?
-   - `@BeforeEach` for method-level?
-   - Both?
-
-3. **Should converter persist across test methods in a class?**
-   - If tests run sequentially → Yes, can share
-   - If tests run in parallel → Each thread gets its own
-
-## Recommendation
-
-Start with the **simpler thread-local only approach** unless you have a 
specific need for class-level sharing. You can always add class-level support 
later if needed.
-
-The simpler approach:
-- Solves the core problem (eliminating need for AssertionArgs)
-- Works perfectly with parallel execution
-- Is easy to implement and maintain
-- Can be enhanced later if class-level sharing is needed
-
diff --git a/TODO.md b/TODO.md
index 3a1e5071e8..85ec6d7c7e 100644
--- a/TODO.md
+++ b/TODO.md
@@ -40,7 +40,7 @@ This file tracks pending tasks for the Apache Juneau project. 
For completed item
 
 - [ ] TODO-19 ClassInfo improvements to getMethod (e.g. getMethodExact vs 
getMethod).
 - [ ] TODO-21 Thrown NotFound causes - javax.servlet.ServletException: Invalid 
method response: 200
-- [ ] TODO-88 Eliminate need for AssertionArgs in BctAssertions by allowing 
DEFAULT_CONVERTER to be overridden and resettable.
+- [x] TODO-88 Eliminate need for AssertionArgs in BctAssertions by allowing 
DEFAULT_CONVERTER to be overridden and resettable. ✅ COMPLETED - Implemented 
thread-local converter with setConverter()/resetConverter() methods, removed 
AssertionArgs class entirely, replaced with Supplier<String> for messages. All 
tests updated and passing.
 - [ ] TODO-89 Add ClassInfoTyped
 
 ## HTTP Response/Exception Improvements
diff --git 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/AssertionArgs.java
 
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/AssertionArgs.java
deleted file mode 100644
index fb40b59c9c..0000000000
--- 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/AssertionArgs.java
+++ /dev/null
@@ -1,265 +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.junit.bct;
-
-import static org.apache.juneau.common.utils.Utils.*;
-
-import java.text.*;
-import java.util.*;
-import java.util.function.*;
-
-/**
- * Configuration and context object for advanced assertion operations.
- *
- * <p>This class encapsulates additional arguments and configuration options 
for assertion methods
- * in the Bean-Centric Testing (BCT) framework. It provides a fluent API for 
customizing assertion
- * behavior including custom converters and enhanced error messaging.</p>
- *
- * <p>The primary purposes of this class are:</p>
- * <ul>
- *     <li><b>Custom Bean Conversion:</b> Override the default {@link 
BeanConverter} for specialized object introspection</li>
- *     <li><b>Enhanced Error Messages:</b> Add context-specific error messages 
with parameter substitution</li>
- *     <li><b>Fluent Configuration:</b> Chain configuration calls for readable 
test setup</li>
- *     <li><b>Assertion Context:</b> Provide additional context for complex 
assertion scenarios</li>
- * </ul>
- *
- * <h5 class='section'>Basic Usage:</h5>
- * <p class='bjava'>
- *     <jc>// Simple usage with default settings</jc>
- *     <jsm>assertBean</jsm>(<jsm>args</jsm>(), <jv>myBean</jv>, 
<js>"name,age"</js>, <js>"John,30"</js>);
- *
- *     <jc>// Custom error message</jc>
- *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"User validation 
failed"</js>),
- *         <jv>user</jv>, <js>"email,active"</js>, 
<js>"[email protected],true"</js>);
- * </p>
- *
- * <h5 class='section'>Custom Bean Converter:</h5>
- * <p class='bjava'>
- *     <jc>// Use custom converter for specialized object handling</jc>
- *     <jk>var</jk> <jv>customConverter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
- *         .defaultSettings()
- *         .addStringifier(MyClass.<jk>class</jk>, <jp>obj</jp> -> 
<jp>obj</jp>.getDisplayName())
- *         .build();
- *
- *     
<jsm>assertBean</jsm>(<jsm>args</jsm>().setBeanConverter(<jv>customConverter</jv>),
- *         <jv>myCustomObject</jv>, <js>"property"</js>, 
<js>"expectedValue"</js>);
- * </p>
- *
- * <h5 class='section'>Advanced Error Messages:</h5>
- * <p class='bjava'>
- *     <jc>// Parameterized error messages</jc>
- *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"Validation 
failed for user {0}"</js>, <jv>userId</jv>),
- *         <jv>user</jv>, <js>"status"</js>, <js>"ACTIVE"</js>);
- *
- *     <jc>// Dynamic error message with supplier</jc>
- *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(() -> <js>"Test 
failed at "</js> + Instant.<jsm>now</jsm>()),
- *         <jv>result</jv>, <js>"success"</js>, <js>"true"</js>);
- * </p>
- *
- * <h5 class='section'>Fluent Configuration:</h5>
- * <p class='bjava'>
- *     <jc>// Chain multiple configuration options</jc>
- *     <jk>var</jk> <jv>testArgs</jv> = args()
- *         .setBeanConverter(<jv>customConverter</jv>)
- *         .setMessage(<js>"Integration test failed for module {0}"</js>, 
<jv>moduleName</jv>);
- *
- *     <jsm>assertBean</jsm>(<jv>testArgs</jv>, <jv>moduleConfig</jv>, 
<js>"enabled,version"</js>, <js>"true,2.1.0"</js>);
- *     <jsm>assertBeans</jsm>(<jv>testArgs</jv>, <jv>moduleList</jv>, 
<js>"name,status"</js>,
- *         <js>"ModuleA,ACTIVE"</js>, <js>"ModuleB,ACTIVE"</js>);
- * </p>
- *
- * <h5 class='section'>Error Message Composition:</h5>
- * <p>When assertion failures occur, error messages are intelligently 
composed:</p>
- * <ul>
- *     <li><b>Base Message:</b> Custom message set via {@link 
#setMessage(String, Object...)} or {@link #setMessage(Supplier)}</li>
- *     <li><b>Assertion Context:</b> Specific context provided by individual 
assertion methods</li>
- *     <li><b>Composite Format:</b> <js>"{base message}, Caused by: {assertion 
context}"</js></li>
- * </ul>
- *
- * <p class='bjava'>
- *     <jc>// Example error message composition:</jc>
- *     <jc>// Base: "User validation failed for user 123"</jc>
- *     <jc>// Context: "Bean assertion failed."</jc>
- *     <jc>// Result: "User validation failed for user 123, Caused by: Bean 
assertion failed."</jc>
- * </p>
- *
- * <h5 class='section'>Thread Safety:</h5>
- * <p>This class is <b>not thread-safe</b> and is intended for single-threaded 
test execution.
- * Each test method should create its own instance using {@link 
BctAssertions#args()} or create
- * a new instance directly with {@code new AssertionArgs()}.</p>
- *
- * <h5 class='section'>Immutability Considerations:</h5>
- * <p>While this class uses fluent setters that return {@code this} for 
chaining, the instance
- * is mutable. For reusable configurations across multiple tests, consider 
creating a factory
- * method that returns pre-configured instances.</p>
- *
- * @see BctAssertions#args()
- * @see BeanConverter
- * @see BasicBeanConverter
- */
-public class AssertionArgs {
-
-       private BeanConverter beanConverter;
-       private Supplier<String> messageSupplier;
-
-       /**
-        * Creates a new instance with default settings.
-        *
-        * <p>Instances start with no custom bean converter and no custom error 
message.
-        * All assertion methods will use default behavior until configured 
otherwise.</p>
-        */
-       public AssertionArgs() { /* no-op */ }
-
-       /**
-        * Sets a custom {@link BeanConverter} for object introspection and 
property access.
-        *
-        * <p>The custom converter allows fine-tuned control over how objects 
are converted to strings,
-        * how collections are listified, and how nested properties are 
accessed. This is particularly
-        * useful for:</p>
-        * <ul>
-        *     <li><b>Custom Object Types:</b> Objects that don't follow 
standard JavaBean patterns</li>
-        *     <li><b>Specialized Formatting:</b> Custom string representations 
for assertion comparisons</li>
-        *     <li><b>Performance Optimization:</b> Cached or optimized 
property access strategies</li>
-        *     <li><b>Domain-Specific Logic:</b> Business-specific property 
resolution rules</li>
-        * </ul>
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *     <jc>// Create converter with custom stringifiers</jc>
-        *     <jk>var</jk> <jv>converter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
-        *         .defaultSettings()
-        *         .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> 
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
-        *         .addStringifier(Money.<jk>class</jk>, <jp>money</jp> -> 
<jp>money</jp>.getAmount().toPlainString())
-        *         .build();
-        *
-        *     <jc>// Use in assertions</jc>
-        *     
<jsm>assertBean</jsm>(<jsm>args</jsm>().setBeanConverter(<jv>converter</jv>),
-        *         <jv>order</jv>, <js>"date,total"</js>, 
<js>"2023-12-01,99.99"</js>);
-        * </p>
-        *
-        * @param value The custom bean converter to use. If null, assertions 
will fall back to the default converter.
-        * @return This instance for method chaining.
-        */
-       public AssertionArgs setBeanConverter(BeanConverter value) {
-               beanConverter = value;
-               return this;
-       }
-
-       /**
-        * Sets a parameterized error message for assertion failures.
-        *
-        * <p>This method uses {@link MessageFormat} to substitute parameters 
into the message template.
-        * The formatting occurs immediately when this method is called, not 
when the assertion fails.</p>
-        *
-        * <h5 class='section'>Parameter Substitution:</h5>
-        * <p>Uses standard MessageFormat patterns:</p>
-        * <ul>
-        *     <li><code>{0}</code> - First parameter</li>
-        *     <li><code>{1}</code> - Second parameter</li>
-        *     <li><code>{0,number,#}</code> - Formatted number</li>
-        *     <li><code>{0,date,short}</code> - Formatted date</li>
-        * </ul>
-        *
-        * <h5 class='section'>Examples:</h5>
-        * <p class='bjava'>
-        *     <jc>// Simple parameter substitution</jc>
-        *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"User {0} 
validation failed"</js>, <jv>userId</jv>),
-        *         <jv>user</jv>, <js>"active"</js>, <js>"true"</js>);
-        *
-        *     <jc>// Multiple parameters</jc>
-        *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"Test {0} 
failed on iteration {1}"</js>, <jv>testName</jv>, <jv>iteration</jv>),
-        *         <jv>result</jv>, <js>"success"</js>, <js>"true"</js>);
-        *
-        *     <jc>// Number formatting</jc>
-        *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"Expected 
{0,number,#.##} but got different value"</js>, <jv>expectedValue</jv>),
-        *         <jv>actual</jv>, <js>"value"</js>, <js>"123.45"</js>);
-        * </p>
-        *
-        * @param message The message template with MessageFormat placeholders.
-        * @param args The parameters to substitute into the message template.
-        * @return This instance for method chaining.
-        */
-       public AssertionArgs setMessage(String message, Object...args) {
-               messageSupplier = fs(message, args);
-               return this;
-       }
-
-       /**
-        * Sets a custom error message supplier for assertion failures.
-        *
-        * <p>The supplier allows for dynamic message generation, including 
context that may only
-        * be available at the time of assertion failure. This is useful 
for:</p>
-        * <ul>
-        *     <li><b>Timestamps:</b> Including the exact time of failure</li>
-        *     <li><b>Test State:</b> Including runtime state information</li>
-        *     <li><b>Expensive Operations:</b> Deferring costly string 
operations until needed</li>
-        *     <li><b>Conditional Messages:</b> Different messages based on 
runtime conditions</li>
-        * </ul>
-        *
-        * <h5 class='section'>Example:</h5>
-        * <p class='bjava'>
-        *     <jc>// Dynamic message with timestamp</jc>
-        *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(() -> 
<js>"Test failed at "</js> + Instant.<jsm>now</jsm>()),
-        *         <jv>result</jv>, <js>"status"</js>, <js>"SUCCESS"</js>);
-        *
-        *     <jc>// Message with expensive computation</jc>
-        *     <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(() -> 
<js>"Failed after "</js> + computeTestDuration() + <js>" ms"</js>),
-        *         <jv>response</jv>, <js>"error"</js>, <js>"null"</js>);
-        * </p>
-        *
-        * @param value The message supplier. Called only when an assertion 
fails.
-        * @return This instance for method chaining.
-        */
-       public AssertionArgs setMessage(Supplier<String> value) {
-               messageSupplier = value;
-               return this;
-       }
-
-       /**
-        * Gets the configured bean converter, if any.
-        *
-        * @return An Optional containing the custom converter, or empty if 
using default behavior.
-        */
-       protected Optional<BeanConverter> getBeanConverter() { return 
opt(beanConverter); }
-
-       /**
-        * Gets the base message supplier for composition with 
assertion-specific messages.
-        *
-        * @return The configured message supplier, or null if no custom 
message was set.
-        */
-       protected Supplier<String> getMessage() { return messageSupplier; }
-
-       /**
-        * Composes the final error message by combining custom and 
assertion-specific messages.
-        *
-        * <p>This method implements the message composition strategy used 
throughout the assertion framework:</p>
-        * <ul>
-        *     <li><b>No Custom Message:</b> Returns the assertion-specific 
message as-is</li>
-        *     <li><b>With Custom Message:</b> Returns <code>"{custom}, Caused 
by: {assertion}"</code></li>
-        * </ul>
-        *
-        * <p>This allows tests to provide high-level context while preserving 
the specific
-        * technical details about what assertion failed.</p>
-        *
-        * @param msg The assertion-specific message template.
-        * @param args Parameters for the assertion-specific message.
-        * @return A supplier that produces the composed error message.
-        */
-       protected Supplier<String> getMessage(String msg, Object...args) {
-               return messageSupplier == null ? fs(msg, args) : fs("{0}, 
Caused by: {1}", messageSupplier.get(), f(msg, args));
-       }
-}
\ No newline at end of file
diff --git 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
 
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
index bb3d14b7e7..24d458f74f 100644
--- 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
+++ 
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/BctAssertions.java
@@ -27,6 +27,7 @@ import java.util.*;
 import java.util.function.*;
 import java.util.stream.*;
 
+import org.apache.juneau.common.function.ResettableSupplier;
 import org.apache.juneau.common.utils.*;
 import org.opentest4j.*;
 
@@ -116,79 +117,171 @@ import org.opentest4j.*;
  *    <jsm>assertMapped</jsm>(<jv>myObject</jv>, (<jp>obj</jp>, <jp>prop</jp>) 
-> <jp>obj</jp>.getProperty(<jp>prop</jp>),
  *       <js>"prop1,prop2"</js>, <js>"value1,value2"</js>);
  * </p>
+ *
+        * <h5 class='section'>Customizing the Default Converter:</h5>
+        * <p>The default bean converter can be customized on a per-thread 
basis using:</p>
+        * <ul>
+        *    <li><b>{@link #setConverter(BeanConverter)}:</b> Set a custom 
converter for the current thread</li>
+        *    <li><b>{@link #resetConverter()}:</b> Reset to the system default 
converter</li>
+        * </ul>
+        *
+        * <p class='bjava'>
+        *    <jc>// Example: Set custom converter in @BeforeEach method</jc>
+        *    <ja>@BeforeEach</ja>
+        *    <jk>void</jk> <jsm>setUp</jsm>() {
+        *       <jk>var</jk> <jv>customConverter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
+        *          .defaultSettings()
+        *          .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> 
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
+        *          .addStringifier(MyType.<jk>class</jk>, <jp>obj</jp> -> 
<jp>obj</jp>.customFormat())
+        *          .build();
+        *       
BctAssertions.<jsm>setConverter</jsm>(<jv>customConverter</jv>);
+        *    }
+        *
+        *    <jc>// All assertions in this test class now use the custom 
converter</jc>
+        *    <ja>@Test</ja>
+        *    <jk>void</jk> <jsm>testWithCustomConverter</jsm>() {
+        *       <jsm>assertBean</jsm>(<jv>myObject</jv>, 
<js>"date,property"</js>, <js>"2023-12-01,value"</js>);
+        *    }
+        *
+        *    <jc>// Clean up in @AfterEach method</jc>
+        *    <ja>@AfterEach</ja>
+        *    <jk>void</jk> <jsm>tearDown</jsm>() {
+        *       BctAssertions.<jsm>resetConverter</jsm>();
+        *    }
+        * </p>
+        *
+        * <p class='bjava'>
+        *    <jc>// Example: Per-test method converter override</jc>
+        *    <ja>@Test</ja>
+        *    <jk>void</jk> <jsm>testSpecificFormat</jsm>() {
+        *       <jk>var</jk> <jv>dateConverter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
+        *          .defaultSettings()
+        *          .addStringifier(LocalDateTime.<jk>class</jk>, <jp>dt</jp> 
-> <jp>dt</jp>.format(DateTimeFormatter.<jsf>ISO_DATE_TIME</jsf>))
+        *          .build();
+        *       BctAssertions.<jsm>setConverter</jsm>(<jv>dateConverter</jv>);
+        *       <jkt>try</jkt> {
+        *          <jsm>assertBean</jsm>(<jv>event</jv>, <js>"timestamp"</js>, 
<js>"2023-12-01T10:30:00"</js>);
+        *       } <jkt>finally</jkt> {
+        *          BctAssertions.<jsm>resetConverter</jsm>();
+        *       }
+        *    }
+ * </p>
  *
  * <h5 class='section'>Performance and Thread Safety:</h5>
  * <p>The BCT framework is designed for high performance with:</p>
  * <ul>
  *    <li><b>Caching:</b> Type-to-handler mappings cached for fast lookup</li>
  *    <li><b>Thread Safety:</b> All operations are thread-safe for concurrent 
testing</li>
+ *    <li><b>Thread-Local Storage:</b> Default converter is stored per-thread, 
allowing parallel test execution</li>
  *    <li><b>Minimal Allocation:</b> Efficient object reuse and minimal 
temporary objects</li>
  * </ul>
  *
  * @see BeanConverter
  * @see BasicBeanConverter
+ * @see #setConverter(BeanConverter)
+ * @see #resetConverter()
  */
 public class BctAssertions {
 
-       private static final BeanConverter DEFAULT_CONVERTER = 
BasicBeanConverter.DEFAULT;
+       // Thread-local memoized supplier for default converter (defaults to 
BasicBeanConverter.DEFAULT)
+       private static final ThreadLocal<ResettableSupplier<BeanConverter>> 
CONVERTER_SUPPLIER = 
+               ThreadLocal.withInitial(() -> memoizeResettable(() -> 
BasicBeanConverter.DEFAULT));
+       
+       // Thread-local override for method-level converter customization
+       private static final ThreadLocal<BeanConverter> CONVERTER_OVERRIDE = 
new ThreadLocal<>();
 
        /**
-        * Creates a new {@link AssertionArgs} instance for configuring 
assertion behavior.
+        * Gets the bean converter for the current thread.
         *
-        * <p>AssertionArgs provides fluent configuration for customizing 
assertion behavior, including:</p>
-        * <ul>
-        *    <li><b>Custom Messages:</b> Static strings, parameterized with 
<code>MessageFormat</code>, or dynamic suppliers</li>
-        *    <li><b>Custom Bean Converters:</b> Override default 
object-to-string conversion behavior</li>
-        *    <li><b>Timeout Configuration:</b> Set timeouts for operations 
that may take time</li>
-        * </ul>
+        * <p>Returns the thread-local converter override if set, otherwise 
returns the memoized default converter.
+        * This method is used internally by all assertion methods to get the 
current thread-local converter.</p>
         *
-        * <h5 class='section'>Usage Examples:</h5>
-        * <p class='bjava'>
-        *    <jc>// Static message</jc>
-        *    <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"User 
validation failed"</js>),
-        *       <jv>user</jv>, <js>"name,age"</js>, <js>"John,30"</js>);
+        * @return The bean converter to use for the current thread.
+        */
+       private static BeanConverter getConverter() {
+               var override = CONVERTER_OVERRIDE.get();
+               if (override != null) {
+                       return override;
+               }
+               return CONVERTER_SUPPLIER.get().get();
+       }
+
+       /**
+        * Sets a custom bean converter for the current thread.
         *
-        *    <jc>// Parameterized message</jc>
-        *    <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(<js>"Test 
failed for user {0}"</js>, <jv>userId</jv>),
-        *       <jv>user</jv>, <js>"status"</js>, <js>"ACTIVE"</js>);
+        * <p>This method allows you to override the default converter for all 
assertions in the current test method.
+        * The converter will be used by all assertion methods in the current 
thread.</p>
         *
-        *    <jc>// Dynamic message with supplier</jc>
-        *    <jsm>assertBean</jsm>(<jsm>args</jsm>().setMessage(() -> 
<js>"Test failed at "</js> + Instant.<jsm>now</jsm>()),
-        *       <jv>result</jv>, <js>"success"</js>, <js>"true"</js>);
+        * <p>This is particularly useful in test setup methods (e.g., {@code 
@BeforeEach}) to configure a custom converter
+        * for all tests in a test class or method.</p>
         *
-        *    <jc>// Custom bean converter</jc>
-        *    <jk>var</jk> <jv>converter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
+        * <h5 class='section'>Usage Example:</h5>
+        * <p class='bjava'>
+        *    <jc>// In @BeforeEach method</jc>
+        *    <jk>var</jk> <jv>customConverter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
         *       .defaultSettings()
         *       .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> 
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
         *       .build();
-        *    
<jsm>assertBean</jsm>(<jsm>args</jsm>().setBeanConverter(<jv>converter</jv>),
-        *       <jv>event</jv>, <js>"date"</js>, <js>"2023-12-01"</js>);
+        *    BctAssertions.<jsm>setConverter</jsm>(<jv>customConverter</jv>);
+        *
+        *    <jc>// All subsequent assertions in this test method will use the 
custom converter</jc>
+        *    <jsm>assertBean</jsm>(<jv>event</jv>, <js>"date"</js>, 
<js>"2023-12-01"</js>);
         * </p>
         *
-        * @return A new AssertionArgs instance for fluent configuration
-        * @see AssertionArgs
+        * <h5 class='section'>Thread Safety:</h5>
+        * <p>This method is thread-safe and uses thread-local storage. Each 
test method running in parallel
+        * will have its own converter instance, preventing cross-thread 
interference.</p>
+        *
+        * @param converter The bean converter to use for the current thread. 
Must not be <jk>null</jk>.
+        * @throws IllegalArgumentException If converter is <jk>null</jk>.
+        * @see #resetConverter()
         */
-       public static AssertionArgs args() {
-               return new AssertionArgs();
+       public static void setConverter(BeanConverter converter) {
+               assertArgNotNull("converter", converter);
+               CONVERTER_OVERRIDE.set(converter);
        }
 
        /**
-        * Same as {@link #assertBean(Object, String, String)} but with 
configurable assertion behavior.
+        * Resets the bean converter for the current thread to the system 
default.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param actual The bean to test. Must not be null.
-        * @param fields A comma-delimited list of bean property names 
(supports nested syntax).
-        * @param expected The expected property values as a comma-delimited 
string.
-        * @see #assertBean(Object, String, String)
-        * @see #args()
+        * <p>This method clears any thread-local converter override set via 
{@link #setConverter(BeanConverter)},
+        * restoring the default converter ({@link BasicBeanConverter#DEFAULT}) 
for subsequent assertions.</p>
+        *
+        * <p>This is typically called in test teardown methods (e.g., {@code 
@AfterEach}) to clean up after tests
+        * that set a custom converter.</p>
+        *
+        * <h5 class='section'>Usage Example:</h5>
+        * <p class='bjava'>
+        *    <jc>// In @AfterEach method</jc>
+        *    BctAssertions.<jsm>resetConverter</jsm>();
+        * </p>
+        *
+        * <h5 class='section'>Thread Safety:</h5>
+        * <p>This method is thread-safe and only affects the current thread's 
converter.</p>
+        *
+        * @see #setConverter(BeanConverter)
         */
-       public static void assertBean(AssertionArgs args, Object actual, String 
fields, String expected) {
-               assertNotNull(actual, "Actual was null.");
-               assertArgNotNull("args", args);
-               assertArgNotNull("fields", fields);
-               assertArgNotNull("expected", expected);
-               assertEquals(expected, tokenize(fields).stream().map(x -> 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).getNested(actual, 
x)).collect(joining(",")),
-                       args.getMessage("Bean assertion failed."));
+       public static void resetConverter() {
+               CONVERTER_OVERRIDE.remove();
+               CONVERTER_SUPPLIER.get().reset();
+       }
+
+       /**
+        * Composes an error message from an optional custom message and a 
default message.
+        *
+        * <p>If a custom message is provided, it is composed with the default 
message in the format:
+        * <js>"{custom}, Caused by: {default}"</js>. Otherwise, the default 
message is returned.</p>
+        *
+        * @param customMessage Optional custom message supplier. Can be 
<jk>null</jk>.
+        * @param defaultMessage Default message template.
+        * @param defaultArgs Arguments for the default message template.
+        * @return A supplier that produces the composed error message.
+        */
+       private static Supplier<String> composeMessage(Supplier<String> 
customMessage, String defaultMessage, Object...defaultArgs) {
+               if (customMessage == null) {
+                       return fs(defaultMessage, defaultArgs);
+               }
+               return fs("{0}, Caused by: {1}", customMessage.get(), 
f(defaultMessage, defaultArgs));
        }
 
        /**
@@ -198,7 +291,7 @@ public class BctAssertions {
         * patterns including nested objects, collections, arrays, method 
chaining, direct field access, collection iteration
         * with <js>"#{property}"</js> syntax, and universal 
<js>"length"</js>/<js>"size"</js> properties for all collection types.</p>
         *
-        * <p>The method uses the {@link BasicBeanConverter#DEFAULT} converter 
internally for object introspection
+        * <p>The method uses the default converter (set via {@link 
#setConverter(BeanConverter)}) for object introspection
         * and value extraction. The converter provides sophisticated property 
access through the {@link BeanConverter}
         * interface, supporting multiple fallback mechanisms for accessing 
object properties and values.</p>
         *
@@ -209,6 +302,12 @@ public class BctAssertions {
         *
         *    <jc>// Test single property</jc>
         *    <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name"</js>, 
<js>"John"</js>);
+        *
+        *    <jc>// With custom error message</jc>
+        *    <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name,age"</js>, 
<js>"John,30"</js>, () -> <js>"User validation failed"</js>);
+        *
+        *    <jc>// With formatted message using Utils.fs() for convenient 
message suppliers with arguments</jc>
+        *    <jsm>assertBean</jsm>(<jv>myBean</jv>, <js>"name,age"</js>, 
<js>"John,30"</js>, <jsm>fs</jsm>(<js>"User {0} validation failed"</js>, 
<js>"John"</js>));
         * </p>
         *
         * <h5 class='section'>Nested Property Testing:</h5>
@@ -354,6 +453,9 @@ public class BctAssertions {
         *    <li><b>Public fields</b> (direct field access)</li>
         * </ol>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
+        *                Use {@link 
org.apache.juneau.common.utils.Utils#fs(String, Object...) Utils.fs()} to 
conveniently
+        *                create message suppliers with format arguments (e.g., 
<code>fs("User {0} validation failed", userName)</code>).
         * @param actual The bean object to test. Must not be null.
         * @param fields Comma-delimited list of property names to test. 
Supports nested syntax with {}.
         * @param expected Comma-delimited list of expected values. Must match 
the order of fields.
@@ -361,58 +463,35 @@ public class BctAssertions {
         * @throws AssertionError if any property values don't match expected 
values
         * @see BeanConverter
         * @see BasicBeanConverter
+        * @see #setConverter(BeanConverter)
+        * @see org.apache.juneau.common.utils.Utils#fs(String, Object...)
         */
-       public static void assertBean(Object actual, String fields, String 
expected) {
-               assertBean(args(), actual, fields, expected);
+       public static void assertBean(Supplier<String> message, Object actual, 
String fields, String expected) {
+               assertNotNull(actual, "Actual was null.");
+               assertArgNotNull("fields", fields);
+               assertArgNotNull("expected", expected);
+               var converter = getConverter();
+               assertEquals(expected, tokenize(fields).stream().map(x -> 
converter.getNested(actual, x)).collect(joining(",")),
+                       composeMessage(message, "Bean assertion failed."));
        }
 
        /**
-        * Same as {@link #assertBeans(Object, String, String...)} but with 
configurable assertion behavior.
+        * Asserts that the fields/properties on the specified bean are the 
specified values after being converted to strings.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param actual The collection of beans to test. Must not be null.
-        * @param fields A comma-delimited list of bean property names 
(supports nested syntax).
-        * @param expected Array of expected value strings, one per bean.
-        * @see #assertBeans(Object, String, String...)
-        * @see #args()
+        * <p>Same as {@link #assertBean(Supplier, Object, String, String)} but 
without a custom message.</p>
+        *
+        * @param actual The bean object to test. Must not be null.
+        * @param fields Comma-delimited list of property names to test. 
Supports nested syntax with {}.
+        * @param expected Comma-delimited list of expected values. Must match 
the order of fields.
+        * @throws NullPointerException if the bean is null
+        * @throws AssertionError if any property values don't match expected 
values
+        * @see #assertBean(Supplier, Object, String, String)
         */
-       public static void assertBeans(AssertionArgs args, Object actual, 
String fields, String...expected) {
-               assertNotNull(actual, "Value was null.");
-               assertArgNotNull("args", args);
-               assertArgNotNull("fields", fields);
-               assertArgNotNull("expected", expected);
-
-               var converter = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER);
-               var tokens = tokenize(fields);
-               var errors = new ArrayList<AssertionFailedError>();
-               var actualList = converter.listify(actual);
-
-               if (ne(expected.length, actualList.size())) {
-                       errors.add(assertEqualsFailed(expected.length, 
actualList.size(), args.getMessage("Wrong number of beans.")));
-               } else {
-                       for (var i = 0; i < actualList.size(); i++) {
-                               var i2 = i;
-                               var e = converter.stringify(expected[i]);
-                               var a = tokens.stream().map(x -> 
converter.getNested(actualList.get(i2), x)).collect(joining(","));
-                               if (ne(e, a)) {
-                                       errors.add(assertEqualsFailed(e, a, 
args.getMessage("Bean at row <{0}> did not match.", i)));
-                               }
-                       }
-               }
-
-               if (errors.isEmpty())
-                       return;
-
-               var actualStrings = new ArrayList<String>();
-               for (var o : actualList) {
-                       actualStrings.add(tokens.stream().map(x -> 
converter.getNested(o, x)).collect(joining(",")));
-               }
-
-               throw 
assertEqualsFailed(Stream.of(expected).map(StringUtils::escapeForJava).collect(joining("\",
 \"", "\"", "\"")),
-                       
actualStrings.stream().map(StringUtils::escapeForJava).collect(joining("\", 
\"", "\"", "\"")),
-                       args.getMessage("{0} bean assertions failed:\n{1}", 
errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
+       public static void assertBean(Object actual, String fields, String 
expected) {
+               assertBean(null, actual, fields, expected);
        }
 
+
        /**
         * Asserts that multiple beans in a collection have the expected 
property values.
         *
@@ -471,28 +550,58 @@ public class BctAssertions {
         * @see #assertBean(Object, String, String)
         */
        public static void assertBeans(Object actual, String fields, 
String...expected) {
-               assertBeans(args(), actual, fields, expected);
+               assertBeans(null, actual, fields, expected);
        }
 
        /**
-        * Same as {@link #assertContains(String, Object)} but with 
configurable assertion behavior.
+        * Asserts that multiple beans in a collection have the expected 
property values.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param expected The substring that must be present.
-        * @param actual The object to test. Must not be null.
-        * @see #assertContains(String, Object)
-        * @see #args()
+        * <p>Same as {@link #assertBeans(Object, String, String...)} but with 
a custom error message.</p>
+        *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
+        * @param actual The collection of beans to check. Must not be null.
+        * @param fields A comma-delimited list of bean property names 
(supports nested syntax).
+        * @param expected Array of expected value strings, one per bean. Each 
string contains comma-delimited values matching the fields.
+        * @throws AssertionError if the collection size doesn't match values 
array length or if any bean properties don't match
+        * @see #assertBean(Object, String, String)
         */
-       public static void assertContains(AssertionArgs args, String expected, 
Object actual) {
-               assertArgNotNull("args", args);
-               assertArgNotNull("expected", expected);
-               assertArgNotNull("actual", actual);
+       public static void assertBeans(Supplier<String> message, Object actual, 
String fields, String...expected) {
                assertNotNull(actual, "Value was null.");
+               assertArgNotNull("fields", fields);
+               assertArgNotNull("expected", expected);
+
+               var converter = getConverter();
+               var tokens = tokenize(fields);
+               var errors = new ArrayList<AssertionFailedError>();
+               List<Object> actualList = converter.listify(actual);
+
+               if (ne(expected.length, actualList.size())) {
+                       errors.add(assertEqualsFailed(expected.length, 
actualList.size(), composeMessage(message, "Wrong number of beans.")));
+               } else {
+                       for (var i = 0; i < actualList.size(); i++) {
+                               var i2 = i;
+                               var e = converter.stringify(expected[i]);
+                               var a = tokens.stream().map(x -> 
converter.getNested(actualList.get(i2), x)).collect(joining(","));
+                               if (ne(e, a)) {
+                                       errors.add(assertEqualsFailed(e, a, 
composeMessage(message, "Bean at row <{0}> did not match.", i)));
+                               }
+                       }
+               }
+
+               if (errors.isEmpty())
+                       return;
+
+               var actualStrings = new ArrayList<String>();
+               for (var o : actualList) {
+                       actualStrings.add(tokens.stream().map(x -> 
converter.getNested(o, x)).collect(joining(",")));
+               }
 
-               var a = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual);
-               assertTrue(a.contains(expected), args.getMessage("String did 
not contain expected substring.  ==> expected: <{0}> but was: <{1}>", expected, 
a));
+               throw 
assertEqualsFailed(Stream.of(expected).map(StringUtils::escapeForJava).collect(joining("\",
 \"", "\"", "\"")),
+                       
actualStrings.stream().map(StringUtils::escapeForJava).collect(joining("\", 
\"", "\"", "\"")),
+                       composeMessage(message, "{0} bean assertions 
failed:\n{1}", errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
        }
 
+
        /**
         * Asserts that the string representation of an object contains the 
expected substring.
         *
@@ -512,36 +621,72 @@ public class BctAssertions {
         *    <jsm>assertContains</jsm>(<js>"\"name\":\"John\""</js>, 
<jv>jsonResponse</jv>);
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param expected The substring that must be present in the actual 
object's string representation
         * @param actual The object to test. Must not be null.
         * @throws AssertionError if the actual object is null or its string 
representation doesn't contain the expected substring
         * @see #assertContainsAll(Object, String...) for multiple substring 
assertions
         * @see #assertString(String, Object) for exact string matching
         */
+       public static void assertContains(Supplier<String> message, String 
expected, Object actual) {
+               assertArgNotNull("expected", expected);
+               assertArgNotNull("actual", actual);
+               assertNotNull(actual, "Value was null.");
+
+               var a = getConverter().stringify(actual);
+               assertTrue(a.contains(expected), composeMessage(message, 
"String did not contain expected substring.  ==> expected: <{0}> but was: 
<{1}>", expected, a));
+       }
+
+       /**
+        * Asserts that the string representation of an object contains the 
expected substring.
+        *
+        * <p>Same as {@link #assertContains(Supplier, String, Object)} but 
without a custom message.</p>
+        *
+        * @param expected The substring that must be present in the actual 
object's string representation
+        * @param actual The object to test. Must not be null.
+        * @throws AssertionError if the actual object is null or its string 
representation doesn't contain the expected substring
+        * @see #assertContains(Supplier, String, Object)
+        */
        public static void assertContains(String expected, Object actual) {
-               assertContains(args(), expected, actual);
+               assertContains(null, expected, actual);
        }
 
+
        /**
-        * Same as {@link #assertContainsAll(Object, String...)} but with 
configurable assertion behavior.
+        * Asserts that the string representation of an object contains all 
specified substrings.
+        *
+        * <p>This method is similar to {@link #assertContains(String, Object)} 
but tests for multiple
+        * required substrings. All provided substrings must be present in the 
actual object's string
+        * representation for the assertion to pass.</p>
+        *
+        * <h5 class='section'>Usage Examples:</h5>
+        * <p class='bjava'>
+        *    <jc>// Test that error contains multiple pieces of 
information</jc>
+        *    <jsm>assertContainsAll</jsm>(<jv>exception</jv>, 
<js>"FileNotFoundException"</js>, <js>"config.xml"</js>, <js>"/etc"</js>);
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
+        *    <jc>// Test that user object contains expected fields</jc>
+        *    <jsm>assertContainsAll</jsm>(<jv>user</jv>, <js>"name=John"</js>, 
<js>"age=30"</js>, <js>"status=ACTIVE"</js>);
+        *
+        *    <jc>// Test log output contains all required entries</jc>
+        *    <jsm>assertContainsAll</jsm>(<jv>logOutput</jv>, <js>"INFO"</js>, 
<js>"Started"</js>, <js>"Successfully"</js>);
+        * </p>
+        *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param actual The object to test. Must not be null.
-        * @param expected Multiple substrings that must all be present.
-        * @see #assertContainsAll(Object, String...)
-        * @see #args()
+        * @param expected Multiple substrings that must all be present in the 
actual object's string representation
+        * @throws AssertionError if the actual object is null or its string 
representation doesn't contain all expected substrings
+        * @see #assertContains(String, Object) for single substring assertions
         */
-       public static void assertContainsAll(AssertionArgs args, Object actual, 
String...expected) {
-               assertArgNotNull("args", args);
+       public static void assertContainsAll(Supplier<String> message, Object 
actual, String...expected) {
                assertArgNotNull("expected", expected);
                assertNotNull(actual, "Value was null.");
 
-               var a = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual);
+               var a = getConverter().stringify(actual);
                var errors = new ArrayList<AssertionFailedError>();
 
                for (var e : expected) {
                        if (! a.contains(e)) {
-                               errors.add(assertEqualsFailed(true, false, 
args.getMessage("String did not contain expected substring.  ==> expected: 
<{0}> but was: <{1}>", e, a)));
+                               errors.add(assertEqualsFailed(true, false, 
composeMessage(message, "String did not contain expected substring.  ==> 
expected: <{0}> but was: <{1}>", e, a)));
                        }
                }
 
@@ -559,51 +704,23 @@ public class BctAssertions {
                }
 
                throw 
assertEqualsFailed(missingSubstrings.stream().map(StringUtils::escapeForJava).collect(joining("\",
 \"", "\"", "\"")), escapeForJava(a),
-                       args.getMessage("{0} substring assertions 
failed:\n{1}", errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
+                       composeMessage(message, "{0} substring assertions 
failed:\n{1}", errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
        }
 
        /**
         * Asserts that the string representation of an object contains all 
specified substrings.
         *
-        * <p>This method is similar to {@link #assertContains(String, Object)} 
but tests for multiple
-        * required substrings. All provided substrings must be present in the 
actual object's string
-        * representation for the assertion to pass.</p>
-        *
-        * <h5 class='section'>Usage Examples:</h5>
-        * <p class='bjava'>
-        *    <jc>// Test that error contains multiple pieces of 
information</jc>
-        *    <jsm>assertContainsAll</jsm>(<jv>exception</jv>, 
<js>"FileNotFoundException"</js>, <js>"config.xml"</js>, <js>"/etc"</js>);
-        *
-        *    <jc>// Test that user object contains expected fields</jc>
-        *    <jsm>assertContainsAll</jsm>(<jv>user</jv>, <js>"name=John"</js>, 
<js>"age=30"</js>, <js>"status=ACTIVE"</js>);
-        *
-        *    <jc>// Test log output contains all required entries</jc>
-        *    <jsm>assertContainsAll</jsm>(<jv>logOutput</jv>, <js>"INFO"</js>, 
<js>"Started"</js>, <js>"Successfully"</js>);
-        * </p>
+        * <p>Same as {@link #assertContainsAll(Supplier, Object, String...)} 
but without a custom message.</p>
         *
         * @param actual The object to test. Must not be null.
         * @param expected Multiple substrings that must all be present in the 
actual object's string representation
         * @throws AssertionError if the actual object is null or its string 
representation doesn't contain all expected substrings
-        * @see #assertContains(String, Object) for single substring assertions
+        * @see #assertContainsAll(Supplier, Object, String...)
         */
        public static void assertContainsAll(Object actual, String...expected) {
-               assertContainsAll(args(), actual, expected);
+               assertContainsAll(null, actual, expected);
        }
 
-       /**
-        * Same as {@link #assertEmpty(Object)} but with configurable assertion 
behavior.
-        *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param value The object to test. Must not be null.
-        * @see #assertEmpty(Object)
-        * @see #args()
-        */
-       public static void assertEmpty(AssertionArgs args, Object value) {
-               assertArgNotNull("args", args);
-               assertNotNull(value, "Value was null.");
-               var size = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).size(value);
-               assertEquals(0, size, args.getMessage("Value was not empty. 
Size=<{0}>", size));
-       }
 
        /**
         * Asserts that a collection-like object, Optional, Value, String, or 
array is not null and empty.
@@ -645,72 +762,32 @@ public class BctAssertions {
         *    <jsm>assertEmpty</jsm>(<jk>new</jk> HashMap&lt;&gt;());
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param value The object to test. Must not be null.
         * @throws AssertionError if the object is null or not empty
         * @see #assertNotEmpty(Object) for testing non-empty collections
         * @see #assertSize(int, Object) for testing specific sizes
         */
-       public static void assertEmpty(Object value) {
-               assertEmpty(args(), value);
+       public static void assertEmpty(Supplier<String> message, Object value) {
+               assertNotNull(value, "Value was null.");
+               var size = getConverter().size(value);
+               assertEquals(0, size, composeMessage(message, "Value was not 
empty. Size=<{0}>", size));
        }
 
        /**
-        * Same as {@link #assertList(Object, Object...)} but with configurable 
assertion behavior.
+        * Asserts that a collection-like object, Optional, Value, String, or 
array is not null and empty.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param actual The List to test. Must not be null.
-        * @param expected Multiple arguments of expected values.
-        * @see #assertList(Object, Object...)
-        * @see #args()
+        * <p>Same as {@link #assertEmpty(Supplier, Object)} but without a 
custom message.</p>
+        *
+        * @param value The object to test. Must not be null.
+        * @throws AssertionError if the object is null or not empty
+        * @see #assertEmpty(Supplier, Object)
         */
-       @SuppressWarnings("unchecked")
-       public static void assertList(AssertionArgs args, Object actual, 
Object...expected) {
-               assertArgNotNull("args", args);
-               assertArgNotNull("expected", expected);
-               assertNotNull(actual, "Value was null.");
-
-               var converter = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER);
-               var list = converter.listify(actual);
-               var errors = new ArrayList<AssertionFailedError>();
-
-               if (ne(expected.length, list.size())) {
-                       errors.add(assertEqualsFailed(expected.length, 
list.size(), args.getMessage("Wrong list length.")));
-               } else {
-                       for (var i = 0; i < expected.length; i++) {
-                               var x = list.get(i);
-                               var e = expected[i];
-                               if (e instanceof String e2) {
-                                       if (ne(e2, converter.stringify(x))) {
-                                               
errors.add(assertEqualsFailed(e2, converter.stringify(x), 
args.getMessage("Element at index {0} did not match.", i)));
-                                       }
-                               } else if (e instanceof Predicate e2) { // 
NOSONAR
-                                       if (! e2.test(x)) {
-                                               errors.add(new 
AssertionFailedError(args.getMessage("Element at index {0} did not pass 
predicate.  ==> actual: <{1}>", i, converter.stringify(x)).get()));
-                                       }
-                               } else {
-                                       if (ne(e, x)) {
-                                               
errors.add(assertEqualsFailed(e, x, args.getMessage("Element at index {0} did 
not match.  ==> expected: <{1}({2})> but was: <{3}({4})>", i, e, cns(e), x, 
cns(x))));
-                                       }
-                               }
-                       }
-               }
-
-               if (errors.isEmpty())
-                       return;
-
-               var actualStrings = new ArrayList<String>();
-               for (var o : list) {
-                       actualStrings.add(converter.stringify(o));
-               }
-
-               if (errors.size() == 1)
-                       throw errors.get(0);
-
-               throw 
assertEqualsFailed(Stream.of(expected).map(converter::stringify).map(StringUtils::escapeForJava).collect(joining("\",
 \"", "[\"", "\"]")),
-                       
actualStrings.stream().map(StringUtils::escapeForJava).collect(joining("\", 
\"", "[\"", "\"]")),
-                       args.getMessage("{0} list assertions failed:\n{1}", 
errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
+       public static void assertEmpty(Object value) {
+               assertEmpty(null, value);
        }
 
+
        /**
         * Asserts that a List or List-like object contains the expected values 
using flexible comparison logic.
         *
@@ -757,28 +834,77 @@ public class BctAssertions {
         *    <jsm>assertList</jsm>(List.<jsm>of</jsm>(<jv>myBean1</jv>, 
<jv>myBean2</jv>), <jv>myBean1</jv>, <jv>myBean2</jv>); <jc>// Custom 
objects</jc>
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param actual The List to test. Must not be null.
         * @param expected Multiple arguments of expected values.
         *                 Can be Strings (readable format comparison), 
Predicates (functional testing), or Objects (direct equality).
         * @throws AssertionError if the List size or contents don't match 
expected values
         */
-       public static void assertList(Object actual, Object...expected) {
-               assertList(args(), actual, expected);
+       @SuppressWarnings("unchecked")
+       public static void assertList(Supplier<String> message, Object actual, 
Object...expected) {
+               assertArgNotNull("expected", expected);
+               assertArgNotNull("actual", actual);
+
+               var converter = getConverter();
+               List<Object> list = converter.listify(actual);
+               var errors = new ArrayList<AssertionFailedError>();
+
+               if (ne(expected.length, list.size())) {
+                       errors.add(assertEqualsFailed(expected.length, 
list.size(), composeMessage(message, "Wrong list length.")));
+               } else {
+                       for (var i = 0; i < expected.length; i++) {
+                               var x = list.get(i);
+                               var e = expected[i];
+                               if (e instanceof String e2) {
+                                       if (ne(e2, converter.stringify(x))) {
+                                               
errors.add(assertEqualsFailed(e2, converter.stringify(x), 
composeMessage(message, "Element at index {0} did not match.", i)));
+                                       }
+                               } else if (e instanceof Predicate e2) { // 
NOSONAR
+                                       if (! e2.test(x)) {
+                                               errors.add(new 
AssertionFailedError(composeMessage(message, "Element at index {0} did not pass 
predicate.  ==> actual: <{1}>", i, converter.stringify(x)).get()));
+                                       }
+                               } else {
+                                       if (ne(e, x)) {
+                                               
errors.add(assertEqualsFailed(e, x, composeMessage(message, "Element at index 
{0} did not match.  ==> expected: <{1}({2})> but was: <{3}({4})>", i, e, 
cns(e), x, cns(x))));
+                                       }
+                               }
+                       }
+               }
+
+               if (errors.isEmpty())
+                       return;
+
+               var actualStrings = new ArrayList<String>();
+               for (var o : list) {
+                       actualStrings.add(converter.stringify(o));
+               }
+
+               if (errors.size() == 1)
+                       throw errors.get(0);
+
+               throw 
assertEqualsFailed(Stream.of(expected).map(converter::stringify).map(StringUtils::escapeForJava).collect(joining("\",
 \"", "[\"", "\"]")),
+                       
actualStrings.stream().map(StringUtils::escapeForJava).collect(joining("\", 
\"", "[\"", "\"]")),
+                       composeMessage(message, "{0} list assertions 
failed:\n{1}", errors.size(), errors.stream().map(x -> 
x.getMessage()).collect(joining("\n"))));
        }
 
        /**
-        * Same as {@link #assertMap(Map, Object...)} but with configurable 
assertion behavior.
+        * Asserts that a List or List-like object contains the expected values 
using flexible comparison logic.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param actual The Map to test. Must not be null.
-        * @param expected Multiple arguments of expected map entries.
-        * @see #assertMap(Map, Object...)
-        * @see #args()
+        * <p>Same as {@link #assertList(Supplier, Object, Object...)} but 
without a custom message.</p>
+        *
+        * @param actual The List to test. Must not be null.
+        * @param expected Multiple arguments of expected values.
+        *                 Can be Strings (readable format comparison), 
Predicates (functional testing), or Objects (direct equality).
+        * @throws IllegalArgumentException if actual is null
+        * @throws AssertionError if the List size or contents don't match 
expected values
+        * @see #assertList(Supplier, Object, Object...)
         */
-       public static void assertMap(AssertionArgs args, Map<?,?> actual, 
Object...expected) {
-               assertList(args, actual, expected);
+       public static void assertList(Object actual, Object...expected) {
+               assertArgNotNull("actual", actual);
+               assertList(null, actual, expected);
        }
 
+
        /**
         * Asserts that a Map contains the expected key/value pairs using 
flexible comparison logic.
         *
@@ -833,44 +959,33 @@ public class BctAssertions {
         * </ul>
         * <p>This ensures predictable test results regardless of the original 
map implementation.</p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param actual The Map to test. Must not be null.
         * @param expected Multiple arguments of expected map entries.
         *                 Can be Strings (readable format comparison), 
Predicates (functional testing), or Objects (direct equality).
         * @throws AssertionError if the Map size or contents don't match 
expected values
-        * @see #assertList(Object, Object...)
+        * @see #assertList(Supplier, Object, Object...)
         */
-       public static void assertMap(Map<?,?> actual, Object...expected) {
-               assertList(args(), actual, expected);
+       public static void assertMap(Supplier<String> message, Map<?,?> actual, 
Object...expected) {
+               assertList(message, actual, expected);
        }
 
        /**
-        * Same as {@link #assertMapped(Object, BiFunction, String, String)} 
but with configurable assertion behavior.
+        * Asserts that a Map contains the expected key/value pairs using 
flexible comparison logic.
         *
-        * @param <T> The object type being tested.
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param actual The object to test. Must not be null.
-        * @param function Custom property access function.
-        * @param properties A comma-delimited list of property names.
-        * @param expected The expected property values as a comma-delimited 
string.
-        * @see #assertMapped(Object, BiFunction, String, String)
-        * @see #args()
+        * <p>Same as {@link #assertMap(Supplier, Map, Object...)} but without 
a custom message.</p>
+        *
+        * @param actual The Map to test. Must not be null.
+        * @param expected Multiple arguments of expected map entries.
+        *                 Can be Strings (readable format comparison), 
Predicates (functional testing), or Objects (direct equality).
+        * @throws AssertionError if the Map size or contents don't match 
expected values
+        * @see #assertMap(Supplier, Map, Object...)
         */
-       public static <T> void assertMapped(AssertionArgs args, T actual, 
BiFunction<T,String,Object> function, String properties, String expected) {
-               assertNotNull(actual, "Value was null.");
-               assertArgNotNull("args", args);
-               assertArgNotNull("function", function);
-               assertArgNotNull("properties", properties);
-               assertArgNotNull("expected", expected);
-
-               var m = new LinkedHashMap<String,Object>();
-               for (var p : tokenize(properties)) {
-                       var pv = p.getValue();
-                       m.put(pv, safe(() -> function.apply(actual, pv)));
-               }
-
-               assertBean(args, m, properties, expected);
+       public static void assertMap(Map<?,?> actual, Object...expected) {
+               assertMap(null, actual, expected);
        }
 
+
        /**
         * Asserts that mapped property access on an object returns expected 
values using a custom BiFunction.
         *
@@ -884,38 +999,49 @@ public class BctAssertions {
         * for value stringification and nested property access.</p>
         *
         * @param <T> The type of object being tested
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param actual The object to test properties on
         * @param function The BiFunction that extracts property values. 
Receives (<jp>object</jp>, <jp>propertyName</jp>) and returns the property 
value.
         * @param properties Comma-delimited list of property names to test
         * @param expected Comma-delimited list of expected values (exceptions 
become simple class names)
         * @throws AssertionError if any mapped property values don't match 
expected values
-        * @see #assertBean(Object, String, String)
+        * @see #assertBean(Supplier, Object, String, String)
         * @see BeanConverter
         * @see BasicBeanConverter
         */
-       public static <T> void assertMapped(T actual, 
BiFunction<T,String,Object> function, String properties, String expected) {
-               assertMapped(args(), actual, function, properties, expected);
+       public static <T> void assertMapped(Supplier<String> message, T actual, 
BiFunction<T,String,Object> function, String properties, String expected) {
+               assertNotNull(actual, "Value was null.");
+               assertArgNotNull("function", function);
+               assertArgNotNull("properties", properties);
+               assertArgNotNull("expected", expected);
+
+               var m = new LinkedHashMap<String,Object>();
+               for (var p : tokenize(properties)) {
+                       var pv = p.getValue();
+                       m.put(pv, safe(() -> function.apply(actual, pv)));
+               }
+
+               assertBean(message, m, properties, expected);
        }
 
        /**
-        * Same as {@link #assertMatchesGlob(String, Object)} but with 
configurable assertion behavior.
+        * Asserts that mapped property access on an object returns expected 
values using a custom BiFunction.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param pattern The glob-style pattern to match against.
-        * @param value The object to test. Must not be null.
-        * @see #assertMatchesGlob(String, Object)
-        * @see #args()
+        * <p>Same as {@link #assertMapped(Supplier, Object, BiFunction, 
String, String)} but without a custom message.</p>
+        *
+        * @param <T> The type of object being tested
+        * @param actual The object to test properties on
+        * @param function The BiFunction that extracts property values. 
Receives (<jp>object</jp>, <jp>propertyName</jp>) and returns the property 
value.
+        * @param properties Comma-delimited list of property names to test
+        * @param expected Comma-delimited list of expected values (exceptions 
become simple class names)
+        * @throws AssertionError if any mapped property values don't match 
expected values
+        * @see #assertMapped(Supplier, Object, BiFunction, String, String)
         */
-       public static void assertMatchesGlob(AssertionArgs args, String 
pattern, Object value) {
-               assertArgNotNull("args", args);
-               assertArgNotNull("pattern", pattern);
-               assertNotNull(value, "Value was null.");
-
-               var v = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(value);
-               var m = StringUtils.getGlobMatchPattern(pattern).matcher(v);
-               assertTrue(m.matches(), args.getMessage("Pattern didn''t match. 
==> pattern: <{0}> but was: <{1}>", pattern, v));
+       public static <T> void assertMapped(T actual, 
BiFunction<T,String,Object> function, String properties, String expected) {
+               assertMapped(null, actual, function, properties, expected);
        }
 
+
        /**
         * Asserts that an object's string representation matches the specified 
glob-style pattern.
         *
@@ -943,31 +1069,37 @@ public class BctAssertions {
         *    <jsm>assertMatchesGlob</jsm>(<js>"log_*_?.txt"</js>, 
<jv>logFile</jv>);
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param pattern The glob-style pattern to match against.
         * @param value The object to test. Must not be null.
         * @throws AssertionError if the value is null or its string 
representation doesn't match the pattern
-        * @see #assertString(String, Object) for exact string matching
-        * @see #assertContains(String, Object) for substring matching
+        * @see #assertString(Supplier, String, Object) for exact string 
matching
+        * @see #assertContains(Supplier, String, Object) for substring matching
         */
-       public static void assertMatchesGlob(String pattern, Object value) {
-               assertMatchesGlob(args(), pattern, value);
+       public static void assertMatchesGlob(Supplier<String> message, String 
pattern, Object value) {
+               assertArgNotNull("pattern", pattern);
+               assertNotNull(value, "Value was null.");
+
+               var v = getConverter().stringify(value);
+               var m = StringUtils.getGlobMatchPattern(pattern).matcher(v);
+               assertTrue(m.matches(), composeMessage(message, "Pattern 
didn''t match. ==> pattern: <{0}> but was: <{1}>", pattern, v));
        }
 
        /**
-        * Same as {@link #assertNotEmpty(Object)} but with configurable 
assertion behavior.
+        * Asserts that an object's string representation matches the specified 
glob-style pattern.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
+        * <p>Same as {@link #assertMatchesGlob(Supplier, String, Object)} but 
without a custom message.</p>
+        *
+        * @param pattern The glob-style pattern to match against.
         * @param value The object to test. Must not be null.
-        * @see #assertNotEmpty(Object)
-        * @see #args()
+        * @throws AssertionError if the value is null or its string 
representation doesn't match the pattern
+        * @see #assertMatchesGlob(Supplier, String, Object)
         */
-       public static void assertNotEmpty(AssertionArgs args, Object value) {
-               assertArgNotNull("args", args);
-               assertNotNull(value, "Value was null.");
-               var size = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).size(value);
-               assertTrue(size > 0, args.getMessage("Value was empty."));
+       public static void assertMatchesGlob(String pattern, Object value) {
+               assertMatchesGlob(null, pattern, value);
        }
 
+
        /**
         * Asserts that a collection-like object, Optional, Value, String, or 
array is not null and not empty.
         *
@@ -1008,31 +1140,32 @@ public class BctAssertions {
         *    <jsm>assertNotEmpty</jsm>(Map.<jsm>of</jsm>(<js>"key"</js>, 
<js>"value"</js>));
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param value The object to test. Must not be null.
         * @throws AssertionError if the object is null or empty
-        * @see #assertEmpty(Object) for testing empty collections
-        * @see #assertSize(int, Object) for testing specific sizes
+        * @see #assertEmpty(Supplier, Object) for testing empty collections
+        * @see #assertSize(Supplier, int, Object) for testing specific sizes
         */
-       public static void assertNotEmpty(Object value) {
-               assertNotEmpty(args(), value);
+       public static void assertNotEmpty(Supplier<String> message, Object 
value) {
+               assertNotNull(value, "Value was null.");
+               int size = getConverter().size(value);
+               assertTrue(size > 0, composeMessage(message, "Value was 
empty."));
        }
 
        /**
-        * Same as {@link #assertSize(int, Object)} but with configurable 
assertion behavior.
+        * Asserts that a collection-like object, Optional, Value, String, or 
array is not null and not empty.
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param expected The expected size/length.
-        * @param actual The object to test. Must not be null.
-        * @see #assertSize(int, Object)
-        * @see #args()
+        * <p>Same as {@link #assertNotEmpty(Supplier, Object)} but without a 
custom message.</p>
+        *
+        * @param value The object to test. Must not be null.
+        * @throws AssertionError if the object is null or empty
+        * @see #assertNotEmpty(Supplier, Object)
         */
-       public static void assertSize(AssertionArgs args, int expected, Object 
actual) {
-               assertArgNotNull("args", args);
-               assertNotNull(actual, "Value was null.");
-               var size = 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).size(actual);
-               assertEquals(expected, size, args.getMessage("Value not 
expected size."));
+       public static void assertNotEmpty(Object value) {
+               assertNotEmpty(null, value);
        }
 
+
        /**
         * Asserts that a collection-like object or string is not null and of 
the specified size.
         *
@@ -1054,30 +1187,32 @@ public class BctAssertions {
         *    <jsm>assertSize</jsm>(2, <jk>new</jk> String[]{<js>"x"</js>, 
<js>"y"</js>});
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
         * @param expected The expected size/length.
         * @param actual The object to test. Must not be null.
         * @throws AssertionError if the object is null or not the expected 
size.
         */
-       public static void assertSize(int expected, Object actual) {
-               assertSize(args(), expected, actual);
+       public static void assertSize(Supplier<String> message, int expected, 
Object actual) {
+               assertNotNull(actual, "Value was null.");
+               var size = getConverter().size(actual);
+               assertEquals(expected, size, composeMessage(message, "Value not 
expected size."));
        }
 
        /**
-        * Same as {@link #assertString(String, Object)} but with configurable 
assertion behavior.
+        * Asserts that a collection-like object or string is not null and of 
the specified size.
+        *
+        * <p>Same as {@link #assertSize(Supplier, int, Object)} but without a 
custom message.</p>
         *
-        * @param args Assertion configuration. See {@link #args()} for usage 
examples.
-        * @param expected The expected string value.
+        * @param expected The expected size/length.
         * @param actual The object to test. Must not be null.
-        * @see #assertString(String, Object)
-        * @see #args()
+        * @throws AssertionError if the object is null or not the expected 
size.
+        * @see #assertSize(Supplier, int, Object)
         */
-       public static void assertString(AssertionArgs args, String expected, 
Object actual) {
-               assertArgNotNull("args", args);
-               assertNotNull(actual, "Value was null.");
-
-               assertEquals(expected, 
args.getBeanConverter().orElse(DEFAULT_CONVERTER).stringify(actual), 
args.getMessage());
+       public static void assertSize(int expected, Object actual) {
+               assertSize(null, expected, actual);
        }
 
+
        /**
         * Asserts that an object's string representation exactly matches the 
expected value.
         *
@@ -1100,14 +1235,32 @@ public class BctAssertions {
         *    <jsm>assertString</jsm>(<js>"[red,green,blue]"</js>, 
<jv>colors</jv>);
         * </p>
         *
+        * @param message Optional custom error message supplier. If provided, 
will be composed with the default assertion message.
+        * @param expected The exact string that the actual object should 
convert to
+        * @param actual The object to test. Must not be null.
+        * @throws AssertionError if the actual object is null or its string 
representation doesn't exactly match expected
+        * @see #assertContains(Supplier, String, Object) for partial string 
matching
+        * @see #assertMatchesGlob(Supplier, String, Object) for pattern-based 
matching
+        */
+       public static void assertString(Supplier<String> message, String 
expected, Object actual) {
+               assertNotNull(actual, "Value was null.");
+
+               var messageSupplier = message != null ? message : fs("");
+               assertEquals(expected, getConverter().stringify(actual), 
messageSupplier);
+       }
+
+       /**
+        * Asserts that an object's string representation exactly matches the 
expected value.
+        *
+        * <p>Same as {@link #assertString(Supplier, String, Object)} but 
without a custom message.</p>
+        *
         * @param expected The exact string that the actual object should 
convert to
         * @param actual The object to test. Must not be null.
         * @throws AssertionError if the actual object is null or its string 
representation doesn't exactly match expected
-        * @see #assertContains(String, Object) for partial string matching
-        * @see #assertMatchesGlob(String, Object) for pattern-based matching
+        * @see #assertString(Supplier, String, Object)
         */
        public static void assertString(String expected, Object actual) {
-               assertString(args(), expected, actual);
+               assertString(null, expected, actual);
        }
 
        private BctAssertions() {}
diff --git 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/package-info.java
 
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/package-info.java
index be615641b7..07c692fd46 100644
--- 
a/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/package-info.java
+++ 
b/juneau-core/juneau-bct/src/main/java/org/apache/juneau/junit/bct/package-info.java
@@ -33,15 +33,14 @@
  *
  * <h5 class='section'>Core Classes:</h5>
  * <ul>
- *    <li><b>{@link org.apache.juneau.junit.BctAssertions}:</b> Main assertion 
methods for BCT</li>
- *    <li><b>{@link org.apache.juneau.junit.BeanConverter}:</b> Interface for 
object conversion and property access</li>
- *    <li><b>{@link org.apache.juneau.junit.BasicBeanConverter}:</b> Default 
implementation with extensible type handlers</li>
- *    <li><b>{@link org.apache.juneau.junit.AssertionArgs}:</b> Configuration 
for assertions with custom messages and converters</li>
+ *    <li><b>{@link org.apache.juneau.junit.bct.BctAssertions}:</b> Main 
assertion methods for BCT</li>
+ *    <li><b>{@link org.apache.juneau.junit.bct.BeanConverter}:</b> Interface 
for object conversion and property access</li>
+ *    <li><b>{@link org.apache.juneau.junit.bct.BasicBeanConverter}:</b> 
Default implementation with extensible type handlers</li>
  * </ul>
  *
  * <h5 class='section'>Quick Start:</h5>
  * <p class='bjava'>
- *    <jk>import static</jk> com.sfdc.junit.bct.BctAssertions.*;
+ *    <jk>import static</jk> org.apache.juneau.junit.bct.BctAssertions.*;
  *
  *    <ja>@Test</ja>
  *    <jk>void</jk> testUser() {
@@ -57,7 +56,7 @@
  *
  * <h5 class='section'>Assertion Method Examples:</h5>
  *
- * <h6 class='figure'>1. {@link 
org.apache.juneau.junit.BctAssertions#assertBean(Object,String,String) 
assertBean()}</h6>
+ * <h6 class='figure'>1. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertBean(Object,String,String) 
assertBean()}</h6>
  * <p>Tests object properties with support for nested syntax and collection 
iteration.</p>
  * <p class='bjava'>
  *    User <jv>user</jv> = <jk>new</jk> User(<js>"Bob"</js>, 30);
@@ -72,7 +71,7 @@
  *    <jsm>assertBean</jsm>(<jv>user</jv>, <js>"address{street,city}"</js>, 
<js>"{456 Oak Ave,Denver}"</js>);
  * </p>
  *
- * <h6 class='figure'>2. {@link 
org.apache.juneau.junit.BctAssertions#assertBeans(Object,String,String...) 
assertBeans()}</h6>
+ * <h6 class='figure'>2. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertBeans(Object,String,String...) 
assertBeans()}</h6>
  * <p>Tests collections of objects by extracting and comparing specific 
fields.</p>
  * <p class='bjava'>
  *    List&lt;User&gt; <jv>users</jv> = Arrays.<jsm>asList</jsm>(
@@ -88,7 +87,7 @@
  *    <jsm>assertBeans</jsm>(<jv>users</jv>, <js>"name,age"</js>, 
<js>"Alice,25"</js>, <js>"Bob,30"</js>, <js>"Carol,35"</js>);
  * </p>
  *
- * <h6 class='figure'>3. {@link 
org.apache.juneau.junit.BctAssertions#assertMapped(Object,java.util.function.BiFunction,String,String)
 assertMapped()}</h6>
+ * <h6 class='figure'>3. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertMapped(Object,java.util.function.BiFunction,String,String)
 assertMapped()}</h6>
  * <p>Tests custom property access using BiFunction for non-standard 
objects.</p>
  * <p class='bjava'>
  *    Map&lt;String,Object&gt; <jv>data</jv> = <jk>new</jk> HashMap&lt;&gt;();
@@ -99,7 +98,7 @@
  *    <jsm>assertMapped</jsm>(<jv>data</jv>, (obj, key) -&gt; obj.get(key), 
<js>"name,score"</js>, <js>"Alice,95"</js>);
  * </p>
  *
- * <h6 class='figure'>4. {@link 
org.apache.juneau.junit.BctAssertions#assertList(Object,Object...) 
assertList()}</h6>
+ * <h6 class='figure'>4. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertList(Object,Object...) 
assertList()}</h6>
  * <p>Tests list/collection elements with varargs for expected values.</p>
  * <p class='bjava'>
  *    List&lt;String&gt; <jv>names</jv> = 
Arrays.<jsm>asList</jsm>(<js>"Alice"</js>, <js>"Bob"</js>, <js>"Carol"</js>);
@@ -110,7 +109,7 @@
  *    <jsm>assertList</jsm>(<jv>colors</jv>, <js>"red"</js>, <js>"green"</js>, 
<js>"blue"</js>);
  * </p>
  *
- * <h6 class='figure'>5. {@link 
org.apache.juneau.junit.BctAssertions#assertContains(String,Object) 
assertContains()}</h6>
+ * <h6 class='figure'>5. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertContains(String,Object) 
assertContains()}</h6>
  * <p>Tests that a string appears somewhere within the stringified object.</p>
  * <p class='bjava'>
  *    User <jv>user</jv> = <jk>new</jk> User(<js>"Alice Smith"</js>, 25);
@@ -121,7 +120,7 @@
  *    <jsm>assertContains</jsm>(<js>"banana"</js>, <jv>items</jv>);
  * </p>
  *
- * <h6 class='figure'>6. {@link 
org.apache.juneau.junit.BctAssertions#assertContainsAll(Object,String...) 
assertContainsAll()}</h6>
+ * <h6 class='figure'>6. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertContainsAll(Object,String...) 
assertContainsAll()}</h6>
  * <p>Tests that all specified strings appear within the stringified 
object.</p>
  * <p class='bjava'>
  *    User <jv>user</jv> = <jk>new</jk> User(<js>"Alice Smith"</js>, 25);
@@ -132,7 +131,7 @@
  *    <jsm>assertContainsAll</jsm>(<jv>user</jv>, <js>"alice"</js>, 
<js>"example.com"</js>);
  * </p>
  *
- * <h6 class='figure'>7. {@link 
org.apache.juneau.junit.BctAssertions#assertEmpty(Object) assertEmpty()}</h6>
+ * <h6 class='figure'>7. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertEmpty(Object) 
assertEmpty()}</h6>
  * <p>Tests that collections, arrays, maps, or strings are empty.</p>
  * <p class='bjava'>
  *    List&lt;String&gt; <jv>emptyList</jv> = <jk>new</jk> ArrayList&lt;&gt;();
@@ -147,7 +146,7 @@
  *    <jsm>assertEmpty</jsm>(<jv>emptyString</jv>);
  * </p>
  *
- * <h6 class='figure'>8. {@link 
org.apache.juneau.junit.BctAssertions#assertNotEmpty(Object) 
assertNotEmpty()}</h6>
+ * <h6 class='figure'>8. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertNotEmpty(Object) 
assertNotEmpty()}</h6>
  * <p>Tests that collections, arrays, maps, or strings are not empty.</p>
  * <p class='bjava'>
  *    List&lt;String&gt; <jv>names</jv> = 
Arrays.<jsm>asList</jsm>(<js>"Alice"</js>);
@@ -162,7 +161,7 @@
  *    <jsm>assertNotEmpty</jsm>(<jv>message</jv>);
  * </p>
  *
- * <h6 class='figure'>9. {@link 
org.apache.juneau.junit.BctAssertions#assertSize(int,Object) assertSize()}</h6>
+ * <h6 class='figure'>9. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertSize(int,Object) 
assertSize()}</h6>
  * <p>Tests the size/length of collections, arrays, maps, or strings.</p>
  * <p class='bjava'>
  *    List&lt;String&gt; <jv>names</jv> = 
Arrays.<jsm>asList</jsm>(<js>"Alice"</js>, <js>"Bob"</js>, <js>"Carol"</js>);
@@ -177,7 +176,7 @@
  *    <jsm>assertSize</jsm>(5, <jv>message</jv>);
  * </p>
  *
- * <h6 class='figure'>10. {@link 
org.apache.juneau.junit.BctAssertions#assertString(String,Object) 
assertString()}</h6>
+ * <h6 class='figure'>10. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertString(String,Object) 
assertString()}</h6>
  * <p>Tests the string representation of an object using the configured 
converter.</p>
  * <p class='bjava'>
  *    User <jv>user</jv> = <jk>new</jk> User(<js>"Alice"</js>, 25);
@@ -190,7 +189,7 @@
  *    <jsm>assertString</jsm>(<js>"2021-01-01"</js>, <jv>date</jv>);
  * </p>
  *
- * <h6 class='figure'>11. {@link 
org.apache.juneau.junit.BctAssertions#assertMatchesGlob(String,Object) 
assertMatchesGlob()}</h6>
+ * <h6 class='figure'>11. {@link 
org.apache.juneau.junit.bct.BctAssertions#assertMatchesGlob(String,Object) 
assertMatchesGlob()}</h6>
  * <p>Tests that the stringified object matches a glob-style pattern (* and ? 
wildcards).</p>
  * <p class='bjava'>
  *    User <jv>user</jv> = <jk>new</jk> User(<js>"Alice Smith"</js>, 25);
@@ -204,22 +203,42 @@
  *    <jsm>assertMatchesGlob</jsm>(<js>"User(name=Alice*, age=25)"</js>, 
<jv>user</jv>);
  * </p>
  *
- * <h5 class='section'>Custom Configuration with {@link 
org.apache.juneau.junit.AssertionArgs}:</h5>
- * <p>All assertion methods support custom configuration through {@link 
org.apache.juneau.junit.AssertionArgs}:</p>
+ * <h5 class='section'>Custom Error Messages:</h5>
+ * <p>All assertion methods support custom error messages via a 
<code>Supplier&lt;String&gt;</code> parameter:</p>
  * <p class='bjava'>
- *    <jc>// Custom error message</jc>
- *    <jsm>assertBean</jsm>(<jsm>args</jsm>(<js>"User validation 
failed"</js>), <jv>user</jv>, <js>"name,age"</js>, <js>"Alice,25"</js>);
- *
- *    <jc>// Custom converter configuration</jc>
- *    AssertionArgs <jv>args</jv> = <jsm>args</jsm>()
- *       
.setConverter(BasicBeanConverter.<jsm>builder</jsm>().<jsm>defaultSettings</jsm>()
- *          .setSetting(<js>"nullValue"</js>, <js>"&lt;empty&gt;"</js>)
- *          .build());
- *    <jsm>assertBean</jsm>(<jv>args</jv>, <jv>user</jv>, 
<js>"name,nickname"</js>, <js>"Alice,&lt;empty&gt;"</js>);
+ *    <jc>// Simple custom message</jc>
+ *    <jsm>assertBean</jsm>(() -> <js>"User validation failed"</js>, 
<jv>user</jv>, <js>"name,age"</js>, <js>"Alice,25"</js>);
+ *
+ *    <jc>// Formatted message using Utils.fs() for convenient message 
suppliers with arguments</jc>
+ *    <jsm>assertBean</jsm>(<jsm>fs</jsm>(<js>"User {0} validation 
failed"</js>, <js>"Alice"</js>), <jv>user</jv>, <js>"name,age"</js>, 
<js>"Alice,25"</js>);
+ * </p>
+ *
+ * <h5 class='section'>Customizing the Default Converter:</h5>
+ * <p>The default bean converter can be customized on a per-thread basis:</p>
+ * <p class='bjava'>
+ *    <jc>// Set custom converter in @BeforeEach method</jc>
+ *    <ja>@BeforeEach</ja>
+ *    <jk>void</jk> <jsm>setUp</jsm>() {
+ *       <jk>var</jk> <jv>converter</jv> = 
BasicBeanConverter.<jsm>builder</jsm>()
+ *          .defaultSettings()
+ *          .addStringifier(LocalDate.<jk>class</jk>, <jp>date</jp> -> 
<jp>date</jp>.format(DateTimeFormatter.<jsf>ISO_LOCAL_DATE</jsf>))
+ *          .build();
+ *       BctAssertions.<jsm>setConverter</jsm>(<jv>converter</jv>);
+ *    }
+ *
+ *    <jc>// All assertions now use the custom converter</jc>
+ *    <jsm>assertBean</jsm>(<jv>user</jv>, <js>"birthDate"</js>, 
<js>"2023-12-01"</js>);
+ *
+ *    <jc>// Reset in @AfterEach method</jc>
+ *    <ja>@AfterEach</ja>
+ *    <jk>void</jk> <jsm>tearDown</jsm>() {
+ *       BctAssertions.<jsm>resetConverter</jsm>();
+ *    }
  * </p>
  *
- * @see org.apache.juneau.junit.BctAssertions
- * @see org.apache.juneau.junit.BeanConverter
- * @see org.apache.juneau.junit.BasicBeanConverter
+ * @see org.apache.juneau.junit.bct.BctAssertions
+ * @see org.apache.juneau.junit.bct.BeanConverter
+ * @see org.apache.juneau.junit.bct.BasicBeanConverter
+ * @see org.apache.juneau.common.utils.Utils#fs(String, Object...)
  */
 package org.apache.juneau.junit.bct;
diff --git a/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md 
b/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
index 6bef635e40..6a4ee71d69 100644
--- a/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
+++ b/juneau-docs/docs/topics/07.01.00.JuneauBctBasics.md
@@ -538,21 +538,37 @@ BCT provides several advanced configuration options for 
customizing assertion be
 
 ### Custom Bean Converters
 
-```java
-// Create converter with custom formatting
-var converter = BasicBeanConverter.builder()
-    .defaultSettings()
-    .addStringifier(LocalDate.class, date -> 
-        date.format(DateTimeFormatter.ISO_LOCAL_DATE))
-    .addStringifier(Money.class, money -> 
-        money.getAmount().toPlainString())
-    .build();
+The default bean converter can be customized on a per-thread basis using 
`setConverter()` and `resetConverter()`. This is particularly useful in test 
setup methods to configure a custom converter for all tests in a test class or 
method.
+
+```java
+// Set custom converter in @BeforeEach method
+@BeforeEach
+void setUp() {
+    var converter = BasicBeanConverter.builder()
+        .defaultSettings()
+        .addStringifier(LocalDate.class, date -> 
+            date.format(DateTimeFormatter.ISO_LOCAL_DATE))
+        .addStringifier(Money.class, money -> 
+            money.getAmount().toPlainString())
+        .build();
+    BctAssertions.setConverter(converter);
+}
 
-// Use in assertions
-assertBean(args().setBeanConverter(converter),
-          order, "date,total", "2023-12-01,99.99");
+// All assertions now use the custom converter
+@Test
+void testWithCustomConverter() {
+    assertBean(order, "date,total", "2023-12-01,99.99");
+}
+
+// Reset in @AfterEach method
+@AfterEach
+void tearDown() {
+    BctAssertions.resetConverter();
+}
 ```
 
+**Thread Safety:** The converter is stored per-thread, allowing parallel test 
execution without cross-thread interference. Each test method running in 
parallel will have its own converter instance.
+
 ### See Advanced Topics
 
 For detailed information on extending and customizing BCT, see:
diff --git a/juneau-docs/docs/topics/07.01.01.Stringifiers.md 
b/juneau-docs/docs/topics/07.01.01.Stringifiers.md
index 4c0fa4c017..ac587d3b58 100644
--- a/juneau-docs/docs/topics/07.01.01.Stringifiers.md
+++ b/juneau-docs/docs/topics/07.01.01.Stringifiers.md
@@ -110,8 +110,12 @@ var converter = BasicBeanConverter.builder()
     .build();
 
 // Usage in tests
-assertBean(args().setBeanConverter(converter),
-          order, "total,customer", "$99.99,{John Smith <joh***@example.com>}");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(order, "total,customer", "$99.99,{John Smith 
<joh***@example.com>}");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Recursive Stringifier
@@ -152,8 +156,12 @@ var converter = BasicBeanConverter.builder()
     .build();
 
 // Use in assertions
-assertBean(args().setBeanConverter(converter),
-          order, "date,total", "2023-12-01,99.99");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(order, "date,total", "2023-12-01,99.99");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ## Best Practices
diff --git a/juneau-docs/docs/topics/07.01.02.Listifiers.md 
b/juneau-docs/docs/topics/07.01.02.Listifiers.md
index dc24c89022..44b3de4c1b 100644
--- a/juneau-docs/docs/topics/07.01.02.Listifiers.md
+++ b/juneau-docs/docs/topics/07.01.02.Listifiers.md
@@ -147,25 +147,37 @@ private void collectDepthFirst(TreeNode node, 
List<Object> result) {
 ```java
 // Test paginated results
 PaginatedResult<User> page = userService.getUsers(pageNumber);
-assertList(args().setBeanConverter(converter),
-          page, "Alice", "Bob", "Charlie");
+BctAssertions.setConverter(converter);
+try {
+    assertList(page, "Alice", "Bob", "Charlie");
+} finally {
+    BctAssertions.resetConverter();
+}
 
 // Test database results
 ResultSet rs = statement.executeQuery("SELECT name FROM users");
-assertList(args().setBeanConverter(converter),
-          rs, 
-          predicate(row -> ((Map)row).get("name").equals("Alice")),
-          predicate(row -> ((Map)row).get("name").equals("Bob")));
+BctAssertions.setConverter(converter);
+try {
+    assertList(rs, 
+              predicate(row -> ((Map)row).get("name").equals("Alice")),
+              predicate(row -> ((Map)row).get("name").equals("Bob")));
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Combining with assertBean
 
 ```java
 // Test collection properties
-assertBean(args().setBeanConverter(converter),
-          paginatedResult, 
-          "items{#{name}},totalCount", 
-          "[{Alice},{Bob},{Charlie}],3");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(paginatedResult, 
+              "items{#{name}},totalCount", 
+              "[{Alice},{Bob},{Charlie}],3");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ## Important Notes
diff --git a/juneau-docs/docs/topics/07.01.03.Swappers.md 
b/juneau-docs/docs/topics/07.01.03.Swappers.md
index 9a570ec2fe..b18be8cb73 100644
--- a/juneau-docs/docs/topics/07.01.03.Swappers.md
+++ b/juneau-docs/docs/topics/07.01.03.Swappers.md
@@ -179,18 +179,20 @@ assertBean(futureOrder, "id,total", "456,99.99");
 ```java
 // Test Result wrapper with custom swapper
 Result<User> result = userService.createUser(userData);
-assertBean(args().setBeanConverter(converter),
-          result, "name,email", "Alice,[email protected]");
-
-// Test validation results
-ValidationResult<Order> validation = orderValidator.validate(order);
-assertBean(args().setBeanConverter(converter),
-          validation, "id,total", "123,99.99");
-
-// Test error case
-ValidationResult<Order> invalidValidation = 
orderValidator.validate(invalidOrder);
-assertList(args().setBeanConverter(converter),
-          invalidValidation, "Missing required field: customer", "Invalid 
total: -10");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(result, "name,email", "Alice,[email protected]");
+    
+    // Test validation results
+    ValidationResult<Order> validation = orderValidator.validate(order);
+    assertBean(validation, "id,total", "123,99.99");
+    
+    // Test error case
+    ValidationResult<Order> invalidValidation = 
orderValidator.validate(invalidOrder);
+    assertList(invalidValidation, "Missing required field: customer", "Invalid 
total: -10");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Testing Lazy Values
@@ -198,8 +200,12 @@ assertList(args().setBeanConverter(converter),
 ```java
 // Test lazy computation
 LazyValue<Report> lazyReport = new LazyValue<>(() -> generateReport());
-assertBean(args().setBeanConverter(converter),
-          lazyReport, "title,itemCount", "Monthly Report,150");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(lazyReport, "title,itemCount", "Monthly Report,150");
+} finally {
+    BctAssertions.resetConverter();
+}
 
 // Swapper ensures the lazy value is evaluated before testing
 ```
diff --git a/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md 
b/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
index bf5a65ec24..4245e4423c 100644
--- a/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
+++ b/juneau-docs/docs/topics/07.01.04.PropertyExtractors.md
@@ -117,8 +117,12 @@ PropertyExtractor aliasExtractor = new PropertyExtractor() 
{
 };
 
 // Usage
-assertBean(args().setBeanConverter(converter),
-          user, "fname,lname,email", "John,Doe,[email protected]");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(user, "fname,lname,email", "John,Doe,[email protected]");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Computed Property Extractor
@@ -148,9 +152,13 @@ PropertyExtractor computedExtractor = new 
PropertyExtractor() {
 };
 
 // Usage
-assertBean(args().setBeanConverter(converter),
-          user, "computed_fullName,computed_age,computed_initials", 
-          "John Doe,30,J.D.");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(user, "computed_fullName,computed_age,computed_initials", 
+              "John Doe,30,J.D.");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ## Complex Property Extraction Examples
@@ -203,8 +211,12 @@ PropertyExtractor privateFieldExtractor = new 
PropertyExtractor() {
 };
 
 // Usage
-assertBean(args().setBeanConverter(converter),
-          myBean, "_privateField1,_privateField2", "value1,value2");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(myBean, "_privateField1,_privateField2", "value1,value2");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### SQL ResultSet Extractor
@@ -265,9 +277,13 @@ PropertyExtractor configExtractor = new 
PropertyExtractor() {
 };
 
 // Usage
-assertBean(args().setBeanConverter(converter),
-          config, "database.host,database.port,app.name", 
-          "localhost,5432,MyApp");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(config, "database.host,database.port,app.name", 
+              "localhost,5432,MyApp");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ## Best Practices
@@ -432,28 +448,34 @@ PropertyExtractor fallbackExtractor = new 
PropertyExtractor() {
 ```java
 // Test database entity
 DatabaseEntity entity = loadEntity(123);
-assertBean(args().setBeanConverter(converter),
-          entity, "id,displayName,createdDate", "123,John Doe,2023-01-15");
-
-// Test configuration
-Configuration config = loadConfig();
-assertBean(args().setBeanConverter(converter),
-          config, "database.host,database.port,app.timeout", 
-          "localhost,5432,30000");
-
-// Test computed properties
-User user = loadUser(456);
-assertBean(args().setBeanConverter(converter),
-          user, "computed_fullName,computed_age", "Alice Smith,28");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(entity, "id,displayName,createdDate", "123,John 
Doe,2023-01-15");
+    
+    // Test configuration
+    Configuration config = loadConfig();
+    assertBean(config, "database.host,database.port,app.timeout", 
+              "localhost,5432,30000");
+    
+    // Test computed properties
+    User user = loadUser(456);
+    assertBean(user, "computed_fullName,computed_age", "Alice Smith,28");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Combining with Other Features
 
 ```java
 // Use property extractor with nested access
-assertBean(args().setBeanConverter(converter),
-          order, "customer{computed_fullName},items{0{name}}", 
-          "{John Doe},{{Laptop}}");
+BctAssertions.setConverter(converter);
+try {
+    assertBean(order, "customer{computed_fullName},items{0{name}}", 
+              "{John Doe},{{Laptop}}");
+} finally {
+    BctAssertions.resetConverter();
+}
 ```
 
 ## See Also
diff --git a/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md 
b/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
index ce21495894..d77ce1cb4f 100644
--- a/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
+++ b/juneau-docs/docs/topics/07.01.05.CustomErrorMessages.md
@@ -9,47 +9,51 @@ Custom error messages allow you to provide contextual 
information when BCT asser
 
 ## Overview
 
-BCT supports three types of custom error messages:
-- **Static messages** - Simple string messages
-- **Dynamic messages with placeholders** - Messages with variable substitution
-- **Supplier-based messages** - Lazy evaluation for expensive message 
generation
+BCT supports custom error messages through a `Supplier<String>` parameter in 
all assertion methods. This provides:
+- **Lazy evaluation** - Messages are only generated when assertions fail
+- **Format support** - Use `Utils.fs()` for convenient formatted messages with 
arguments
+- **Flexible composition** - Combine custom messages with default assertion 
messages
 
 ## Basic Usage
 
-### Static Messages
+### Simple Messages
 
 ```java
 // Simple static message
-assertBean(args().setMessage("User validation failed"), 
+assertBean(() -> "User validation failed", 
           user, "email", "[email protected]");
 
 // More descriptive context
-assertBean(args().setMessage("Expected user to be active but was inactive"),
+assertBean(() -> "Expected user to be active but was inactive",
           user, "isActive", "true");
 
 // Test context information
-assertBean(args().setMessage("Order status check for order #12345"),
+assertBean(() -> "Order status check for order #12345",
           order, "status", "PENDING");
 ```
 
-### Dynamic Messages with Placeholders
+### Formatted Messages with Utils.fs()
+
+The `Utils.fs()` method provides a convenient way to create message suppliers 
with format arguments:
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 // Single placeholder
 String testName = "validateUser";
-assertBean(args().setMessage("Test {0} failed", testName),
+assertBean(fs("Test {0} failed", testName),
           result, "status", "SUCCESS");
 
 // Multiple placeholders
 String userName = "Alice";
 int iteration = 5;
-assertBean(args().setMessage("User {0} validation failed on iteration {1}", 
userName, iteration),
+assertBean(fs("User {0} validation failed on iteration {1}", userName, 
iteration),
           user, "isValid", "true");
 
 // Contextual information
 String orderId = "ORD-123";
 String expectedStatus = "COMPLETED";
-assertBean(args().setMessage("Order {0} expected status {1}", orderId, 
expectedStatus),
+assertBean(fs("Order {0} expected status {1}", orderId, expectedStatus),
           order, "status", expectedStatus);
 ```
 
@@ -57,25 +61,25 @@ assertBean(args().setMessage("Order {0} expected status 
{1}", orderId, expectedS
 
 ```java
 // Lazy evaluation for expensive computation
-assertBean(args().setMessage(() -> "Test failed at " + Instant.now()),
+assertBean(() -> "Test failed at " + Instant.now(),
           user, "lastLogin", expectedTime);
 
 // Complex context information
-assertBean(args().setMessage(() -> {
+assertBean(() -> {
     return String.format("Test failed in %s on thread %s",
         Thread.currentThread().getName(),
         Thread.currentThread().getId());
-}),
+},
           result, "status", "SUCCESS");
 
 // Conditional message generation
-assertBean(args().setMessage(() -> {
+assertBean(() -> {
     if (isDebugMode()) {
         return "Debug: Full stack trace available";
     } else {
         return "Test failed - enable debug for details";
     }
-}),
+},
           user, "email", "[email protected]");
 ```
 
@@ -84,13 +88,15 @@ assertBean(args().setMessage(() -> {
 ### Testing in Loops
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @Test
 void testMultipleOrders() {
     List<Order> orders = getOrders();
     
     for (int i = 0; i < orders.size(); i++) {
         Order order = orders.get(i);
-        assertBean(args().setMessage("Order validation failed at index {0}", 
i),
+        assertBean(fs("Order validation failed at index {0}", i),
                   order, "status,total", "PENDING,99.99");
     }
 }
@@ -99,6 +105,8 @@ void testMultipleOrders() {
 ### Testing with Context Information
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @Test
 void testUsersByRole() {
     Map<String, User> usersByRole = getUsersByRole();
@@ -107,7 +115,7 @@ void testUsersByRole() {
         String role = entry.getKey();
         User user = entry.getValue();
         
-        assertBean(args().setMessage("User validation failed for role: {0}", 
role),
+        assertBean(fs("User validation failed for role: {0}", role),
                   user, "role,isActive", role + ",true");
     }
 }
@@ -120,8 +128,8 @@ void testUsersByRole() {
 void testOrderProcessing() {
     Order order = processOrder();
     
-    assertBean(args().setMessage(() -> 
-        String.format("Order processed at %s, validation failed", 
LocalDateTime.now())),
+    assertBean(() -> 
+        String.format("Order processed at %s, validation failed", 
LocalDateTime.now()),
         order, "status", "COMPLETED");
 }
 ```
@@ -129,12 +137,14 @@ void testOrderProcessing() {
 ### Testing with Environment Information
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @Test
 void testConfiguration() {
     Config config = loadConfig();
     String environment = System.getProperty("env", "unknown");
     
-    assertBean(args().setMessage("Config validation failed in environment: 
{0}", environment),
+    assertBean(fs("Config validation failed in environment: {0}", environment),
               config, "database.host,database.port", "localhost,5432");
 }
 ```
@@ -170,11 +180,11 @@ void testConfiguration() {
 ```java
 // Bad - expensive computation always executed
 String heavyComputation = performExpensiveOperation();
-assertBean(args().setMessage("Test failed with: " + heavyComputation),
+assertBean(() -> "Test failed with: " + heavyComputation,
           user, "status", "ACTIVE");
 
 // Good - computation only happens on failure
-assertBean(args().setMessage(() -> "Test failed with: " + 
performExpensiveOperation()),
+assertBean(() -> "Test failed with: " + performExpensiveOperation(),
           user, "status", "ACTIVE");
 ```
 
@@ -183,27 +193,40 @@ assertBean(args().setMessage(() -> "Test failed with: " + 
performExpensiveOperat
 ### Custom Messages with Custom Converters
 
 ```java
-var converter = BasicBeanConverter.builder()
-    .defaultSettings()
-    .addStringifier(LocalDate.class, date -> 
-        date.format(DateTimeFormatter.ISO_LOCAL_DATE))
-    .build();
-
-assertBean(args()
-    .setBeanConverter(converter)
-    .setMessage("Date validation failed for user {0}", userId),
+import static org.apache.juneau.common.utils.Utils.fs;
+
+// Set custom converter in @BeforeEach
+@BeforeEach
+void setUp() {
+    var converter = BasicBeanConverter.builder()
+        .defaultSettings()
+        .addStringifier(LocalDate.class, date -> 
+            date.format(DateTimeFormatter.ISO_LOCAL_DATE))
+        .build();
+    BctAssertions.setConverter(converter);
+}
+
+// Use custom message with the converter
+assertBean(fs("Date validation failed for user {0}", userId),
     user, "birthDate", "1990-01-15");
+
+@AfterEach
+void tearDown() {
+    BctAssertions.resetConverter();
+}
 ```
 
 ### Custom Messages in Parameterized Tests
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @ParameterizedTest
 @ValueSource(strings = {"[email protected]", "[email protected]", 
"[email protected]"})
 void testUserEmails(String email) {
     User user = findUserByEmail(email);
     
-    assertBean(args().setMessage("User validation failed for email: {0}", 
email),
+    assertBean(fs("User validation failed for email: {0}", email),
               user, "email,isVerified", email + ",true");
 }
 ```
@@ -211,13 +234,15 @@ void testUserEmails(String email) {
 ### Custom Messages with Dynamic Test Names
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @TestFactory
 Stream<DynamicTest> testOrders() {
     return orders.stream()
         .map(order -> DynamicTest.dynamicTest(
             "Test Order #" + order.getId(),
             () -> assertBean(
-                args().setMessage("Order {0} validation failed", 
order.getId()),
+                fs("Order {0} validation failed", order.getId()),
                 order, "status", "PENDING")));
 }
 ```
@@ -251,7 +276,7 @@ void testOrderProcessing() {
     processOrder(order);
     
     // Validate with detailed context
-    assertBean(args().setMessage(() -> 
+    assertBean(() -> 
         String.format(
             "Order validation failed:\n" +
             "  Order ID: %s\n" +
@@ -262,7 +287,7 @@ void testOrderProcessing() {
             order.getCustomer().getName(),
             Duration.between(order.getCreatedAt(), Instant.now()),
             Thread.currentThread().getName()
-        )),
+        ),
         order, "status,isPaid,isShipped", "COMPLETED,true,true");
 }
 ```
@@ -287,17 +312,17 @@ Actual: PENDING
 void testUserLifecycle() {
     // Creation phase
     User user = createUser();
-    assertBean(args().setMessage("User creation phase failed"),
+    assertBean(() -> "User creation phase failed",
               user, "status", "NEW");
     
     // Activation phase
     activateUser(user);
-    assertBean(args().setMessage("User activation phase failed"),
+    assertBean(() -> "User activation phase failed",
               user, "status", "ACTIVE");
     
     // Verification phase
     verifyUser(user);
-    assertBean(args().setMessage("User verification phase failed"),
+    assertBean(() -> "User verification phase failed",
               user, "status,isVerified", "ACTIVE,true");
 }
 ```
@@ -305,6 +330,8 @@ void testUserLifecycle() {
 ### Batch Processing
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @Test
 void testBatchProcessing() {
     List<Order> orders = loadOrders();
@@ -313,7 +340,7 @@ void testBatchProcessing() {
         Order order = orders.get(i);
         processOrder(order);
         
-        assertBean(args().setMessage(
+        assertBean(fs(
             "Batch processing failed at index {0} of {1}, Order ID: {2}",
             i, orders.size(), order.getId()),
             order, "status", "PROCESSED");
@@ -324,12 +351,14 @@ void testBatchProcessing() {
 ### Conditional Testing
 
 ```java
+import static org.apache.juneau.common.utils.Utils.fs;
+
 @Test
 void testUserPermissions() {
     User user = loadUser();
     String expectedRole = user.isAdmin() ? "ADMIN" : "USER";
     
-    assertBean(args().setMessage(
+    assertBean(fs(
         "Permission check failed for {0} user (expected role: {1})",
         user.isAdmin() ? "admin" : "regular",
         expectedRole),
@@ -351,7 +380,7 @@ void testOrderIntegration() {
     Result result = service.processOrder(order);
     
     // Validate with full context
-    assertBean(args().setMessage(() -> 
+    assertBean(() -> 
         String.format(
             "Integration test failed:\n" +
             "  Test ID: %s\n" +
@@ -362,7 +391,7 @@ void testOrderIntegration() {
             service.getClass().getSimpleName(),
             order.getId(),
             result.getResponseTime()
-        )),
+        ),
         result, "status,errorCode", "SUCCESS,null");
 }
 ```
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/AssertionArgs_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/AssertionArgs_Test.java
deleted file mode 100644
index 10d3479bbb..0000000000
--- 
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/AssertionArgs_Test.java
+++ /dev/null
@@ -1,526 +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.junit.bct;
-
-import static org.apache.juneau.common.utils.CollectionUtils.*;
-import static org.apache.juneau.junit.bct.BctAssertions.*;
-import static org.junit.jupiter.api.Assertions.*;
-
-import java.text.*;
-import java.time.*;
-import java.util.*;
-import java.util.function.*;
-
-import org.apache.juneau.*;
-import org.junit.jupiter.api.*;
-
-/**
- * Unit tests for {@link AssertionArgs}.
- *
- * <p>Tests the configuration and behavior of the assertion arguments class 
including
- * bean converter customization, error message composition, and fluent API 
functionality.</p>
- */
-class AssertionArgs_Test extends TestBase {
-
-       // Test objects for assertions
-       static class TestBean {
-               private String name;
-               private int age;
-               private boolean active;
-
-               public TestBean(String name, int age, boolean active) {
-                       this.name = name;
-                       this.age = age;
-                       this.active = active;
-               }
-
-               public String getName() { return name; }
-               public int getAge() { return age; }
-               public boolean isActive() { return active; }
-       }
-
-       static class CustomObject {
-               private String value;
-
-               public CustomObject(String value) {
-                       this.value = value;
-               }
-
-               public String getValue() { return value; }
-
-               @Override
-               public String toString() {
-                       return "CustomObject[" + value + "]";
-               }
-       }
-
-       @Test
-       void a01_defaultConstruction() {
-               var args = new AssertionArgs();
-
-               // Should have no custom converter
-               assertEmpty(args.getBeanConverter());
-
-               // Should have no custom message
-               assertNull(args.getMessage());
-       }
-
-       @Test
-       void a02_fluentAPIReturnsThis() {
-               var args = new AssertionArgs();
-               var mockConverter = createMockConverter();
-
-               // Fluent methods should return the same instance
-               assertSame(args, args.setBeanConverter(mockConverter));
-               assertSame(args, args.setMessage("test message"));
-               assertSame(args, args.setMessage(() -> "dynamic message"));
-       }
-
-       @Test
-       void b01_beanConverterConfiguration() {
-               var args = new AssertionArgs();
-               var mockConverter = createMockConverter();
-
-               // Initially empty
-               assertEmpty(args.getBeanConverter());
-
-               // Set converter
-               args.setBeanConverter(mockConverter);
-               assertTrue(args.getBeanConverter().isPresent());
-               assertSame(mockConverter, args.getBeanConverter().get());
-
-               // Set to null should clear
-               args.setBeanConverter(null);
-               assertEmpty(args.getBeanConverter());
-       }
-
-       @Test
-       void b02_customConverterInAssertion() {
-               // Create a mock custom converter for testing
-               var customConverter = createCustomConverter();
-
-               var args = args().setBeanConverter(customConverter);
-               var obj = new TestBeanWithCustomObject("test", new 
CustomObject("value"));
-
-               // Should use custom converter for stringification
-               assertBean(args, obj, "custom", "CUSTOM:value");
-       }
-
-       static class TestBeanWithCustomObject {
-               private String name;
-               private CustomObject custom;
-
-               public TestBeanWithCustomObject(String name, CustomObject 
custom) {
-                       this.name = name;
-                       this.custom = custom;
-               }
-
-               public String getName() { return name; }
-               public CustomObject getCustom() { return custom; }
-       }
-
-       @Test
-       void c01_messageSupplierConfiguration() {
-               var args = new AssertionArgs();
-
-               // Initially null
-               assertNull(args.getMessage());
-
-               // Set supplier
-               Supplier<String> supplier = () -> "test message";
-               args.setMessage(supplier);
-               assertNotNull(args.getMessage());
-               assertEquals("test message", args.getMessage().get());
-
-               // Set different supplier
-               args.setMessage(() -> "different message");
-               assertEquals("different message", args.getMessage().get());
-       }
-
-       @Test
-       void c02_parameterizedMessageConfiguration() {
-               var args = new AssertionArgs();
-
-               // Simple parameter substitution
-               args.setMessage("Hello {0}", "World");
-               assertEquals("Hello World", args.getMessage().get());
-
-               // Multiple parameters
-               args.setMessage("User {0} has {1} points", "John", 100);
-               assertEquals("User John has 100 points", 
args.getMessage().get());
-
-               // Number formatting
-               args.setMessage("Value: {0,number,#.##}", 123.456);
-               assertEquals("Value: 123.46", args.getMessage().get());
-       }
-
-       @Test
-       void c03_dynamicMessageSupplier() {
-               var counter = new int[1]; // Mutable counter for testing
-               var args = new AssertionArgs();
-
-               args.setMessage(() -> "Call #" + (++counter[0]));
-
-               // Each call should increment the counter
-               assertEquals("Call #1", args.getMessage().get());
-               assertEquals("Call #2", args.getMessage().get());
-               assertEquals("Call #3", args.getMessage().get());
-       }
-
-       @Test
-       void d01_messageCompositionWithoutCustomMessage() {
-               var args = new AssertionArgs();
-
-               // No custom message, should return assertion message as-is
-               var composedMessage = args.getMessage("Bean assertion failed");
-               assertEquals("Bean assertion failed", composedMessage.get());
-
-               // With parameters
-               var composedWithParams = args.getMessage("Element at index {0} 
did not match", 5);
-               assertEquals("Element at index 5 did not match", 
composedWithParams.get());
-       }
-
-       @Test
-       void d02_messageCompositionWithCustomMessage() {
-               var args = new AssertionArgs();
-               args.setMessage("User validation failed");
-
-               // Should compose: custom + assertion
-               var composedMessage = args.getMessage("Bean assertion failed");
-               assertEquals("User validation failed, Caused by: Bean assertion 
failed", composedMessage.get());
-
-               // With parameters in assertion message
-               var composedWithParams = args.getMessage("Element at index {0} 
did not match", 3);
-               assertEquals("User validation failed, Caused by: Element at 
index 3 did not match", composedWithParams.get());
-       }
-
-       @Test
-       void d03_messageCompositionWithParameterizedCustomMessage() {
-               var args = new AssertionArgs();
-               args.setMessage("Test {0} failed on iteration {1}", 
"UserValidation", 42);
-
-               var composedMessage = args.getMessage("Bean assertion failed");
-               assertEquals("Test UserValidation failed on iteration 42, 
Caused by: Bean assertion failed", composedMessage.get());
-       }
-
-       @Test
-       void d04_messageCompositionWithDynamicCustomMessage() {
-               var timestamp = Instant.now().toString();
-               var args = new AssertionArgs();
-               args.setMessage(() -> "Test failed at " + timestamp);
-
-               var composedMessage = args.getMessage("Bean assertion failed");
-               assertEquals("Test failed at " + timestamp + ", Caused by: Bean 
assertion failed", composedMessage.get());
-       }
-
-       @Test
-       void e01_fluentConfigurationChaining() {
-               var converter = createMockConverter();
-
-               // Chain multiple configurations
-               var args = new AssertionArgs()
-                       .setBeanConverter(converter)
-                       .setMessage("Integration test failed for module {0}", 
"AuthModule");
-
-               // Verify both configurations applied
-               assertTrue(args.getBeanConverter().isPresent());
-               assertSame(converter, args.getBeanConverter().get());
-               assertEquals("Integration test failed for module AuthModule", 
args.getMessage().get());
-       }
-
-       @Test
-       void e02_configurationOverwriting() {
-               var args = new AssertionArgs();
-               var converter1 = createMockConverter();
-               var converter2 = createMockConverter();
-
-               // Set initial values
-               args.setBeanConverter(converter1).setMessage("First message");
-
-               // Overwrite with new values
-               args.setBeanConverter(converter2).setMessage("Second message");
-
-               // Should have latest values
-               assertSame(converter2, args.getBeanConverter().get());
-               assertEquals("Second message", args.getMessage().get());
-       }
-
-       @Test
-       void f01_integrationWithAssertBean() {
-               var bean = new TestBean("John", 30, true);
-               var args = args().setMessage("User test failed");
-
-               // Should work with custom message
-               assertBean(args, bean, "name,age,active", "John,30,true");
-
-               // Test assertion failure message composition
-               var exception = assertThrows(AssertionError.class, () -> {
-                       assertBean(args, bean, "name", "Jane");
-               });
-
-               assertTrue(exception.getMessage().contains("User test failed"));
-               assertTrue(exception.getMessage().contains("Caused by:"));
-       }
-
-       @Test
-       void f02_integrationWithAssertBeans() {
-               var beans = l(
-                       new TestBean("Alice", 25, true),
-                       new TestBean("Bob", 35, false)
-               );
-               var args = args().setMessage("Batch validation failed");
-
-               // Should work with custom message
-               assertBeans(args, beans, "name,age", "Alice,25", "Bob,35");
-
-               // Test assertion failure message composition
-               var exception = assertThrows(AssertionError.class, () -> {
-                       assertBeans(args, beans, "name", "Charlie", "David");
-               });
-
-               assertTrue(exception.getMessage().contains("Batch validation 
failed"));
-               assertTrue(exception.getMessage().contains("Caused by:"));
-       }
-
-       @Test
-       void f03_integrationWithAssertList() {
-               var list = l("apple", "banana", "cherry");
-               var args = args().setMessage("List validation failed");
-
-               // Should work with custom message
-               assertList(args, list, "apple", "banana", "cherry");
-
-               // Test assertion failure message composition
-               var exception = assertThrows(AssertionError.class, () -> {
-                       assertList(args, list, "orange", "banana", "cherry");
-               });
-
-               assertTrue(exception.getMessage().contains("List validation 
failed"));
-               assertTrue(exception.getMessage().contains("Caused by:"));
-       }
-
-       @Test
-       void g01_edgeCaseNullValues() {
-               var args = new AssertionArgs();
-
-               // Null converter should work
-               args.setBeanConverter(null);
-               assertEmpty(args.getBeanConverter());
-
-               // Null message supplier should work
-               args.setMessage((Supplier<String>) null);
-               assertNull(args.getMessage());
-       }
-
-       @Test
-       void g02_edgeCaseEmptyMessages() {
-               var args = new AssertionArgs();
-
-               // Empty string message
-               args.setMessage("");
-               assertEquals("", args.getMessage().get());
-
-               // Empty supplier result
-               args.setMessage(() -> "");
-               assertEquals("", args.getMessage().get());
-
-               // Composition with empty custom message
-               var composedMessage = args.getMessage("Bean assertion failed");
-               assertEquals(", Caused by: Bean assertion failed", 
composedMessage.get());
-       }
-
-       @Test
-       void g03_edgeCaseComplexParameterFormatting() {
-               var args = new AssertionArgs();
-               var date = new Date();
-
-               // Date formatting
-               args.setMessage("Test executed on {0,date,short}", date);
-               var expectedDatePart = 
DateFormat.getDateInstance(DateFormat.SHORT).format(date);
-               assertTrue(args.getMessage().get().contains(expectedDatePart));
-
-               // Complex number formatting
-               args.setMessage("Processing {0,number,percent} complete", 0.85);
-               assertTrue(args.getMessage().get().contains("85%"));
-       }
-
-       @Test
-       void h01_threadSafetyDocumentationCompliance() {
-               // This test documents that AssertionArgs is NOT thread-safe
-               // Each thread should create its own instance
-
-               var sharedArgs = new AssertionArgs();
-               var results = Collections.synchronizedList(list());
-
-               // Simulate multiple threads modifying the same instance
-               var threads = new Thread[5];
-               for (var i = 0; i < threads.length; i++) {
-                       final int threadId = i;
-                       threads[i] = new Thread(() -> {
-                               sharedArgs.setMessage("Thread " + threadId + " 
message");
-                               // Small delay to increase chance of race 
condition
-                               try { Thread.sleep(1); } catch 
(InterruptedException e) {}
-                               results.add(sharedArgs.getMessage().get());
-                       });
-               }
-
-               // Start all threads
-               for (var thread : threads) {
-                       thread.start();
-               }
-
-               // Wait for completion
-               for (var thread : threads) {
-                       try { thread.join(); } catch (InterruptedException e) {}
-               }
-
-               // Due to race conditions, we may not get the expected messages
-               // This demonstrates why each test should create its own 
instance
-               assertSize(5, results);
-               // Note: We don't assert specific values due to race conditions
-       }
-
-       @Test
-       void h02_recommendedUsagePattern() {
-               // Demonstrate the recommended pattern: create new instance per 
test
-
-               // Test 1: User validation
-               var userArgs = args().setMessage("User validation test");
-               var user = new TestBean("Alice", 25, true);
-               assertBean(userArgs, user, "name,active", "Alice,true");
-
-               // Test 2: Product validation (separate instance)
-               var productArgs = args().setMessage("Product validation test");
-               var products = l("Laptop", "Phone", "Tablet");
-               assertList(productArgs, products, "Laptop", "Phone", "Tablet");
-
-               // Each test has its own configuration without interference
-               assertEquals("User validation test", 
userArgs.getMessage().get());
-               assertEquals("Product validation test", 
productArgs.getMessage().get());
-       }
-
-       // Helper method to create a mock converter for testing
-       private static BeanConverter createMockConverter() {
-               return new BeanConverter() {
-                       @Override
-                       public String stringify(Object o) {
-                               return String.valueOf(o);
-                       }
-
-                       @Override
-                       public List<Object> listify(Object o) {
-                               if (o instanceof List) return (List<Object>) o;
-                               return l(o);
-                       }
-
-                       @Override
-                       public int size(Object o) {
-                               if (o instanceof List o2) return o2.size();
-                               if (o instanceof String o3) return o3.length();
-                               return 1;
-                       }
-
-                       @Override
-                       public boolean canListify(Object o) {
-                               return true;
-                       }
-
-                       @Override
-                       public Object swap(Object o) {
-                               return o;
-                       }
-
-                       @Override
-                       public Object getProperty(Object object, String name) {
-                               // Simple mock implementation
-                               if ("name".equals(name) && object instanceof 
TestBean object2) {
-                                       return object2.getName();
-                               }
-                               if ("custom".equals(name) && object instanceof 
TestBeanWithCustomObject object3) {
-                                       return object3.getCustom();
-                               }
-                               return null;
-                       }
-
-                       @Override
-                       public <T> T getSetting(String key, T defaultValue) {
-                               return defaultValue;
-                       }
-
-                       @Override
-                       public String getNested(Object o, NestedTokenizer.Token 
token) {
-                               var propValue = getProperty(o, 
token.getValue());
-                               return stringify(propValue);
-                       }
-               };
-       }
-
-       // Helper method to create a custom converter for testing
-       private static BeanConverter createCustomConverter() {
-               return new BeanConverter() {
-                       @Override
-                       public String stringify(Object o) {
-                               if (o instanceof CustomObject o2) {
-                                       return "CUSTOM:" + o2.getValue();
-                               }
-                               return String.valueOf(o);
-                       }
-
-                       @Override
-                       public List<Object> listify(Object o) {
-                               if (o instanceof List) return (List<Object>) o;
-                               return l(o);
-                       }
-
-                       @Override
-                       public int size(Object o) {
-                               if (o instanceof List o2) return o2.size();
-                               if (o instanceof String o3) return o3.length();
-                               return 1;
-                       }
-
-                       @Override
-                       public boolean canListify(Object o) {
-                               return true;
-                       }
-
-                       @Override
-                       public Object swap(Object o) {
-                               return o;
-                       }
-
-                       @Override
-                       public Object getProperty(Object object, String name) {
-                               if ("custom".equals(name) && object instanceof 
TestBeanWithCustomObject object2) {
-                                       return object2.getCustom();
-                               }
-                               return null;
-                       }
-
-                       @Override
-                       public <T> T getSetting(String key, T defaultValue) {
-                               return defaultValue;
-                       }
-
-                       @Override
-                       public String getNested(Object o, NestedTokenizer.Token 
token) {
-                               var propValue = getProperty(o, 
token.getValue());
-                               return stringify(propValue);
-                       }
-               };
-       }
-}
\ No newline at end of file
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
index 9916611af1..b0b616474f 100644
--- 
a/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/junit/bct/BctAssertions_Test.java
@@ -38,22 +38,6 @@ import org.opentest4j.*;
  */
 class BctAssertions_Test extends TestBase {
 
-       // 
====================================================================================================
-       // AssertionArgs Tests
-       // 
====================================================================================================
-
-       @Nested
-       class A_assertionArgs extends TestBase {
-
-               @Test
-               void a01_args() {
-                       var args = args();
-                       assertNotNull(args);
-                       assertEmpty(args.getBeanConverter());
-                       assertNull(args.getMessage());
-               }
-       }
-
        // 
====================================================================================================
        // Bean Property Tests
        // 
====================================================================================================
@@ -70,11 +54,10 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void b02_withCustomArgs() {
+               void b02_withCustomMessage() {
                        var person = new TestPerson("Bob", 30);
-                       var args = args().setMessage("Custom message");
 
-                       assertDoesNotThrow(() -> assertBean(args, person, 
"name", "Bob"));
+                       assertDoesNotThrow(() -> assertBean(() -> "Custom 
message", person, "name", "Bob"));
                }
 
                @Test
@@ -95,9 +78,8 @@ class BctAssertions_Test extends TestBase {
                @Test
                void b05_customMessage() {
                        var person = new TestPerson("Charlie", 35);
-                       var args = args().setMessage("Custom error message");
 
-                       var e = assertThrows(AssertionFailedError.class, () -> 
assertBean(args, person, "name", "Wrong"));
+                       var e = assertThrows(AssertionFailedError.class, () -> 
assertBean(() -> "Custom error message", person, "name", "Wrong"));
                        assertContains("Custom error message", e.getMessage());
                }
        }
@@ -117,11 +99,10 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void c02_withCustomArgs() {
+               void c02_withCustomMessage() {
                        var people = l(new TestPerson("Charlie", 35));
-                       var args = args().setMessage("Custom beans message");
 
-                       assertDoesNotThrow(() -> assertBeans(args, people, 
"name", "Charlie"));
+                       assertDoesNotThrow(() -> assertBeans(() -> "Custom 
beans message", people, "name", "Charlie"));
                }
 
                @Test
@@ -168,12 +149,11 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void d02_withCustomArgs() {
+               void d02_withCustomMessage() {
                        var person = new TestPerson("Henry", 45);
-                       var args = args().setMessage("Custom mapped message");
                        BiFunction<TestPerson,String,Object> mapper = (p, prop) 
-> p.getName();
 
-                       assertDoesNotThrow(() -> assertMapped(args, person, 
mapper, "name", "Henry"));
+                       assertDoesNotThrow(() -> assertMapped(() -> "Custom 
mapped message", person, mapper, "name", "Henry"));
                }
 
                @Test
@@ -200,10 +180,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void e02_withCustomArgs() {
-                       var args = args().setMessage("Custom contains message");
-
-                       assertDoesNotThrow(() -> assertContains(args, "Test", 
"Test String"));
+               void e02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertContains(() -> "Custom 
contains message", "Test", "Test String"));
                }
 
                @Test
@@ -232,10 +210,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void f02_withCustomArgs() {
-                       var args = args().setMessage("Custom contains all 
message");
-
-                       assertDoesNotThrow(() -> assertContainsAll(args, 
(Object)"Testing", "Test"));
+               void f02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertContainsAll(() -> 
"Custom contains all message", (Object)"Testing", "Test"));
                }
 
                @Test
@@ -302,10 +278,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void g02_withCustomArgs() {
-                       var args = args().setMessage("Custom empty message");
-
-                       assertDoesNotThrow(() -> assertEmpty(args, l()));
+               void g02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertEmpty(() -> "Custom 
empty message", l()));
                }
 
                @Test
@@ -334,10 +308,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void h02_withCustomArgs() {
-                       var args = args().setMessage("Custom list message");
-
-                       assertDoesNotThrow(() -> assertList(args, l(1, 2), 1, 
2));
+               void h02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertList(() -> "Custom list 
message", l(1, 2), 1, 2));
                }
 
                @Test
@@ -354,18 +326,18 @@ class BctAssertions_Test extends TestBase {
 
                @Test
                void h05_nullValue() {
-                       var e = assertThrows(IllegalArgumentException.class, () 
-> assertList(null, "test"));
+                       Object nullList = null;
+                       var e = assertThrows(IllegalArgumentException.class, () 
-> assertList(nullList, "test"));
                        assertContains("cannot be null", e.getMessage());
                }
 
                @Test
                void h06_predicateValidation() {
                        // Test lines 765-766: Predicate-based element 
validation
-                       var args = args().setMessage("Custom predicate 
message");
                        var numbers = l(1, 2, 3, 4, 5);
 
                        // Test successful predicate validation
-                       assertDoesNotThrow(() -> assertList(args, numbers, 
(Predicate<Integer>)x -> x == 1,   // First element should equal 1
+                       assertDoesNotThrow(() -> assertList(() -> "Custom 
predicate message", numbers, (Predicate<Integer>)x -> x == 1,   // First 
element should equal 1
                                (Predicate<Integer>)x -> x > 1,    // Second 
element should be > 1
                                "3",                                // Third 
element as string
                                (Predicate<Integer>)x -> x % 2 == 0, // Fourth 
element should be even
@@ -374,7 +346,7 @@ class BctAssertions_Test extends TestBase {
 
                        // Test failed predicate validation - use single 
element list to avoid length mismatch
                        var singleNumber = l(1);
-                       var e = assertThrows(AssertionFailedError.class, () -> 
assertList(args, singleNumber, (Predicate<Integer>)x -> x == 99)); // Should 
fail
+                       var e = assertThrows(AssertionFailedError.class, () -> 
assertList(() -> "Custom predicate message", singleNumber, 
(Predicate<Integer>)x -> x == 99)); // Should fail
                        assertContains("Element at index 0 did not pass 
predicate", e.getMessage());
                        assertContains("actual: <1>", e.getMessage());
                }
@@ -428,9 +400,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void h02_withCustomArgs() {
-                       var args = args().setMessage("Custom map message");
-                       assertDoesNotThrow(() -> assertMap(args, m("key", 
"value"), "key=value"));
+               void h02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertMap(() -> "Custom map 
message", m("key", "value"), "key=value"));
                }
 
                @Test
@@ -447,23 +418,23 @@ class BctAssertions_Test extends TestBase {
 
                @Test
                void h05_nullValue() {
-                       var e = assertThrows(AssertionFailedError.class, () -> 
assertMap(null, "test"));
-                       assertContains("Value was null", e.getMessage());
+                       Map<?,?> nullMap = null;
+                       var e = assertThrows(IllegalArgumentException.class, () 
-> assertMap(nullMap, "test"));
+                       assertContains("cannot be null", e.getMessage());
                }
 
                @Test
                void h06_predicateValidation() {
                        // Test predicate-based map entry validation
-                       var args = args().setMessage("Custom predicate 
message");
                        var map = m("count", 42, "enabled", true);
 
                        // Test successful predicate validation
-                       assertDoesNotThrow(() -> assertMap(args, map, 
(Predicate<Map.Entry<String,Object>>)entry -> entry.getKey().equals("count") && 
entry.getValue().equals(42),
+                       assertDoesNotThrow(() -> assertMap(() -> "Custom 
predicate message", map, (Predicate<Map.Entry<String,Object>>)entry -> 
entry.getKey().equals("count") && entry.getValue().equals(42),
                                (Predicate<Map.Entry<String,Object>>)entry -> 
entry.getKey().equals("enabled") && entry.getValue().equals(true)));
 
                        // Test failed predicate validation
                        var singleEntryMap = m("count", 1);
-                       var e = assertThrows(AssertionFailedError.class, () -> 
assertMap(args, singleEntryMap, (Predicate<Map.Entry<String,Object>>)entry -> 
entry.getValue().equals(99))); // Should fail
+                       var e = assertThrows(AssertionFailedError.class, () -> 
assertMap(() -> "Custom predicate message", singleEntryMap, 
(Predicate<Map.Entry<String,Object>>)entry -> entry.getValue().equals(99))); // 
Should fail
                        assertContains("Element at index 0 did not pass 
predicate", e.getMessage());
                        assertContains("actual: <count=1>", e.getMessage());
                }
@@ -566,10 +537,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void i02_withCustomArgs() {
-                       var args = args().setMessage("Custom not empty 
message");
-
-                       assertDoesNotThrow(() -> assertNotEmpty(args, 
l("content")));
+               void i02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertNotEmpty(() -> "Custom 
not empty message", l("content")));
                }
 
                @Test
@@ -599,10 +568,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void j02_withCustomArgs() {
-                       var args = args().setMessage("Custom size message");
-
-                       assertDoesNotThrow(() -> assertSize(args, 2, l("a", 
"b")));
+               void j02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertSize(() -> "Custom size 
message", 2, l("a", "b")));
                }
 
                @Test
@@ -631,10 +598,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void k02_withCustomArgs() {
-                       var args = args().setMessage("Custom string message");
-
-                       assertDoesNotThrow(() -> assertString(args, "test", 
"test"));
+               void k02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertString(() -> "Custom 
string message", "test", "test"));
                }
 
                @Test
@@ -665,10 +630,8 @@ class BctAssertions_Test extends TestBase {
                }
 
                @Test
-               void l02_withCustomArgs() {
-                       var args = args().setMessage("Custom glob message");
-
-                       assertDoesNotThrow(() -> assertMatchesGlob(args, 
"test*", "testing"));
+               void l02_withCustomMessage() {
+                       assertDoesNotThrow(() -> assertMatchesGlob(() -> 
"Custom glob message", "test*", "testing"));
                }
 
                @Test


Reply via email to