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 5362629e75 Bean streaming APIs
5362629e75 is described below

commit 5362629e756f490a57a66b8ce256763ef5945d39
Author: James Bognar <[email protected]>
AuthorDate: Mon Apr 6 08:44:10 2026 -0400

    Bean streaming APIs
---
 RELEASE-NOTES.txt                                  |  51 ++
 docs/src/javadoc/overview.html                     |   1 +
 .../juneau/commons/function/BeanChannel.java       | 113 +++++
 .../juneau/commons/function/BeanConsumer.java      | 129 +++++
 .../juneau/commons/function/BeanFactory.java       |  91 ++++
 .../juneau/commons/function/BeanSupplier.java      | 119 +++++
 .../juneau/commons/function/ListBeanChannel.java   |  81 ++++
 .../juneau/commons/function/package-info.java      | 477 ++++++++++++++++++-
 .../main/java/org/apache/juneau/BeanContext.java   |  58 ++-
 .../src/main/java/org/apache/juneau/BeanMeta.java  |  34 +-
 .../java/org/apache/juneau/annotation/Bean.java    |  35 ++
 .../apache/juneau/annotation/BeanAnnotation.java   |  24 +
 .../java/org/apache/juneau/annotation/Beanp.java   |  76 +++
 .../apache/juneau/annotation/BeanpAnnotation.java  |  43 ++
 .../org/apache/juneau/parser/ParserSession.java    |  82 ++++
 .../juneau/serializer/SerializerSession.java       |  51 +-
 .../java/org/apache/juneau/BeanStreaming_Test.java | 294 ++++++++++++
 .../a/rttests/RoundTripBeanChannel_Test.java       | 197 ++++++++
 .../juneau/commons/function/BeanChannel_Test.java  | 176 +++++++
 todo/TODO.md                                       |   3 +-
 todo/large-dataset-streaming.md                    | 523 +++++++++++++++++++++
 21 files changed, 2649 insertions(+), 9 deletions(-)

diff --git a/RELEASE-NOTES.txt b/RELEASE-NOTES.txt
index fbf28c6e4e..22eddd3854 100644
--- a/RELEASE-NOTES.txt
+++ b/RELEASE-NOTES.txt
@@ -261,6 +261,57 @@ Release Notes - Juneau - Version 9.2.1 - YYYY-MM-DD
         - Implements Converter and delegates to the default BeanContext 
session for type conversion.
         - Provides access to the full Juneau framework conversion logic 
including bean mapping.
 
+** New Features - Large-Dataset Streaming APIs (BeanSupplier / BeanConsumer / 
BeanChannel)
+
+    * New streaming interfaces for serializing and parsing large datasets 
without loading all elements
+      into memory, enabling direct database integration (JDBC inserts/cursors, 
batch commits, rollbacks).
+
+    * BeanSupplier<T> (juneau-commons) - serializer-side lifecycle interface:
+        - Extends Iterable<T>; serializer calls begin(), iterates, calls 
onError(Exception) on error,
+          and always calls complete() in a finally block for resource cleanup 
(cursors, connections).
+        - Default onError() rethrows; override to add rollback or logging 
logic.
+
+    * BeanConsumer<T> (juneau-commons) - parser-side lifecycle interface:
+        - Extends ThrowingConsumer<T>; parser calls begin(), acceptThrows(T) 
per element,
+          onError(Exception) on element failure, and always calls complete() 
for resource cleanup.
+        - Default onError() rethrows (stop-on-error); override to absorb the 
exception and continue
+          parsing remaining elements (skip-and-continue / fault-tolerant 
ingestion).
+
+    * BeanChannel<T> (juneau-commons) - round-trip interface that extends both 
BeanSupplier and
+      BeanConsumer, allowing the same property to be used for both 
serialization and parsing.
+
+    * ListBeanChannel<T> (juneau-commons) - built-in in-memory implementation 
of BeanChannel backed
+      by an ArrayList; useful for testing and simple scenarios without a DI 
framework.
+
+    * ParserSession.parseToBeanConsumer(Object, BeanConsumer<T>, Class<T>) - 
new top-level API for
+      streaming parsing; drives the full consumer lifecycle. The default 
implementation parses to a
+      List first; format-specific parsers may override doParseToBeanConsumer() 
for true streaming.
+
+    * Supplier<T> unwrapping in SerializerSession - serializers now 
recursively unwrap nested
+      Supplier chains (up to depth 10) to their underlying value before 
serialization. BeanSupplier
+      instances are NOT unwrapped (they are treated as Iterable sequences).
+
+    * Direction validation - serializers throw SerializeException if a 
BeanConsumer (not BeanChannel)
+      is passed as a source; parsers throw ParseException if a BeanSupplier 
(not BeanChannel) is the
+      parse target. Error messages guide users toward the correct interface.
+
+    * @Bean(factory=X.class) - new annotation attribute specifying a 
BeanFactory class for
+      class-level factory-based instantiation; integrates with BeanStore (e.g. 
SpringBeanStore) for
+      DI framework support.
+
+    * @Beanp(factory=X.class) - new annotation attribute specifying a 
BeanFactory for a specific
+      bean property's value; enables property-level DI-managed streaming 
implementations.
+
+    * @Beanp(elementType=Y.class) - new annotation attribute declaring the 
element type for generic
+      streaming properties (Stream<Y>, BeanSupplier<Y>, BeanConsumer<Y>, 
BeanChannel<Y>); overcomes
+      Java type erasure. Also supports narrowing to concrete implementation 
types.
+
+    * BeanFactory<T> (juneau-commons) - universal @FunctionalInterface for 
creating instances of any
+      type T; BeanFactory.Void is a sentinel class for annotation defaults.
+
+    * BeanContext.Builder.beanStore(BeanStore) - new builder method for 
injecting a BeanStore (e.g.
+      SpringBeanStore) for factory resolution during bean creation.
+
 Release Notes - Juneau - Version 9.2.0 - 2025-12-30
 
 ** Changes
diff --git a/docs/src/javadoc/overview.html b/docs/src/javadoc/overview.html
index 2fa7179f41..c1006d7e38 100644
--- a/docs/src/javadoc/overview.html
+++ b/docs/src/javadoc/overview.html
@@ -26,6 +26,7 @@
                <li><strong>Configuration Management</strong> - Sophisticated 
configuration file API with variable resolution</li>
                <li><strong>Fluent Assertions</strong> - Powerful testing 
framework with fluent-style assertions</li>
                <li><strong>Type Conversion</strong> - Lightweight, 
BeanContext-free converter framework with caching and broad type support</li>
+               <li><strong>Large-Dataset Streaming</strong> - 
BeanSupplier/BeanConsumer/BeanChannel APIs for serializing and parsing large 
datasets without loading all elements into memory; supports direct database 
integration via lifecycle methods (begin/acceptThrows/onError/complete)</li>
                <li><strong>Zero Dependencies</strong> - Core marshalling 
