merge of the actual 2.x into the 3.0

diff --git a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
index 6c9d87d..d53401e 100644
--- a/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
+++ b/connectors/apache-connector/src/main/java/org/glassfish/jersey/apache/connector/ApacheConnector.java
@@ -937,12 +937,12 @@
         }
 
         @Override
-        public synchronized void mark(int readlimit) {
+        public void mark(int readlimit) {
             in.mark(readlimit);
         }
 
         @Override
-        public synchronized void reset() throws IOException {
+        public void reset() throws IOException {
             checkAborted();
             in.reset();
         }
diff --git a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java
index 2e03b30..56f8dd5 100644
--- a/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java
+++ b/connectors/apache5-connector/src/main/java/org/glassfish/jersey/apache5/connector/Apache5Connector.java
@@ -941,12 +941,12 @@
         }
 
         @Override
-        public synchronized void mark(int readlimit) {
+        public void mark(int readlimit) {
             in.mark(readlimit);
         }
 
         @Override
-        public synchronized void reset() throws IOException {
+        public void reset() throws IOException {
             checkAborted();
             in.reset();
         }
diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java
index 15725e7..4070300 100644
--- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java
+++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/HttpParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2021 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -496,7 +496,7 @@
 
         if (contentLengths != null) {
             try {
-                int bodyLength = Integer.parseInt(contentLengths.get(0));
+                long bodyLength = Long.parseLong(contentLengths.get(0));
                 if (bodyLength == 0) {
                     expectContent = false;
                     return;
diff --git a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java
index 0a3ede0..7dccd87 100644
--- a/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java
+++ b/connectors/jdk-connector/src/main/java/org/glassfish/jersey/jdk/connector/internal/TransferEncodingParser.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2015, 2022 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2015, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -31,7 +31,7 @@
 
     abstract boolean parse(ByteBuffer input) throws ParseException;
 
-    static TransferEncodingParser createFixedLengthParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
+    static TransferEncodingParser createFixedLengthParser(AsynchronousBodyInputStream responseBody, long expectedLength) {
         return new FixedLengthEncodingParser(responseBody, expectedLength);
     }
 
@@ -42,11 +42,11 @@
 
     private static class FixedLengthEncodingParser extends TransferEncodingParser {
 
-        private final int expectedLength;
+        private final long expectedLength;
         private final AsynchronousBodyInputStream responseBody;
-        private volatile int consumedLength = 0;
+        private volatile long consumedLength = 0;
 
-        FixedLengthEncodingParser(AsynchronousBodyInputStream responseBody, int expectedLength) {
+        FixedLengthEncodingParser(AsynchronousBodyInputStream responseBody, long expectedLength) {
             this.expectedLength = expectedLength;
             this.responseBody = responseBody;
         }
diff --git a/docs/src/main/docbook/appendix-properties.xml b/docs/src/main/docbook/appendix-properties.xml
index 5f91902..a85d6df 100644
--- a/docs/src/main/docbook/appendix-properties.xml
+++ b/docs/src/main/docbook/appendix-properties.xml
@@ -2210,4 +2210,80 @@
             </tgroup>
         </table>
     </section>
+    <section xml:id="appendix-properties-multipart">
+        <title>Multipart configuration properties</title>
+
+        <para>
+            List of multipart configuration properties that can be found in &jersey.media.multipart.MultiPartProperties; class.
+        </para>
+
+        <table>
+            <title>
+                List of multipart configuration properties settable in the
+                &jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE; configuration file.
+            </title>
+            <tgroup cols="3">
+                <thead>
+                    <row>
+                        <entry>Constant</entry>
+                        <entry>Value</entry>
+                        <entry>Description</entry>
+                    </row>
+                </thead>
+                <tbody>
+                    <row>
+                        <entry>&jersey.media.multipart.MultiPartProperties.BUFFER_THRESHOLD;</entry>
+                        <entry><literal>jersey.config.multipart.bufferThreshold</literal></entry>
+                        <entry>
+                            <para>
+                                Name of the resource property for the threshold size (in bytes) above which a
+                                body part entity will be buffered to disk instead of being held in memory.
+                            </para>
+                            <para>
+                                The default value is &jersey.message.MessageProperties.IO_DEFAULT_BUFFER_SIZE;
+                            </para>
+                        </entry>
+                    </row>
+                    <row>
+                        <entry>&jersey.media.multipart.MultiPartProperties.MAX_PARTS;</entry>
+                        <entry><literal>jersey.config.multipart.maxParts</literal></entry>
+                        <entry>
+                            <para>
+                                Limit the maximum number of parts the multipart entity can have. If the limit is over,
+                                the error response status <literal>413 - REQUEST_ENTITY_TOO_LARGE</literal>
+                                is returned.
+                            </para>
+                            <para>
+                                By default, the number is unlimited.
+                            </para>
+                        </entry>
+                    </row>
+                    <row>
+                        <entry>&jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE;</entry>
+                        <entry><literal>jersey-multipart-config.properties</literal></entry>
+                        <entry>
+                            <para>
+                                Name of a properties resource that (if found in the classpath
+                                for this application) will be used to configure the settings returned
+                                by our getter methods.
+                            </para>
+                        </entry>
+                    </row>
+                    <row>
+                        <entry>&jersey.media.multipart.MultiPartProperties.TEMP_DIRECTORY;</entry>
+                        <entry><literal>jersey.config.multipart.tempDir</literal></entry>
+                        <entry>
+                            <para>
+                                Name of the resource property for the directory to store temporary files containing body parts
+                                of multipart message that extends allowed memory threshold.
+                            </para>
+                            <para>
+                                The default value is not set (will be taken from <literal>java.io.tmpdir</literal> system property).
+                            </para>
+                        </entry>
+                    </row>
+                </tbody>
+            </tgroup>
+        </table>
+    </section>
 </appendix>
\ No newline at end of file
diff --git a/docs/src/main/docbook/jersey.ent b/docs/src/main/docbook/jersey.ent
index 6c285ea..1822bfb 100644
--- a/docs/src/main/docbook/jersey.ent
+++ b/docs/src/main/docbook/jersey.ent
@@ -536,7 +536,12 @@
 <!ENTITY jersey.media.multipart.FormDataParam "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/FormDataParam.html'>@FormDataParam</link>" >
 <!ENTITY jersey.media.multipart.MultiPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPart.html'>MultiPart</link>" >
 <!ENTITY jersey.media.multipart.MultiPartFeature "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartFeature.html'>MultiPartFeature</link>" >
-<!ENTITY jersey.media.multipart.StreamDataBodyPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/file/StreamDataBodyPart.html'>StreamDataBodyPart</link>" >
+<!ENTITY jersey.media.multipart.MultiPartProperties "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html'>MultiPartProperties</link>" >
+<!ENTITY jersey.media.multipart.MultiPartProperties.BUFFER_THRESHOLD "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#BUFFER_THRESHOLD'>MultiPartProperties.BUFFER_THRESHOLD</link>">
+<!ENTITY jersey.media.multipart.MultiPartProperties.MAX_PARTS "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#MAX_PARTS'>MultiPartProperties.MAX_PARTS</link>">
+<!ENTITY jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#MULTI_PART_CONFIG_RESOURCE'>MultiPartProperties.MULTI_PART_CONFIG_RESOURCE</link>">
+<!ENTITY jersey.media.multipart.MultiPartProperties.TEMP_DIRECTORY "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/MultiPartProperties.html#TEMP_DIRECTORY'>MultiPartProperties.TEMP_DIRECTORY</link>">
+<!ENTITY jersey.media.multipart.StreamDataBodyPart "<link xlink:href='&jersey.javadoc.uri.prefix;/media/multipart/file/StreamDataBodyPart.html'>StreamDataBodyPart</link>">
 <!ENTITY jersey.message.MessageBodyWorkers "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageBodyWorkers.html'>MessageBodyWorkers</link>">
 <!ENTITY jersey.message.MessageProperties "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageProperties.html'>MessageProperties</link>">
 <!ENTITY jersey.message.MessageProperties.DEFLATE_WITHOUT_ZLIB "<link xlink:href='&jersey.javadoc.uri.prefix;/message/MessageProperties.html#DEFLATE_WITHOUT_ZLIB'>MessageProperties.DEFLATE_WITHOUT_ZLIB</link>">
diff --git a/docs/src/main/docbook/media.xml b/docs/src/main/docbook/media.xml
index 6849118..dccbe9b 100644
--- a/docs/src/main/docbook/media.xml
+++ b/docs/src/main/docbook/media.xml
@@ -1,7 +1,7 @@
 <?xml version="1.0" standalone="no"?>
 <!--
 
-    Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
+    Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
 
     This program and the accompanying materials are made available under the
     terms of the Eclipse Public License v. 2.0, which is available at
@@ -1878,5 +1878,24 @@
                 </tip>
             </section>
         </section>
+        <section xml:id="multipart.configuration">
+            <title>Properties for configuring the Multipart</title>
+            <para>
+                There are multiple options that can be used when configuring
+                the multipart. See &jersey.media.multipart.MultiPartProperties; or <xref linkend="appendix-properties-multipart"/>
+                for the possibilities.
+            </para>
+            <para>
+                The options can set in a configuration file specified by the
+                &jersey.media.multipart.MultiPartProperties.MULTI_PART_CONFIG_RESOURCE; property.
+                That is the standard Java properties file.
+            </para>
+            <para>
+                Or the options can be set programmatically,
+                by registering <literal>ContextResolver&lt;MultiPartProperties&gt;</literal>. For instance:
+            </para>
+            <programlisting language="java">ResourceConfig resourceConfig = new ResourceConfig();
+resourceConfig.register(new MultiPartProperties().bufferThreshold(65535).maxParts(2).resolver());</programlisting>
+        </section>
     </section>
 </chapter>
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartProperties.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartProperties.java
index 68abedb..00d6aba 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartProperties.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/MultiPartProperties.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2010, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -23,6 +23,7 @@
 import jakarta.ws.rs.ext.ContextResolver;
 
 import org.glassfish.jersey.internal.util.PropertiesClass;
+import org.glassfish.jersey.message.internal.ReaderWriter;
 
 /**
  * Injectable JavaBean containing the configuration parameters for
@@ -38,12 +39,17 @@
     /**
      * Default threshold size for buffer.
      */
-    public static final int DEFAULT_BUFFER_THRESHOLD = 4096;
+    public static final int DEFAULT_BUFFER_THRESHOLD = ReaderWriter.BUFFER_SIZE;
 
     /**
+     * <p>
      * Name of a properties resource that (if found in the classpath
      * for this application) will be used to configure the settings returned
      * by our getter methods.
+     * </p>
+     * <p>
+     *     The resource name is {@code jersey-multipart-config.properties}.
+     * </p>
      */
     public static final String MULTI_PART_CONFIG_RESOURCE = "jersey-multipart-config.properties";
 
@@ -51,7 +57,7 @@
      * Name of the resource property for the threshold size (in bytes) above which a body part entity will be
      * buffered to disk instead of being held in memory.
      *
-     * The default value is {@value #DEFAULT_BUFFER_THRESHOLD}.
+     * The default value is {@link #DEFAULT_BUFFER_THRESHOLD}.
      */
     public static final String BUFFER_THRESHOLD = "jersey.config.multipart.bufferThreshold";
 
@@ -61,8 +67,20 @@
     public static final int BUFFER_THRESHOLD_MEMORY_ONLY = -1;
 
     /**
+     * <p>
+     *     Limit the maximum number of parts the multipart entity can have. If the limit is over,
+     *     the error response status {@link jakarta.ws.rs.core.Response.Status#REQUEST_ENTITY_TOO_LARGE} is returned.
+     * </p>
+     * <p>
+     *     By default, the number is unlimited.
+     * </p>
+     * @since 2.44
+     */
+    public static final String MAX_PARTS = "jersey.config.multipart.maxParts";
+
+    /**
      * Name of the resource property for the directory to store temporary files containing body parts of multipart message that
-     * extends allowed memory threshold..
+     * extends allowed memory threshold.
      *
      * The default value is not set (will be taken from {@code java.io.tmpdir} system property).
      */
@@ -80,6 +98,11 @@
     private String tempDir = null;
 
     /**
+     * Maximum number of entity parts allowed.
+     */
+    private int maxParts = Integer.MAX_VALUE;
+
+    /**
      * Load and customize (if necessary) the configuration values for the
      * {@code jersey-multipart} injection binder.
      *
@@ -114,6 +137,15 @@
     }
 
     /**
+     * Return maximum number of entity parts allowed.
+     * @return maximum number of parts.
+     * @since 2.44
+     */
+    public int getMaxParts() {
+        return maxParts;
+    }
+
+    /**
      * Set the size (in bytes) of the entity of an incoming {@link BodyPart} before it will be buffered to disk.
      *
      * @param threshold size of body part.
@@ -139,6 +171,17 @@
     }
 
     /**
+     * Set the maximum number of received parts of a multipart entity.
+     * @param maxParts The maximum number of entity parts.
+     * @return {@code MultiPartProperties} instance.
+     * @since 2.44
+     */
+    public MultiPartProperties maxParts(int maxParts) {
+        this.maxParts = maxParts;
+        return this;
+    }
+
+    /**
      * Configure the values returned by this instance's getters based on
      * the contents of a properties resource, if it exists on the classpath
      * for this application.
@@ -169,6 +212,9 @@
             if (props.containsKey(TEMP_DIRECTORY)) {
                 this.tempDir = props.getProperty(TEMP_DIRECTORY);
             }
+            if (props.contains(MAX_PARTS)) {
+                this.maxParts = Integer.parseInt(props.getProperty(MAX_PARTS));
+            }
         } catch (final IOException e) {
             throw new IllegalArgumentException(e);
         } finally {
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
index d95b320..2da4623 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/FormDataParamValueParamProvider.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2020 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -20,6 +20,8 @@
 import java.io.File;
 import java.io.IOException;
 import java.io.InputStream;
+import java.lang.annotation.Annotation;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashSet;
@@ -232,7 +234,13 @@
         @Override
         public Object apply(ContainerRequest request) {
             // Return the field value for the field specified by the sourceName property.
-            final List<FormDataBodyPart> parts = getEntity(request).getFields(parameter.getSourceName());
+            final String sourceName = parameter.getAnnotations().length == 1
+                    ? parameter.getSourceName()
+                    : Arrays.stream(parameter.getAnnotations())
+                        .filter(ann -> FormDataParam.class.isInstance(ann))
+                        .map(ann -> FormDataParam.class.cast(ann))
+                        .findFirst().get().value();
+            final List<FormDataBodyPart> parts = getEntity(request).getFields(sourceName);
 
             final FormDataBodyPart part = parts != null ? parts.get(0) : null;
             final MediaType mediaType = part != null ? part.getMediaType() : MediaType.TEXT_PLAIN_TYPE;
@@ -357,34 +365,38 @@
             } else {
                 return null;
             }
-        } else if (parameter.getSourceAnnotation().annotationType() == FormDataParam.class) {
-            final String paramName = parameter.getSourceName();
-            if (paramName == null || paramName.isEmpty()) {
-                // Invalid query parameter name
-                return null;
-            }
+        } else {
+            for (Annotation sourceAnnotation : parameter.getAnnotations()) {
+                if (sourceAnnotation.annotationType() == FormDataParam.class) {
+                    final String paramName = ((FormDataParam) sourceAnnotation).value(); // sourceName refers to the last anno
+                    if (paramName == null || paramName.isEmpty()) {
+                        // Invalid query parameter name
+                        return null;
+                    }
 
-            if (Collection.class == rawType || List.class == rawType) {
-                final Class clazz = ReflectionHelper.getGenericTypeArgumentClasses(parameter.getType()).get(0);
+                    if (Collection.class == rawType || List.class == rawType) {
+                        final Class clazz = ReflectionHelper.getGenericTypeArgumentClasses(parameter.getType()).get(0);
 
-                if (FormDataBodyPart.class == clazz) {
-                    // Return a collection of form data body part.
-                    return new ListFormDataBodyPartValueProvider(paramName);
-                } else if (FormDataContentDisposition.class == clazz) {
-                    // Return a collection of form data content disposition.
-                    return new ListFormDataContentDispositionProvider(paramName);
-                } else {
-                    // Return a collection of specific type.
-                    return new FormDataParamValueProvider(parameter, get(parameter));
+                        if (FormDataBodyPart.class == clazz) {
+                            // Return a collection of form data body part.
+                            return new ListFormDataBodyPartValueProvider(paramName);
+                        } else if (FormDataContentDisposition.class == clazz) {
+                            // Return a collection of form data content disposition.
+                            return new ListFormDataContentDispositionProvider(paramName);
+                        } else {
+                            // Return a collection of specific type.
+                            return new FormDataParamValueProvider(parameter, get(parameter));
+                        }
+                    } else if (FormDataBodyPart.class == rawType) {
+                        return new FormDataBodyPartProvider(paramName);
+                    } else if (FormDataContentDisposition.class == rawType) {
+                        return new FormDataContentDispositionProvider(paramName);
+                    } else if (File.class == rawType) {
+                        return new FileProvider(paramName);
+                    } else {
+                        return new FormDataParamValueProvider(parameter, get(parameter));
+                    }
                 }
-            } else if (FormDataBodyPart.class == rawType) {
-                return new FormDataBodyPartProvider(paramName);
-            } else if (FormDataContentDisposition.class == rawType) {
-                return new FormDataContentDispositionProvider(paramName);
-            } else if (File.class == rawType) {
-                return new FileProvider(paramName);
-            } else {
-                return new FormDataParamValueProvider(parameter, get(parameter));
             }
         }
 
diff --git a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartReaderClientSide.java b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartReaderClientSide.java
index c867078..edb80bd 100644
--- a/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartReaderClientSide.java
+++ b/media/multipart/src/main/java/org/glassfish/jersey/media/multipart/internal/MultiPartReaderClientSide.java
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2012, 2023 Oracle and/or its affiliates. All rights reserved.
+ * Copyright (c) 2012, 2024 Oracle and/or its affiliates. All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v. 2.0, which is available at
@@ -28,6 +28,7 @@
 import java.util.logging.Logger;
 
 import jakarta.ws.rs.BadRequestException;
+import jakarta.ws.rs.ClientErrorException;
 import jakarta.ws.rs.ConstrainedTo;
 import jakarta.ws.rs.Consumes;
 import jakarta.ws.rs.RuntimeType;
@@ -36,6 +37,7 @@
 import jakarta.ws.rs.core.HttpHeaders;
 import jakarta.ws.rs.core.MediaType;
 import jakarta.ws.rs.core.MultivaluedMap;
+import jakarta.ws.rs.core.Response;
 import jakarta.ws.rs.ext.ContextResolver;
 import jakarta.ws.rs.ext.MessageBodyReader;
 import jakarta.ws.rs.ext.Providers;
@@ -79,6 +81,7 @@
      */
     private Provider<MessageBodyWorkers> messageBodyWorkers;
     private final MIMEConfig mimeConfig;
+    private final int maxParts;
 
     /**
      * Accepts constructor injection of the configuration parameters for this
@@ -98,6 +101,8 @@
             properties = new MultiPartProperties();
         }
 
+        maxParts = properties.getMaxParts();
+
         this.messageBodyWorkers = messageBodyWorkers;
         mimeConfig = createMimeConfig(properties);
     }
@@ -205,7 +210,12 @@
             fileNameFix = userAgent != null && userAgent.contains(" MSIE ");
         }
 
-        for (final MIMEPart mimePart : getMimeParts(mimeMessage)) {
+        final List<MIMEPart> mimeParts = getMimeParts(mimeMessage);
+        if (mimeParts.size() > maxParts) {
+            throw new ClientErrorException(Response.Status.REQUEST_ENTITY_TOO_LARGE);
+        }
+
+        for (final MIMEPart mimePart : mimeParts) {
             final BodyPart bodyPart = formData ? new FormDataBodyPart(fileNameFix) : new BodyPart();
 
             // Configure providers.
diff --git a/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/OrderParamTest.java b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/OrderParamTest.java
new file mode 100644
index 0000000..23cc7a8
--- /dev/null
+++ b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/OrderParamTest.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart.internal;
+
+import org.glassfish.jersey.media.multipart.FormDataMultiPart;
+import org.glassfish.jersey.media.multipart.FormDataParam;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.message.internal.ReaderWriter;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.server.model.ParamQualifier;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.Consumes;
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+import java.io.IOException;
+import java.io.InputStream;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+public class OrderParamTest extends JerseyTest {
+    @Target({ElementType.PARAMETER, ElementType.METHOD, ElementType.FIELD})
+    @Retention(RetentionPolicy.RUNTIME)
+    @ParamQualifier
+    public static @interface AnnoWithValue {
+        String value() default "";
+    }
+
+    @Path("/order")
+    public static class OrderTestResource {
+        @POST
+        @Path("/dataAfter")
+        @Consumes(value = MediaType.MULTIPART_FORM_DATA)
+        public String orderBefore(@FormDataParam("file") @AnnoWithValue("xxx") InputStream inputStream) throws IOException {
+            return ReaderWriter.readFromAsString(inputStream, MediaType.TEXT_PLAIN_TYPE);
+        }
+
+        @POST
+        @Path("/dataBefore")
+        @Consumes(value = MediaType.MULTIPART_FORM_DATA)
+        public String orderAfter(@AnnoWithValue("zzz") @FormDataParam("file") InputStream inputStream) throws IOException {
+            return ReaderWriter.readFromAsString(inputStream, MediaType.TEXT_PLAIN_TYPE);
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(OrderTestResource.class).register(MultiPartFeature.class);
+    }
+
+    @Test
+    public void testOrder() {
+        final String MSG = "Hello";
+        FormDataMultiPart multiPart = new FormDataMultiPart();
+        multiPart.field("file", MSG, MediaType.TEXT_PLAIN_TYPE);
+        try (Response response = target("order")
+                .register(MultiPartFeature.class)
+                .path("dataBefore").request()
+                .post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
+            assertEquals(200, response.getStatus());
+            assertEquals(MSG, response.readEntity(String.class));
+        }
+
+        try (Response response = target("order")
+                .register(MultiPartFeature.class)
+                .path("dataAfter").request()
+                .post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
+            assertEquals(200, response.getStatus());
+            assertEquals(MSG, response.readEntity(String.class));
+        }
+    }
+}
diff --git a/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/RestrictionsTest.java b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/RestrictionsTest.java
new file mode 100644
index 0000000..9906f11
--- /dev/null
+++ b/media/multipart/src/test/java/org/glassfish/jersey/media/multipart/internal/RestrictionsTest.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2024 Oracle and/or its affiliates. All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v. 2.0, which is available at
+ * http://www.eclipse.org/legal/epl-2.0.
+ *
+ * This Source Code may also be made available under the following Secondary
+ * Licenses when the conditions for such availability set forth in the
+ * Eclipse Public License v. 2.0 are satisfied: GNU General Public License,
+ * version 2 with the GNU Classpath Exception, which is available at
+ * https://www.gnu.org/software/classpath/license.html.
+ *
+ * SPDX-License-Identifier: EPL-2.0 OR GPL-2.0 WITH Classpath-exception-2.0
+ */
+
+package org.glassfish.jersey.media.multipart.internal;
+
+import org.glassfish.jersey.client.ClientConfig;
+import org.glassfish.jersey.media.multipart.FormDataMultiPart;
+import org.glassfish.jersey.media.multipart.FormDataParam;
+import org.glassfish.jersey.media.multipart.MultiPartFeature;
+import org.glassfish.jersey.media.multipart.MultiPartProperties;
+import org.glassfish.jersey.server.ResourceConfig;
+import org.glassfish.jersey.test.JerseyTest;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.Test;
+
+import jakarta.ws.rs.POST;
+import jakarta.ws.rs.Path;
+import jakarta.ws.rs.client.Entity;
+import jakarta.ws.rs.core.Application;
+import jakarta.ws.rs.core.MediaType;
+import jakarta.ws.rs.core.Response;
+
+public class RestrictionsTest extends JerseyTest {
+    @Path("/")
+    public static class RestrictionsTestResource {
+        @POST
+        @Path("max.parts")
+        public String postMaxPart(@FormDataParam("part1") String part1, @FormDataParam("part2") String part2) {
+            return part1 + part2;
+        }
+    }
+
+    @Override
+    protected Application configure() {
+        return new ResourceConfig(RestrictionsTestResource.class)
+                .register(MultiPartFeature.class)
+                .register(new MultiPartProperties().maxParts(2).resolver());
+    }
+
+    @Override
+    protected void configureClient(ClientConfig config) {
+        config.register(MultiPartFeature.class);
+    }
+
+    @Test
+    public void testPassNumberOfParts() {
+        FormDataMultiPart multiPart = new FormDataMultiPart();
+        multiPart.field("part1", "he", MediaType.TEXT_PLAIN_TYPE);
+        multiPart.field("part2", "llo", MediaType.TEXT_PLAIN_TYPE);
+        try (Response r = target("max.parts").request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
+            Assertions.assertEquals(200, r.getStatus());
+            Assertions.assertEquals("hello", r.readEntity(String.class));
+        }
+    }
+
+    @Test
+    public void testFailsNumberOfParts() {
+        FormDataMultiPart multiPart = new FormDataMultiPart();
+        multiPart.field("part1", "he", MediaType.TEXT_PLAIN_TYPE);
+        multiPart.field("part2", "llo", MediaType.TEXT_PLAIN_TYPE);
+        multiPart.field("part3", "!", MediaType.TEXT_PLAIN_TYPE);
+        try (Response r = target("max.parts").request().post(Entity.entity(multiPart, MediaType.MULTIPART_FORM_DATA_TYPE))) {
+            Assertions.assertEquals(Response.Status.REQUEST_ENTITY_TOO_LARGE.getStatusCode(), r.getStatus());
+        }
+    }
+}
diff --git a/pom.xml b/pom.xml
index 701e0f7..8261cea 100644
--- a/pom.xml
+++ b/pom.xml
@@ -2116,7 +2116,6 @@
         <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
 <!--        <release.tests.args>-Dmaven.test.skip=false</release.tests.args>-->
 <!--        <release.preparationGoals>clean install</release.preparationGoals>-->
-        <skip.e2e>false</skip.e2e>
         <skip.tests>false</skip.tests>
         <xdk.absolute.path />
         <surefire.security.argline />
diff --git a/tests/e2e/pom.xml b/tests/e2e/pom.xml
index 024afea..94d1d8e 100644
--- a/tests/e2e/pom.xml
+++ b/tests/e2e/pom.xml
@@ -41,7 +41,6 @@
                     <forkCount>1</forkCount>
                     <reuseForks>false</reuseForks>
                     <enableAssertions>false</enableAssertions>
-                    <skipTests>${skip.e2e}</skipTests>
                     <excludes>
                         <!--TODO remove after jakartification-->
                         <exclude>org/glassfish/jersey/tests/e2e/server/wadl/NoJAXBNoWadlTest.java</exclude>