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

reta pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/cxf.git


The following commit(s) were added to refs/heads/main by this push:
     new 7f09b5eae4 CXF-9136: Add EntityPart MessageBodyReader / 
MessageBodyWriter support (#2698)
7f09b5eae4 is described below

commit 7f09b5eae4420a101d07b08147d6ad08f0161ac3
Author: Andriy Redko <[email protected]>
AuthorDate: Tue Nov 11 14:20:57 2025 -0500

    CXF-9136: Add EntityPart MessageBodyReader / MessageBodyWriter support 
(#2698)
---
 .../attachment/AttachmentBoundaryDeserializer.java |  93 +++++++++
 .../cxf/attachment/AttachmentDeserializer.java     | 147 +-------------
 .../cxf/attachment/AttachmentDeserializerUtil.java | 175 +++++++++++++++++
 .../CloseableAttachmentDeserializer.java           |  26 +++
 .../cxf/attachment/DelegatingInputStream.java      |   7 +-
 .../cxf/jaxrs/impl/EntityPartBuilderImpl.java      |  93 +++++----
 .../org/apache/cxf/jaxrs/impl/EntityPartImpl.java  | 140 ++++++++++---
 .../cxf/jaxrs/provider/EntityPartProvider.java     | 216 +++++++++++++++++++++
 .../apache/cxf/jaxrs/provider/ProviderFactory.java |   3 +-
 .../apache/cxf/jaxrs/utils/EntityPartUtils.java    | 109 +++++++++++
 .../java/org/apache/cxf/jaxrs/utils/FormUtils.java |  19 ++
 .../org/apache/cxf/jaxrs/utils/InjectionUtils.java |   5 +
 .../org/apache/cxf/jaxrs/utils/JAXRSUtils.java     |  11 +-
 ...e.java => ParameterizedCollectionBaseType.java} |  16 +-
 .../jaxrs/utils/ParameterizedCollectionType.java   |  30 +--
 .../cxf/jaxrs/utils/ParameterizedListType.java     |  28 +++
 .../cxf/jaxrs/provider/ProviderFactoryTest.java    |  54 +++---
 .../apache/cxf/systest/jaxrs/EntityPartServer.java |  62 ++++++
 .../apache/cxf/systest/jaxrs/EntityPartStore.java  | 101 ++++++++++
 .../cxf/systest/jaxrs/JAXRSEntityPartTest.java     | 172 ++++++++++++++++
 20 files changed, 1226 insertions(+), 281 deletions(-)

diff --git 
a/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
new file mode 100644
index 0000000000..72675a0e5c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentBoundaryDeserializer.java
@@ -0,0 +1,93 @@
+/**
+ * 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.cxf.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+import java.util.Map;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.message.Attachment;
+import org.apache.cxf.message.Message;
+import org.apache.cxf.message.MessageUtils;
+
+public class AttachmentBoundaryDeserializer {
+    private static final Pattern INPUT_STREAM_BOUNDARY_PATTERN = 
Pattern.compile("^--(\\S*)$", Pattern.MULTILINE);
+    private static final int PUSHBACK_AMOUNT = 2048;
+    
+    private final int maxHeaderLength;
+    private final Message message;
+
+    public AttachmentBoundaryDeserializer(Message message) {
+        this.message = message;
+        this.maxHeaderLength = MessageUtils.getContextualInteger(message, 
+            AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE, 
AttachmentDeserializer.DEFAULT_MAX_HEADER_SIZE);
+    }
+
+    public Attachment read(InputStream body) throws IOException {
+        final PushbackInputStream stream = new PushbackInputStream(body, 
PUSHBACK_AMOUNT);
+        final String boundaryString = findBoundaryFromInputStream(stream);
+
+        // If a boundary still wasn't found, throw an exception
+        if (null == boundaryString) {
+            throw new IOException("Couldn't determine the boundary from the 
message!");
+        }
+
+        final byte[] boundary = 
boundaryString.getBytes(StandardCharsets.UTF_8);
+        if (!AttachmentDeserializerUtil.readTillFirstBoundary(stream, 
boundary)) {
+            throw new IOException("Couldn't find MIME boundary: " + 
boundaryString);
+        }
+
+        Map<String, List<String>> ih = 
AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength);
+        String val = AttachmentUtil.getHeader(ih, "Content-Transfer-Encoding");
+
+        MimeBodyPartInputStream mmps = new MimeBodyPartInputStream(stream, 
boundary, PUSHBACK_AMOUNT);
+        InputStream ins = AttachmentUtil.decode(mmps, val);
+        if (ins != mmps) {
+            ih.remove("Content-Transfer-Encoding");
+        }
+
+        return AttachmentUtil.createAttachment(new DelegatingInputStream(ins, 
is -> stream.close()), ih, message);
+    }
+
+    private String findBoundaryFromInputStream(PushbackInputStream stream) 
throws IOException {
+        //boundary should definitely be in the first 2K;
+        byte[] buf = new byte[2048];
+        int i = stream.read(buf);
+        int len = i;
+        while (i > 0 && len < buf.length) {
+            i = stream.read(buf, len, buf.length - len);
+            if (i > 0) {
+                len += i;
+            }
+        }
+        String msg = IOUtils.newStringFromBytes(buf, 0, len);
+        stream.unread(buf, 0, len);
+
+        // Use regex to get the boundary and return null if it's not found
+        Matcher m = INPUT_STREAM_BOUNDARY_PATTERN.matcher(msg);
+        return m.find() ? "--" + m.group(1) : null;
+    }
+}
diff --git 
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
index 9782c8be11..925fd98b23 100644
--- a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
+++ b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializer.java
@@ -30,13 +30,10 @@ import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
-import java.util.TreeMap;
-import java.util.logging.Logger;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import jakarta.activation.DataSource;
-import org.apache.cxf.common.logging.LogUtils;
 import org.apache.cxf.common.util.StringUtils;
 import org.apache.cxf.common.util.SystemPropertyAction;
 import org.apache.cxf.helpers.HttpHeaderHelper;
@@ -84,8 +81,6 @@ public class AttachmentDeserializer {
     private static final Pattern INPUT_STREAM_BOUNDARY_PATTERN =
             Pattern.compile("^--(\\S*)$", Pattern.MULTILINE);
 
-    private static final Logger LOG = 
LogUtils.getL7dLogger(AttachmentDeserializer.class);
-
     private static final int PUSHBACK_AMOUNT = 2048;
 
     private boolean lazyLoading = true;
@@ -161,11 +156,11 @@ public class AttachmentDeserializer {
             boundary = boundaryString.getBytes(StandardCharsets.UTF_8);
 
             stream = new 
PushbackInputStream(message.getContent(InputStream.class), PUSHBACK_AMOUNT);
-            if (!readTillFirstBoundary(stream, boundary)) {
+            if (!AttachmentDeserializerUtil.readTillFirstBoundary(stream, 
boundary)) {
                 throw new IOException("Couldn't find MIME boundary: " + 
boundaryString);
             }
 
-            Map<String, List<String>> ih = loadPartHeaders(stream);
+            Map<String, List<String>> ih = 
AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength);
             message.put(ATTACHMENT_PART_HEADERS, ih);
             String val = AttachmentUtil.getHeader(ih, "Content-Type", "; ");
             if (!StringUtils.isEmpty(val)) {
@@ -230,7 +225,7 @@ public class AttachmentDeserializer {
         }
         stream.unread(v);
 
-        Map<String, List<String>> headers = loadPartHeaders(stream);
+        Map<String, List<String>> headers = 
AttachmentDeserializerUtil.loadPartHeaders(stream, maxHeaderLength);
         return (AttachmentImpl)createAttachment(headers);
     }
 
@@ -271,48 +266,6 @@ public class AttachmentDeserializer {
         }
     }
 
-    /**
-     * Move the read pointer to the begining of the first part read till the 
end
-     * of first boundary
-     *
-     * @param pushbackInStream
-     * @param boundary
-     * @throws IOException
-     */
-    private static boolean readTillFirstBoundary(PushbackInputStream 
pushbackInStream,
-        byte[] boundary) throws IOException {
-
-        // work around a bug in PushBackInputStream where the buffer isn't
-        // initialized
-        // and available always returns 0.
-        int value = pushbackInStream.read();
-        pushbackInStream.unread(value);
-        while (value != -1) {
-            value = pushbackInStream.read();
-            if ((byte) value == boundary[0]) {
-                int boundaryIndex = 0;
-                while (value != -1 
-                    && boundaryIndex < boundary.length 
-                    && (byte)value == boundary[boundaryIndex]) {
-
-                    value = pushbackInStream.read();
-                    if (value == -1) {
-                        throw new IOException("Unexpected End while searching 
for first Mime Boundary");
-                    }
-                    boundaryIndex++;
-                }
-                if (boundaryIndex == boundary.length) {
-                    // boundary found, read the newline
-                    if (value == 13) {
-                        pushbackInStream.read();
-                    }
-                    return true;
-                }
-            }
-        }
-        return false;
-    }
-
     /**
      * Create an Attachment from the MIME stream. If there is a previous 
attachment
      * that is not read, cache that attachment.
@@ -366,98 +319,4 @@ public class AttachmentDeserializer {
         stream.unread(v);
         return true;
     }
-
-
-
-    private Map<String, List<String>> loadPartHeaders(InputStream in) throws 
IOException {
-        StringBuilder buffer = new StringBuilder(128);
-        StringBuilder b = new StringBuilder(128);
-        Map<String, List<String>> heads = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
-
-        // loop until we hit the end or a null line
-        while (readLine(in, b)) {
-            // lines beginning with white space get special handling
-            char c = b.charAt(0);
-            if (c == ' ' || c == '\t') {
-                if (buffer.length() != 0) {
-                    // preserve the line break and append the continuation
-                    buffer.append("\r\n");
-                    buffer.append(b);
-                }
-            } else {
-                // if we have a line pending in the buffer, flush it
-                if (buffer.length() > 0) {
-                    addHeaderLine(heads, buffer);
-                    buffer.setLength(0);
-                }
-                // add this to the accumulator
-                buffer.append(b);
-            }
-        }
-
-        // if we have a line pending in the buffer, flush it
-        if (buffer.length() > 0) {
-            addHeaderLine(heads, buffer);
-        }
-        return heads;
-    }
-
-    private boolean readLine(InputStream in, StringBuilder buffer) throws 
IOException {
-        if (buffer.length() != 0) {
-            buffer.setLength(0);
-        }
-        int c;
-
-        while ((c = in.read()) != -1) {
-            // a linefeed is a terminator, always.
-            if (c == '\n') {
-                break;
-            } else if (c == '\r') {
-                //just ignore the CR.  The next character SHOULD be an NL.  If 
not, we're
-                //just going to discard this
-                continue;
-            } else {
-                // just add to the buffer
-                buffer.append((char)c);
-            }
-
-            if (buffer.length() > maxHeaderLength) {
-                LOG.fine("The attachment header size has exceeded the 
configured parameter: " + maxHeaderLength);
-                throw new HeaderSizeExceededException();
-            }
-        }
-
-        // no characters found...this was either an eof or a null line.
-        return buffer.length() != 0;
-    }
-
-    private void addHeaderLine(Map<String, List<String>> heads, StringBuilder 
line) {
-        // null lines are a nop
-        final int size = line.length();
-        if (size == 0) {
-            return;
-        }
-        int separator = line.indexOf(":");
-        final String name;
-        String value = "";
-        if (separator == -1) {
-            name = line.toString().trim();
-        } else {
-            name = line.substring(0, separator);
-            // step past the separator.  Now we need to remove any leading 
white space characters.
-            separator++;
-
-            while (separator < size) {
-                char ch = line.charAt(separator);
-                if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
-                    break;
-                }
-                separator++;
-            }
-            value = line.substring(separator);
-        }
-        List<String> v = heads.computeIfAbsent(name, k -> new ArrayList<>(1));
-        v.add(value);
-    }
-
 }
diff --git 
a/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
new file mode 100644
index 0000000000..98ee9a468c
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cxf/attachment/AttachmentDeserializerUtil.java
@@ -0,0 +1,175 @@
+/**
+ * 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.cxf.attachment;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.PushbackInputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.TreeMap;
+import java.util.logging.Logger;
+
+import org.apache.cxf.common.logging.LogUtils;
+
+final class AttachmentDeserializerUtil {
+    /* Keep the log under AttachmentDeserializer */
+    private static final Logger LOG = 
LogUtils.getL7dLogger(AttachmentDeserializer.class);
+
+    private AttachmentDeserializerUtil() {
+    }
+    
+    /**
+     * Move the read pointer to the begining of the first part read till the 
end
+     * of first boundary
+     *
+     * @param pushbackInStream
+     * @param boundary
+     * @throws IOException
+     */
+    static boolean readTillFirstBoundary(PushbackInputStream pushbackInStream,
+        byte[] boundary) throws IOException {
+
+        // work around a bug in PushBackInputStream where the buffer isn't
+        // initialized
+        // and available always returns 0.
+        int value = pushbackInStream.read();
+        pushbackInStream.unread(value);
+        while (value != -1) {
+            value = pushbackInStream.read();
+            if ((byte) value == boundary[0]) {
+                int boundaryIndex = 0;
+                while (value != -1 
+                    && boundaryIndex < boundary.length 
+                    && (byte)value == boundary[boundaryIndex]) {
+
+                    value = pushbackInStream.read();
+                    if (value == -1) {
+                        throw new IOException("Unexpected End while searching 
for first Mime Boundary");
+                    }
+                    boundaryIndex++;
+                }
+                if (boundaryIndex == boundary.length) {
+                    // boundary found, read the newline
+                    if (value == 13) {
+                        pushbackInStream.read();
+                    }
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+
+    static Map<String, List<String>> loadPartHeaders(InputStream in, int 
maxHeaderLength) throws IOException {
+        StringBuilder buffer = new StringBuilder(128);
+        StringBuilder b = new StringBuilder(128);
+        Map<String, List<String>> heads = new 
TreeMap<>(String.CASE_INSENSITIVE_ORDER);
+
+        // loop until we hit the end or a null line
+        while (readLine(in, b, maxHeaderLength)) {
+            // lines beginning with white space get special handling
+            char c = b.charAt(0);
+            if (c == ' ' || c == '\t') {
+                if (buffer.length() != 0) {
+                    // preserve the line break and append the continuation
+                    buffer.append("\r\n");
+                    buffer.append(b);
+                }
+            } else {
+                // if we have a line pending in the buffer, flush it
+                if (buffer.length() > 0) {
+                    addHeaderLine(heads, buffer);
+                    buffer.setLength(0);
+                }
+                // add this to the accumulator
+                buffer.append(b);
+            }
+        }
+
+        // if we have a line pending in the buffer, flush it
+        if (buffer.length() > 0) {
+            addHeaderLine(heads, buffer);
+        }
+        return heads;
+    }
+
+    private static boolean readLine(InputStream in, StringBuilder buffer, int 
maxHeaderLength) throws IOException {
+        if (buffer.length() != 0) {
+            buffer.setLength(0);
+        }
+        int c;
+
+        while ((c = in.read()) != -1) {
+            // a linefeed is a terminator, always.
+            if (c == '\n') {
+                break;
+            } else if (c == '\r') {
+                //just ignore the CR.  The next character SHOULD be an NL.  If 
not, we're
+                //just going to discard this
+                continue;
+            } else {
+                // just add to the buffer
+                buffer.append((char)c);
+            }
+
+            if (buffer.length() > maxHeaderLength) {
+                LOG.fine("The attachment header size has exceeded the 
configured parameter: " + maxHeaderLength);
+                throw new HeaderSizeExceededException();
+            }
+        }
+
+        // no characters found...this was either an eof or a null line.
+        return buffer.length() != 0;
+    }
+
+    private static void addHeaderLine(Map<String, List<String>> heads, 
StringBuilder line) {
+        // null lines are a nop
+        final int size = line.length();
+        if (size == 0) {
+            return;
+        }
+        int separator = line.indexOf(":");
+        final String name;
+        String value = "";
+        if (separator == -1) {
+            name = line.toString().trim();
+        } else {
+            name = line.substring(0, separator);
+            // step past the separator.  Now we need to remove any leading 
white space characters.
+            separator++;
+
+            while (separator < size) {
+                char ch = line.charAt(separator);
+                if (ch != ' ' && ch != '\t' && ch != '\r' && ch != '\n') {
+                    break;
+                }
+                separator++;
+            }
+            value = line.substring(separator);
+        }
+        List<String> v = heads.computeIfAbsent(name, k -> new ArrayList<>(1));
+        v.add(value);
+    }
+
+
+}
diff --git 
a/core/src/main/java/org/apache/cxf/attachment/CloseableAttachmentDeserializer.java
 
b/core/src/main/java/org/apache/cxf/attachment/CloseableAttachmentDeserializer.java
new file mode 100644
index 0000000000..e047a6670e
--- /dev/null
+++ 
b/core/src/main/java/org/apache/cxf/attachment/CloseableAttachmentDeserializer.java
@@ -0,0 +1,26 @@
+/**
+ * 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.cxf.attachment;
+
+import java.io.IOException;
+
+interface CloseableAttachmentDeserializer {
+    void markClosed(DelegatingInputStream is) throws IOException;
+}
diff --git 
a/core/src/main/java/org/apache/cxf/attachment/DelegatingInputStream.java 
b/core/src/main/java/org/apache/cxf/attachment/DelegatingInputStream.java
index cc6c771168..fac31883da 100644
--- a/core/src/main/java/org/apache/cxf/attachment/DelegatingInputStream.java
+++ b/core/src/main/java/org/apache/cxf/attachment/DelegatingInputStream.java
@@ -29,13 +29,18 @@ import org.apache.cxf.io.Transferable;
 
 public class DelegatingInputStream extends InputStream implements Transferable 
{
     private InputStream is;
-    private AttachmentDeserializer deserializer;
+    private CloseableAttachmentDeserializer deserializer;
     private boolean isClosed;
 
     DelegatingInputStream(InputStream is, AttachmentDeserializer ads) {
+        this(is, ads::markClosed);
+    }
+
+    DelegatingInputStream(InputStream is, CloseableAttachmentDeserializer ads) 
{
         this.is = is;
         deserializer = ads;
     }
+
     DelegatingInputStream(InputStream is) {
         this.is = is;
         deserializer = null;
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartBuilderImpl.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartBuilderImpl.java
index 6d4a0e00fe..e5ae991fa6 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartBuilderImpl.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartBuilderImpl.java
@@ -19,11 +19,10 @@
 
 package org.apache.cxf.jaxrs.impl;
 
-import java.io.ByteArrayInputStream;
-import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
-import java.util.Objects;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Type;
 
 import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.EntityPart;
@@ -32,7 +31,11 @@ import jakarta.ws.rs.core.GenericType;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedHashMap;
 import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.ext.ContextResolver;
+import jakarta.ws.rs.ext.ExceptionMapper;
+import jakarta.ws.rs.ext.MessageBodyReader;
 import jakarta.ws.rs.ext.MessageBodyWriter;
+import jakarta.ws.rs.ext.Providers;
 import org.apache.cxf.BusFactory;
 import org.apache.cxf.jaxrs.provider.ProviderFactory;
 import org.apache.cxf.jaxrs.provider.ServerProviderFactory;
@@ -87,7 +90,6 @@ public class EntityPartBuilderImpl implements 
EntityPart.Builder {
     @Override
     public Builder content(InputStream in) throws IllegalArgumentException {
         this.content = in;
-        this.type = InputStream.class;
         return this;
     }
 
@@ -109,57 +111,54 @@ public class EntityPartBuilderImpl implements 
EntityPart.Builder {
 
     @Override
     public EntityPart build() throws IllegalStateException, IOException, 
WebApplicationException {
-        final MediaType mt = Objects.requireNonNullElse(mediaType, 
MediaType.APPLICATION_OCTET_STREAM_TYPE);
-
-        Message message = JAXRSUtils.getCurrentMessage();
-        ProviderFactory factory = null;
+        return new EntityPartImpl(getProviderFactory(), name, fileName, 
content, type, genericType, headers, mediaType);
+    }
 
+    private static Providers getProviderFactory() {
+        final Message message = JAXRSUtils.getCurrentMessage();
         if (message == null) {
-            message = new MessageImpl();
-            factory = 
ServerProviderFactory.createInstance(BusFactory.getThreadDefaultBus());
-        } else {
-            factory = ProviderFactory.getInstance(message);
-        }
-
-        if (genericType != null) {
-            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-                writeTo(factory, genericType, mt, message, out);
-                return new EntityPartImpl(factory, name, fileName, new 
ByteArrayInputStream(out.toByteArray()),
-                    headers, mt);
-            }
-        } else if (type != null) {
-            try (ByteArrayOutputStream out = new ByteArrayOutputStream()) {
-                writeTo(factory, type, mt, message, out);
-                return new EntityPartImpl(factory, name, fileName, new 
ByteArrayInputStream(out.toByteArray()),
-                    headers, mt);
-            }
+            return new 
ReaderWriterProviders(ServerProviderFactory.createInstance(BusFactory.getThreadDefaultBus()));
         } else {
-            throw new IllegalStateException("Either type or genericType is 
expected for the content");
+            return new 
ReaderWriterProviders(ProviderFactory.getInstance(message));
         }
     }
+    
 
-    @SuppressWarnings("unchecked")
-    private <T> void writeTo(final ProviderFactory providers, final 
GenericType<T> t, final MediaType mt,
-            final Message message, final ByteArrayOutputStream out) throws 
IOException {
-
-        final MessageBodyWriter<T> writer = (MessageBodyWriter<T>) providers
-            .createMessageBodyWriter(t.getRawType(), t.getType(), null, mt, 
message);
+    private static final class ReaderWriterProviders implements Providers {
+        private final ProviderFactory factory;
 
-        writer.writeTo((T) content, t.getRawType(), t.getType(), null, mt, 
cast(headers), out);
-    }
+        private ReaderWriterProviders(final ProviderFactory factory) {
+            this.factory = factory;
+        }
 
-    @SuppressWarnings("unchecked")
-    private <T> void writeTo(final ProviderFactory providers, final Class<T> 
t, final MediaType mt,
-            final Message message, final ByteArrayOutputStream out) throws 
IOException {
+        @Override
+        public <T> MessageBodyWriter<T> getMessageBodyWriter(Class<T> type, 
Type genericType,
+                Annotation[] annotations, MediaType mediaType) {
+            return factory.createMessageBodyWriter(type, genericType, 
annotations, mediaType, getCurrentMessage());
+        }
+        
+        @Override
+        public <T> MessageBodyReader<T> getMessageBodyReader(Class<T> type, 
Type genericType,
+                Annotation[] annotations, MediaType mediaType) {
+            return factory.createMessageBodyReader(type, genericType, 
annotations, mediaType, getCurrentMessage());
+        }
+        
+        @Override
+        public <T extends Throwable> ExceptionMapper<T> 
getExceptionMapper(Class<T> type) {
+            throw new UnsupportedOperationException();
+        }
 
-        final MessageBodyWriter<T> writer = (MessageBodyWriter<T>) providers
-                .createMessageBodyWriter(t, null, null, mt, message);
+        @Override
+        public <T> ContextResolver<T> getContextResolver(Class<T> contextType, 
MediaType mediaType) {
+            throw new UnsupportedOperationException();
+        }
 
-        writer.writeTo((T) content, t, null, null, mt, cast(headers), out);
-    }
-    
-    @SuppressWarnings("unchecked")
-    private static <T, U> MultivaluedMap<T, U> cast(MultivaluedMap<?, ?> p) {
-        return (MultivaluedMap<T, U>)p;
-    }
+        private static Message getCurrentMessage() {
+            Message message = JAXRSUtils.getCurrentMessage();
+            if (message == null) { 
+                message = new MessageImpl();
+            }
+            return message;
+        }
+    };
 }
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
index d302768b66..0c1a68a3f6 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/impl/EntityPartImpl.java
@@ -19,8 +19,16 @@
 
 package org.apache.cxf.jaxrs.impl;
 
+import java.io.ByteArrayInputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.PipedInputStream;
+import java.io.PipedOutputStream;
+import java.io.UncheckedIOException;
+import java.lang.reflect.Type;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.Objects;
 import java.util.Optional;
 
 import jakarta.ws.rs.WebApplicationException;
@@ -29,27 +37,40 @@ import jakarta.ws.rs.core.GenericType;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
 import jakarta.ws.rs.ext.MessageBodyReader;
-import org.apache.cxf.jaxrs.provider.ProviderFactory;
-import org.apache.cxf.jaxrs.utils.JAXRSUtils;
-import org.apache.cxf.message.Message;
+import jakarta.ws.rs.ext.MessageBodyWriter;
+import jakarta.ws.rs.ext.Providers;
 
 public class EntityPartImpl implements EntityPart {
     private final String name;
     private final String fileName;
-    private final InputStream content;
+    private final Object content;
     private final MultivaluedMap<String, String> headers;
     private final MediaType mediaType;
-    private final ProviderFactory providers;
+    private final GenericType<?> genericType;
+    private final Class<?> type;
+    private final Providers providers;
 
-    EntityPartImpl(final ProviderFactory providers, String name, String 
fileName, InputStream content,
-            MultivaluedMap<String, String> headers, MediaType mediaType) {
+    //CHECKSTYLE:OFF
+    public EntityPartImpl(Providers providers, String name, String fileName, 
Object content, Class<?> type,
+            GenericType<?> genericType, MultivaluedMap<String, String> 
headers, MediaType mediaType) {
         this.providers = providers;
         this.name = name;
         this.fileName = fileName;
         this.content = content;
         this.headers = headers;
         this.mediaType = mediaType;
+        this.genericType = genericType;
+        if (type == null) {
+            if (content != null) {
+                this.type = content.getClass();
+            } else {
+                this.type = InputStream.class;
+            }
+        } else {
+            this.type = type;
+        }
     }
+    //CHECKSTYLE:ON
 
     @Override
     public String getName() {
@@ -63,32 +84,98 @@ public class EntityPartImpl implements EntityPart {
 
     @Override
     public InputStream getContent() {
-        return content;
+        try {
+            if (content instanceof InputStream) {
+                return (InputStream) content;
+            }  else if (content instanceof byte[]) { 
+                return new ByteArrayInputStream((byte[]) content);
+            } else if (fileName != null && !fileName.isBlank()) { 
+                return Files.newInputStream(Path.of(fileName));
+            } else {
+                return contentAsStream();
+            } 
+        } catch (final IOException ex) {
+            throw new UncheckedIOException(ex);
+        }
     }
 
+    @SuppressWarnings("unchecked")
+    private <T> InputStream contentAsStream() throws IOException {
+        final MediaType mt = Objects.requireNonNullElse(mediaType, 
MediaType.APPLICATION_OCTET_STREAM_TYPE);
+        final Type generic = (genericType != null) ? genericType.getType() : 
null;
+
+        final MessageBodyWriter<T> writer = (MessageBodyWriter<T>) providers
+            .getMessageBodyWriter(type, generic, null, mt);
+
+        if (writer == null) {
+            throw new IllegalArgumentException("No suitable MessageBodyWriter 
available to handle "
+                + type.getName() + ", media type " + mediaType);
+        }
+
+        final PipedInputStream pipedInputStream = new PipedInputStream();
+        try (PipedOutputStream pipedOutputStream = new 
PipedOutputStream(pipedInputStream)) {
+            writer.writeTo((T)content, (Class<T>)type, generic, null, mt, 
cast(headers), pipedOutputStream);
+            return pipedInputStream;
+        }
+    }
+
+    @SuppressWarnings("unchecked")
     @Override
-    public <T> T getContent(Class<T> type) 
-            throws IllegalArgumentException, IllegalStateException, 
IOException, WebApplicationException {
-        final Message message = JAXRSUtils.getCurrentMessage();
+    public <T> T getContent(Class<T> asType) throws IllegalArgumentException, 
IllegalStateException,
+            IOException, WebApplicationException {
+
+        if (asType == null) {
+            throw new NullPointerException("The type is required");
+        }
 
-        @SuppressWarnings({ "unchecked", "rawtypes" })
-        final MessageBodyReader<T> reader = (MessageBodyReader) providers
-            .createMessageBodyReader(type, null, null, mediaType, message);
+        if (asType.isInstance(content)) {
+            return (T) content;
+        }
 
-        return reader.readFrom(type, null, null, mediaType, headers, content);
+        final MessageBodyReader<T> reader = (MessageBodyReader<T>) providers
+            .getMessageBodyReader(asType, null, null, mediaType);
+
+        if (reader != null) {
+            // The implementation is required to close the content stream when 
this method
+            // is invoked, so it may only be invoked once. 
+            try (InputStream is = getContent()) {
+                return reader.readFrom(asType, null, null, mediaType, headers, 
is);
+            }
+        } else {
+            throw new IllegalArgumentException("No suitable MessageBodyReader 
available to handle "
+                + asType.getName() + ", media type " + mediaType);
+        }
     }
 
     @SuppressWarnings("unchecked")
     @Override
-    public <T> T getContent(GenericType<T> type)
-            throws IllegalArgumentException, IllegalStateException, 
IOException, WebApplicationException {
-        final Message message = JAXRSUtils.getCurrentMessage();
-        
-        @SuppressWarnings("rawtypes")
-        final MessageBodyReader<T> reader = (MessageBodyReader) providers
-            .createMessageBodyReader(type.getRawType(), type.getType(), null, 
mediaType, message);
-
-        return reader.readFrom((Class<T>) type.getRawType(), type.getType(), 
null, mediaType, headers, content);
+    public <T> T getContent(GenericType<T> asType) throws 
IllegalArgumentException, IllegalStateException,
+            IOException, WebApplicationException {
+
+        if (asType == null) {
+            throw new NullPointerException("The generic type is required");
+        }
+
+        if (asType.getRawType().isInstance(content)) {
+            return (T) content;
+        }
+
+        final MessageBodyReader<T> reader = (MessageBodyReader<T>) providers
+            .getMessageBodyReader(asType.getRawType(), asType.getType(), null, 
mediaType);
+
+        // The implementation is required to close the content stream when 
this method
+        // is invoked, so it may only be invoked once. 
+        if (reader != null) {
+            // The implementation is required to close the content stream when 
this method
+            // is invoked, so it may only be invoked once. 
+            try (InputStream is = getContent()) {
+                return reader.readFrom((Class<T>) asType.getRawType(), 
asType.getType(),
+                    null, mediaType, headers, is);
+            }
+        } else {
+            throw new IllegalArgumentException("No suitable MessageBodyReader 
available to handle "
+                + asType.getRawType().getName() + ", media type " + mediaType);
+        }
     }
 
     @Override
@@ -100,4 +187,9 @@ public class EntityPartImpl implements EntityPart {
     public MediaType getMediaType() {
         return mediaType;
     }
+    
+    @SuppressWarnings("unchecked")
+    private static <T, U> MultivaluedMap<T, U> cast(MultivaluedMap<?, ?> p) {
+        return (MultivaluedMap<T, U>)p;
+    }
 }
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
new file mode 100644
index 0000000000..4323ede07c
--- /dev/null
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/EntityPartProvider.java
@@ -0,0 +1,216 @@
+/**
+ * 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.cxf.jaxrs.provider;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.Type;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Objects;
+import java.util.logging.Logger;
+
+import jakarta.activation.DataHandler;
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.Context;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedHashMap;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.ext.MessageBodyReader;
+import jakarta.ws.rs.ext.MessageBodyWriter;
+import jakarta.ws.rs.ext.Provider;
+import jakarta.ws.rs.ext.Providers;
+import org.apache.cxf.attachment.AttachmentUtil;
+import org.apache.cxf.common.logging.LogUtils;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.helpers.CastUtils;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartOutputFilter;
+import org.apache.cxf.jaxrs.impl.EntityPartImpl;
+import org.apache.cxf.jaxrs.utils.ExceptionUtils;
+import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
+
+/**
+ * Jakarta RESTful Web Services 3.1: 
+ * {@code java.util.List<EntityPart>}, Multipart data ( multipart/form-data )
+ */
+@Consumes({ "multipart/form-data" })
+@Produces({ "multipart/form-data" })
+@Provider
+public class EntityPartProvider extends AbstractConfigurableProvider
+        implements MessageBodyReader<List<EntityPart>>, 
MessageBodyWriter<List<EntityPart>> {
+    private static final Logger LOG = 
LogUtils.getL7dLogger(EntityPartProvider.class);
+
+    @Context private Providers providers;
+    @Context private MessageContext mc;
+    private String attachmentDir;
+    private String attachmentThreshold;
+    private String attachmentMaxSize;
+
+    public void setAttachmentDirectory(String dir) {
+        attachmentDir = dir;
+    }
+
+    public void setAttachmentThreshold(String threshold) {
+        attachmentThreshold = threshold;
+    }
+
+    public void setAttachmentMaxSize(String maxSize) {
+        attachmentMaxSize = maxSize;
+    }
+
+    @Override
+    public boolean isWriteable(Class<?> type, Type genericType,
+            Annotation[] annotations, MediaType mediaType) {
+
+        if (List.class.isAssignableFrom(type) && 
ParameterizedType.class.isInstance(genericType)) {
+            final Type arg = 
((ParameterizedType)genericType).getActualTypeArguments()[0];
+            if (arg instanceof Class<?>) {
+                return EntityPart.class.isAssignableFrom((Class<?>)arg);
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public void writeTo(List<EntityPart> parts, Class<?> type, Type 
genericType,
+            Annotation[] anns, MediaType mediaType,
+            MultivaluedMap<String, Object> httpHeaders,
+            OutputStream os)
+            throws IOException, WebApplicationException {
+
+        final List<Attachment> handlers = convertToDataHandlers(parts, type, 
genericType, anns);
+        if (mc.get(AttachmentUtils.OUT_FILTERS) != null) {
+            List<MultipartOutputFilter> filters = 
CastUtils.cast((List<?>)mc.get(AttachmentUtils.OUT_FILTERS));
+            for (MultipartOutputFilter filter : filters) {
+                filter.filter(handlers);
+            }
+        }
+
+        mc.put(MultipartBody.OUTBOUND_MESSAGE_ATTACHMENTS, handlers);
+        handlers.get(0).getDataHandler().writeTo(os);
+    }
+
+    @Override
+    public boolean isReadable(Class<?> type, Type genericType, Annotation[] 
annotations, MediaType mediaType) {
+        if (List.class.isAssignableFrom(type) && 
ParameterizedType.class.isInstance(genericType)) {
+            final Type arg = 
((ParameterizedType)genericType).getActualTypeArguments()[0];
+            if (arg instanceof Class<?>) {
+                return EntityPart.class.isAssignableFrom((Class<?>)arg);
+            }
+        }
+
+        return false;
+    }
+
+    @Override
+    public List<EntityPart> readFrom(final Class<List<EntityPart>> type, final 
Type genericType,
+            final Annotation[] anns, final MediaType mediaType, final 
MultivaluedMap<String, String> headers,
+                final InputStream is) throws IOException, 
WebApplicationException {
+
+        checkContentLength();
+        final List<Attachment> infos = AttachmentUtils.getAttachments(
+                mc, attachmentDir, attachmentThreshold, attachmentMaxSize);
+
+        final List<EntityPart> parts = new ArrayList<>();
+        for (Attachment a : infos) {
+            parts.add(fromAttachment(a, InputStream.class, genericType, anns, 
headers, mediaType));
+        }
+
+        return parts;
+    }
+    
+    private <T> EntityPart fromAttachment(Attachment attachment, Class<T> c, 
final Type type, Annotation[] anns, 
+                final MultivaluedMap<String, String> headers, final MediaType 
mediaType) throws IOException {
+
+        final GenericType<T> genericType = (type != null) ? new 
GenericType<>(type) : null;
+        final MessageBodyReader<T> r = providers.getMessageBodyReader(c, type, 
anns, attachment.getContentType());
+        if (r != null) {
+            final InputStream is = 
attachment.getDataHandler().getInputStream();
+            final ContentDisposition cd = attachment.getContentDisposition();
+            
+            final String fileName = (cd != null) ? cd.getFilename() : null;
+            final String cdName = cd == null ? null : cd.getParameter("name");
+            final String contentId = attachment.getContentId();
+            final String name = StringUtils.isEmpty(cdName) ? contentId : 
cdName.replace("\"", "").replace("'", "");
+
+            if (!StringUtils.isEmpty(fileName)) {
+                return new EntityPartImpl(providers, name, fileName, is, c, 
genericType, headers, mediaType);
+            } else {
+                return new EntityPartImpl(providers, name, null, is, c, 
genericType, headers, mediaType);
+            }
+        } else {
+            throw ExceptionUtils.toBadRequestException(null, null);
+        }
+    }
+
+    protected void checkContentLength() {
+        if (mc != null && isPayloadEmpty(mc.getHttpHeaders())) {
+            String message = new 
org.apache.cxf.common.i18n.Message("EMPTY_BODY", BUNDLE).toString();
+            LOG.warning(message);
+            throw new WebApplicationException(400);
+        }
+    }
+
+    private static <T> List<Attachment> convertToDataHandlers(final 
List<EntityPart> parts, final  Class<T> type,
+            final Type genericType, final Annotation[] anns)  throws 
IOException {
+        final List<Attachment> attachments = new ArrayList<>(parts.size());
+        for (EntityPart part: parts) {
+            attachments.add(createDataHandler(part, type, genericType, anns));
+        }
+        return attachments;
+    }
+
+    private static <T> Attachment createDataHandler(final EntityPart part, 
final Class<T> type,
+            final Type genericType, final Annotation[] anns) throws 
IOException {
+
+        final String mt = Objects
+            .requireNonNullElse(part.getMediaType(), 
MediaType.APPLICATION_OCTET_STREAM_TYPE)
+            .toString();
+
+        final MultivaluedMap<String, String> headers = new 
MultivaluedHashMap<>();
+        if (part.getHeaders() != null) {
+            headers.putAll(part.getHeaders());
+        }
+
+        return part.getFileName()
+            .map(fileName -> {
+                final ContentDisposition cd = new 
ContentDisposition("form-data;name=file;filename=" + fileName);
+                return new Attachment(AttachmentUtil.BODY_ATTACHMENT_ID, 
part.getContent(), cd);
+            })
+            .orElseGet(() -> {
+                headers.putSingle("Content-Type", mt);
+                return new Attachment(part.getName(),
+                    new DataHandler(new 
InputStreamDataSource(part.getContent(), mt)), headers);
+            });
+    }
+}
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
index b9f6424a01..18f0a9e109 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/provider/ProviderFactory.java
@@ -222,7 +222,8 @@ public abstract class ProviderFactory {
                      new PrimitiveTextProvider<Object>(),
                      JAXB_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
                      
JAXB_ELEMENT_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
-                     
MULTIPART_PROVIDER_CLASS.tryCreateInstance(factory.getBus()));
+                     
MULTIPART_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
+                     new EntityPartProvider());
         final Object skipJakartaJsonProviders = 
factory.getBus().getProperty(SKIP_JAKARTA_JSON_PROVIDERS_REGISTRATION);
         if (!PropertyUtils.isTrue(skipJakartaJsonProviders)) {
             factory.setProviders(false, false, 
JSONP_PROVIDER_CLASS.tryCreateInstance(factory.getBus()),
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
new file mode 100644
index 0000000000..08f7c2837b
--- /dev/null
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/EntityPartUtils.java
@@ -0,0 +1,109 @@
+/**
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.cxf.jaxrs.utils;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.charset.StandardCharsets;
+import java.util.ArrayList;
+import java.util.List;
+
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.ext.Providers;
+import org.apache.cxf.attachment.AttachmentBoundaryDeserializer;
+import org.apache.cxf.common.util.StringUtils;
+import org.apache.cxf.jaxrs.ext.MessageContext;
+import org.apache.cxf.jaxrs.ext.multipart.Attachment;
+import org.apache.cxf.jaxrs.ext.multipart.ContentDisposition;
+import org.apache.cxf.jaxrs.ext.multipart.MultipartBody;
+import org.apache.cxf.jaxrs.impl.EntityPartImpl;
+import org.apache.cxf.jaxrs.impl.ProvidersImpl;
+import org.apache.cxf.jaxrs.utils.multipart.AttachmentUtils;
+import org.apache.cxf.message.Message;
+
+final class EntityPartUtils {
+    private static final String MULTIPART_FORM_DATA_TYPE = "form-data";
+    private static final String CONTENT_DISPOSITION_FILES_PARAM = "files";
+
+    private EntityPartUtils() {
+    }
+
+    public static List<EntityPart> getEntityParts(final MessageContext mc) {
+        return from(mc.getProviders(), AttachmentUtils.getMultipartBody(mc));
+    }
+    
+    public static EntityPart getEntityPart(final String value, final Message 
message) {
+        final Providers providers = new ProvidersImpl(message);
+        try (InputStream is = new 
ByteArrayInputStream(value.getBytes(StandardCharsets.UTF_8))) {
+            final AttachmentBoundaryDeserializer deserializer = new 
AttachmentBoundaryDeserializer(message);
+            final Attachment attachment = new 
Attachment(deserializer.read(is), providers);
+            return createFromAttachment(providers, attachment);
+        } catch (IOException ex) {
+            throw ExceptionUtils.toBadRequestException(null, null);
+        }
+    }
+
+    private static List<EntityPart> from(final Providers providers, final 
MultipartBody body) {
+        final List<Attachment> atts = body.getAllAttachments();
+        final List<EntityPart> parts = new ArrayList<>(atts.size());
+
+        for (Attachment a : atts) {
+            final EntityPart part = createFromAttachment(providers,  a);
+            if (part != null) {
+                parts.add(part);
+            }
+        }
+
+        return parts;
+    }
+
+    private static EntityPart createFromAttachment(final Providers providers, 
Attachment a) {
+        final ContentDisposition cd = a.getContentDisposition();
+        if (cd != null && 
!MULTIPART_FORM_DATA_TYPE.equalsIgnoreCase(cd.getType())) {
+            return null;
+        }
+
+        final String fileName = (cd != null) ? cd.getFilename() : null;
+        final String cdName = cd == null ? null : cd.getParameter("name");
+        final String contentId = a.getContentId();
+        final String name = StringUtils.isEmpty(cdName) ? contentId : 
cdName.replace("\"", "").replace("'", "");
+        if (StringUtils.isEmpty(name) && StringUtils.isEmpty(fileName)) {
+            throw ExceptionUtils.toBadRequestException(null, null);
+        }
+
+        if (CONTENT_DISPOSITION_FILES_PARAM.equals(name)) {
+            return null;
+        }
+
+        try {
+            final InputStream is = a.getDataHandler().getInputStream();
+            if (!StringUtils.isEmpty(fileName)) {
+                return new EntityPartImpl(providers, name, fileName, is, 
InputStream.class, null,
+                    a.getHeaders(), a.getContentType());
+            } else {
+                return new EntityPartImpl(providers, name, null, is, 
InputStream.class, null,
+                    a.getHeaders(), a.getContentType());
+            }
+        } catch (IOException ex) {
+            throw ExceptionUtils.toBadRequestException(null, null);
+        }
+    }
+}
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
index e4404f247c..f8443785ba 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/FormUtils.java
@@ -35,6 +35,7 @@ import java.util.logging.Logger;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.ws.rs.HttpMethod;
 import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.EntityPart;
 import jakarta.ws.rs.core.Form;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
@@ -231,6 +232,24 @@ public final class FormUtils {
 
         }
     }
+    
+    public static void populateMapFromEntityParts(MultivaluedMap<String, 
String> params,
+                                                List<EntityPart> parts,
+                                                Message m,
+                                                boolean decode) {
+        checkNumberOfParts(m, parts.size());
+        for (EntityPart part : parts) {
+            try {
+                final String value = part.getContent(String.class);
+                params.add(HttpUtils.urlDecode(part.getName()), decode ? 
HttpUtils.urlDecode(value) : value);
+            } catch (IllegalArgumentException ex) {
+                throw ExceptionUtils.toInternalServerErrorException(ex, null);
+            } catch (IOException ex) {
+                throw ExceptionUtils.toBadRequestException(null, null);
+            }
+        }
+    }
+
 
     public static void populateMapFromMultipart(MultivaluedMap<String, String> 
params,
                                                 MultipartBody body,
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
index ce12204d6c..17e15d63ff 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/InjectionUtils.java
@@ -55,6 +55,7 @@ import java.util.logging.Logger;
 
 import jakarta.ws.rs.WebApplicationException;
 import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.EntityPart;
 import jakarta.ws.rs.core.GenericEntity;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
@@ -522,6 +523,10 @@ public final class InjectionUtils {
                 result = null;
             }
         }
+        
+        if (EntityPart.class.isAssignableFrom(pClass) && message != null) {
+            result = EntityPartUtils.getEntityPart(value, message);
+        }
 
         if (result == null) {
             reportServerError("WRONG_PARAMETER_TYPE", pClass.getName());
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
index c2b1d504ec..a2ca3d98f2 100644
--- a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
+++ b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/JAXRSUtils.java
@@ -67,6 +67,7 @@ import jakarta.ws.rs.container.ResourceInfo;
 import jakarta.ws.rs.core.Application;
 import jakarta.ws.rs.core.Configuration;
 import jakarta.ws.rs.core.Cookie;
+import jakarta.ws.rs.core.EntityPart;
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
@@ -175,6 +176,7 @@ public final class JAXRSUtils {
         Arrays.asList(InputStream.class, Reader.class, StreamingOutput.class));
     private static final Set<String> STREAMING_LIKE_OUT_TYPES = new HashSet<>(
         Arrays.asList(
+            "jakarta.ws.rs.core.EntityPart",
             "org.apache.cxf.jaxrs.ext.xml.XMLSource", 
             "org.apache.cxf.jaxrs.ext.multipart.InputStreamDataSource", 
             "org.apache.cxf.jaxrs.ext.multipart.MultipartBody", 
@@ -1199,8 +1201,13 @@ public final class JAXRSUtils {
             } else {
                 if ("multipart".equalsIgnoreCase(mt.getType())
                     && MediaType.MULTIPART_FORM_DATA_TYPE.isCompatible(mt)) {
-                    MultipartBody body = AttachmentUtils.getMultipartBody(mc);
-                    FormUtils.populateMapFromMultipart(params, body, m, 
decode);
+                    if (pClass.isAssignableFrom(EntityPart.class)) {
+                        final List<EntityPart> body = 
EntityPartUtils.getEntityParts(mc);
+                        FormUtils.populateMapFromEntityParts(params, body, m, 
decode);
+                    } else {
+                        MultipartBody body = 
AttachmentUtils.getMultipartBody(mc);
+                        FormUtils.populateMapFromMultipart(params, body, m, 
decode);
+                    }
                 } else {
                     org.apache.cxf.common.i18n.Message errorMsg =
                         new 
org.apache.cxf.common.i18n.Message("WRONG_FORM_MEDIA_TYPE",
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionBaseType.java
similarity index 73%
copy from 
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
copy to 
rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionBaseType.java
index dad33f5c52..bdbe8a65dd 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionBaseType.java
@@ -23,19 +23,17 @@ import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Collection;
 
-public final class ParameterizedCollectionType implements ParameterizedType {
+abstract class ParameterizedCollectionBaseType implements ParameterizedType {
+    private final Class<? extends Collection<?>> collectionClass;
     private final Class<?> collectionMemberClass;
     private final Type[] typeArgs;
 
-    public ParameterizedCollectionType(Class<?> collectionMemberClass) {
+    @SuppressWarnings({ "unchecked", "rawtypes" })
+    ParameterizedCollectionBaseType(Class<? extends Collection> 
collectionClass, Class<?> collectionMemberClass) {
+        this.collectionClass = (Class<? extends Collection<?>>) 
collectionClass;
         this.collectionMemberClass = collectionMemberClass;
         this.typeArgs = new Type[] {collectionMemberClass};
     }
-    
-    public ParameterizedCollectionType(Type pt) {
-        this.collectionMemberClass = InjectionUtils.getRawType(pt);
-        this.typeArgs = new Type[] {pt};
-    }
 
     public Type[] getActualTypeArguments() {
         return typeArgs;
@@ -46,10 +44,10 @@ public final class ParameterizedCollectionType implements 
ParameterizedType {
     }
 
     public Type getRawType() {
-        return Collection.class;
+        return collectionClass;
     }
 
     public String toString() {
-        return "java.util.Collection<" + collectionMemberClass.getName() + ">";
+        return collectionClass.getName() + "<" + 
collectionMemberClass.getName() + ">";
     }
 }
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
index dad33f5c52..c50dafd42c 100644
--- 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedCollectionType.java
@@ -19,37 +19,15 @@
 
 package org.apache.cxf.jaxrs.utils;
 
-import java.lang.reflect.ParameterizedType;
 import java.lang.reflect.Type;
 import java.util.Collection;
 
-public final class ParameterizedCollectionType implements ParameterizedType {
-    private final Class<?> collectionMemberClass;
-    private final Type[] typeArgs;
-
+public final class ParameterizedCollectionType extends 
ParameterizedCollectionBaseType {
     public ParameterizedCollectionType(Class<?> collectionMemberClass) {
-        this.collectionMemberClass = collectionMemberClass;
-        this.typeArgs = new Type[] {collectionMemberClass};
-    }
-    
-    public ParameterizedCollectionType(Type pt) {
-        this.collectionMemberClass = InjectionUtils.getRawType(pt);
-        this.typeArgs = new Type[] {pt};
-    }
-
-    public Type[] getActualTypeArguments() {
-        return typeArgs;
+        super(Collection.class, collectionMemberClass);
     }
 
-    public Type getOwnerType() {
-        return null;
-    }
-
-    public Type getRawType() {
-        return Collection.class;
-    }
-
-    public String toString() {
-        return "java.util.Collection<" + collectionMemberClass.getName() + ">";
+    public ParameterizedCollectionType(Type pt) {
+        super(Collection.class, InjectionUtils.getRawType(pt));
     }
 }
diff --git 
a/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedListType.java
 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedListType.java
new file mode 100644
index 0000000000..91bd873bd5
--- /dev/null
+++ 
b/rt/frontend/jaxrs/src/main/java/org/apache/cxf/jaxrs/utils/ParameterizedListType.java
@@ -0,0 +1,28 @@
+/**
+ * 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.cxf.jaxrs.utils;
+
+import java.util.List;
+
+public final class ParameterizedListType extends 
ParameterizedCollectionBaseType {
+    public ParameterizedListType(Class<?> collectionMemberClass) {
+        super(List.class, collectionMemberClass);
+    }
+}
diff --git 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/ProviderFactoryTest.java
 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/ProviderFactoryTest.java
index 27592dbefc..da351ce764 100644
--- 
a/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/ProviderFactoryTest.java
+++ 
b/rt/frontend/jaxrs/src/test/java/org/apache/cxf/jaxrs/provider/ProviderFactoryTest.java
@@ -184,9 +184,9 @@ public class ProviderFactoryTest {
         WildcardReader2 reader2 = new WildcardReader2();
         pf.registerUserProvider(reader2);
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(11, readers.size());
-        assertSame(reader1, readers.get(7).getProvider());
-        assertSame(reader2, readers.get(8).getProvider());
+        assertEquals(12, readers.size());
+        assertSame(reader1, readers.get(8).getProvider());
+        assertSame(reader2, readers.get(9).getProvider());
     }
 
     @Test
@@ -212,13 +212,13 @@ public class ProviderFactoryTest {
 
         // writers
         List<ProviderInfo<MessageBodyWriter<?>>> writers = 
pf.getMessageWriters();
-        assertEquals(10, writers.size());
-        Object lastWriter = writers.get(9).getProvider();
+        assertEquals(11, writers.size());
+        Object lastWriter = writers.get(10).getProvider();
         assertTrue(lastWriter instanceof StringTextProvider);
         //readers
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(9, readers.size());
-        Object lastReader = readers.get(8).getProvider();
+        assertEquals(10, readers.size());
+        Object lastReader = readers.get(9).getProvider();
         assertTrue(lastReader instanceof StringTextProvider);
     }
     @Test
@@ -242,13 +242,13 @@ public class ProviderFactoryTest {
 
         // writers
         List<ProviderInfo<MessageBodyWriter<?>>> writers = 
pf.getMessageWriters();
-        assertEquals(10, writers.size());
-        Object lastWriter = writers.get(9).getProvider();
+        assertEquals(11, writers.size());
+        Object lastWriter = writers.get(10).getProvider();
         assertTrue(lastWriter instanceof StringTextProvider);
         //readers
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(9, readers.size());
-        Object lastReader = readers.get(8).getProvider();
+        assertEquals(10, readers.size());
+        Object lastReader = readers.get(9).getProvider();
         assertTrue(lastReader instanceof StringTextProvider);
     }
     @SuppressWarnings("rawtypes")
@@ -276,13 +276,13 @@ public class ProviderFactoryTest {
 
         // writers
         List<ProviderInfo<MessageBodyWriter<?>>> writers = 
pf.getMessageWriters();
-        assertEquals(10, writers.size());
+        assertEquals(11, writers.size());
         Object lastWriter = writers.get(8).getProvider();
         assertFalse(lastWriter instanceof StringTextProvider);
         //readers
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(9, readers.size());
-        Object lastReader = readers.get(8).getProvider();
+        assertEquals(10, readers.size());
+        Object lastReader = readers.get(9).getProvider();
         assertTrue(lastReader instanceof StringTextProvider);
     }
     @Test
@@ -309,13 +309,13 @@ public class ProviderFactoryTest {
 
         // writers
         List<ProviderInfo<MessageBodyWriter<?>>> writers = 
pf.getMessageWriters();
-        assertEquals(10, writers.size());
-        Object lastWriter = writers.get(9).getProvider();
+        assertEquals(11, writers.size());
+        Object lastWriter = writers.get(10).getProvider();
         assertTrue(lastWriter instanceof StringTextProvider);
         //readers
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(9, readers.size());
-        Object lastReader = readers.get(8).getProvider();
+        assertEquals(10, readers.size());
+        Object lastReader = readers.get(9).getProvider();
         assertFalse(lastReader instanceof StringTextProvider);
     }
 
@@ -401,9 +401,9 @@ public class ProviderFactoryTest {
         ProviderFactory pf = ServerProviderFactory.createInstance(bus);
         pf.registerUserProvider(wc2);
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(11, readers.size());
-        assertSame(wc2, readers.get(7).getProvider());
-        assertSame(wc1, readers.get(8).getProvider());
+        assertEquals(12, readers.size());
+        assertSame(wc2, readers.get(8).getProvider());
+        assertSame(wc1, readers.get(9).getProvider());
     }
     @Test
     public void testCustomProviderSortingWithBus2() {
@@ -414,9 +414,9 @@ public class ProviderFactoryTest {
         ProviderFactory pf = ServerProviderFactory.createInstance(bus);
         pf.registerUserProvider(wc1);
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(11, readers.size());
-        assertSame(wc1, readers.get(7).getProvider());
-        assertSame(wc2, readers.get(8).getProvider());
+        assertEquals(12, readers.size());
+        assertSame(wc1, readers.get(8).getProvider());
+        assertSame(wc2, readers.get(9).getProvider());
     }
 
     @Test
@@ -1548,11 +1548,11 @@ public class ProviderFactoryTest {
         pf.registerUserProvider(reader);
         
         List<ProviderInfo<MessageBodyReader<?>>> readers = 
pf.getMessageReaders();
-        assertEquals(10, readers.size());
-        assertSame(reader, readers.get(7).getProvider());
+        assertEquals(11, readers.size());
+        assertSame(reader, readers.get(8).getProvider());
 
         pf.registerUserProvider(new ClientWildcardReader());
-        assertEquals(10, pf.getMessageReaders().size());
+        assertEquals(11, pf.getMessageReaders().size());
     }
 
     private static class RuntimeExceptionA extends RuntimeException {
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartServer.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartServer.java
new file mode 100644
index 0000000000..9cdf5fe6b3
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartServer.java
@@ -0,0 +1,62 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.apache.cxf.Bus;
+import org.apache.cxf.attachment.AttachmentDeserializer;
+import org.apache.cxf.endpoint.Server;
+import org.apache.cxf.ext.logging.LoggingInInterceptor;
+import org.apache.cxf.jaxrs.JAXRSServerFactoryBean;
+import org.apache.cxf.jaxrs.lifecycle.SingletonResourceProvider;
+import org.apache.cxf.jaxrs.provider.ProviderFactory;
+import org.apache.cxf.testutil.common.AbstractServerTestServerBase;
+
+public class EntityPartServer extends AbstractServerTestServerBase {
+    public static final String PORT = allocatePort(EntityPartServer.class);
+
+    @Override
+    protected Server createServer(Bus bus) throws Exception {
+        
bus.setProperty(ProviderFactory.SKIP_JAKARTA_JSON_PROVIDERS_REGISTRATION, true);
+        bus.getInInterceptors().add(new LoggingInInterceptor());
+
+        JAXRSServerFactoryBean sf = new JAXRSServerFactoryBean();
+        sf.setResourceClasses(EntityPartStore.class);
+
+        Map<String, Object> props = new HashMap<>();
+        props.put(AttachmentDeserializer.ATTACHMENT_MAX_SIZE, 
String.valueOf(1024 * 10));
+        props.put(AttachmentDeserializer.ATTACHMENT_MEMORY_THRESHOLD, 
String.valueOf(1024 * 5));
+        props.put(AttachmentDeserializer.ATTACHMENT_MAX_HEADER_SIZE, 
String.valueOf(400));
+        sf.setProperties(props);
+        //default lifecycle is per-request, change it to singleton
+        sf.setResourceProvider(EntityPartStore.class,
+                               new SingletonResourceProvider(new 
EntityPartStore()));
+        sf.setAddress("http://localhost:"; + PORT + "/");
+
+        return sf.create();
+    }
+
+    public static void main(String[] args) throws Exception {
+        new EntityPartServer().start();
+    }
+
+}
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartStore.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartStore.java
new file mode 100644
index 0000000000..f03eda7275
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/EntityPartStore.java
@@ -0,0 +1,101 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.FormParam;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.Produces;
+import jakarta.ws.rs.WebApplicationException;
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
+
+@Path("/bookstore")
+public class EntityPartStore {
+    @POST
+    @Path("/books/images")
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    public List<EntityPart> postImags(List<EntityPart> parts) throws 
IOException {
+        final List<EntityPart> replies = new ArrayList<>(parts.size());
+
+        for (EntityPart part : parts) {
+            final Optional<String> fileName = part.getFileName();
+            final MultivaluedMap<String, String> headers = part.getHeaders();
+            final MediaType mediaType = part.getMediaType();
+            try (InputStream is = part.getContent()) {
+                replies.add(
+                    EntityPart
+                        .withFileName(fileName.get())
+                        .headers(headers)
+                        .mediaType(mediaType)
+                        .content(is.readAllBytes())
+                        .build());
+            }
+        }
+
+        return replies;
+    }
+    
+    @POST
+    @Path("/books/jaxbform")
+    @Produces(MediaType.APPLICATION_XML)
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    public Response addBookJaxbFromForm(@FormParam("bookXML") EntityPart part) 
throws Exception {
+        Book b1 = part.getContent(Book.class);
+        b1.setId(124);
+        return Response.ok(b1).build();
+    }
+
+    @POST
+    @Path("/books/jsonform")
+    @Produces(MediaType.APPLICATION_XML)
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    public Response addBookJsonFromForm(@FormParam("gazetteer") EntityPart 
part) throws Exception {
+        Book b1 = part.getContent(Book.class);
+        b1.setId(124);
+        return Response.ok(b1).build();
+    }
+
+    @POST
+    @Path("/books/jsonjaxbform")
+    @Produces(MediaType.APPLICATION_XML)
+    @Consumes(MediaType.MULTIPART_FORM_DATA)
+    public Response addBookJaxbJsonForm(@FormParam("bookXML") EntityPart part1,
+            @FormParam("gazetteer") EntityPart part2) throws Exception {
+        final Book b1 = part1.getContent(Book.class);
+        final Book b2 = part2.getContent(Book.class);
+        if (!"CXF in Action - 2".equals(b1.getName())
+            || !"CXF in Action - 2".equals(b2.getName())) {
+            throw new WebApplicationException();
+        }
+        b2.setId(124);
+        return Response.ok(b2).build();
+    }
+}
diff --git 
a/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
new file mode 100644
index 0000000000..b536c69383
--- /dev/null
+++ 
b/systests/jaxrs/src/test/java/org/apache/cxf/systest/jaxrs/JAXRSEntityPartTest.java
@@ -0,0 +1,172 @@
+/**
+ * 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.cxf.systest.jaxrs;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.util.List;
+
+import jakarta.ws.rs.core.EntityPart;
+import jakarta.ws.rs.core.GenericType;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import org.apache.cxf.helpers.IOUtils;
+import org.apache.cxf.jaxrs.client.WebClient;
+import org.apache.cxf.jaxrs.utils.ParameterizedListType;
+import org.apache.cxf.testutil.common.AbstractBusClientServerTestBase;
+
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import static org.hamcrest.CoreMatchers.equalTo;
+import static org.hamcrest.CoreMatchers.is;
+import static org.hamcrest.CoreMatchers.not;
+import static org.hamcrest.CoreMatchers.nullValue;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+public class JAXRSEntityPartTest extends AbstractBusClientServerTestBase {
+    public static final String PORT = EntityPartServer.PORT;
+    public static final String PORTINV = 
allocatePort(JAXRSEntityPartTest.class, 1);
+    
+    @BeforeClass
+    public static void startServers() throws Exception {
+        assertTrue("server did not launch correctly", 
launchServer(EntityPartServer.class, true));
+        createStaticBus();
+    }
+
+    @Test
+    public void testUploadImage() throws Exception {
+        final File file = new 
File(getClass().getResource("/org/apache/cxf/systest/jaxrs/resources/java.jpg")
+                .toURI().getPath());
+        final String address = "http://localhost:"; + PORT + 
"/bookstore/books/images";
+        final WebClient client = WebClient.create(address);
+        
client.type(MediaType.MULTIPART_FORM_DATA).accept(MediaType.MULTIPART_FORM_DATA);
+
+        try (InputStream is = Files.newInputStream(file.toPath())) {
+            final EntityPart part = EntityPart
+                .withFileName(file.getName())
+                .content(is)
+                .build();
+
+            try (Response response = client.postCollection(List.of(part), 
EntityPart.class)) {
+                assertThat(response.getStatus(), equalTo(200));
+
+                @SuppressWarnings("unchecked")
+                final List<EntityPart> parts = (List<EntityPart>) response
+                    .readEntity(new GenericType<>(new 
ParameterizedListType(EntityPart.class)));
+
+                assertThat(parts, hasSize(1));
+                assertThat(parts.get(0), is(not(nullValue())));
+
+                assertThat(parts.get(0).getFileName().isPresent(), is(true));
+                assertThat(parts.get(0).getFileName().get(), 
equalTo(part.getFileName().get()));
+
+                
assertArrayEquals(IOUtils.readBytesFromStream(parts.get(0).getContent()), 
+                    
IOUtils.readBytesFromStream(Files.newInputStream(file.toPath())));
+            }
+        }
+    }
+    
+    @Test
+    public void testBookJSONForm() throws Exception {
+        String address = "http://localhost:"; + PORT + 
"/bookstore/books/jsonform";
+        doAddFormBook(address, "attachmentFormJson", "gazetteer", 
MediaType.APPLICATION_JSON, 200);
+    }
+
+    @Test
+    public void testBookJSONJAXBForm() throws Exception {
+        String address = "http://localhost:"; + PORT + 
"/bookstore/books/jsonjaxbform";
+        
+        final WebClient client = WebClient.create(address);
+        
client.type(MediaType.MULTIPART_FORM_DATA).accept(MediaType.APPLICATION_XML);
+
+        try (InputStream is1 = 
+                
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/attachmentFormJaxb");
+            InputStream is2 = 
+                
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/attachmentFormJson"))
 {
+
+            final EntityPart part1 = EntityPart
+                    .withName("bookXML")
+                    .content(is1)
+                    .mediaType(MediaType.APPLICATION_XML)
+                    .build();
+
+            final EntityPart part2 = EntityPart
+                    .withName("gazetteer")
+                    .content(is2)
+                    .mediaType(MediaType.APPLICATION_JSON)
+                    .build();
+
+            try (Response response = client.postCollection(List.of(part1, 
part2), EntityPart.class)) {
+                assertThat(response.getStatus(), equalTo(200));
+
+                try (InputStream expected = 
getClass().getResourceAsStream("resources/expected_add_book.txt")) {
+                    
assertEquals(stripXmlInstructionIfNeeded(IOUtils.toString(expected)),
+                       
stripXmlInstructionIfNeeded(response.readEntity(String.class)));
+                }
+            }
+        }
+    }
+
+    @Test
+    public void testBookJaxbForm() throws Exception {
+        String address = "http://localhost:"; + PORT + 
"/bookstore/books/jaxbform";
+        doAddFormBook(address, "attachmentFormJaxb", "bookXML", 
MediaType.APPLICATION_XML, 200);
+    }
+
+    private void doAddFormBook(String address, String resourceName, 
+            String name, String mt, int status) throws Exception {
+
+        final WebClient client = WebClient.create(address);
+        
client.type(MediaType.MULTIPART_FORM_DATA).accept(MediaType.APPLICATION_XML);
+
+        try (InputStream is = 
+                
getClass().getResourceAsStream("/org/apache/cxf/systest/jaxrs/resources/" + 
resourceName)) {
+
+            final EntityPart part = EntityPart
+                    .withName(name)
+                    .content(is)
+                    .mediaType(mt)
+                    .build();
+
+            try (Response response = client.postCollection(List.of(part), 
EntityPart.class)) {
+                assertThat(response.getStatus(), equalTo(200));
+
+                try (InputStream expected = 
getClass().getResourceAsStream("resources/expected_add_book.txt")) {
+                    
assertEquals(stripXmlInstructionIfNeeded(IOUtils.toString(expected)),
+                       
stripXmlInstructionIfNeeded(response.readEntity(String.class)));
+                }
+            }
+        }
+    }
+        
+    private static String stripXmlInstructionIfNeeded(String str) {
+        if (str != null && str.startsWith("<?xml")) {
+            int index = str.indexOf("?>");
+            str = str.substring(index + 2);
+        }
+        return str;
+    }
+}

Reply via email to