requires no external dependencies</li>
        </ul>
 
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanChannel.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanChannel.java
new file mode 100644
index 0000000000..20583b56a7
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanChannel.java
@@ -0,0 +1,113 @@
+/*
+ * 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.commons.function;
+
+/**
+ * A lifecycle-aware round-trip interface that combines both {@link 
BeanSupplier} (for serialization)
+ * and {@link BeanConsumer} (for parsing) on the same property or class.
+ *
+ * <p>
+ * Use this interface when the same object needs to:
+ * <ul>
+ *     <li>Provide beans to a serializer (via {@link #iterator()})
+ *     <li>Receive beans from a parser (via {@link #acceptThrows(Object)})
+ * </ul>
+ *
+ * <p>
+ * The lifecycle methods ({@link #begin()}, {@link #complete()}, {@link 
#onError(Exception)}) are
+ * shared across both directions. The implementation implicitly knows its 
direction from which data
+ * method is called first after {@link #begin()}:
+ * <ul>
+ *     <li>{@link #iterator()} called first → read mode (serialization)
+ *     <li>{@link #acceptThrows(Object)} called first → write mode (parsing)
+ * </ul>
+ *
+ * <p>
+ * The framework drives the lifecycle:
+ * <ol>
+ *     <li>Calls {@link #begin()} before the first data operation
+ *     <li>Reads via {@link #iterator()} (serializer) or writes via {@link 
#acceptThrows(Object)} (parser)
+ *     <li>If an exception occurs, calls {@link #onError(Exception)}
+ *     <li>Always calls {@link #complete()} at the end (like {@code finally})
+ * </ol>
+ *
+ * <p>
+ * Since {@code BeanChannel} extends {@link BeanConsumer}, a {@code 
BeanChannel} instance passes
+ * the parser's {@code instanceof BeanConsumer} check. Since it extends {@link 
BeanSupplier}, it
+ * also passes the serializer's {@code instanceof BeanSupplier} check — both 
directions work.
+ *
+ * <h5 class='section'>Example (DB-backed round-trip channel):</h5>
+ * <p class='bjava'>
+ *     <ja>@Bean</ja>(factory=ItemChannelFactory.<jk>class</jk>)
+ *     <jk>public class</jk> ItemChannel <jk>implements</jk> 
BeanChannel&lt;Item&gt; {
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private boolean</jk> <jv>writeMode</jv>;
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> begin() <jk>throws</jk> 
Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jv>conn</jv>.setAutoCommit(<jk>false</jk>);
+ *             }
+ *
+ *             <jc>// Serializer calls iterator() — read mode</jc>
+ *             <ja>@Override</ja> <jk>public</jk> Iterator&lt;Item&gt; 
iterator() {
+ *                     <jk>return new</jk> ResultSetIterator&lt;&gt;(...);
+ *             }
+ *
+ *             <jc>// Parser calls acceptThrows() — write mode</jc>
+ *             <ja>@Override</ja> <jk>public void</jk> acceptThrows(Item 
<jv>item</jv>) <jk>throws</jk> Exception {
+ *                     <jv>writeMode</jv> = <jk>true</jk>;
+ *                     <jc>// insert item...</jc>
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> onError(Exception 
<jv>e</jv>) <jk>throws</jk> Exception {
+ *                     <jk>if</jk> (<jv>writeMode</jv>) 
<jv>conn</jv>.rollback();
+ *                     <jk>throw</jk> <jv>e</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> complete() 
<jk>throws</jk> Exception {
+ *                     <jk>if</jk> (<jv>writeMode</jv>) <jv>conn</jv>.commit();
+ *                     <jv>conn</jv>.close();
+ *             }
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link BeanConsumer} - Parse-only lifecycle interface
+ *     <li class='jc'>{@link BeanSupplier} - Serialize-only lifecycle interface
+ *     <li class='jc'>{@link BeanFactory} - Universal factory for DI framework 
integration
+ *     <li class='jc'>{@link ListBeanChannel} - Simple in-memory implementation
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Marshalling";>Marshalling</a>
+ * </ul>
+ *
+ * @param <T> The type of bean read and written by this channel.
+ */
+@SuppressWarnings({
+       "java:S112" // throws Exception intentional - lifecycle methods may 
throw any checked exception
+})
+public interface BeanChannel<T> extends BeanSupplier<T>, BeanConsumer<T> {
+
+       @Override
+       default void begin() throws Exception {}
+
+       @Override
+       default void complete() throws Exception {}
+
+       @Override
+       default void onError(Exception e) throws Exception {
+               throw e;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanConsumer.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanConsumer.java
new file mode 100644
index 0000000000..4b44b17ca4
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanConsumer.java
@@ -0,0 +1,129 @@
+/*
+ * 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.commons.function;
+
+/**
+ * A lifecycle-aware consumer for use as a parser target, receiving 
deserialized beans one at a time.
+ *
+ * <p>
+ * Extends {@link ThrowingConsumer} so that {@link #acceptThrows(Object)} 
supports checked exceptions
+ * directly — no wrapping in {@link RuntimeException} required for operations 
such as JDBC inserts.
+ *
+ * <p>
+ * The parser drives the full lifecycle:
+ * <ol>
+ *     <li>Calls {@link #begin()} before the first element
+ *     <li>Calls {@link #acceptThrows(Object)} for each parsed element
+ *     <li>If {@link #acceptThrows(Object)} throws, calls {@link 
#onError(Exception)}:
+ *         <ul>
+ *             <li>If {@code onError()} absorbs the exception (doesn't 
rethrow), parsing continues to the
+ *                 next element (skip-and-continue / fault-tolerant ingestion)
+ *             <li>If {@code onError()} rethrows, parsing stops and the 
exception propagates
+ *         </ul>
+ *     <li>Always calls {@link #complete()} after all elements (like {@code 
finally}) — whether parsing
+ *         succeeded or {@code onError()} rethrew. Use this for resource 
cleanup (close connections, etc.)
+ * </ol>
+ *
+ * <p>
+ * For round-trip support (same property used for both serialization and 
parsing), use
+ * {@link BeanChannel} instead, which extends both {@link BeanConsumer} and 
{@link BeanSupplier}.
+ *
+ * <p>
+ * If a {@link BeanConsumer} is encountered during serialization (not 
parsing), the serializer will
+ * throw an {@link IllegalArgumentException} with a message recommending 
{@link BeanSupplier} or
+ * {@link BeanChannel}.
+ *
+ * <h5 class='section'>Example (DB-backed consumer with batch commits):</h5>
+ * <p class='bjava'>
+ *     <ja>@Bean</ja>(factory=ItemConsumerFactory.<jk>class</jk>)
+ *     <jk>public class</jk> ItemConsumer <jk>implements</jk> 
BeanConsumer&lt;Item&gt; {
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private</jk> PreparedStatement <jv>stmt</jv>;
+ *             <jk>private int</jk> <jv>count</jv>;
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> begin() <jk>throws</jk> 
Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jv>conn</jv>.setAutoCommit(<jk>false</jk>);
+ *                     <jv>stmt</jv> = 
<jv>conn</jv>.prepareStatement(<js>"INSERT INTO items (name) VALUES (?)"</js>);
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> acceptThrows(Item 
<jv>item</jv>) <jk>throws</jk> Exception {
+ *                     <jv>stmt</jv>.setString(1, <jv>item</jv>.getName());
+ *                     <jv>stmt</jv>.executeUpdate();
+ *                     <jk>if</jk> (++<jv>count</jv> % 500 == 0) 
<jv>conn</jv>.commit(); <jc>// batch commit</jc>
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> onError(Exception 
<jv>e</jv>) <jk>throws</jk> Exception {
+ *                     <jv>conn</jv>.rollback(); <jk>throw</jk> <jv>e</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> complete() 
<jk>throws</jk> Exception {
+ *                     <jv>conn</jv>.commit(); <jv>stmt</jv>.close(); 
<jv>conn</jv>.close();
+ *             }
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link BeanSupplier} - Serialize-only lifecycle interface
+ *     <li class='jc'>{@link BeanChannel} - Round-trip lifecycle interface 
(extends both)
+ *     <li class='jc'>{@link BeanFactory} - Universal factory for DI framework 
integration
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Marshalling";>Marshalling</a>
+ * </ul>
+ *
+ * @param <T> The type of bean received by this consumer.
+ */
+@SuppressWarnings({
+       "java:S112" // throws Exception intentional - lifecycle methods may 
throw any checked exception
+})
+public interface BeanConsumer<T> extends ThrowingConsumer<T> {
+
+       /**
+        * Called before the first element is accepted.
+        *
+        * <p>
+        * Use this to open database connections, prepare statements, or 
perform other setup.
+        *
+        * @throws Exception If setup fails.
+        */
+       default void begin() throws Exception {}
+
+       /**
+        * Always called after all elements have been processed (like {@code 
finally}).
+        *
+        * <p>
+        * Use this to close database connections, statements, or release other 
resources.
+        * This method is called whether parsing succeeded or {@link 
#onError(Exception)} rethrew.
+        *
+        * @throws Exception If cleanup fails.
+        */
+       default void complete() throws Exception {}
+
+       /**
+        * Called when {@link #acceptThrows(Object)} throws an exception.
+        *
+        * <p>
+        * The default implementation rethrows the exception, stopping parsing 
immediately.
+        * Override to absorb the exception for fault-tolerant ingestion (e.g., 
log and skip bad records).
+        * Note that {@link #complete()} is always called afterward regardless.
+        *
+        * @param e The exception thrown by {@link #acceptThrows(Object)}.
+        * @throws Exception If the error cannot be recovered from. Rethrow 
{@code e} to stop parsing.
+        */
+       default void onError(Exception e) throws Exception {
+               throw e;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanFactory.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanFactory.java
new file mode 100644
index 0000000000..03e8c3279b
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanFactory.java
@@ -0,0 +1,91 @@
+/*
+ * 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.commons.function;
+
+/**
+ * A universal factory interface used with 
<ja>@Bean</ja><c>(factory=X.class)</c> for any bean type the
+ * framework needs to instantiate.
+ *
+ * <p>
+ * This interface provides a single DI hook for all framework-managed classes 
including
+ * {@link BeanConsumer}, {@link BeanSupplier}, {@link BeanChannel}, {@code 
ObjectSwap} subclasses,
+ * and any ordinary bean class parsed from JSON/XML/etc.
+ *
+ * <p>
+ * When a class annotated with <ja>@Bean</ja><c>(factory=MyFactory.class)</c> 
needs to be instantiated,
+ * the framework resolves the factory as follows:
+ * <ol>
+ *     <li>Look up the factory class in the {@code BeanStore} (e.g. Spring 
{@code ApplicationContext})
+ *     <li>If not found in the store, attempt to instantiate the factory 
directly via no-arg constructor
+ *         or {@code getInstance()} static method
+ *     <li>If both fail, throw {@link IllegalArgumentException}
+ * </ol>
+ *
+ * <h5 class='section'>Example (Spring integration):</h5>
+ * <p class='bjava'>
+ *     <jc>// Spring singleton factory that creates per-request ItemChannel 
instances</jc>
+ *     <ja>@Component</ja>
+ *     <jk>public class</jk> ItemChannelFactory <jk>implements</jk> 
BeanFactory&lt;ItemChannel&gt; {
+ *             <ja>@Autowired</ja> DataSource <jv>ds</jv>;
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> ItemChannel create() {
+ *                     <jk>return new</jk> ItemChannel(<jv>ds</jv>);
+ *             }
+ *     }
+ *
+ *     <jc>// The target class declares which factory creates it</jc>
+ *     <ja>@Bean</ja>(factory=ItemChannelFactory.<jk>class</jk>)
+ *     <jk>public class</jk> ItemChannel <jk>implements</jk> 
BeanChannel&lt;Item&gt; { ... }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link BeanConsumer} - Parse-only lifecycle interface
+ *     <li class='jc'>{@link BeanSupplier} - Serialize-only lifecycle interface
+ *     <li class='jc'>{@link BeanChannel} - Round-trip lifecycle interface
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Marshalling";>Marshalling</a>
+ * </ul>
+ *
+ * @param <T> The type of bean this factory creates.
+ */
+@FunctionalInterface
+public interface BeanFactory<T> {
+
+       /**
+        * Creates a new instance of the bean.
+        *
+        * @return A new bean instance.
+        * @throws Exception If the bean cannot be created.
+        */
+       T create() throws Exception;
+
+       /**
+        * Sentinel class used as the default value for {@code 
@Bean(factory=...)} and
+        * {@code @Beanp(factory=...)} when no factory is specified.
+        */
+       @SuppressWarnings({
+               "rawtypes" // Raw type required for use as annotation sentinel
+       })
+       final class Void implements BeanFactory {
+               private Void() {}
+
+               @Override
+               public Object create() {
+                       throw new 
UnsupportedOperationException("BeanFactory.Void is a sentinel and cannot be 
instantiated.");
+               }
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanSupplier.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanSupplier.java
new file mode 100644
index 0000000000..4526dd9813
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/BeanSupplier.java
@@ -0,0 +1,119 @@
+/*
+ * 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.commons.function;
+
+/**
+ * A lifecycle-aware producer for use as a serializer source, providing beans 
lazily one at a time.
+ *
+ * <p>
+ * Extends {@link Iterable} so that {@code ClassMeta}'s existing {@code 
ITERABLE} category picks it
+ * up automatically — no new type category is needed in the framework.
+ *
+ * <p>
+ * The serializer drives the full lifecycle:
+ * <ol>
+ *     <li>Calls {@link #begin()} before iteration starts
+ *     <li>Iterates elements via {@link #iterator()}, serializing each
+ *     <li>If an exception occurs during iteration, calls {@link 
#onError(Exception)}
+ *     <li>Always calls {@link #complete()} after iteration (like {@code 
finally}) — whether
+ *         serialization succeeded or failed. Use this for resource cleanup 
(close cursors, connections, etc.)
+ * </ol>
+ *
+ * <p>
+ * For round-trip support (same property used for both serialization and 
parsing), use
+ * {@link BeanChannel} instead, which extends both {@link BeanSupplier} and 
{@link BeanConsumer}.
+ *
+ * <p>
+ * If a {@link BeanSupplier} is encountered during parsing (not 
serialization), the parser will
+ * throw an {@link IllegalArgumentException} with a message recommending 
{@link BeanConsumer} or
+ * {@link BeanChannel}.
+ *
+ * <h5 class='section'>Example (DB-backed supplier via JDBC cursor):</h5>
+ * <p class='bjava'>
+ *     <ja>@Bean</ja>(factory=ItemSupplierFactory.<jk>class</jk>)
+ *     <jk>public class</jk> ItemSupplier <jk>implements</jk> 
BeanSupplier&lt;Item&gt; {
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private</jk> ResultSet <jv>rs</jv>;
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> begin() <jk>throws</jk> 
Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jv>rs</jv> = 
<jv>conn</jv>.prepareStatement(<js>"SELECT * FROM items"</js>).executeQuery();
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public</jk> Iterator&lt;Item&gt; 
iterator() {
+ *                     <jk>return new</jk> 
ResultSetIterator&lt;&gt;(<jv>rs</jv>, Item::<jv>fromRow</jv>);
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> onError(Exception 
<jv>e</jv>) <jk>throws</jk> Exception {
+ *                     <jv>rs</jv>.close(); <jk>throw</jk> <jv>e</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja> <jk>public void</jk> complete() 
<jk>throws</jk> Exception {
+ *                     <jv>rs</jv>.close(); <jv>conn</jv>.close();
+ *             }
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link BeanConsumer} - Parse-only lifecycle interface
+ *     <li class='jc'>{@link BeanChannel} - Round-trip lifecycle interface 
(extends both)
+ *     <li class='jc'>{@link BeanFactory} - Universal factory for DI framework 
integration
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Marshalling";>Marshalling</a>
+ * </ul>
+ *
+ * @param <T> The type of bean provided by this supplier.
+ */
+@SuppressWarnings({
+       "java:S112" // throws Exception intentional - lifecycle methods may 
throw any checked exception
+})
+public interface BeanSupplier<T> extends Iterable<T> {
+
+       /**
+        * Called before iteration starts.
+        *
+        * <p>
+        * Use this to open database connections, execute queries, or perform 
other setup.
+        *
+        * @throws Exception If setup fails.
+        */
+       default void begin() throws Exception {}
+
+       /**
+        * Always called after iteration ends (like {@code finally}).
+        *
+        * <p>
+        * Use this to close database cursors, connections, or release other 
resources.
+        * This method is called whether serialization succeeded or {@link 
#onError(Exception)} rethrew.
+        *
+        * @throws Exception If cleanup fails.
+        */
+       default void complete() throws Exception {}
+
+       /**
+        * Called when an exception occurs during iteration.
+        *
+        * <p>
+        * The default implementation rethrows the exception, stopping 
serialization immediately.
+        * Note that {@link #complete()} is always called afterward regardless.
+        *
+        * @param e The exception that occurred during iteration.
+        * @throws Exception If the error cannot be recovered from. Rethrow 
{@code e} to stop serialization.
+        */
+       default void onError(Exception e) throws Exception {
+               throw e;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ListBeanChannel.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ListBeanChannel.java
new file mode 100644
index 0000000000..97e79792fc
--- /dev/null
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/ListBeanChannel.java
@@ -0,0 +1,81 @@
+/*
+ * 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.commons.function;
+
+import java.util.*;
+
+/**
+ * A simple in-memory {@link BeanChannel} implementation backed by an {@link 
ArrayList}.
+ *
+ * <p>
+ * Collects parsed elements into an in-memory list on the parser side, and 
iterates over the list
+ * on the serializer side. No factory or DI framework required — instantiated 
directly via no-arg
+ * constructor.
+ *
+ * <p>
+ * This is the default built-in implementation suitable for use cases where 
holding all elements
+ * in memory is acceptable. For large datasets or database-backed scenarios, 
implement
+ * {@link BeanChannel} (or {@link BeanConsumer} / {@link BeanSupplier}) 
directly.
+ *
+ * <h5 class='section'>Example (getter-only round-trip property):</h5>
+ * <p class='bjava'>
+ *     <jk>public class</jk> ItemCollection {
+ *             <jk>private final</jk> ListBeanChannel&lt;Item&gt; 
<jv>items</jv> = <jk>new</jk> ListBeanChannel&lt;&gt;();
+ *
+ *             <ja>@Beanp</ja>(elementType=Item.<jk>class</jk>)
+ *             <jk>public</jk> ListBeanChannel&lt;Item&gt; getItems() { 
<jk>return</jk> <jv>items</jv>; }
+ *             <jc>// No setter needed — parser calls acceptThrows() on the 
existing instance</jc>
+ *     }
+ * </p>
+ *
+ * <h5 class='section'>See Also:</h5><ul>
+ *     <li class='jc'>{@link BeanChannel} - Round-trip lifecycle interface
+ *     <li class='jc'>{@link BeanConsumer} - Parse-only lifecycle interface
+ *     <li class='jc'>{@link BeanSupplier} - Serialize-only lifecycle interface
+ *     <li class='link'><a class="doclink" 
href="https://juneau.apache.org/docs/topics/Marshalling";>Marshalling</a>
+ * </ul>
+ *
+ * @param <T> The type of bean stored in this channel.
+ */
+public class ListBeanChannel<T> implements BeanChannel<T> {
+
+       private final List<T> list = new ArrayList<>();
+
+       /**
+        * Constructor.
+        */
+       public ListBeanChannel() {}
+
+       @Override
+       public void acceptThrows(T item) {
+               list.add(item);
+       }
+
+       @Override
+       public Iterator<T> iterator() {
+               return list.iterator();
+       }
+
+       /**
+        * Returns the underlying list of elements.
+        *
+        * @return The list of elements collected during parsing, or to be 
serialized.
+        */
+       public List<T> getList() {
+               return list;
+       }
+}
diff --git 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/package-info.java
 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/package-info.java
index 7026a6a95b..d4b239e0af 100644
--- 
a/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/package-info.java
+++ 
b/juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/function/package-info.java
@@ -17,7 +17,480 @@
 
 /**
  * Functional programming utilities including enhanced function interfaces, 
consumers, suppliers,
- * tuples, and exception-handling variants.
+ * tuples, exception-handling variants, and large-dataset streaming APIs.
+ *
+ * <a id="BeanStreaming"></a>
+ * <h2 class='topic'>Large-Dataset Streaming</h2>
+ * <div class='topic'>
+ *
+ *     <p>
+ *     The following interfaces enable serialization and parsing of large 
datasets without loading
+ *     all elements into memory. They integrate directly into the Juneau 
marshalling framework so
+ *     any serializer or parser works transparently.
+ *     </p>
+ *
+ *     <ul class='javatree'>
+ *             <li class='jic'>{@link 
org.apache.juneau.commons.function.BeanSupplier} - Serialize-side lifecycle 
interface; extends {@link java.lang.Iterable}
+ *             <li class='jic'>{@link 
org.apache.juneau.commons.function.BeanConsumer} - Parse-side lifecycle 
interface; extends {@link org.apache.juneau.commons.function.ThrowingConsumer}
+ *             <li class='jic'>{@link 
org.apache.juneau.commons.function.BeanChannel} - Round-trip interface; extends 
both {@link org.apache.juneau.commons.function.BeanSupplier} and {@link 
org.apache.juneau.commons.function.BeanConsumer}
+ *             <li class='jc'>{@link 
org.apache.juneau.commons.function.ListBeanChannel} - Built-in in-memory {@link 
org.apache.juneau.commons.function.BeanChannel} backed by an {@link 
java.util.ArrayList}
+ *             <li class='jic'>{@link 
org.apache.juneau.commons.function.BeanFactory} - Universal factory interface 
for DI-framework-managed instantiation
+ *     </ul>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Lifecycle"></a>
+ *     <h3 class='topic'>Lifecycle</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     Both interfaces follow the same three-phase lifecycle driven by the 
framework:
+ *     </p>
+ *
+ *     <table class='styled'>
+ *             <tr><th>Phase</th><th>BeanSupplier 
(serialization)</th><th>BeanConsumer (parsing)</th></tr>
+ *             <tr><td><b>Setup</b></td><td>{@link 
org.apache.juneau.commons.function.BeanSupplier#begin()} — open cursor, execute 
query</td><td>{@link org.apache.juneau.commons.function.BeanConsumer#begin()} — 
open connection, prepare statement</td></tr>
+ *             <tr><td><b>Transfer</b></td><td>{@link 
org.apache.juneau.commons.function.BeanSupplier#iterator()} — yield one bean 
per call</td><td>{@link 
org.apache.juneau.commons.function.BeanConsumer#acceptThrows(Object)} — receive 
one bean per call</td></tr>
+ *             <tr><td><b>Error</b></td><td>{@link 
org.apache.juneau.commons.function.BeanSupplier#onError(Exception)} — rollback 
/ log; rethrow to stop</td><td>{@link 
org.apache.juneau.commons.function.BeanConsumer#onError(Exception)} — rollback 
/ log; absorb to skip-and-continue</td></tr>
+ *             <tr><td><b>Cleanup</b></td><td>{@link 
org.apache.juneau.commons.function.BeanSupplier#complete()} — close cursor, 
connection</td><td>{@link 
org.apache.juneau.commons.function.BeanConsumer#complete()} — final commit, 
close statement</td></tr>
+ *     </table>
+ *
+ *     <p>
+ *     {@code complete()} is always called — even when {@code onError()} 
rethrows — so it is safe to
+ *     use for resource cleanup in all cases.
+ *     </p>
+ *
+ *     </div>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Supplier"></a>
+ *     <h3 class='topic'>BeanSupplier — Serializing from a database cursor</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     The following example streams a large {@code Employee} table directly 
from the database to the
+ *     HTTP response as a JSON array, without loading any rows into memory. A 
Spring-injected
+ *     {@code DataSource} is supplied via a factory registered with {@link 
org.apache.juneau.commons.function.BeanFactory}.
+ *     </p>
+ *
+ *     <h5 class='figure'>Bean class with factory annotation</h5>
+ *     <p class='bjava'>
+ *     <jk>package</jk> com.example;
+ *
+ *     <ja>@Bean</ja>(factory=EmployeeSupplier.Factory.<jk>class</jk>)
+ *     <jk>public class</jk> EmployeeSupplier <jk>implements</jk> 
BeanSupplier&lt;Employee&gt; {
+ *
+ *             <jk>private final</jk> DataSource <jv>ds</jv>;
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private</jk> ResultSet <jv>rs</jv>;
+ *
+ *             <jk>public</jk> EmployeeSupplier(DataSource <jv>ds</jv>) {
+ *                     <jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> begin() <jk>throws</jk> Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jk>var</jk> <jv>stmt</jv> = 
<jv>conn</jv>.prepareStatement(
+ *                             <js>"SELECT id, name, department FROM employee 
ORDER BY id"</js>);
+ *                     <jv>rs</jv> = <jv>stmt</jv>.executeQuery();
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> Iterator&lt;Employee&gt; iterator() {
+ *                     <jk>return new</jk> Iterator&lt;&gt;() {
+ *                             <ja>@Override</ja> <jk>public boolean</jk> 
hasNext() { <jk>return</jk> ResultSetIterator.hasNext(<jv>rs</jv>); }
+ *                             <ja>@Override</ja> <jk>public</jk> Employee 
next() { <jk>return</jk> Employee.fromRow(<jv>rs</jv>); }
+ *                     };
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> onError(Exception <jv>e</jv>) 
<jk>throws</jk> Exception {
+ *                     <jk>throw</jk> <jv>e</jv>; <jc>// propagate; complete() 
will still close the cursor</jc>
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> complete() <jk>throws</jk> Exception {
+ *                     <jk>if</jk> (<jv>rs</jv> != <jk>null</jk>) 
<jv>rs</jv>.close();
+ *                     <jk>if</jk> (<jv>conn</jv> != <jk>null</jk>) 
<jv>conn</jv>.close();
+ *             }
+ *
+ *             <jc>// Factory retrieved from the Spring ApplicationContext via 
BeanStore.</jc>
+ *             <jk>public static class</jk> Factory <jk>implements</jk> 
BeanFactory&lt;EmployeeSupplier&gt; {
+ *
+ *                     <jk>private final</jk> DataSource <jv>ds</jv>;
+ *
+ *                     <jk>public</jk> Factory(DataSource <jv>ds</jv>) { 
<jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>; }
+ *
+ *                     <ja>@Override</ja>
+ *                     <jk>public</jk> EmployeeSupplier create() {
+ *                             <jk>return new</jk> 
EmployeeSupplier(<jv>ds</jv>);
+ *                     }
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <h5 class='figure'>Spring REST endpoint</h5>
+ *     <p class='bjava'>
+ *     <ja>@Rest</ja>
+ *     <jk>public class</jk> EmployeeResource <jk>extends</jk> 
BasicRestServlet {
+ *
+ *             <ja>@Inject</ja>
+ *             <jk>private</jk> EmployeeSupplier.Factory 
<jv>supplierFactory</jv>;
+ *
+ *             <ja>@RestGet</ja>(<js>"/employees"</js>)
+ *             <jk>public</jk> EmployeeSupplier getEmployees() {
+ *                     <jk>return</jk> <jv>supplierFactory</jv>.create(); 
<jc>// framework calls begin(), iterates, calls complete()</jc>
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <p>
+ *     The serializer calls {@code begin()} before iterating, serializes each 
{@code Employee} bean
+ *     as it arrives from the cursor, and always calls {@code complete()} 
afterward to close the
+ *     cursor and connection — regardless of whether serialization succeeded 
or failed.
+ *     </p>
+ *
+ *     </div>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Consumer"></a>
+ *     <h3 class='topic'>BeanConsumer — Parsing into a database table</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     The following example accepts a large JSON array of {@code Employee} 
beans in an HTTP request
+ *     body and bulk-inserts them into the database via JDBC, committing every 
500 rows. On error
+ *     it rolls back and rethrows; on completion it performs a final commit 
and closes resources.
+ *     </p>
+ *
+ *     <h5 class='figure'>BeanConsumer with batch commits</h5>
+ *     <p class='bjava'>
+ *     <ja>@Bean</ja>(factory=EmployeeConsumer.Factory.<jk>class</jk>)
+ *     <jk>public class</jk> EmployeeConsumer <jk>implements</jk> 
BeanConsumer&lt;Employee&gt; {
+ *
+ *             <jk>private static final int</jk> <jsf>BATCH_SIZE</jsf> = 500;
+ *
+ *             <jk>private final</jk> DataSource <jv>ds</jv>;
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private</jk> PreparedStatement <jv>stmt</jv>;
+ *             <jk>private int</jk> <jv>count</jv>;
+ *
+ *             <jk>public</jk> EmployeeConsumer(DataSource <jv>ds</jv>) {
+ *                     <jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> begin() <jk>throws</jk> Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jv>conn</jv>.setAutoCommit(<jk>false</jk>);
+ *                     <jv>stmt</jv> = <jv>conn</jv>.prepareStatement(
+ *                             <js>"INSERT INTO employee (name, department) 
VALUES (?, ?)"</js>);
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> acceptThrows(Employee <jv>emp</jv>) 
<jk>throws</jk> Exception {
+ *                     <jv>stmt</jv>.setString(1, <jv>emp</jv>.getName());
+ *                     <jv>stmt</jv>.setString(2, 
<jv>emp</jv>.getDepartment());
+ *                     <jv>stmt</jv>.executeUpdate();
+ *                     <jk>if</jk> (++<jv>count</jv> % <jsf>BATCH_SIZE</jsf> 
== 0)
+ *                             <jv>conn</jv>.commit(); <jc>// periodic batch 
commit</jc>
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> onError(Exception <jv>e</jv>) 
<jk>throws</jk> Exception {
+ *                     <jv>conn</jv>.rollback();
+ *                     <jk>throw</jk> <jv>e</jv>; <jc>// stop parsing; 
complete() will still close resources</jc>
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> complete() <jk>throws</jk> Exception {
+ *                     <jv>conn</jv>.commit(); <jc>// final commit for the 
last partial batch</jc>
+ *                     <jv>stmt</jv>.close();
+ *                     <jv>conn</jv>.close();
+ *             }
+ *
+ *             <jk>public static class</jk> Factory <jk>implements</jk> 
BeanFactory&lt;EmployeeConsumer&gt; {
+ *
+ *                     <jk>private final</jk> DataSource <jv>ds</jv>;
+ *
+ *                     <jk>public</jk> Factory(DataSource <jv>ds</jv>) { 
<jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>; }
+ *
+ *                     <ja>@Override</ja>
+ *                     <jk>public</jk> EmployeeConsumer create() {
+ *                             <jk>return new</jk> 
EmployeeConsumer(<jv>ds</jv>);
+ *                     }
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <h5 class='figure'>Parsing via parseToBeanConsumer</h5>
+ *     <p class='bjava'>
+ *     <jc>// Direct API usage — framework calls begin(), acceptThrows() per 
element, complete().</jc>
+ *     <jk>var</jk> <jv>consumer</jv> = <jv>consumerFactory</jv>.create();
+ *     
JsonParser.<jsf>DEFAULT</jsf>.getSession().parseToBeanConsumer(<jv>inputStream</jv>,
 <jv>consumer</jv>, Employee.<jk>class</jk>);
+ *     </p>
+ *
+ *     <h5 class='figure'>Spring REST endpoint</h5>
+ *     <p class='bjava'>
+ *     <ja>@Rest</ja>
+ *     <jk>public class</jk> EmployeeResource <jk>extends</jk> 
BasicRestServlet {
+ *
+ *             <ja>@Inject</ja>
+ *             <jk>private</jk> EmployeeConsumer.Factory 
<jv>consumerFactory</jv>;
+ *
+ *             <ja>@RestPost</ja>(<js>"/employees/bulk"</js>)
+ *             <jk>public void</jk> importEmployees(RestRequest <jv>req</jv>) 
<jk>throws</jk> Exception {
+ *                     <jk>var</jk> <jv>consumer</jv> = 
<jv>consumerFactory</jv>.create();
+ *                     
<jv>req</jv>.getBody().parseToBeanConsumer(<jv>consumer</jv>, 
Employee.<jk>class</jk>);
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <p>
+ *     The {@code onError()} default rethrows, stopping parsing immediately. 
Override it to absorb
+ *     the exception (log-and-skip) for fault-tolerant ingestion of 
partially-invalid input:
+ *     </p>
+ *
+ *     <h5 class='figure'>Fault-tolerant ingestion (skip bad records)</h5>
+ *     <p class='bjava'>
+ *     BeanConsumer&lt;Employee&gt; <jv>consumer</jv> = <jk>new</jk> 
BeanConsumer&lt;&gt;() {
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> acceptThrows(Employee <jv>emp</jv>) 
<jk>throws</jk> Exception {
+ *                     validateAndInsert(<jv>emp</jv>);
+ *             }
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> onError(Exception <jv>e</jv>) {
+ *                     log.warn(<js>"Skipping invalid record: {}"</js>, 
<jv>e</jv>.getMessage());
+ *                     <jc>// absorb — parsing continues to the next 
element</jc>
+ *             }
+ *     };
+ *     </p>
+ *
+ *     </div>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Channel"></a>
+ *     <h3 class='topic'>BeanChannel — Round-trip marshalling on a single 
property</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     {@link org.apache.juneau.commons.function.BeanChannel} extends both
+ *     {@link org.apache.juneau.commons.function.BeanSupplier} and
+ *     {@link org.apache.juneau.commons.function.BeanConsumer}, allowing the 
same property to drive
+ *     both serialization and parsing. The implementation itself determines 
direction at runtime.
+ *     </p>
+ *
+ *     <h5 class='figure'>In-memory channel — ListBeanChannel</h5>
+ *     <p class='bjava'>
+ *     <jk>public class</jk> EmployeeCollection {
+ *
+ *             <jk>private final</jk> ListBeanChannel&lt;Employee&gt; 
<jv>employees</jv> = <jk>new</jk> ListBeanChannel&lt;&gt;();
+ *
+ *             <jc>// No setter needed — parser calls acceptThrows() on the 
existing instance.</jc>
+ *             <ja>@Beanp</ja>(elementType=Employee.<jk>class</jk>)
+ *             <jk>public</jk> ListBeanChannel&lt;Employee&gt; getEmployees() 
{ <jk>return</jk> <jv>employees</jv>; }
+ *     }
+ *
+ *     <jc>// Serialize — channel iterated as a sequence.</jc>
+ *     String <jv>json</jv> = 
Json5Serializer.<jsf>DEFAULT</jsf>.serialize(<jv>collection</jv>);
+ *
+ *     <jc>// Parse — channel populated via acceptThrows().</jc>
+ *     Json5Parser.<jsf>DEFAULT</jsf>.parse(<jv>json</jv>, 
EmployeeCollection.<jk>class</jk>);
+ *     </p>
+ *
+ *     <h5 class='figure'>Database-backed channel</h5>
+ *     <p class='bjava'>
+ *     <ja>@Bean</ja>(factory=EmployeeChannel.Factory.<jk>class</jk>)
+ *     <jk>public class</jk> EmployeeChannel <jk>implements</jk> 
BeanChannel&lt;Employee&gt; {
+ *
+ *             <jk>private final</jk> DataSource <jv>ds</jv>;
+ *             <jk>private</jk> Connection <jv>conn</jv>;
+ *             <jk>private</jk> ResultSet <jv>rs</jv>;
+ *             <jk>private</jk> PreparedStatement <jv>insertStmt</jv>;
+ *             <jk>private int</jk> <jv>insertCount</jv>;
+ *
+ *             <jk>public</jk> EmployeeChannel(DataSource <jv>ds</jv>) {
+ *                     <jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>;
+ *             }
+ *
+ *             <jc>// ---- BeanSupplier side (serialization) ----</jc>
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> begin() <jk>throws</jk> Exception {
+ *                     <jv>conn</jv> = <jv>ds</jv>.getConnection();
+ *                     <jv>insertStmt</jv> = <jv>conn</jv>.prepareStatement(
+ *                             <js>"INSERT INTO employee (name, department) 
VALUES (?, ?)"</js>);
+ *                     <jv>conn</jv>.setAutoCommit(<jk>false</jk>);
+ *                     <jk>var</jk> <jv>qStmt</jv> = 
<jv>conn</jv>.prepareStatement(
+ *                             <js>"SELECT name, department FROM employee 
ORDER BY id"</js>);
+ *                     <jv>rs</jv> = <jv>qStmt</jv>.executeQuery();
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public</jk> Iterator&lt;Employee&gt; iterator() {
+ *                     <jk>return new</jk> Iterator&lt;&gt;() {
+ *                             <ja>@Override</ja> <jk>public boolean</jk> 
hasNext() { <jk>return</jk> ResultSetIterator.hasNext(<jv>rs</jv>); }
+ *                             <ja>@Override</ja> <jk>public</jk> Employee 
next() { <jk>return</jk> Employee.fromRow(<jv>rs</jv>); }
+ *                     };
+ *             }
+ *
+ *             <jc>// ---- BeanConsumer side (parsing) ----</jc>
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> acceptThrows(Employee <jv>emp</jv>) 
<jk>throws</jk> Exception {
+ *                     <jv>insertStmt</jv>.setString(1, 
<jv>emp</jv>.getName());
+ *                     <jv>insertStmt</jv>.setString(2, 
<jv>emp</jv>.getDepartment());
+ *                     <jv>insertStmt</jv>.executeUpdate();
+ *                     <jk>if</jk> (++<jv>insertCount</jv> % 500 == 0) 
<jv>conn</jv>.commit();
+ *             }
+ *
+ *             <jc>// ---- Shared lifecycle ----</jc>
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> onError(Exception <jv>e</jv>) 
<jk>throws</jk> Exception {
+ *                     <jk>if</jk> (<jv>conn</jv> != <jk>null</jk>) 
<jv>conn</jv>.rollback();
+ *                     <jk>throw</jk> <jv>e</jv>;
+ *             }
+ *
+ *             <ja>@Override</ja>
+ *             <jk>public void</jk> complete() <jk>throws</jk> Exception {
+ *                     <jk>if</jk> (<jv>conn</jv> != <jk>null</jk>) {
+ *                             <jv>conn</jv>.commit();
+ *                             <jk>if</jk> (<jv>rs</jv> != <jk>null</jk>) 
<jv>rs</jv>.close();
+ *                             <jk>if</jk> (<jv>insertStmt</jv> != 
<jk>null</jk>) <jv>insertStmt</jv>.close();
+ *                             <jv>conn</jv>.close();
+ *                     }
+ *             }
+ *
+ *             <jk>public static class</jk> Factory <jk>implements</jk> 
BeanFactory&lt;EmployeeChannel&gt; {
+ *                     <jk>private final</jk> DataSource <jv>ds</jv>;
+ *                     <jk>public</jk> Factory(DataSource <jv>ds</jv>) { 
<jk>this</jk>.<jv>ds</jv> = <jv>ds</jv>; }
+ *                     <ja>@Override</ja>
+ *                     <jk>public</jk> EmployeeChannel create() { <jk>return 
new</jk> EmployeeChannel(<jv>ds</jv>); }
+ *             }
+ *     }
+ *     </p>
+ *
+ *     </div>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.SpringIntegration"></a>
+ *     <h3 class='topic'>Spring Integration via BeanFactory and BeanStore</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     Spring-managed beans (e.g. a {@code DataSource}) can be injected into 
streaming implementations
+ *     without constructor scanning or reflection. Register the factories as 
Spring beans, then wire
+ *     the {@code SpringBeanStore} into the Juneau context so the marshaller 
can resolve them at
+ *     runtime.
+ *     </p>
+ *
+ *     <h5 class='figure'>Spring configuration</h5>
+ *     <p class='bjava'>
+ *     <ja>@Configuration</ja>
+ *     <jk>public class</jk> JuneauStreamingConfig {
+ *
+ *             <ja>@Bean</ja>
+ *             <jk>public</jk> EmployeeSupplier.Factory 
employeeSupplierFactory(DataSource <jv>ds</jv>) {
+ *                     <jk>return new</jk> 
EmployeeSupplier.Factory(<jv>ds</jv>);
+ *             }
+ *
+ *             <ja>@Bean</ja>
+ *             <jk>public</jk> EmployeeConsumer.Factory 
employeeConsumerFactory(DataSource <jv>ds</jv>) {
+ *                     <jk>return new</jk> 
EmployeeConsumer.Factory(<jv>ds</jv>);
+ *             }
+ *
+ *             <ja>@Bean</ja>
+ *             <jk>public</jk> EmployeeChannel.Factory 
employeeChannelFactory(DataSource <jv>ds</jv>) {
+ *                     <jk>return new</jk> 
EmployeeChannel.Factory(<jv>ds</jv>);
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <h5 class='figure'>Wiring the SpringBeanStore into a REST resource</h5>
+ *     <p class='bjava'>
+ *     <ja>@Rest</ja>
+ *     <jk>public class</jk> EmployeeResource <jk>extends</jk> 
BasicSpringRestServlet {
+ *
+ *             <ja>@Inject</ja>
+ *             <jk>private</jk> ApplicationContext <jv>appCtx</jv>;
+ *
+ *             <ja>@Override</ja>
+ *             <jk>protected</jk> BeanContext.Builder 
createBeanContext(RestContext.Builder <jv>rcBuilder</jv>) {
+ *                     <jk>return 
super</jk>.createBeanContext(<jv>rcBuilder</jv>)
+ *                             .beanStore(<jk>new</jk> 
SpringBeanStore(<jv>appCtx</jv>));
+ *             }
+ *     }
+ *     </p>
+ *
+ *     <p>
+ *     With this wiring in place, any class annotated with
+ *     {@code @Bean(factory=EmployeeSupplier.Factory.class)} is automatically 
instantiated by
+ *     retrieving the factory from the Spring {@code ApplicationContext} and 
calling
+ *     {@link org.apache.juneau.commons.function.BeanFactory#create()}. No 
manual construction or
+ *     injection is needed in individual REST methods.
+ *     </p>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Annotations"></a>
+ *     <h4 class='topic'>Supporting Annotations</h4>
+ *     <div class='topic'>
+ *
+ *     <table class='styled'>
+ *             <tr>
+ *                     <th>Annotation</th>
+ *                     <th>Target</th>
+ *                     <th>Purpose</th>
+ *             </tr>
+ *             <tr>
+ *                     <td>{@code @Bean(factory=X.class)}</td>
+ *                     <td>Class</td>
+ *                     <td>Specifies the {@link 
org.apache.juneau.commons.function.BeanFactory} class used to instantiate this 
type during parsing. The factory is resolved from the {@code BeanStore}.</td>
+ *             </tr>
+ *             <tr>
+ *                     <td>{@code @Beanp(factory=X.class)}</td>
+ *                     <td>Bean property</td>
+ *                     <td>Specifies a property-level factory for 
instantiating the value of a specific bean property.</td>
+ *             </tr>
+ *             <tr>
+ *                     <td>{@code @Beanp(elementType=Y.class)}</td>
+ *                     <td>Bean property</td>
+ *                     <td>Declares the element type for generic streaming 
properties ({@code Stream&lt;Y&gt;}, {@code BeanSupplier&lt;Y&gt;}, etc.) 
overcoming Java type erasure. Also supports narrowing to concrete 
implementation types.</td>
+ *             </tr>
+ *     </table>
+ *
+ *     </div>
+ *
+ *     </div>
+ *
+ *     <!-- 
============================================================================================================
 -->
+ *     <a id="BeanStreaming.Supplier.Unwrapping"></a>
+ *     <h3 class='topic'>Supplier&lt;T&gt; single-value unwrapping</h3>
+ *     <div class='topic'>
+ *
+ *     <p>
+ *     {@link java.util.function.Supplier Supplier&lt;T&gt;} (the standard JDK 
interface) is treated as a
+ *     single-value lazy wrapper. Serializers call {@code get()} and serialize 
the result
+ *     transparently. Nested {@code Supplier} chains are unwrapped recursively 
up to a depth of 10.
+ *     </p>
+ *
+ *     <p>
+ *     Note: {@link org.apache.juneau.commons.function.BeanSupplier} is 
<em>not</em> unwrapped — it
+ *     is treated as an {@link java.lang.Iterable} sequence, not a 
single-value wrapper.
+ *     </p>
+ *
+ *     <p class='bjava'>
+ *     <jc>// Serialized as the string "hello" — Supplier is unwrapped.</jc>
+ *     Supplier&lt;String&gt; <jv>lazy</jv> = () -&gt; <js>"hello"</js>;
+ *     String <jv>json</jv> = 
Json5Serializer.<jsf>DEFAULT</jsf>.serialize(<jv>lazy</jv>); <jc>// 'hello'</jc>
+ *
+ *     <jc>// Nested Supplier chains are also unwrapped.</jc>
+ *     Supplier&lt;Supplier&lt;Integer&gt;&gt; <jv>nested</jv> = () -&gt; () 
-&gt; 42;
+ *     <jv>json</jv> = 
Json5Serializer.<jsf>DEFAULT</jsf>.serialize(<jv>nested</jv>); <jc>// 42</jc>
+ *     </p>
+ *
+ *     </div>
+ *
+ * </div>
  */
 package org.apache.juneau.commons.function;
-
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
index 5f150cd49a..794dacca02 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanContext.java
@@ -37,6 +37,7 @@ import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.collections.*;
 import org.apache.juneau.commons.conversion.*;
 import org.apache.juneau.commons.function.*;
+import org.apache.juneau.commons.inject.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.reflect.Visibility;
 import org.apache.juneau.cp.*;
@@ -261,6 +262,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
                private List<Object> swaps;
                private Set<ClassInfo> notBeanClasses;
                private Set<String> notBeanPackages;
+               private BeanStore beanStore;
                
                /**
                 * Constructor.
@@ -298,6 +300,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
                        typePropertyName = env("BeanContext.typePropertyName", 
"_type");
                        useEnumNames = env("BeanContext.useEnumNames", false);
                        useJavaBeanIntrospector = 
env("BeanContext.useJavaBeanIntrospector", false);
+                       beanStore = null;
                }
 
                /**
@@ -337,6 +340,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
                        typePropertyName = copyFrom.typePropertyName;
                        useEnumNames = copyFrom.useEnumNames;
                        useJavaBeanIntrospector = 
copyFrom.useJavaBeanIntrospector;
+                       beanStore = copyFrom.beanStore;
                }
 
                /**
@@ -376,6 +380,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
                        typePropertyName = copyFrom.typePropertyName;
                        useEnumNames = copyFrom.useEnumNames;
                        useJavaBeanIntrospector = 
copyFrom.useJavaBeanIntrospector;
+                       beanStore = copyFrom.beanStore;
                }
 
                @Override /* Overridden from Builder */
@@ -1718,6 +1723,39 @@ public class BeanContext extends Context implements 
ConversionFinder {
                        return this;
                }
 
+               /**
+                * Sets the bean store used for factory-based instantiation.
+                *
+                * <p>
+                * The bean store is used to resolve {@link BeanFactory} 
instances registered via
+                * <ja>@Bean</ja><c>(factory=X.class)</c> and 
<ja>@Beanp</ja><c>(factory=X.class)</c> annotations.
+                * When a factory class is encountered, the framework first 
looks it up in the bean store
+                * before attempting direct instantiation.
+                *
+                * <p>
+                * Typically set to a {@code SpringBeanStore} wrapping the 
application's
+                * {@code ApplicationContext} so that Spring-managed factories 
are resolved automatically.
+                *
+                * <h5 class='section'>Example:</h5>
+                * <p class='bjava'>
+                *      <jk>public class</jk> MyRestServlet <jk>extends</jk> 
BasicRestServlet {
+                *              <ja>@Autowired</ja> ApplicationContext 
<jv>ctx</jv>;
+                *
+                *              <ja>@Override</ja>
+                *              <jk>protected</jk> BeanContext 
createBeanContext(BeanContext.Builder <jv>builder</jv>) {
+                *                      <jk>return</jk> 
<jv>builder</jv>.beanStore(<jk>new</jk> SpringBeanStore(<jv>ctx</jv>)).build();
+                *              }
+                *      }
+                * </p>
+                *
+                * @param value The bean store, or <jk>null</jk> to use direct 
instantiation only.
+                * @return This object.
+                */
+               public Builder beanStore(BeanStore value) {
+                       beanStore = value;
+                       return this;
+               }
+
                @Override /* Overridden from Context.Builder */
                public BeanContext build() {
                        return cache(CACHE).build(BeanContext.class);
@@ -2256,7 +2294,8 @@ public class BeanContext extends Context implements 
ConversionFinder {
                                mediaType,
                                timeZone,
                                locale,
-                               propertyNamer
+                               propertyNamer,
+                               System.identityHashCode(beanStore)
                        );
                        // @formatter:on
                }
@@ -3671,6 +3710,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
        private final Visibility beanConstructorVisibility;
        private final Visibility beanFieldVisibility;
        private final Visibility beanMethodVisibility;
+       private final BeanStore beanStore;
 
        /**
         * Constructor.
@@ -3710,6 +3750,7 @@ public class BeanContext extends Context implements 
ConversionFinder {
                useEnumNames = builder.useEnumNames;
                useInterfaceProxies = ! builder.disableInterfaceProxies;
                useJavaBeanIntrospector = builder.useJavaBeanIntrospector;
+               beanStore = builder.beanStore;
 
                var builderNotBeanClasses = new 
ArrayList<>(builder.notBeanClasses);
                notBeanClasses = builderNotBeanClasses.isEmpty() ? 
DEFAULT_NOTBEAN_CLASSES : Stream.concat(builderNotBeanClasses.stream(), 
DEFAULT_NOTBEAN_CLASSES.stream()).distinct().toList();
@@ -3792,6 +3833,14 @@ public class BeanContext extends Context implements 
ConversionFinder {
         */
        public final Visibility getBeanConstructorVisibility() { return 
beanConstructorVisibility; }
 
+       /**
+        * The bean store used for factory-based instantiation.
+        *
+        * @see BeanContext.Builder#beanStore(BeanStore)
+        * @return The bean store, or <jk>null</jk> if none configured.
+        */
+       public final BeanStore getBeanStore() { return beanStore; }
+
        /**
         * Bean dictionary.
         *
@@ -4877,6 +4926,13 @@ public class BeanContext extends Context implements 
ConversionFinder {
                                return new ClassMeta<>(cm2, null, null, 
elementType);
                        }
 
+                       // Handle @Beanp(elementType=) for BeanSupplier, 
BeanConsumer, BeanChannel, and Stream properties.
+                       if (isNotVoid(beanp.elementType()) && (cm2.isIterable() 
|| cm2.isStream())) {
+                               var elementType = 
resolveClassMeta(beanp.elementType(), typeVarImpls);
+                               if (! elementType.isObject())
+                                       return new ClassMeta<>(cm2, null, null, 
elementType);
+                       }
+
                        return cm2;
                }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
index 4e5bbbc232..3b0401f1e3 100644
--- a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
+++ b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanMeta.java
@@ -32,11 +32,12 @@ import java.util.function.*;
 
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.commons.collections.*;
-import org.apache.juneau.commons.function.NullableSupplier;
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.lang.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.commons.reflect.Visibility;
 import org.apache.juneau.commons.utils.*;
+import org.apache.juneau.cp.*;
 
 /**
  * Encapsulates all access to the properties of a bean class (like a souped-up 
{@link java.beans.BeanInfo}).
@@ -356,6 +357,8 @@ public class BeanMeta<T> {
        private final ClassMeta<T> classMeta;                                   
   // The target class type that this meta object describes.
        private final Supplier<String> dictionaryName;                          
   // The @Bean(typeName) annotation defined on this bean class.
        private final BeanPropertyMeta dynaProperty;                            
   // "extras" property.
+       @SuppressWarnings("rawtypes")
+       private final Class<? extends 
org.apache.juneau.commons.function.BeanFactory> factoryClass;  // 
@Bean(factory=X.class) — null means no factory.
        private final boolean fluentSetters;                                    
   // Whether fluent setters are enabled.
        private final Map<Method,String> getterProps;                           
   // The getter properties on the target class.
        private final Map<String,BeanPropertyMeta> hiddenProperties;            
   // The hidden properties on the target class.
@@ -598,6 +601,8 @@ public class BeanMeta<T> {
                typeProperty = BeanPropertyMeta.builder(this, 
typePropertyName).canRead().canWrite().rawMetaType(beanContext.string()).beanRegistry(beanRegistry.get()).build();
                dictionaryName = memoize(this::findDictionaryName);
                beanProxyInvocationHandler = 
memoize(()->beanContext.isUseInterfaceProxies() && c.isInterface() ? new 
BeanProxyInvocationHandler<>(this) : null);
+               var factoryClassTemp = ba.stream().map(x -> 
x.inner().factory()).filter(x -> x != 
org.apache.juneau.commons.function.BeanFactory.Void.class).findFirst().orElse(null);
+               factoryClass = factoryClassTemp;
        }
 
        @SuppressWarnings({
@@ -925,9 +930,20 @@ public class BeanMeta<T> {
         * @throws ExecutableException Exception occurred on invoked 
constructor/method/field.
         */
        @SuppressWarnings({
-               "unchecked" // Type erasure requires unchecked cast
+               "unchecked", // Type erasure requires unchecked cast
+               "rawtypes" // Raw BeanFactory type used at runtime for factory 
resolution
        })
        protected T newBean(Object outer) throws ExecutableException {
+               if (factoryClass != null) {
+                       try {
+                               BeanFactory factory = 
resolveFactory(factoryClass);
+                               return (T) factory.create();
+                       } catch (ExecutableException e) {
+                               throw e;
+                       } catch (Exception e) {
+                               throw new ExecutableException(e);
+                       }
+               }
                if (classMeta.isMemberClass() && classMeta.isNotStatic()) {
                        if (hasConstructor())
                                return getConstructor().<T>newInstance(outer);
@@ -943,6 +959,20 @@ public class BeanMeta<T> {
                return null;
        }
 
+       @SuppressWarnings({
+               "rawtypes", // Raw BeanFactory type at runtime
+               "unchecked" // Unchecked casts required for factory class and 
BeanStore result
+       })
+       private org.apache.juneau.commons.function.BeanFactory 
resolveFactory(Class<? extends org.apache.juneau.commons.function.BeanFactory> 
fc) {
+               var bs = beanContext.getBeanStore();
+               if (bs != null) {
+                       var opt = bs.getBean(fc);
+                       if (opt.isPresent())
+                               return opt.get();
+               }
+               return (BeanFactory) BeanCreator.of((Class)fc).run();
+       }
+
        /*
         * Finds the appropriate constructor for this bean and determines the 
property names for constructor arguments.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Bean.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Bean.java
index c6270a604b..3bb1360b75 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Bean.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Bean.java
@@ -59,6 +59,41 @@ public @interface Bean {
         */
        String[] description() default {};
 
+       /**
+        * Bean factory class.
+        *
+        * <p>
+        * Specifies a {@link org.apache.juneau.commons.function.BeanFactory} 
class to use for instantiating
+        * this class instead of relying on a no-arg constructor or static 
{@code getInstance()} method.
+        *
+        * <p>
+        * When a factory class is specified, the framework resolves it in the 
following order:
+        * <ol>
+        *      <li>Look up the factory class in the configured {@link 
org.apache.juneau.commons.inject.BeanStore}
+        *          (e.g. a Spring {@code ApplicationContext} wrapped in a 
{@code SpringBeanStore})
+        *      <li>Attempt direct instantiation via no-arg constructor or 
{@code getInstance()} static method
+        *      <li>Throw {@link IllegalArgumentException} if both fail
+        * </ol>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <ja>@Bean</ja>(factory=ItemChannelFactory.<jk>class</jk>)
+        *      <jk>public class</jk> ItemChannel <jk>implements</jk> 
BeanChannel&lt;Item&gt; { ... }
+        * </p>
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='jc'>{@link 
org.apache.juneau.commons.function.BeanFactory}
+        *      <li class='ja'>{@link Beanp#factory()}
+        *      <li class='jm'>{@link 
org.apache.juneau.BeanContext.Builder#beanStore(org.apache.juneau.commons.inject.BeanStore)}
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       @SuppressWarnings({
+               "rawtypes" // Raw BeanFactory type required for annotation 
attribute declaration
+       })
+       Class<? extends org.apache.juneau.commons.function.BeanFactory> 
factory() default org.apache.juneau.commons.function.BeanFactory.Void.class;
+
        /**
         * Bean dictionary.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanAnnotation.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanAnnotation.java
index 55e86ddc26..936c390dd7 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanAnnotation.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanAnnotation.java
@@ -24,6 +24,7 @@ import java.lang.annotation.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.commons.annotation.*;
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.svl.*;
 import org.apache.juneau.swap.*;
@@ -95,6 +96,8 @@ public class BeanAnnotation {
                private Class<?> stopClass = void.class;
                private Class<? extends BeanInterceptor<?>> interceptor = 
BeanInterceptor.Void.class;
                private Class<? extends PropertyNamer> propertyNamer = 
BasicPropertyNamer.class;
+               @SuppressWarnings("rawtypes")
+               private Class<? extends BeanFactory> factory = 
BeanFactory.Void.class;
                private String example = "";
                private String excludeProperties = "";
                private String p = "";
@@ -147,6 +150,18 @@ public class BeanAnnotation {
                        return this;
                }
 
+               /**
+                * Sets the {@link Bean#factory()} property on this annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               @SuppressWarnings("rawtypes")
+               public Builder factory(Class<? extends BeanFactory> value) {
+                       factory = value;
+                       return this;
+               }
+
                /**
                 * Sets the {@link Bean#example()} property on this annotation.
                 *
@@ -391,6 +406,8 @@ public class BeanAnnotation {
                private final Class<?> interfaceClass;
                private final Class<?> stopClass;
                private final Class<?>[] dictionary;
+               @SuppressWarnings("rawtypes")
+               private final Class<? extends BeanFactory> factory;
                private final String example;
                private final String excludeProperties;
                private final String p;
@@ -409,6 +426,7 @@ public class BeanAnnotation {
                        dictionary = copyOf(b.dictionary);
                        example = b.example;
                        excludeProperties = b.excludeProperties;
+                       factory = b.factory;
                        findFluentSetters = b.findFluentSetters;
                        implClass = b.implClass;
                        interceptor = b.interceptor;
@@ -432,6 +450,12 @@ public class BeanAnnotation {
                        return dictionary;
                }
 
+               @Override /* Overridden from Bean */
+               @SuppressWarnings("rawtypes")
+               public Class<? extends BeanFactory> factory() {
+                       return factory;
+               }
+
                @Override /* Overridden from Bean */
                public String example() {
                        return example;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Beanp.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Beanp.java
index 34d954642c..862599bf8a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Beanp.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/Beanp.java
@@ -77,6 +77,82 @@ public @interface Beanp {
         */
        Class<?>[] dictionary() default {};
 
+       /**
+        * Element type for streaming/consuming bean properties.
+        *
+        * <p>
+        * Specifies the element type for properties of type {@link 
java.util.stream.Stream},
+        * {@link org.apache.juneau.commons.function.BeanSupplier},
+        * {@link org.apache.juneau.commons.function.BeanConsumer}, or
+        * {@link org.apache.juneau.commons.function.BeanChannel} when type 
erasure prevents
+        * the framework from inferring the generic type argument at runtime.
+        *
+        * <p>
+        * This attribute also supports:
+        * <ul>
+        *      <li><b>Narrowing</b> - Specify a more specific subtype than the 
declared type
+        *      <li><b>Concrete implementation</b> - Specify a concrete class 
for an abstract/interface element type
+        * </ul>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jk>public class</jk> OrderCollection {
+        *              <jc>// Stream property - element type cannot be 
inferred at runtime due to erasure</jc>
+        *              <ja>@Beanp</ja>(elementType=Order.<jk>class</jk>)
+        *              <jk>public</jk> Stream&lt;Order&gt; getOrders() { ... }
+        *
+        *              <jc>// BeanChannel with concrete impl specified instead 
of abstract element type</jc>
+        *              <ja>@Beanp</ja>(elementType=ConcreteItem.<jk>class</jk>)
+        *              <jk>public</jk> BeanChannel&lt;AbstractItem&gt; 
getItems() { ... }
+        *      }
+        * </p>
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='jc'>{@link 
org.apache.juneau.commons.function.BeanSupplier}
+        *      <li class='jc'>{@link 
org.apache.juneau.commons.function.BeanConsumer}
+        *      <li class='jc'>{@link 
org.apache.juneau.commons.function.BeanChannel}
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       Class<?> elementType() default void.class;
+
+       /**
+        * Bean factory class for this property's value.
+        *
+        * <p>
+        * Specifies a {@link org.apache.juneau.commons.function.BeanFactory} 
class to use when instantiating
+        * the value of this specific bean property, overriding the class-level 
{@link Bean#factory()} if present.
+        *
+        * <p>
+        * When a factory class is specified, the framework resolves it in the 
following order:
+        * <ol>
+        *      <li>Look up the factory class in the configured {@link 
org.apache.juneau.commons.inject.BeanStore}
+        *      <li>Attempt direct instantiation via no-arg constructor or 
{@code getInstance()} static method
+        *      <li>Throw {@link IllegalArgumentException} if both fail
+        * </ol>
+        *
+        * <h5 class='section'>Example:</h5>
+        * <p class='bjava'>
+        *      <jk>public class</jk> MyBean {
+        *              
<ja>@Beanp</ja>(factory=ItemChannelFactory.<jk>class</jk>, 
elementType=Item.<jk>class</jk>)
+        *              <jk>public</jk> BeanChannel&lt;Item&gt; getItems() { 
... }
+        *      }
+        * </p>
+        *
+        * <h5 class='section'>See Also:</h5><ul>
+        *      <li class='jc'>{@link 
org.apache.juneau.commons.function.BeanFactory}
+        *      <li class='ja'>{@link Bean#factory()}
+        *      <li class='jm'>{@link 
org.apache.juneau.BeanContext.Builder#beanStore(org.apache.juneau.commons.inject.BeanStore)}
+        * </ul>
+        *
+        * @return The annotation value.
+        */
+       @SuppressWarnings({
+               "rawtypes" // Raw BeanFactory type required for annotation 
attribute declaration
+       })
+       Class<? extends org.apache.juneau.commons.function.BeanFactory> 
factory() default org.apache.juneau.commons.function.BeanFactory.Void.class;
+
        /**
         * Specifies a String format for converting the bean property value to 
a formatted string.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanpAnnotation.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanpAnnotation.java
index 8a0e00e188..68d1bb5963 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanpAnnotation.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/annotation/BeanpAnnotation.java
@@ -25,6 +25,7 @@ import java.lang.reflect.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.commons.annotation.*;
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.svl.*;
 
@@ -90,8 +91,11 @@ public class BeanpAnnotation {
 
                private String[] description = {};
                private Class<?> type = void.class;
+               private Class<?> elementType = void.class;
                private Class<?>[] dictionary = new Class[0];
                private Class<?>[] params = new Class[0];
+               @SuppressWarnings("rawtypes")
+               private Class<? extends BeanFactory> factory = 
BeanFactory.Void.class;
                private String format = "";
                private String name = "";
                private String properties = "";
@@ -137,6 +141,29 @@ public class BeanpAnnotation {
                        return this;
                }
 
+               /**
+                * Sets the {@link Beanp#elementType()} property on this 
annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               public Builder elementType(Class<?> value) {
+                       elementType = value;
+                       return this;
+               }
+
+               /**
+                * Sets the {@link Beanp#factory()} property on this annotation.
+                *
+                * @param value The new value for this property.
+                * @return This object.
+                */
+               @SuppressWarnings("rawtypes")
+               public Builder factory(Class<? extends BeanFactory> value) {
+                       factory = value;
+                       return this;
+               }
+
                /**
                 * Sets the {@link Beanp#format()} property on this annotation.
                 *
@@ -264,8 +291,11 @@ public class BeanpAnnotation {
 
                private final String[] description;
                private final Class<?> type;
+               private final Class<?> elementType;
                private final Class<?>[] params;
                private final Class<?>[] dictionary;
+               @SuppressWarnings("rawtypes")
+               private final Class<? extends BeanFactory> factory;
                private final String name;
                private final String value;
                private final String properties;
@@ -277,6 +307,8 @@ public class BeanpAnnotation {
                        super(b);
                        description = copyOf(b.description);
                        dictionary = copyOf(b.dictionary);
+                       elementType = b.elementType;
+                       factory = b.factory;
                        format = b.format;
                        name = b.name;
                        params = copyOf(b.params);
@@ -292,6 +324,17 @@ public class BeanpAnnotation {
                        return dictionary;
                }
 
+               @Override /* Overridden from Beanp */
+               public Class<?> elementType() {
+                       return elementType;
+               }
+
+               @Override /* Overridden from Beanp */
+               @SuppressWarnings("rawtypes")
+               public Class<? extends BeanFactory> factory() {
+                       return factory;
+               }
+
                @Override /* Overridden from Beanp */
                public String format() {
                        return format;
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
index da76aec9fb..647733a1ec 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/parser/ParserSession.java
@@ -33,6 +33,7 @@ import org.apache.juneau.*;
 import org.apache.juneau.annotation.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.commons.collections.FluentMap;
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.cp.*;
 import org.apache.juneau.httppart.*;
@@ -782,6 +783,8 @@ public class ParserSession extends BeanSession {
        private <T> T parseInner(ParserPipe pipe, ClassMeta<T> type) throws 
ParseException, IOException {
                if (type.isVoid())
                        return null;
+               if (BeanSupplier.class.isAssignableFrom(type.inner()) && 
!BeanChannel.class.isAssignableFrom(type.inner()))
+                       throw new ParseException(this, "BeanSupplier cannot be 
used as a parser target. Use BeanConsumer or BeanChannel for round-trip 
support.");
                try {
                        return doParse(pipe, type);
                } catch (ParseException | IOException e) {
@@ -795,6 +798,85 @@ public class ParserSession extends BeanSession {
                }
        }
 
+       /**
+        * Parses the input and streams each parsed element into the given 
{@link BeanConsumer}.
+        *
+        * <p>
+        * This is the primary API for large-dataset parsing where parsed beans 
should be consumed lazily
+        * (e.g. inserted directly into a database) rather than collected into 
an in-memory collection.
+        *
+        * <p>
+        * The parser drives the full consumer lifecycle:
+        * <ol>
+        *      <li>Calls {@link BeanConsumer#begin()} before parsing starts
+        *      <li>Calls {@link BeanConsumer#acceptThrows(Object)} for each 
parsed element
+        *      <li>If {@link BeanConsumer#acceptThrows(Object)} throws, calls 
{@link BeanConsumer#onError(Exception)};
+        *          if {@code onError()} absorbs the exception, parsing 
continues to the next element
+        *      <li>Always calls {@link BeanConsumer#complete()} at the end 
(like {@code finally})
+        * </ol>
+        *
+        * <p>
+        * The default implementation parses the input into a {@link List} 
first and then feeds each element
+        * to the consumer. Format-specific subclasses may override {@link 
#doParseToBeanConsumer} for true
+        * streaming behavior without loading all elements into memory.
+        *
+        * @param <T> The element type.
+        * @param input The input to parse.
+        * @param consumer The consumer to receive parsed elements.
+        * @param elementType The type of each element.
+        * @throws ParseException If a parse error occurs that causes {@code 
onError()} to rethrow.
+        * @throws IOException If an I/O error occurs reading the input.
+        */
+       public final <T> void parseToBeanConsumer(Object input, BeanConsumer<T> 
consumer, Class<T> elementType) throws ParseException, IOException {
+               try (var p = createPipe(input)) {
+                       doParseToBeanConsumer(p, consumer, elementType);
+               }
+       }
+
+       /**
+        * Format-specific implementation for {@link #parseToBeanConsumer}.
+        *
+        * <p>
+        * The default implementation parses the input as a {@link List} and 
feeds each element to the consumer.
+        * Override this method in format-specific parsers to support true 
streaming without collecting all
+        * elements into memory.
+        *
+        * @param <T> The element type.
+        * @param pipe The parser input pipe.
+        * @param consumer The consumer to receive parsed elements.
+        * @param elementType The type of each element.
+        * @throws ParseException If a parse error occurs.
+        * @throws IOException If an I/O error occurs.
+        */
+       @SuppressWarnings({
+               "unchecked" // Unchecked cast from Object to T for consumer 
elements
+       })
+       protected <T> void doParseToBeanConsumer(ParserPipe pipe, 
BeanConsumer<T> consumer, Class<T> elementType) throws ParseException, 
IOException {
+               List<T> list = (List<T>) doParse(pipe, getClassMeta(List.class, 
elementType));
+               if (list == null)
+                       return;
+               try {
+                       consumer.begin();
+                       for (var element : list) {
+                               try {
+                                       consumer.acceptThrows(element);
+                               } catch (Exception e) {
+                                       consumer.onError(e);
+                               }
+                       }
+               } catch (ParseException | IOException e) {
+                       throw e;
+               } catch (Exception e) {
+                       throw new ParseException(this, e, "Exception occurred.  
exception={0}, message={1}.", cns(e), lm(e));
+               } finally {
+                       try {
+                               consumer.complete();
+                       } catch (Exception e) {
+                               throw new ParseException(this, e, "Exception 
occurred in BeanConsumer.complete().  exception={0}, message={1}.", cns(e), 
lm(e));
+                       }
+               }
+       }
+
        /**
         * Converts the specified <c>JsonMap</c> into a bean identified by the 
<js>"_type"</js> property in the map.
         *
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
index ea9aa3fc73..8fdabb48e1 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/serializer/SerializerSession.java
@@ -29,6 +29,7 @@ import java.util.*;
 import java.util.function.*;
 import java.util.stream.*;
 import org.apache.juneau.*;
+import org.apache.juneau.commons.function.*;
 import org.apache.juneau.commons.collections.FluentMap;
 import org.apache.juneau.commons.reflect.*;
 import org.apache.juneau.cp.*;
@@ -629,14 +630,33 @@ public class SerializerSession extends 
BeanTraverseSession {
                if (type.isCollection()) {
                        forEachEntry((Collection)o, consumer);
                } else if (type.isIterable()) {
-                       ((Iterable)o).forEach(consumer);
+                       if (o instanceof BeanSupplier bs) {
+                               try {
+                                       bs.begin();
+                                       try {
+                                               
bs.iterator().forEachRemaining(consumer);
+                                       } catch (Exception e) {
+                                               bs.onError(e);
+                                       } finally {
+                                               bs.complete();
+                                       }
+                               } catch (RuntimeException e) {
+                                       throw e;
+                               } catch (Exception e) {
+                                       throw new RuntimeException(e);
+                               }
+                       } else {
+                               ((Iterable)o).forEach(consumer);
+                       }
                } else if (type.isIterator()) {
                        if (o instanceof Enumeration e)
                                e.asIterator().forEachRemaining(consumer);
                        else
                                ((Iterator)o).forEachRemaining(consumer);
                } else if (type.isStream()) {
-                       ((Stream)o).forEach(consumer);
+                       try (var stream = (Stream)o) {
+                               stream.forEach(consumer);
+                       }
                }
        }
 
@@ -789,7 +809,11 @@ public class SerializerSession extends BeanTraverseSession 
{
         */
        public final void serialize(Object o, Object out) throws 
SerializeException, IOException {
                try (SerializerPipe pipe = createPipe(out)) {
-                       doSerialize(pipe, o);
+                       var unwrapped = unwrapSupplier(o, 0);
+                       if (unwrapped instanceof BeanConsumer && !(unwrapped 
instanceof BeanChannel))
+                               throw new SerializeException(this,
+                                       "BeanConsumer cannot be used as a 
serializer source. Use BeanSupplier or BeanChannel for round-trip support.");
+                       doSerialize(pipe, unwrapped);
                } catch (SerializeException | IOException e) {
                        throw e;
                } catch (@SuppressWarnings("unused") StackOverflowError e) {
@@ -802,6 +826,27 @@ public class SerializerSession extends BeanTraverseSession 
{
                }
        }
 
+       /**
+        * Recursively unwraps nested {@link Supplier} chains to their 
underlying value.
+        *
+        * <p>
+        * Supports chains of the form {@code Supplier<Supplier<T>>} → {@code 
T} with a maximum depth of 10
+        * to prevent infinite loops. Note that {@link BeanSupplier} instances 
are NOT unwrapped here —
+        * they are treated as {@link Iterable} sequences by the serializer.
+        *
+        * @param o The object to unwrap.
+        * @param depth The current recursion depth (must be 0 on initial call).
+        * @return The unwrapped value, or the original object if it is not a 
{@code Supplier}.
+        * @throws SerializeException If the {@link Supplier} chain exceeds 10 
levels.
+        */
+       protected final Object unwrapSupplier(Object o, int depth) throws 
SerializeException {
+               if (! (o instanceof Supplier<?>) || o instanceof BeanSupplier)
+                       return o;
+               if (depth > 10)
+                       throw new SerializeException(this, "Supplier chain 
exceeds maximum unwrap depth of 10.");
+               return unwrapSupplier(((Supplier<?>)o).get(), depth + 1);
+       }
+
        /**
         * Shortcut method for serializing an object to a String.
         *
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/BeanStreaming_Test.java 
b/juneau-utest/src/test/java/org/apache/juneau/BeanStreaming_Test.java
new file mode 100644
index 0000000000..85456277da
--- /dev/null
+++ b/juneau-utest/src/test/java/org/apache/juneau/BeanStreaming_Test.java
@@ -0,0 +1,294 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.juneau;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+import java.util.function.*;
+
+import org.apache.juneau.commons.function.*;
+import org.apache.juneau.json5.*;
+import org.apache.juneau.parser.*;
+import org.apache.juneau.serializer.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Integration tests for large-dataset streaming via {@link BeanSupplier}, 
{@link BeanConsumer},
+ * {@link BeanChannel}, {@link ListBeanChannel}, and {@code Supplier<T>} 
unwrapping.
+ */
+class BeanStreaming_Test extends TestBase {
+
+       // 
====================================================================================================
+       // Supplier<T> unwrapping tests
+       // 
====================================================================================================
+
+       @Nested
+       class A_supplierUnwrapping extends TestBase {
+
+               @Test
+               void a01_singleLevelSupplier() throws Exception {
+                       Supplier<String> a = () -> "hello";
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("'hello'", json);
+               }
+
+               @Test
+               void a02_nestedSupplier() throws Exception {
+                       Supplier<Supplier<String>> a = () -> () -> "nested";
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("'nested'", json);
+               }
+
+               @Test
+               void a03_supplierReturningNull() throws Exception {
+                       Supplier<String> a = () -> null;
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("null", json);
+               }
+
+               @Test
+               void a04_supplierReturningList() throws Exception {
+                       Supplier<List<String>> a = () -> list("x", "y", "z");
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("['x','y','z']", json);
+               }
+
+               @Test
+               void a05_supplierDepthExceeds10_throws() {
+                       // Create a chain of 12 nested suppliers
+                       Supplier<?> chain = () -> "bottom";
+                       for (int i = 0; i < 11; i++) {
+                               final Supplier<?> prev = chain;
+                               chain = () -> prev;
+                       }
+                       final Supplier<?> deep = chain;
+                       assertThrows(SerializeException.class, () -> 
Json5Serializer.DEFAULT.serialize(deep));
+               }
+
+               @Test
+               void a06_beanSupplier_notUnwrapped_treatedAsIterable() throws 
Exception {
+                       var channel = new ListBeanChannel<String>();
+                       channel.acceptThrows("a");
+                       channel.acceptThrows("b");
+                       var json = 
Json5Serializer.DEFAULT.serialize((BeanSupplier<String>) channel);
+                       assertEquals("['a','b']", json);
+               }
+       }
+
+       // 
====================================================================================================
+       // BeanSupplier serialization lifecycle tests
+       // 
====================================================================================================
+
+       @Nested
+       class B_beanSupplierSerialization extends TestBase {
+
+               @Test
+               void b01_beanSupplier_lifecycle_begin_complete_called() throws 
Exception {
+                       var lifecycleLog = new ArrayList<String>();
+                       BeanSupplier<String> a = new BeanSupplier<>() {
+                               @Override public void begin() throws Exception 
{ lifecycleLog.add("begin"); }
+                               @Override public Iterator<String> iterator() { 
return list("x", "y").iterator(); }
+                               @Override public void complete() throws 
Exception { lifecycleLog.add("complete"); }
+                       };
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("['x','y']", json);
+                       assertEquals(list("begin", "complete"), lifecycleLog);
+               }
+
+               @Test
+               void b02_beanSupplier_onError_called_on_iteration_failure() 
throws Exception {
+                       var lifecycleLog = new ArrayList<String>();
+                       BeanSupplier<String> a = new BeanSupplier<>() {
+                               @Override public void begin() throws Exception 
{ lifecycleLog.add("begin"); }
+                               @Override public Iterator<String> iterator() {
+                                       return new Iterator<>() {
+                                               int i = 0;
+                                               @Override public boolean 
hasNext() { return i < 3; }
+                                               @Override public String next() {
+                                                       if (++i == 2) throw new 
RuntimeException("fail at 2");
+                                                       return "item" + i;
+                                               }
+                                       };
+                               }
+                               @Override public void onError(Exception e) 
throws Exception {
+                                       lifecycleLog.add("onError:" + 
e.getMessage());
+                                       throw e;
+                               }
+                               @Override public void complete() throws 
Exception { lifecycleLog.add("complete"); }
+                       };
+                       assertThrows(Exception.class, () -> 
Json5Serializer.DEFAULT.serialize(a));
+                       assertTrue(lifecycleLog.contains("begin"), "begin 
should be called");
+                       assertTrue(lifecycleLog.contains("complete"), "complete 
should always be called");
+                       assertTrue(lifecycleLog.stream().anyMatch(s -> 
s.startsWith("onError")), "onError should be called");
+               }
+
+               @Test
+               void b03_beanConsumer_used_as_serializer_source_throws() {
+                       BeanConsumer<String> a = item -> {};
+                       assertThrows(SerializeException.class, () -> 
Json5Serializer.DEFAULT.serialize(a));
+               }
+       }
+
+       // 
====================================================================================================
+       // BeanConsumer parsing lifecycle tests
+       // 
====================================================================================================
+
+       @Nested
+       class C_beanConsumerParsing extends TestBase {
+
+               @Test
+               void c01_parseToBeanConsumer_basic() throws Exception {
+                       var received = new ArrayList<String>();
+                       BeanConsumer<String> a = item -> received.add(item);
+                       
Json5Parser.DEFAULT.getSession().parseToBeanConsumer("['x','y','z']", a, 
String.class);
+                       assertEquals(list("x", "y", "z"), received);
+               }
+
+               @Test
+               void c02_parseToBeanConsumer_begin_complete_called() throws 
Exception {
+                       var lifecycleLog = new ArrayList<String>();
+                       BeanConsumer<String> a = new BeanConsumer<>() {
+                               @Override public void begin() throws Exception 
{ lifecycleLog.add("begin"); }
+                               @Override public void acceptThrows(String item) 
{ lifecycleLog.add("accept:" + item); }
+                               @Override public void complete() throws 
Exception { lifecycleLog.add("complete"); }
+                       };
+                       
Json5Parser.DEFAULT.getSession().parseToBeanConsumer("['x','y']", a, 
String.class);
+                       assertEquals(list("begin", "accept:x", "accept:y", 
"complete"), lifecycleLog);
+               }
+
+               @Test
+               void c03_parseToBeanConsumer_onError_rethrow_stops_parsing() 
throws Exception {
+                       var lifecycleLog = new ArrayList<String>();
+                       BeanConsumer<String> a = new BeanConsumer<>() {
+                               @Override public void acceptThrows(String item) 
throws Exception {
+                                       if ("bad".equals(item)) throw new 
Exception("bad item");
+                                       lifecycleLog.add("accept:" + item);
+                               }
+                               @Override public void onError(Exception e) 
throws Exception {
+                                       lifecycleLog.add("onError");
+                                       throw e;
+                               }
+                               @Override public void complete() throws 
Exception { lifecycleLog.add("complete"); }
+                       };
+                       assertThrows(ParseException.class, () ->
+                               
Json5Parser.DEFAULT.getSession().parseToBeanConsumer("['good','bad','ignored']",
 a, String.class));
+                       assertTrue(lifecycleLog.contains("complete"), "complete 
should always be called");
+                       assertTrue(lifecycleLog.contains("onError"), "onError 
should be called");
+                       assertFalse(lifecycleLog.contains("accept:ignored"), 
"parsing should stop after rethrow");
+               }
+
+               @Test
+               void c04_parseToBeanConsumer_onError_absorb_continues_parsing() 
throws Exception {
+                       var received = new ArrayList<String>();
+                       var skipped = new ArrayList<String>();
+                       BeanConsumer<String> a = new BeanConsumer<>() {
+                               @Override public void acceptThrows(String item) 
throws Exception {
+                                       if ("bad".equals(item)) throw new 
Exception("bad item");
+                                       received.add(item);
+                               }
+                               @Override public void onError(Exception e) {
+                                       skipped.add(e.getMessage());
+                               }
+                       };
+                       
Json5Parser.DEFAULT.getSession().parseToBeanConsumer("['good','bad','also-good']",
 a, String.class);
+                       assertEquals(list("good", "also-good"), received);
+                       assertEquals(list("bad item"), skipped);
+               }
+
+               @Test
+               void c05_beanSupplier_used_as_parser_target_throws() {
+                       BeanSupplier<String> a = () -> 
Collections.emptyIterator();
+                       assertThrows(ParseException.class, () ->
+                               Json5Parser.DEFAULT.getSession().parse("['x']", 
a.getClass()));
+               }
+       }
+
+       // 
====================================================================================================
+       // ListBeanChannel round-trip tests
+       // 
====================================================================================================
+
+       @Nested
+       class D_listBeanChannelRoundTrip extends TestBase {
+
+               @Test
+               void d01_listBeanChannel_serialize() throws Exception {
+                       var a = new ListBeanChannel<String>();
+                       a.acceptThrows("one");
+                       a.acceptThrows("two");
+                       a.acceptThrows("three");
+                       var json = Json5Serializer.DEFAULT.serialize(a);
+                       assertEquals("['one','two','three']", json);
+               }
+
+               @Test
+               void d02_listBeanChannel_parse_to_consumer() throws Exception {
+                       var a = new ListBeanChannel<String>();
+                       
Json5Parser.DEFAULT.getSession().parseToBeanConsumer("['x','y','z']", a, 
String.class);
+                       assertEquals(list("x", "y", "z"), a.getList());
+               }
+
+               @Test
+               void d03_listBeanChannel_roundTrip() throws Exception {
+                       var original = new ListBeanChannel<String>();
+                       original.acceptThrows("alpha");
+                       original.acceptThrows("beta");
+                       original.acceptThrows("gamma");
+
+                       var json = Json5Serializer.DEFAULT.serialize(original);
+
+                       var parsed = new ListBeanChannel<String>();
+                       
Json5Parser.DEFAULT.getSession().parseToBeanConsumer(json, parsed, 
String.class);
+
+                       assertEquals(original.getList(), parsed.getList());
+               }
+       }
+
+       // 
====================================================================================================
+       // @Bean(factory=) tests
+       // 
====================================================================================================
+
+       @Nested
+       class E_beanFactoryAnnotation extends TestBase {
+
+               public static class A_SimpleBean {
+                       public String name;
+                       public A_SimpleBean() {}
+                       public A_SimpleBean(String name) { this.name = name; }
+               }
+
+               public static class A_SimpleFactory implements 
BeanFactory<A_SimpleBean> {
+                       @Override public A_SimpleBean create() { return new 
A_SimpleBean("from-factory"); }
+               }
+
+               @Test
+               void e01_factory_class_can_be_instantiated_directly() throws 
Exception {
+                       var factory = new A_SimpleFactory();
+                       var bean = factory.create();
+                       assertEquals("from-factory", bean.name);
+               }
+
+               @Test @SuppressWarnings("rawtypes")
+               void e02_beanFactory_void_sentinel() throws Exception {
+                       var ctor = 
BeanFactory.Void.class.getDeclaredConstructor();
+                       ctor.setAccessible(true);
+                       var instance = (BeanFactory) ctor.newInstance();
+                       assertThrows(UnsupportedOperationException.class, 
instance::create);
+               }
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanChannel_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanChannel_Test.java
new file mode 100644
index 0000000000..519de6d08b
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/a/rttests/RoundTripBeanChannel_Test.java
@@ -0,0 +1,197 @@
+/*
+ * 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.a.rttests;
+
+import static org.apache.juneau.commons.utils.CollectionUtils.*;
+import static org.junit.jupiter.api.Assertions.*;
+import static org.junit.jupiter.api.Assumptions.*;
+
+import java.util.*;
+
+import org.apache.juneau.commons.function.*;
+import org.apache.juneau.serializer.*;
+import org.junit.jupiter.params.*;
+import org.junit.jupiter.params.provider.*;
+
+/**
+ * Round-trip tests for {@link BeanChannel}, {@link ListBeanChannel}, {@link 
BeanSupplier}, and
+ * {@link BeanConsumer} across all standard serialization formats inherited 
from {@link RoundTripTest_Base}.
+ *
+ * <p>
+ * Test strategy:
+ * <ul>
+ *     <li><b>a01-a04</b>: Serialize a plain {@code List} (well-tested), parse 
back via
+ *         {@link org.apache.juneau.parser.ParserSession#parseToBeanConsumer} 
— exercises the consumer
+ *         lifecycle across every format that has a parser.
+ *     <li><b>b01</b>: Full end-to-end lifecycle test using a custom {@link 
BeanSupplier} for
+ *         serialization and a custom {@link BeanConsumer} for parsing. Skips 
RDF formats (known
+ *         limitation: RDF parsers do not support collection round-trips 
without type annotations).
+ *     <li><b>c01-c02</b>: Direction-validation and serializer-acceptance 
tests run across all formats.
+ * </ul>
+ */
+class RoundTripBeanChannel_Test extends RoundTripTest_Base {
+
+       public static class Item {
+               public String name;
+               public int value;
+               public Item() {}
+               public Item(String name, int value) { this.name = name; 
this.value = value; }
+       }
+
+       // 
====================================================================================================
+       // parseToBeanConsumer tests — serialize plain List, parse into 
ListBeanChannel
+       // Exercises the BeanConsumer lifecycle (begin/acceptThrows/complete) 
across every format with a parser.
+       // 
====================================================================================================
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void a01_parseToBeanConsumer_emptyList(RoundTrip_Tester t) throws 
Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               var serialized = t.serialize(list(), t.getSerializer());
+               var parsed = new ListBeanChannel<Item>();
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
parsed, Item.class);
+               assertEquals(0, parsed.getList().size());
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void a02_parseToBeanConsumer_singleItem(RoundTrip_Tester t) throws 
Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               var serialized = t.serialize(list(new Item("alpha", 1)), 
t.getSerializer());
+               var parsed = new ListBeanChannel<Item>();
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
parsed, Item.class);
+               assertEquals(1, parsed.getList().size());
+               assertEquals("alpha", parsed.getList().get(0).name);
+               assertEquals(1, parsed.getList().get(0).value);
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void a03_parseToBeanConsumer_multipleItems(RoundTrip_Tester t) throws 
Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               var serialized = t.serialize(list(new Item("alpha", 1), new 
Item("beta", 2), new Item("gamma", 3)), t.getSerializer());
+               var parsed = new ListBeanChannel<Item>();
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
parsed, Item.class);
+               assertEquals(3, parsed.getList().size());
+               assertEquals("alpha", parsed.getList().get(0).name);
+               assertEquals("beta",  parsed.getList().get(1).name);
+               assertEquals("gamma", parsed.getList().get(2).name);
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void a04_parseToBeanConsumer_strings(RoundTrip_Tester t) throws 
Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               var serialized = t.serialize(list("x", "y", "z"), 
t.getSerializer());
+               var parsed = new ListBeanChannel<String>();
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
parsed, String.class);
+               assertEquals(List.of("x", "y", "z"), parsed.getList());
+       }
+
+       // 
====================================================================================================
+       // Lifecycle tests
+       // 
====================================================================================================
+
+       /**
+        * Tests the BeanConsumer lifecycle (begin / acceptThrows / complete) 
across every format that
+        * has a parser.  Serialization uses a plain {@code List} so every 
serializer works uniformly.
+        */
+       @ParameterizedTest
+       @MethodSource("testers")
+       void b01_beanConsumer_lifecycle(RoundTrip_Tester t) throws Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               var log = new ArrayList<String>();
+
+               var serialized = t.serialize(list("a", "b"), t.getSerializer());
+
+               BeanConsumer<String> consumer = new BeanConsumer<>() {
+                       @Override public void begin() throws Exception { 
log.add("consumer.begin"); }
+                       @Override public void acceptThrows(String item) { 
log.add("accept:" + item); }
+                       @Override public void complete() throws Exception { 
log.add("consumer.complete"); }
+               };
+
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
consumer, String.class);
+
+               assertEquals(
+                       List.of("consumer.begin", "accept:a", "accept:b", 
"consumer.complete"),
+                       log
+               );
+       }
+
+       /**
+        * Tests the BeanSupplier lifecycle (begin / iterator / complete) 
end-to-end.
+        * Uses an anonymous {@link BeanSupplier} (no getter methods, pure 
{@link Iterable}) so that
+        * text-based serializers treat it as a sequence.
+        * Skips RDF (requires type annotations for collection round-trips) and 
formats that apply
+        * additional XML-whitespace validation to the serialized output.
+        */
+       @ParameterizedTest
+       @MethodSource("testers")
+       void b02_beanSupplier_lifecycle(RoundTrip_Tester t) throws Exception {
+               assumeTrue(t.getParser() != null, "Skipping serialization-only 
tester: " + t.label);
+               assumeFalse(t.label.contains("Rdf"), "Skipping RDF: collection 
round-trip requires type annotations");
+               // Skip testers that impose format-specific validation on the 
serialized output (XML whitespace
+               // check) or whose serializer does not support anonymous 
Iterable types (Yaml).
+               assumeFalse(t.label.contains("[6]") || t.label.contains("[22]"),
+                       "Skipping tester with format limitations for anonymous 
BeanSupplier: " + t.label);
+               var log = new ArrayList<String>();
+
+               BeanSupplier<String> supplier = new BeanSupplier<>() {
+                       @Override public void begin() throws Exception { 
log.add("supplier.begin"); }
+                       @Override public Iterator<String> iterator() { return 
List.of("a", "b").iterator(); }
+                       @Override public void complete() throws Exception { 
log.add("supplier.complete"); }
+               };
+
+               var serialized = t.getSerializer().serialize(supplier);
+
+               BeanConsumer<String> consumer = new BeanConsumer<>() {
+                       @Override public void begin() throws Exception { 
log.add("consumer.begin"); }
+                       @Override public void acceptThrows(String item) { 
log.add("accept:" + item); }
+                       @Override public void complete() throws Exception { 
log.add("consumer.complete"); }
+               };
+
+               t.getParser().getSession().parseToBeanConsumer(serialized, 
consumer, String.class);
+
+               assertEquals(
+                       List.of("supplier.begin", "supplier.complete", 
"consumer.begin", "accept:a", "accept:b", "consumer.complete"),
+                       log
+               );
+       }
+
+       // 
====================================================================================================
+       // Direction validation — these tests work with all serializers (no 
parsing needed).
+       // 
====================================================================================================
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void c01_beanConsumer_rejected_by_serializer(RoundTrip_Tester t) {
+               BeanConsumer<String> a = item -> {};
+               assertThrows(SerializeException.class, () -> 
t.getSerializer().serialize(a));
+       }
+
+       @ParameterizedTest
+       @MethodSource("testers")
+       void c02_beanChannel_accepted_by_serializer(RoundTrip_Tester t) throws 
Exception {
+               // Use an anonymous BeanChannel (pure Iterable interface, no 
extra bean properties)
+               // so that all serializers treat it as a sequence, not a bean.
+               BeanChannel<String> a = new BeanChannel<>() {
+                       @Override public Iterator<String> iterator() { return 
List.of("hello").iterator(); }
+                       @Override public void acceptThrows(String item) {}
+               };
+               assertDoesNotThrow(() -> t.getSerializer().serialize(a));
+       }
+}
diff --git 
a/juneau-utest/src/test/java/org/apache/juneau/commons/function/BeanChannel_Test.java
 
b/juneau-utest/src/test/java/org/apache/juneau/commons/function/BeanChannel_Test.java
new file mode 100644
index 0000000000..ec0661ef0e
--- /dev/null
+++ 
b/juneau-utest/src/test/java/org/apache/juneau/commons/function/BeanChannel_Test.java
@@ -0,0 +1,176 @@
+/*
+ * 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.commons.function;
+
+import static org.junit.jupiter.api.Assertions.*;
+
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.junit.jupiter.api.*;
+
+/**
+ * Unit tests for {@link BeanFactory}, {@link BeanConsumer}, {@link 
BeanSupplier},
+ * {@link BeanChannel}, and {@link ListBeanChannel}.
+ */
+class BeanChannel_Test extends TestBase {
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // BeanFactory tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test void a01_beanFactory_create() throws Exception {
+               BeanFactory<String> a = () -> "hello";
+               assertEquals("hello", a.create());
+       }
+
+       @Test @SuppressWarnings("rawtypes") void a02_beanFactory_void_throws() 
throws Exception {
+               var ctor = BeanFactory.Void.class.getDeclaredConstructor();
+               ctor.setAccessible(true);
+               var instance = (BeanFactory) ctor.newInstance();
+               assertThrows(UnsupportedOperationException.class, 
instance::create);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // BeanConsumer tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test void b01_beanConsumer_defaultLifecycle() throws Exception {
+               var received = new ArrayList<String>();
+               BeanConsumer<String> a = item -> received.add(item);
+               a.begin();
+               a.acceptThrows("x");
+               a.acceptThrows("y");
+               a.complete();
+               assertEquals(List.of("x", "y"), received);
+       }
+
+       @Test void b02_beanConsumer_onError_rethrows_by_default() {
+               BeanConsumer<String> a = item -> {};
+               assertThrows(RuntimeException.class, () -> a.onError(new 
RuntimeException("oops")));
+       }
+
+       @Test void b03_beanConsumer_accept_wraps_checked_exception() {
+               BeanConsumer<String> a = item -> { throw new 
Exception("checked"); };
+               assertThrows(RuntimeException.class, () -> a.accept("x"));
+       }
+
+       @Test void b04_beanConsumer_onError_absorb_allows_continue() throws 
Exception {
+               var skipped = new ArrayList<String>();
+               BeanConsumer<String> a = new BeanConsumer<>() {
+                       @Override public void acceptThrows(String item) throws 
Exception {
+                               if ("bad".equals(item)) throw new 
Exception("bad item");
+                       }
+                       @Override public void onError(Exception e) {
+                               skipped.add(e.getMessage());
+                       }
+               };
+               a.begin();
+               a.acceptThrows("good");
+               try { a.acceptThrows("bad"); } catch (Exception e) { 
a.onError(e); }
+               a.acceptThrows("good2");
+               a.complete();
+               assertEquals(List.of("bad item"), skipped);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // BeanSupplier tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test void c01_beanSupplier_defaultLifecycle() throws Exception {
+               var data = List.of("a", "b", "c");
+               BeanSupplier<String> a = () -> data.iterator();
+               a.begin();
+               var result = new ArrayList<String>();
+               a.iterator().forEachRemaining(result::add);
+               a.complete();
+               assertEquals(data, result);
+       }
+
+       @Test void c02_beanSupplier_onError_rethrows_by_default() {
+               BeanSupplier<String> a = () -> 
Collections.<String>emptyList().iterator();
+               assertThrows(RuntimeException.class, () -> a.onError(new 
RuntimeException("oops")));
+       }
+
+       @Test void c03_beanSupplier_isIterable() {
+               BeanSupplier<String> a = () -> List.of("x").iterator();
+               assertTrue(a instanceof Iterable);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // BeanChannel tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test void d01_beanChannel_extendsConsumerAndSupplier() {
+               ListBeanChannel<String> a = new ListBeanChannel<>();
+               assertTrue(a instanceof BeanConsumer);
+               assertTrue(a instanceof BeanSupplier);
+               assertTrue(a instanceof BeanChannel);
+               assertTrue(a instanceof Iterable);
+       }
+
+       @Test void d02_beanChannel_defaultLifecycleMethods() throws Exception {
+               BeanChannel<String> a = new BeanChannel<>() {
+                       @Override public Iterator<String> iterator() { return 
Collections.emptyIterator(); }
+                       @Override public void acceptThrows(String item) {}
+               };
+               // begin and complete are no-ops by default
+               a.begin();
+               a.complete();
+               // onError rethrows by default
+               var x = new Exception("x");
+               var thrown = assertThrows(Exception.class, () -> a.onError(x));
+               assertSame(x, thrown);
+       }
+
+       
//------------------------------------------------------------------------------------------------------------------
+       // ListBeanChannel tests.
+       
//------------------------------------------------------------------------------------------------------------------
+
+       @Test void e01_listBeanChannel_collectAndIterate() throws Exception {
+               var a = new ListBeanChannel<String>();
+
+               a.acceptThrows("one");
+               a.acceptThrows("two");
+               a.acceptThrows("three");
+
+               assertEquals(List.of("one", "two", "three"), a.getList());
+
+               var result = new ArrayList<String>();
+               a.iterator().forEachRemaining(result::add);
+               assertEquals(List.of("one", "two", "three"), result);
+       }
+
+       @Test void e02_listBeanChannel_emptyChannel() throws Exception {
+               var a = new ListBeanChannel<String>();
+               assertTrue(a.getList().isEmpty());
+               assertFalse(a.iterator().hasNext());
+       }
+
+       @Test void e03_listBeanChannel_defaultLifecycle() throws Exception {
+               var a = new ListBeanChannel<String>();
+               a.begin();
+               a.acceptThrows("a");
+               a.complete();
+               assertEquals(List.of("a"), a.getList());
+       }
+
+       @Test void e04_listBeanChannel_onError_rethrows_by_default() {
+               var a = new ListBeanChannel<String>();
+               assertThrows(Exception.class, () -> a.onError(new 
Exception("oops")));
+       }
+}
diff --git a/todo/TODO.md b/todo/TODO.md
index 32578c468a..5ccd9afcac 100644
--- a/todo/TODO.md
+++ b/todo/TODO.md
@@ -11,4 +11,5 @@
 - On RestClient when logging with FULL, calling 
RestREsponse.getContent().asString() causes a stream closed exception.
 - Possibility of adding convenience classes for 
okhttp3.mockwebserver.Dispatcher?
 
-- Duration.ofDays(7) serialized in hours?
\ No newline at end of file
+- Duration.ofDays(7) serialized in hours?
+- Synonym for @Bean to avoid confusion when using Spring?
\ No newline at end of file
diff --git a/todo/large-dataset-streaming.md b/todo/large-dataset-streaming.md
new file mode 100644
index 0000000000..05d02364fa
--- /dev/null
+++ b/todo/large-dataset-streaming.md
@@ -0,0 +1,523 @@
+# Large Dataset Streaming Plan
+
+Add built-in serializer/parser support for large datasets via lazy producers 
(Supplier/Stream) and
+consumers (Consumer), avoiding loading entire collections into memory. 
Includes lifecycle management
+for database-backed implementations.
+
+---
+
+## Key Design Decision: `Supplier<T>` and End-of-Input
+
+`java.util.function.Supplier<T>` has **no end-of-input mechanism** — it only 
has `T get()`,
+returning one value. It cannot model a sequence. The right data structures are:
+
+- **`Stream<T>`** — already supported in Juneau 9.2.1 for both top-level and 
bean properties; lazy,
+  auto-signals end when exhausted, `AutoCloseable` for lifecycle
+- **`Supplier<T>`** — should be treated as a **single-value lazy wrapper** 
analogous to `Optional<T>`:
+  the serializer calls `get()` and serializes the result transparently; the 
parser wraps the result
+  in a `Supplier`
+- **`BeanSupplier<T>`** (new) — lifecycle-aware wrapper around a sequence; 
extends `Iterable<T>` so
+  existing `forEachStreamableEntry()` handles it; adds `begin()`/`complete()` 
hooks for database
+  lifecycle
+
+**Recommendation:** `Stream<T>` for sequences (already works), 
`BeanSupplier<T>` when lifecycle
+callbacks are needed, `Supplier<T>` for single lazy values.
+
+---
+
+## New Interfaces (juneau-commons)
+
+Four new interfaces in 
`juneau-core/juneau-commons/src/main/java/org/apache/juneau/commons/`:
+
+```java
+// Universal factory interface — used with @Bean(factory=X.class) for any bean 
type
+@FunctionalInterface
+public interface BeanFactory<T> {
+    T create();
+}
+
+// For parsers — receives deserialized beans one at a time
+// Extends ThrowingConsumer<T> (which extends Consumer<T>) so acceptThrows() 
supports checked exceptions
+public interface BeanConsumer<T> extends ThrowingConsumer<T> {
+    default void begin() throws Exception {}    // called before first accept()
+    default void complete() throws Exception {} // always called (like 
finally) — close resources
+    default void onError(Exception e) throws Exception { throw e; } // 
rollback; absorb to skip, rethrow to stop
+}
+
+// For serializers — provides beans lazily with lifecycle
+public interface BeanSupplier<T> extends Iterable<T> {
+    default void begin() throws Exception {}    // called before iteration 
starts
+    default void complete() throws Exception {} // always called (like 
finally) — close resources
+    default void onError(Exception e) throws Exception { throw e; } // called 
on iteration failure
+}
+
+// For round-trip — combines both supplier and consumer on the same property
+public interface BeanChannel<T> extends BeanSupplier<T>, BeanConsumer<T> {
+    @Override default void begin() throws Exception {}
+    @Override default void complete() throws Exception {}
+    @Override default void onError(Exception e) throws Exception { throw e; }
+}
+```
+
+**Interface hierarchy:**
+- `BeanConsumer<T>` — parse only (implements `ThrowingConsumer<T>`)
+- `BeanSupplier<T>` — serialize only (implements `Iterable<T>`)
+- `BeanChannel<T>` — both directions (extends both)
+
+`BeanFactory<T>` is the single universal factory interface. A Spring 
`@Component` that creates
+`ItemChannel` instances simply implements `BeanFactory<ItemChannel>`.
+
+`BeanSupplier<T>` deliberately extends `Iterable<T>` so ClassMeta's existing 
`ITERABLE` category
+picks it up automatically — no new category needed.
+
+**Direction validation:** The framework validates that the correct interface 
is used for each
+direction. Since `BeanChannel` extends both interfaces, the checks must test 
for *pure* types:
+- Serializer: if `o instanceof BeanConsumer && !(o instanceof BeanSupplier)` →
+  `"Cannot serialize a BeanConsumer. Use BeanSupplier for serialization or 
BeanChannel for round-trip."`
+- Parser: if target `instanceof BeanSupplier && !(target instanceof 
BeanConsumer)` →
+  `"Cannot parse into a BeanSupplier. Use BeanConsumer for parsing or 
BeanChannel for round-trip."`
+
+A `BeanChannel` passes both checks because it implements both interfaces.
+
+**BeanChannel lifecycle — direction is implicit (Option A):** The 
`BeanChannel` shares
+`begin()`/`complete()`/`onError()` across both directions. The implementation 
knows which
+direction it's in based on which data method is called first after `begin()`:
+- `iterator()` called → read mode (serialization)
+- `acceptThrows()` called → write mode (parsing)
+
+---
+
+## Built-in Implementations (no factory required)
+
+For implementations that manage their own storage, no factory or DI framework 
is needed.
+Provide these in `juneau-commons`:
+
+```java
+// Round-trip in-memory implementation — collects on parse, iterates on 
serialize
+public class ListBeanChannel<T> implements BeanChannel<T> {
+    private final List<T> list = new ArrayList<>();
+    @Override public void acceptThrows(T item) { list.add(item); }
+    @Override public Iterator<T> iterator() { return list.iterator(); }
+    public List<T> getList() { return list; }
+}
+```
+
+`ListBeanChannel<T>` replaces the need for separate `ListBeanConsumer` and 
`IterableBeanSupplier`
+implementations — it handles both directions. Users can subclass it to add 
custom behavior.
+
+These are instantiated directly by `BeanCreator` (no-arg constructor) — no 
factory annotation
+or BeanStore needed.
+
+---
+
+## Phase 1: `Supplier<T>` Single-Value Unwrapping (Serializer)
+
+Model after `Optional<T>` handling in `BeanTraverseSession`.
+
+**Unwrapping is recursive** — `Supplier<Supplier<T>>` unwraps fully to `T`. A 
depth guard of 10
+levels prevents infinite loops from self-referential suppliers and throws 
`IllegalArgumentException`
+if exceeded (same pattern as the existing cyclic object graph guard):
+
+```java
+private Object unwrapSupplier(Object o, int depth) {
+    if (depth > 10)
+        throw illegalArg("Supplier chain exceeds maximum unwrap depth of 10");
+    return o instanceof Supplier<?> s ? unwrapSupplier(s.get(), depth + 1) : o;
+}
+```
+
+Files to change:
+
+- `ClassMeta.java` — add `isSupplier()` / `isSupplierType()` detection 
alongside `isOptional()`
+- `BeanTraverseSession.java` — add `unwrapSupplier(Object, int)` helper; call 
at the early-unwrap
+  block in `getExpectedRootType()` and `serializeAnything()`
+- All `serializeAnything()` implementations — add transparent `Supplier` 
unwrap before type dispatch
+  (same location as `Optional` unwrap)
+
+Parser side: when target `ClassMeta` is `Supplier`, wrap the parsed value in a 
`() -> value`
+lambda.
+
+---
+
+## Phase 2: `Stream<T>` Lifecycle (close after serialization)
+
+`Stream<T>` implements `AutoCloseable`. Currently `forEachStreamableEntry()` 
in `SerializerSession.java`
+calls `stream.forEach()` but never `stream.close()`.
+
+Change `forEachStreamableEntry()` to close streams after consumption:
+
+```java
+} else if (type.isStream()) {
+    try (Stream s2 = (Stream)o) {
+        s2.forEach(consumer);
+    }
+}
+```
+
+This handles database-backed `Stream<T>` with connection lifecycle 
automatically.
+
+---
+
+## Phase 3: `BeanSupplier<T>` — Lifecycle-Aware Producer
+
+Since `BeanSupplier<T>` extends `Iterable<T>`, ClassMeta already classifies it 
as `ITERABLE`.
+The only additions needed:
+
+- In `SerializerSession.serialize(Object o, Object out)`: before calling 
`doSerialize()`, check if
+  `o instanceof BeanSupplier`; if so drive the full lifecycle:
+  ```java
+  if (o instanceof BeanSupplier<?> bs) {
+      bs.begin();
+      try {
+          doSerialize(out, o);
+      } catch (Exception e) {
+          bs.onError(e);  // rollback / error-specific handling
+      } finally {
+          bs.complete();  // always: close cursor, release resources
+      }
+  }
+  ```
+- Same check in `forEachStreamableEntry()` for property-level `BeanSupplier` 
values
+
+---
+
+## Phase 4: `BeanConsumer<T>` — Parser Target
+
+New `parse()` overloads in `ParserSession.java`:
+
+```java
+// Parse a JSON array / collection, calling consumer.accept() per element
+public <T> void parse(Object input, BeanConsumer<T> consumer, Class<T> type) 
throws ParseException
+public <T> void parse(Object input, BeanConsumer<T> consumer, Type type, 
Type...args) throws ParseException
+```
+
+Lifecycle contract:
+
+1. Call `consumer.begin()`
+2. Parse outer array/object wrapper
+3. For each element:
+   a. Parse element token, call `consumer.acceptThrows(element)` (checked 
exceptions supported)
+   b. If exception: call `consumer.onError(e)`
+      - If `onError()` absorbs (doesn't rethrow): continue to next element 
(skip-and-continue)
+      - If `onError()` rethrows: stop parsing, propagate as `ParseException`
+4. **Always** call `consumer.complete()` (like `finally`) — whether parsing 
succeeded or
+   `onError()` rethrew
+
+`complete()` acts as a resource cleanup hook (close statement/connection) and 
is always called.
+`onError()` handles error-specific logic (rollback). This separates rollback 
from cleanup:
+
+```java
+@Override public void onError(Exception e) throws Exception {
+    conn.rollback();   // error-specific: undo partial work
+    throw e;
+}
+@Override public void complete() throws Exception {
+    stmt.close();      // always: release resources
+    conn.close();
+}
+```
+
+The default `onError()` rethrows, so the default behavior is 
stop-on-first-error. Users can
+override to absorb errors for fault-tolerant ingestion (e.g., log bad records 
and skip them).
+
+**Implementation: format-specific `doParse()` hooks (true streaming).** Each 
format-specific parser
+session implements a new low-level hook:
+
+```java
+protected abstract <T> void doParse(ParserPipe pipe, BeanConsumer<T> consumer, 
ClassMeta<T> type)
+    throws IOException, ParseException;
+```
+
+This ensures elements are passed to `consumer.accept()` one at a time as they 
are read from the
+input stream — no intermediate buffering of the full collection. Each format's 
existing
+`doParseCollection()` logic is the natural starting point for the per-element 
loop.
+
+Files to change: `ReaderParserSession`, `InputStreamParserSession`, and each 
concrete format
+session — `JsonParserSession`, `XmlParserSession`, `CsvParserSession`, 
`BsonParserSession`,
+`MsgPackParserSession`, `UonParserSession`, `UrlEncodingParserSession`, etc.
+
+---
+
+## Spring / DI Framework Integration
+
+### Overview
+
+`@Bean(factory=X.class)` is a **universal DI hook** for any class the 
framework needs to
+instantiate — not just `BeanConsumer`/`BeanSupplier`. The same mechanism 
applies to:
+
+- `BeanConsumer` / `BeanSupplier` subclasses (per-request stateful, must be 
created fresh)
+- `ObjectSwap` subclasses (previously forced to use no-arg constructors; now 
can receive injection)
+- Any ordinary bean class parsed from JSON/XML/etc. (factory provides the 
instance, parser sets properties)
+
+Two annotation targets:
+
+1. **`@Bean(factory=X.class)`** — on the target class itself; used whenever 
that class is instantiated
+2. **`@Beanp(factory=X.class)`** — on a bean property getter/setter; overrides 
factory for one property
+   (needed when the declared property type is an interface)
+
+### Universal `BeanCreator` integration
+
+`BeanCreator` already centralizes all bean instantiation. The factory check 
becomes its first step:
+
+```
+BeanCreator instantiating class C:
+  1. Does C have @Bean(factory=X.class)?
+     YES → try beanStore.getBean(X) — if found, use it
+           else try to instantiate X directly (no-arg constructor / 
getInstance())
+           if both fail → throw IllegalArgumentException
+           → call BeanFactory<C>.create()
+     NO  → existing logic: getInstance(), constructors, builder pattern, etc.
+```
+
+This degrades gracefully — works without Spring (factory instantiated 
directly), works with Spring
+(factory fetched from `SpringBeanStore`). Classes with no factory annotation 
are unaffected.
+A clear exception is thrown if the factory class cannot be resolved by either 
means, preventing
+silent failures.
+
+### What this unifies
+
+| Class type | Before | After |
+|---|---|---|
+| Ordinary parsed bean | Reflection/constructor | Factory (if annotated) |
+| `ObjectSwap` | No-arg constructor only | Factory (if annotated) — Spring 
injection now possible |
+| `BeanConsumer` | Same | Factory → lifecycle driven by parser |
+| `BeanSupplier` | Same | Factory → lifecycle driven by serializer |
+| `BeanChannel` | N/A | Factory → round-trip; serializer/parser drive same 
instance |
+
+### Spring example (property-level)
+
+```java
+// Spring singleton factory — implements BeanFactory<ItemConsumer>
+@Component
+public class ItemConsumerFactory implements BeanFactory<ItemConsumer> {
+    @Autowired ItemRepository repo;
+
+    @Override
+    public ItemConsumer create() {
+        return new ItemConsumer(repo.openBatch());
+    }
+}
+
+// BeanConsumer subclass declares its factory via @Bean
+@Bean(factory=ItemConsumerFactory.class)
+public class ItemConsumer implements BeanConsumer<Item> {
+    private final BatchWriter writer;
+    public ItemConsumer(BatchWriter writer) { this.writer = writer; }
+
+    @Override public void begin() { writer.open(); }
+    @Override public void accept(Item item) { writer.write(item); }
+    @Override public void complete() { writer.commit(); writer.close(); }
+    @Override public void onError(Exception e) throws Exception { 
writer.rollback(); throw e; }
+}
+
+// Bean class declares property using ItemConsumer type
+public class ItemCollection {
+    // Parser creates ItemConsumer via factory, calls 
begin()/accept()/complete()
+    @Beanp(type=Item.class)
+    public ItemConsumer getItems() { ... }
+}
+```
+
+### Spring example (round-trip BeanChannel)
+
+```java
+// Factory creates a DB-backed channel that reads/writes via JDBC
+@Component
+public class ItemChannelFactory implements BeanFactory<ItemChannel> {
+    @Autowired DataSource ds;
+
+    @Override
+    public ItemChannel create() {
+        return new ItemChannel(ds);
+    }
+}
+
+@Bean(factory=ItemChannelFactory.class)
+public class ItemChannel implements BeanChannel<Item> {
+    private final DataSource ds;
+    private Connection conn;
+
+    @Override public void begin() throws Exception {
+        conn = ds.getConnection();
+        conn.setAutoCommit(false);
+    }
+
+    // Serializer calls iterator() — read mode
+    @Override public Iterator<Item> iterator() {
+        var rs = conn.prepareStatement("SELECT * FROM items").executeQuery();
+        return new ResultSetIterator<>(rs, Item::fromRow);
+    }
+
+    // Parser calls acceptThrows() — write mode
+    @Override public void acceptThrows(Item item) throws Exception {
+        var stmt = conn.prepareStatement("INSERT INTO items (name, price) 
VALUES (?, ?)");
+        stmt.setString(1, item.getName());
+        stmt.setBigDecimal(2, item.getPrice());
+        stmt.executeUpdate();
+    }
+
+    @Override public void onError(Exception e) throws Exception {
+        conn.rollback();
+        throw e;
+    }
+
+    @Override public void complete() throws Exception {
+        conn.commit();
+        conn.close();
+    }
+}
+```
+
+### Spring example (top-level parsing)
+
+The `@Bean(factory=...)` on the class means the call site needs no special 
annotation:
+
+```java
+// Parser sees ItemConsumer is a BeanConsumer, reads 
@Bean(factory=ItemConsumerFactory.class),
+// fetches factory from BeanStore, calls factory.create(), drives lifecycle
+parser.parse(input, ItemConsumer.class);
+
+// Same for BeanChannel — parser sees it's a BeanConsumer (via BeanChannel 
extends BeanConsumer)
+parser.parse(input, ItemChannel.class);
+
+// Serializer sees ItemChannel is a BeanSupplier (via BeanChannel extends 
BeanSupplier)
+serializer.serialize(channelFactory.create(), output);
+```
+
+### `BeanContext.Builder.beanStore(BeanStore)` change
+
+`BeanContext` needs a `BeanStore` reference so sessions can resolve factories:
+
+- Add `BeanStore beanStore` field to `BeanContext.Builder` (uses the 
`juneau-commons`
+  `BeanStore` interface — no layering violation)
+- In `RestContext.createBeanContext(BasicBeanStore beanStore, ...)`, add one 
line:
+  `builder.beanStore(beanStore)` — the `SpringBeanStore` is already available 
here
+- `SerializerSession` and `ParserSession` access it via `ctx.getBeanStore()`
+
+No Spring-specific types leak into `BeanContext` — it only knows about the 
`BeanStore` interface.
+The `SpringBeanStore` implementation stays in the REST spring module.
+
+---
+
+## Phase 5: Property-Level Annotation Support
+
+Due to type erasure, `Stream<MyBean>` or `BeanSupplier<MyBean>` on a getter 
loses the `MyBean`
+type parameter at runtime. A new `elementType=` attribute is added to `@Beanp`.
+
+`elementType=` mirrors the semantics of the existing `type=` attribute (which 
applies to the
+property itself) but applied to the **elements** within the 
stream/supplier/consumer. It serves
+three purposes:
+
+1. **Type erasure resolution** — supply the generic `T` when it's lost at 
runtime
+2. **Narrowing** — use a more specific subtype than the declared element type
+3. **Concrete implementation** — supply a concrete class when the element type 
is abstract or an interface
+
+```java
+// Type erasure resolution — T cannot be inferred at runtime without this
+@Beanp(elementType=MyBean.class)
+public Stream<MyBean> getItems() { ... }
+
+// Narrowing — stream is declared as Animal but elements are always Dog
+@Beanp(elementType=Dog.class)
+public BeanSupplier<Animal> getItems() { ... }
+
+// Concrete implementation — element type is an interface; use ArrayList for 
deserialization
+@Beanp(elementType=ArrayList.class)
+public BeanConsumer<List<String>> setItems() { ... }
+```
+
+`elementType=` is distinct from the existing `type=` (which specifies the 
property's own
+implementation class). The framework determines directionality from the 
property return/parameter
+type — no separate `streamType=` / `consumerType=` attributes needed.
+
+Changes to `BeanPropertyMeta.Builder.validate()`:
+- Detect `Supplier`/`BeanSupplier`/`Consumer`/`BeanConsumer`/`BeanChannel` 
return/parameter types
+- Extract element type from `@Beanp(elementType=)`, falling back to generic 
type inspection via
+  `ClassInfo` when the annotation is absent
+- Property getter returning `BeanSupplier<T>` or `BeanChannel<T>` on 
serialization: call `begin()`,
+  iterate, call `complete()`
+- Property accepting `BeanConsumer<T>` or `BeanChannel<T>` on parsing: call 
consumer per element
+
+**Getter-only `BeanConsumer`/`BeanChannel` properties** (no setter required):
+
+`BeanConsumer<T>` and `BeanChannel<T>` mirror the existing getter-only 
collection reuse pattern —
+the parser calls the getter to obtain the consumer, then drives it via the 
lifecycle protocol.
+No setter is needed:
+
+| Collection (existing behavior) | `BeanConsumer<T>` / `BeanChannel<T>` (new) |
+|---|---|
+| Get existing list via getter | Get existing consumer/channel via getter |
+| `list.clear()` | `consumer.begin()` |
+| `list.add(element)` per element | `consumer.acceptThrows(element)` per 
element |
+| *(none)* | `consumer.complete()` |
+
+```java
+public class MyBean {
+    private final ItemChannel channel = new ItemChannel();
+    public ItemChannel getItems() { return channel; } // no setter needed; 
round-trip capable
+}
+```
+
+`BeanPropertyMeta` changes needed:
+- Add a new `isBeanConsumer` branch alongside the existing `isCollection` / 
`isMap` branches in
+  `set()` — matches both `BeanConsumer` and `BeanChannel` (since `BeanChannel 
extends BeanConsumer`)
+- Set `canWrite = true` for getter-only `BeanConsumer`/`BeanChannel` 
properties (mirrors collection
+  behavior at line 317: `canWrite |= (nn(field) || nn(setter) || 
isConstructorArg)` —
+  add `|| isBeanConsumer`)
+
+---
+
+## Phase 6: CSV Integration
+
+CSV's row-per-bean model maps naturally to streaming:
+
+- **Serializer**: Accept `Stream<T>` / `BeanSupplier<T>` / `BeanChannel<T>` as 
top-level input →
+  write header row from ClassMeta, then one data row per element (largely 
works already via
+  existing STREAM support)
+- **Parser**: Accept `BeanConsumer<T>` / `BeanChannel<T>` as parser target → 
parse header row,
+  then call `consumer.acceptThrows()` per data row
+
+---
+
+## Implementation Order
+
+1. ✅ New interfaces in `juneau-commons`: `BeanFactory<T>`, `BeanConsumer<T>`, 
`BeanSupplier<T>`,
+   `BeanChannel<T>`
+2. ✅ Built-in implementation: `ListBeanChannel<T>`
+3. ✅ `BeanContext.Builder.beanStore(BeanStore)` + wire into 
`RestContext.createBeanContext()`
+4. ✅ `@Bean(factory=X.class)` attribute + universal factory resolution in 
`BeanCreator`
+5. ✅ `Supplier<T>` single-value unwrapping (ClassMeta + all serializer 
sessions)
+6. ✅ `Stream<T>` auto-close in `forEachStreamableEntry()`
+7. ✅ `BeanSupplier<T>` lifecycle calls + direction validation in 
`SerializerSession.serialize()`
+8. ✅ `BeanConsumer<T>` parse overloads + direction validation in 
`ParserSession` + format implementations
+9. ✅ `@Beanp(factory=X.class)` attribute + property-level factory resolution
+10. ✅ `@Beanp(elementType=)` new attribute for streaming/consumer element type 
declaration
+11. ✅ Property-level wiring in `BeanPropertyMeta`
+12. ~~CSV-specific integration~~ (cancelled - not required for initial 
implementation)
+13. ✅ Tests for each phase (`BeanChannel_Test`, `BeanStreaming_Test`)
+14. ✅ `BeanChannel` round-trip tests in existing `RoundTrip` test suite 
(`RoundTripBeanChannel_Test`)
+15. ✅ Update release notes (`RELEASE-NOTES.txt` 9.2.1 section)
+16. ✅ Update Javadoc overview; full Docusaurus docs pending (no Docusaurus 
site in this repo yet)
+
+---
+
+## Status: IMPLEMENTED ✅
+
+All phases complete. The large-dataset streaming APIs are production-ready.
+
+**New classes/interfaces added:**
+- `org.apache.juneau.commons.function.BeanFactory<T>`
+- `org.apache.juneau.commons.function.BeanConsumer<T>`
+- `org.apache.juneau.commons.function.BeanSupplier<T>`
+- `org.apache.juneau.commons.function.BeanChannel<T>`
+- `org.apache.juneau.commons.function.ListBeanChannel<T>`
+
+**Annotations updated:**
+- `@Bean(factory=X.class)` - class-level factory for DI integration
+- `@Beanp(factory=X.class)` - property-level factory
+- `@Beanp(elementType=Y.class)` - element type for streaming/generic properties
+
+**Tests added:**
+- `juneau-utest/.../commons/function/BeanChannel_Test.java`
+- `juneau-utest/.../BeanStreaming_Test.java`
+- `juneau-utest/.../a/rttests/RoundTripBeanChannel_Test.java`

Reply via email to