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

jamesbognar pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/juneau.git


The following commit(s) were added to refs/heads/master by this push:
     new ada5eef  RestClient tests
ada5eef is described below

commit ada5eef63907c073887bf108cdfd3c509f679cdc
Author: JamesBognar <[email protected]>
AuthorDate: Sat Apr 4 10:13:42 2020 -0400

    RestClient tests
---
 .../org/apache/juneau/BasicAssertionError.java     |  63 ++++
 .../apache/juneau/http/annotation/FormData.java    |  17 +
 .../org/apache/juneau/http/annotation/Header.java  |  17 +
 .../org/apache/juneau/http/annotation/Query.java   |  17 +
 juneau-doc/docs/ReleaseNotes/8.1.4.html            |   7 +
 .../27.OpenApiDetails/02.Serializers.html          |   8 +-
 .../juneau/rest/test/client/RestClientTest.java    |   4 +-
 .../juneau/rest/client2/RestClientBuilderTest.java |  76 ++--
 .../apache/juneau/rest/client2/RestResponse.java   |  14 +-
 .../juneau/rest/client2/RestResponseBody.java      |  40 +-
 .../juneau/rest/client2/RestResponseHeader.java    |  47 ++-
 juneau-rest/juneau-rest-server/pom.xml             |   4 +
 .../apache/juneau/rest/BasicRestCallHandler.java   |   3 +
 .../org/apache/juneau/rest/RequestFormData.java    | 419 ++++++++++++++++++---
 .../org/apache/juneau/rest/RequestHeader.java}     | 133 +++----
 .../org/apache/juneau/rest/RequestHeaders.java     | 147 +++++++-
 .../java/org/apache/juneau/rest/RequestPath.java   |  10 +-
 .../java/org/apache/juneau/rest/RequestQuery.java  |  14 +-
 .../main/java/org/apache/juneau/rest/RestCall.java |  11 +
 .../java/org/apache/juneau/rest/RestContext.java   |  18 +-
 .../org/apache/juneau/rest/RestParamDefaults.java  |  51 ++-
 .../java/org/apache/juneau/rest/RestRequest.java   |   8 +
 .../java/org/apache/juneau/rest/RestResponse.java  |  62 ++-
 .../juneau/rest/reshandlers/DefaultHandler.java    |   2 +
 pom.xml                                            |   6 +
 25 files changed, 945 insertions(+), 253 deletions(-)

diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
new file mode 100644
index 0000000..63a6ba5
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BasicAssertionError.java
@@ -0,0 +1,63 @@
+// 
***************************************************************************************************************************
+// * Licensed to the Apache Software Foundation (ASF) under one or more 
contributor license agreements.  See the NOTICE file *
+// * distributed with this work for additional information regarding copyright 
ownership.  The ASF licenses this file        *
+// * to you under the Apache License, Version 2.0 (the "License"); you may not 
use this file except in compliance            *
+// * with the License.  You may obtain a copy of the License at                
                                              *
+// *                                                                           
                                              *
+// *  http://www.apache.org/licenses/LICENSE-2.0                               
                                              *
+// *                                                                           
                                              *
+// * Unless required by applicable law or agreed to in writing, software 
distributed under the License is distributed on an  *
+// * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
+// * specific language governing permissions and limitations under the 
License.                                              *
+// 
***************************************************************************************************************************
+package org.apache.juneau;
+
+import static org.apache.juneau.internal.StringUtils.*;
+
+import java.text.*;
+
+/**
+ * An extension of {@link AssertionError} with helper constructors for 
messages with message-style arguments.
+ */
+public class BasicAssertionError extends AssertionError {
+
+       private static final long serialVersionUID = 1L;
+
+       /**
+        * Constructor.
+        *
+        * @param message The {@link MessageFormat}-style message.
+        * @param args Optional {@link MessageFormat}-style arguments.
+        */
+       public BasicAssertionError(String message, Object...args) {
+               super(format(message, args));
+       }
+
+       /**
+        * Constructor.
+        *
+        * @param cause The cause of this exception.
+        * @param message The {@link MessageFormat}-style message.
+        * @param args Optional {@link MessageFormat}-style arguments.
+        */
+       public BasicAssertionError(Throwable cause, String message, 
Object...args) {
+               this(getMessage(cause, message, null), args);
+               initCause(cause);
+       }
+
+       /**
+        * Finds the message.
+        *
+        * @param cause The cause.
+        * @param msg The message.
+        * @param def The default value if both above are <jk>null</jk>.
+        * @return The resolved message.
+        */
+       protected static final String getMessage(Throwable cause, String msg, 
String def) {
+               if (msg != null)
+                       return msg;
+               if (cause != null)
+                       return cause.getMessage();
+               return def;
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/FormData.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/FormData.java
index 8c2be4d..d3e3d31 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/FormData.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/FormData.java
@@ -147,6 +147,23 @@ public @interface FormData {
         */
        Class<? extends HttpPartParser> parser() default 
HttpPartParser.Null.class;
 
+       /**
+        * Denotes a multi-part parameter (e.g. multiple entries with the same 
name).
+        *
+        * <h5 class='figure'>Example</h5>
+        *      <jk>public void</jk> doPost(
+        *              <ja>@FormData</ja>(name=<js>"beans"</js>, 
multi=<jk>true</jk>) MyBean[] beans
+        *      ) {
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Meant to be used on multi-part parameters (e.g. 
<js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=@(1,2,3)"</js>)
+        *      <li>
+        *              The data type must be a collection or array type.
+        * </ul>
+        */
+       boolean multi() default false;
+
        
//=================================================================================================================
        // Attributes common to all Swagger Parameter objects
        
//=================================================================================================================
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Header.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Header.java
index 3aa1388..45dc38c 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Header.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Header.java
@@ -120,6 +120,23 @@ public @interface Header {
         */
        Class<? extends HttpPartParser> parser() default 
HttpPartParser.Null.class;
 
+       /**
+        * Denotes a multi-part parameter (e.g. multiple entries with the same 
name).
+        *
+        * <h5 class='figure'>Example</h5>
+        *      <jk>public void</jk> doPost(
+        *              <ja>@Header</ja>(name=<js>"Beans"</js>, 
multi=<jk>true</jk>) MyBean[] beans
+        *      ) {
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Meant to be used on multi-part parameters (e.g. 
<js>"Header: h1"</js>, <js>"Header: h2"</js> instead of <js>"Header: 
@(h1,h2)"</js>)
+        *      <li>
+        *              The data type must be a collection or array type.
+        * </ul>
+        */
+       boolean multi() default false;
+
        
//=================================================================================================================
        // Attributes common to all Swagger Parameter objects
        
//=================================================================================================================
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Query.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Query.java
index a7a1db7..b7704a7 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Query.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Query.java
@@ -128,6 +128,23 @@ public @interface Query {
         */
        Class<? extends HttpPartParser> parser() default 
HttpPartParser.Null.class;
 
+       /**
+        * Denotes a multi-part parameter (e.g. multiple entries with the same 
name).
+        *
+        * <h5 class='figure'>Example</h5>
+        *      <jk>public void</jk> doPost(
+        *              <ja>@Query</ja>(name=<js>"beans"</js>, 
multi=<jk>true</jk>) MyBean[] beans
+        *      ) {
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Meant to be used on multi-part parameters (e.g. 
<js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=@(1,2,3)"</js>)
+        *      <li>
+        *              The data type must be a collection or array type.
+        * </ul>
+        */
+       boolean multi() default false;
+
        
//=================================================================================================================
        // Attributes common to all Swagger Parameter objects
        
//=================================================================================================================
diff --git a/juneau-doc/docs/ReleaseNotes/8.1.4.html 
b/juneau-doc/docs/ReleaseNotes/8.1.4.html
index 984bb0d..49a3475 100644
--- a/juneau-doc/docs/ReleaseNotes/8.1.4.html
+++ b/juneau-doc/docs/ReleaseNotes/8.1.4.html
@@ -176,6 +176,13 @@
        }
                </p>            
        <li>
+               New annotations for multi-part support:
+               <ul>
+                       <li class='jm'>{@link 
oaj.http.annotation.Header#multi()}
+                       <li class='jm'>{@link oaj.http.annotation.Query#multi()}
+                       <li class='jm'>{@link 
oaj.http.annotation.FormData#multi()}
+               </ul>
+       <li>
                HTML-Schema support is being deprecated due to low-use and 
difficulty in maintaining.  It will be removed in 9.0.
 </ul>
 
diff --git 
a/juneau-doc/docs/Topics/02.juneau-marshall/27.OpenApiDetails/02.Serializers.html
 
b/juneau-doc/docs/Topics/02.juneau-marshall/27.OpenApiDetails/02.Serializers.html
index 29a268e..2bca333 100644
--- 
a/juneau-doc/docs/Topics/02.juneau-marshall/27.OpenApiDetails/02.Serializers.html
+++ 
b/juneau-doc/docs/Topics/02.juneau-marshall/27.OpenApiDetails/02.Serializers.html
@@ -51,14 +51,12 @@ OpenAPI Serializers
        Under-the-covers, this gets converted to the following schema object:
 </p>
 <p class='bpcode w800'>
-       <jk>import static</jk> org.apache.juneau.httppart.HttpPartSchema.*;
-
-       HttpPartSchema schema = <jsm>create</jsm>()
+       HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
                .items(
-                       <jsm>create</jsm>()
+                       HttpPartSchema.<jsm>create</jsm>()
                                .collectionFormat(<js>"pipes"</js>)
                                .items(
-                                       <jsm>create</jsm>()
+                                       HttpPartSchema.<jsm>create</jsm>()
                                                
.collectionFormat(<js>"csv"</js>)
                                                .type(<js>"integer"</js>) 
                                                .format(<js>"int64"</js>)
diff --git 
a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/RestClientTest.java
 
b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/RestClientTest.java
index fe1ca75..17779fe 100644
--- 
a/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/RestClientTest.java
+++ 
b/juneau-microservice/juneau-microservice-ftest/src/test/java/org/apache/juneau/rest/test/client/RestClientTest.java
@@ -43,7 +43,7 @@ public class RestClientTest extends RestTestcase {
                try {
                        c.post(URL, new 
StringEntity("xxxFAILURExxx")).run().getBody().assertContains("SUCCESS");
                        fail();
-               } catch (RestCallException e) {
+               } catch (AssertionError e) {
                        assertTrue(e.getLocalizedMessage().contains("Response 
did not have the expected substring for body."));
                }
        }
@@ -64,7 +64,7 @@ public class RestClientTest extends RestTestcase {
                try {
                        c.post(URL, new 
StringEntity("xxxFAILURExxx")).run().getBody().assertValue(x -> ! 
x.contains("FAILURE"));
                        fail();
-               } catch (RestCallException e) {
+               } catch (AssertionError e) {
                        assertTrue(e.getLocalizedMessage().contains("Response 
did not have the expected value for body."));
                }
        }
diff --git 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientBuilderTest.java
 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientBuilderTest.java
index 07d020f..7faca72 100644
--- 
a/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientBuilderTest.java
+++ 
b/juneau-rest/juneau-rest-client-utest/src/test/java/org/apache/juneau/rest/client2/RestClientBuilderTest.java
@@ -33,6 +33,7 @@ import org.apache.juneau.*;
 import org.apache.juneau.collections.*;
 import org.apache.juneau.http.*;
 import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.http.annotation.Header;
 import org.apache.juneau.http.exception.*;
 import org.apache.juneau.http.response.*;
 import org.apache.juneau.httppart.*;
@@ -2182,30 +2183,57 @@ public class RestClientBuilderTest {
                }
        }
 
-//     @Test
-//     public void k17_restClient_partParserClass() throws Exception {
-//             RestClient rc = MockRestClient
-//                     .create(A.class)
-//                     .simpleJson()
-//                     
.serializers(XmlSerializer.DEFAULT,JsonSerializer.DEFAULT)
-//                     .parsers(XmlParser.DEFAULT,JsonParser.DEFAULT)
-//                     .build();
-//
-//     }
-////   public RestClientBuilder partParser(Class<? extends HttpPartParser> 
value) {
-//
-//     @Test
-//     public void k18_restClient_partParserObject() throws Exception { 
fail(); }
-////   public RestClientBuilder partParser(HttpPartParser value) {
-//
-//     @Test
-//     public void k19_restClient_partSerializerClass() throws Exception { 
fail(); }
-////   public RestClientBuilder partSerializer(Class<? extends 
HttpPartSerializer> value) {
-//
-//     @Test
-//     public void k20_restClient_partSerializerObject() throws Exception { 
fail(); }
-////   public RestClientBuilder partSerializer(HttpPartSerializer value) {
-//
+       @Rest(partSerializer=XPartSerializer.class, 
partParser=XPartParser.class,debug="true")
+       public static class K extends BasicRest {
+               @RestMethod(path="/")
+               public Ok get(@Header(name="Foo",multi=true) Bean[] foo, 
org.apache.juneau.rest.RestRequest req, org.apache.juneau.rest.RestResponse 
res) throws Exception {
+                       assertEquals(2, foo.length);
+                       assertObjectEquals("['x{f:1}','x{f:1}']", 
req.getHeaders().getAll("Foo", String[].class));
+                       assertEquals("{f:1}", foo[0].toString());
+                       assertEquals("{f:1}", foo[1].toString());
+                       res.header("Foo", bean);
+                       return Ok.OK;
+               }
+       }
+
+       @Test
+       public void k17_restClient_partSerializer_partParser_Class() throws 
Exception {
+               RestClient rc = MockRestClient
+                       .create(K.class)
+                       .simpleJson()
+                       .header("Foo",bean)
+                       .partSerializer(XPartSerializer.class)
+                       .partParser(XPartParser.class)
+                       .header("")
+                       .build();
+               Bean b = rc
+                       .get("/")
+                       .header("Foo",bean)
+                       .run()
+                       .getHeader("Foo").assertValue("x{f:1}")
+                       .getHeader("Foo").as(Bean.class);
+               assertEquals("{f:1}", b.toString());
+       }
+
+       @Test
+       public void k18_restClient_partSerializer_partParser_Object() throws 
Exception {
+               RestClient rc = MockRestClient
+                       .create(K.class)
+                       .simpleJson()
+                       .header("Foo",bean)
+                       .partSerializer(new XPartSerializer())
+                       .partParser(new XPartParser())
+                       .header("")
+                       .build();
+               Bean b = rc
+                       .get("/")
+                       .header("Foo",bean)
+                       .run()
+                       .getHeader("Foo").assertValue("x{f:1}")
+                       .getHeader("Foo").as(Bean.class);
+               assertEquals("{f:1}", b.toString());
+       }
+
 //     @Test
 //     public void k21_restClient_serializerClass() throws Exception { fail(); 
}
 ////   public RestClientBuilder serializer(Class<? extends Serializer> value) {
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponse.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponse.java
index 3618bc4..e2fb349 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponse.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponse.java
@@ -154,14 +154,15 @@ public final class RestResponse implements HttpResponse {
         *
         * @param validCodes The list of valid codes.
         * @return This object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertStatusCode(int...validCodes) throws 
RestCallException {
+       public RestResponse assertStatusCode(int...validCodes) throws 
RestCallException, AssertionError {
                int sc = getStatusCode();
                for (int c : validCodes)
                        if (c == sc)
                                return this;
-               throw new RestCallException("Response did not have the expected 
status code.\n\tExpected=[{0}]\n\tActual=[{1}]", validCodes, sc);
+               throw new BasicAssertionError("Response did not have the 
expected status code.\n\tExpected=[{0}]\n\tActual=[{1}]", validCodes, sc);
        }
 
        /**
@@ -169,13 +170,14 @@ public final class RestResponse implements HttpResponse {
         *
         * @param test The test.
         * @return This object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertStatusCode(Predicate<Integer> test) throws 
RestCallException {
+       public RestResponse assertStatusCode(Predicate<Integer> test) throws 
RestCallException, AssertionError {
                int sc = getStatusCode();
                if (test.test(sc))
                        return this;
-               throw new RestCallException("Response did not have the expected 
status code.\n\tActual=[{0}]", sc);
+               throw new BasicAssertionError("Response did not have the 
expected status code.\n\tActual=[{0}]", sc);
        }
 
        
//------------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseBody.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseBody.java
index 4ddbd30..5472c3e 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseBody.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseBody.java
@@ -1542,12 +1542,13 @@ public class RestResponseBody implements HttpEntity {
         *
         * @param value The value to check against.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(String value) throws RestCallException {
+       public RestResponse assertValue(String value) throws RestCallException, 
AssertionError {
                String text = asString();
                if (! StringUtils.isEquals(value, text))
-                       throw new RestCallException("Response did not have the 
expected value for body.\n\tExpected=[{0}]\n\tActual=[{1}]", value, text);
+                       throw new BasicAssertionError("Response did not have 
the expected value for body.\n\tExpected=[{0}]\n\tActual=[{1}]", value, text);
                return response;
        }
 
@@ -1576,13 +1577,14 @@ public class RestResponseBody implements HttpEntity {
         *
         * @param values The values to check against.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws AssertionError If assertion failed.
+        * @throws RestCallException If REST call failed.
         */
-       public RestResponse assertContains(String...values) throws 
RestCallException {
+       public RestResponse assertContains(String...values) throws 
RestCallException, AssertionError {
                String text = asString();
                for (String substring : values)
                        if (! StringUtils.contains(text, substring))
-                               throw new RestCallException("Response did not 
have the expected substring for body.\n\tExpected=[{0}]\n\tBody=[{1}]", 
substring, text);
+                               throw new BasicAssertionError("Response did not 
have the expected substring for body.\n\tExpected=[{0}]\n\tBody=[{1}]", 
substring, text);
                return response;
        }
 
@@ -1611,12 +1613,13 @@ public class RestResponseBody implements HttpEntity {
         *
         * @param test The predicate to use to test the body context.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(Predicate<String> test) throws 
RestCallException {
+       public RestResponse assertValue(Predicate<String> test) throws 
RestCallException, AssertionError {
                String text = asString();
                if (! test.test(text))
-                       throw new RestCallException("Response did not have the 
expected value for body.\n\tActual=[{0}]", text);
+                       throw new BasicAssertionError("Response did not have 
the expected value for body.\n\tActual=[{0}]", text);
                return response;
        }
 
@@ -1645,9 +1648,10 @@ public class RestResponseBody implements HttpEntity {
         *
         * @param regex The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex) throws 
RestCallException {
+       public RestResponse assertMatches(String regex) throws 
RestCallException, AssertionError {
                return assertMatches(regex, 0);
        }
 
@@ -1677,13 +1681,14 @@ public class RestResponseBody implements HttpEntity {
         * @param regex The pattern to test for.
         * @param flags Pattern match flags.  See {@link 
Pattern#compile(String, int)}.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex, int flags) throws 
RestCallException {
+       public RestResponse assertMatches(String regex, int flags) throws 
RestCallException, AssertionError {
                String text = asString();
                Pattern p = Pattern.compile(regex, flags);
                if (! p.matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern.\n\tpattern=[{0}]\n\tBody=[{1}]", regex, text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern.\n\tpattern=[{0}]\n\tBody=[{1}]", regex, text);
                return response;
        }
 
@@ -1713,12 +1718,13 @@ public class RestResponseBody implements HttpEntity {
         *
         * @param pattern The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion fails.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(Pattern pattern) throws 
RestCallException {
+       public RestResponse assertMatches(Pattern pattern) throws 
RestCallException, AssertionError {
                String text = asString();
                if (! pattern.matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern.\n\tpattern=[{0}]\n\tBody=[{1}]", pattern.pattern(), text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern.\n\tpattern=[{0}]\n\tBody=[{1}]", pattern.pattern(), text);
                return response;
        }
 
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
index 0023f46..650bbe7 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
@@ -516,11 +516,12 @@ public class RestResponseHeader implements Header {
         * </p>
         *
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertExists() throws RestCallException {
+       public RestResponse assertExists() throws RestCallException, 
AssertionError {
                if (! exists())
-                       throw new RestCallException("Response did not have the 
expected header {0}.", getName());
+                       throw new BasicAssertionError("Response did not have 
the expected header {0}.", getName());
                return response;
        }
 
@@ -538,11 +539,12 @@ public class RestResponseHeader implements Header {
         *
         * @param value The value to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(String value) throws RestCallException {
+       public RestResponse assertValue(String value) throws RestCallException, 
AssertionError {
                if (! StringUtils.isEquals(value, asString()))
-                       throw new RestCallException("Response did not have the 
expected value for header {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", getName(), 
value, asString());
+                       throw new BasicAssertionError("Response did not have 
the expected value for header {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", 
getName(), value, asString());
                return response;
        }
 
@@ -560,12 +562,13 @@ public class RestResponseHeader implements Header {
         *
         * @param test The predicate to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(Predicate<String> test) throws 
RestCallException {
+       public RestResponse assertValue(Predicate<String> test) throws 
RestCallException, AssertionError {
                String text = asString();
                if (! test.test(text))
-                       throw new RestCallException("Response did not have the 
expected value for header {0}.\n\tActual=[{1}]", getName(), text);
+                       throw new BasicAssertionError("Response did not have 
the expected value for header {0}.\n\tActual=[{1}]", getName(), text);
                return response;
        }
 
@@ -583,13 +586,14 @@ public class RestResponseHeader implements Header {
         *
         * @param values The substrings to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertContains(String...values) throws 
RestCallException {
+       public RestResponse assertContains(String...values) throws 
RestCallException, AssertionError {
                String text = asString();
                for (String substring : values)
                        if (! StringUtils.contains(text, substring))
-                               throw new RestCallException("Response did not 
have the expected substring in header {0}.\n\tExpected=[{1}]\n\tHeader=[{2}]", 
getName(), substring, text);
+                               throw new BasicAssertionError("Response did not 
have the expected substring in header {0}.\n\tExpected=[{1}]\n\tHeader=[{2}]", 
getName(), substring, text);
                return response;
        }
 
@@ -607,9 +611,10 @@ public class RestResponseHeader implements Header {
         *
         * @param regex The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex) throws 
RestCallException {
+       public RestResponse assertMatches(String regex) throws 
RestCallException, AssertionError {
                return assertMatches(regex, 0);
        }
 
@@ -628,12 +633,13 @@ public class RestResponseHeader implements Header {
         * @param regex The pattern to test for.
         * @param flags Pattern match flags.  See {@link 
Pattern#compile(String, int)}.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex, int flags) throws 
RestCallException {
+       public RestResponse assertMatches(String regex, int flags) throws 
RestCallException, AssertionError {
                String text = asString();
                if (! Pattern.compile(regex, flags).matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
regex, text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
regex, text);
                return response;
        }
 
@@ -655,12 +661,13 @@ public class RestResponseHeader implements Header {
         *
         * @param pattern The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws RestCallException If REST call failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(Pattern pattern) throws 
RestCallException {
+       public RestResponse assertMatches(Pattern pattern) throws 
RestCallException, AssertionError {
                String text = asString();
                if (! pattern.matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
pattern.pattern(), text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
pattern.pattern(), text);
                return response;
        }
 
diff --git a/juneau-rest/juneau-rest-server/pom.xml 
b/juneau-rest/juneau-rest-server/pom.xml
index dc03d7c..ecacb46 100644
--- a/juneau-rest/juneau-rest-server/pom.xml
+++ b/juneau-rest/juneau-rest-server/pom.xml
@@ -60,6 +60,10 @@
                        <groupId>com.sun.activation</groupId>
                        <artifactId>javax.activation</artifactId>
                </dependency>
+               <dependency>
+                       <groupId>org.apache.httpcomponents</groupId>
+                       <artifactId>httpcore</artifactId>
+               </dependency>
        </dependencies>
 
        <properties>
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
index 3c5a964..262e868 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestCallHandler.java
@@ -344,6 +344,9 @@ public class BasicRestCallHandler implements 
RestCallHandler {
 
                call.exception(e);
 
+               if (call.isDebug())
+                       e.printStackTrace();
+
                int occurrence = context == null ? 0 : 
context.getStackTraceOccurrence(e);
 
                int code = 500;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
index 3fef82e..949b7eb 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestFormData.java
@@ -16,6 +16,7 @@ import static org.apache.juneau.internal.ArrayUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
 
 import java.lang.reflect.*;
+import java.lang.reflect.Type;
 import java.util.*;
 
 import javax.servlet.http.*;
@@ -120,20 +121,12 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        /**
         * Returns a form-data parameter value.
         *
-        * <p>
-        * Parameter lookup is case-insensitive (consistent with WAS, but 
differs from Tomcat).
-        *
         * <ul class='notes'>
         *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
         *              This method returns the raw unparsed value, and differs 
from calling
-        *              <code>get(name, String.<jk>class</jk>)</code> which 
will convert the value from UON
-        *              notation:
-        *              <ul>
-        *                      <li><js>"null"</js> =&gt; <jk>null</jk>
-        *                      <li><js>"'null'"</js> =&gt; <js>"null"</js>
-        *                      <li><js>"'foo bar'"</js> =&gt; <js>"foo 
bar"</js>
-        *                      <li><js>"foo~~bar"</js> =&gt; <js>"foo~bar"</js>
-        *              </ul>
+        *              <code>get(name, String.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
         * </ul>
         *
         * @param name The form-data parameter name.
@@ -154,7 +147,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String)} except returns a default value if 
<jk>null</jk> or empty.
+        * Returns a form-data parameter value.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
+        *              This method returns the raw unparsed value, and differs 
from calling
+        *              <code>get(name, String.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
+        * </ul>
         *
         * @param name The form-data parameter name.
         * @param def The default value.
@@ -166,7 +167,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String)} but converts the value to an 
integer.
+        * Returns a form-data parameter value as an integer.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
+        *              This method returns the raw unparsed value, and differs 
from calling
+        *              <code>get(name, Integer.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
+        * </ul>
         *
         * @param name The form-data parameter name.
         * @return The parameter value, or <c>0</c> if parameter does not exist 
or is <jk>null</jk> or empty.
@@ -176,7 +185,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String,String)} but converts the value to 
an integer.
+        * Returns a form-data parameter value as an integer.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
+        *              This method returns the raw unparsed value, and differs 
from calling
+        *              <code>get(name, Integer.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
+        * </ul>
         *
         * @param name The form-data parameter name.
         * @param def The default value.
@@ -188,7 +205,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String)} but converts the value to a 
boolean.
+        * Returns a form-data parameter value as a boolean.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
+        *              This method returns the raw unparsed value, and differs 
from calling
+        *              <code>get(name, Boolean.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
+        * </ul>
         *
         * @param name The form-data parameter name.
         * @return The parameter value, or <jk>false</jk> if parameter does not 
exist or is <jk>null</jk> or empty.
@@ -198,7 +223,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String,String)} but converts the value to 
a boolean.
+        * Returns a form-data parameter value as a boolean.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              Parameter lookup is case-insensitive (consistent with 
WAS, but differs from Tomcat).
+        *      <li>
+        *              This method returns the raw unparsed value, and differs 
from calling
+        *              <code>get(name, Boolean.<jk>class</jk>)</code> which 
uses the {@link HttpPartParser} for parsing the value.
+        * </ul>
         *
         * @param name The form-data parameter name.
         * @param def The default value.
@@ -246,7 +279,84 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #get(String, Object, Class)} but allows you to 
override the part parser.
+        * Returns the specified form-data parameter value converted to a POJO 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *      <jc>// Parse into a 2d long array.</jc>
+        *      <jk>long</jk>[][] myparams = formData.get(schema, 
<js>"myparam"</js>, <jk>long</jk>[][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
+        *
+        * @param schema
+        *      The schema object that defines the format of the input.
+        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
+        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
+        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @param <T> The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T get(HttpPartSchema schema, String name, Class<T> type) 
throws BadRequest, InternalServerError {
+               return getInner(null, schema, name, null, getClassMeta(type));
+       }
+
+       /**
+        * Returns the specified form-data parameter value converted to a POJO 
using the specified {@link HttpPartParser}.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *  HttpPartParserSession parser = 
OpenApiParser.<jsf>DEFAULT</jsf>.createSession();
+        *
+        *      <jc>// Parse into a 2d long array.</jc>
+        *      <jk>long</jk>[][] myparams = formData.get(parser, schema, 
<js>"myparam"</js>, <jk>long</jk>[][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
         *
         * @param parser
         *      The parser to use for parsing the string value.
@@ -268,7 +378,29 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #get(String, Class)} except returns a default value 
if not specified.
+        * Returns the specified form-data parameter value converted to a POJO 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Parse into an integer.</jc>
+        *      <jk>int</jk> myparam = formData.get(<js>"myparam"</js>, -1, 
<jk>int</jk>.<jk>class</jk>);
+        *
+        *      <jc>// Parse into an int array.</jc>
+        *      <jk>int</jk>[] myparam = formData.get(<js>"myparam"</js>, 
<jk>new int</jk>[0], <jk>int</jk>[].<jk>class</jk>);
+
+        *      <jc>// Parse into a bean.</jc>
+        *      MyBean myparam = formData.get(<js>"myparam"</js>, <jk>new</jk> 
MyBean(), MyBean.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a linked-list of objects.</jc>
+        *      List myparam = formData.get(<js>"myparam"</js>, 
Collections.<jsm>emptyList</jsm>(), LinkedList.<jk>class</jk>);
+        *
+        *      <jc>// Parse into a map of object keys/values.</jc>
+        *      Map myparam = formData.get(<js>"myparam"</js>, 
Collections.<jsm>emptyMap</jsm>(), TreeMap.<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
         *
         * @param name The parameter name.
         * @param def The default value if the parameter was not specified or 
is <jk>null</jk>.
@@ -283,11 +415,36 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #get(String, Object, Class)} but allows you to 
override the part parser.
+        * Returns the specified form-data parameter value converted to a POJO 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *      <jc>// Parse into a 2d long array.</jc>
+        *      <jk>long</jk>[][] myparams = formData.get(schema, 
<js>"myparam"</js>, <jk>new long</jk>[][0], <jk>long</jk>[][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
         *
-        * @param parser
-        *      The parser to use for parsing the string value.
-        *      <br>If <jk>null</jk>, uses the part parser defined on the 
resource/method.
         * @param schema
         *      The schema object that defines the format of the input.
         *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
@@ -298,35 +455,45 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
         * @param type The class type to convert the parameter value to.
         * @param <T> The class type to convert the parameter value to.
         * @return The parameter value converted to the specified class type.
-        * @throws BadRequest Thrown if input could not be parsed or fails 
schema validation.
-        * @throws InternalServerError Thrown if any other exception occurs.
-        */
-       public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, T def, Class<T> type) throws BadRequest, InternalServerError {
-               return getInner(parser, schema, name, def, getClassMeta(type));
-       }
-
-       /**
-        * Same as {@link #get(String, Class)} except for use on multi-part 
parameters
-        * (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of 
<js>"key=@(1,2,3)"</js>)
-        *
-        * <p>
-        * This method must only be called when parsing into classes of type 
Collection or array.
-        *
-        * @param name The parameter name.
-        * @param type The class type to convert the parameter value to.
-        * @return The parameter value converted to the specified class type.
         * @throws BadRequest Thrown if input could not be parsed.
         * @throws InternalServerError Thrown if any other exception occurs.
         */
-       public <T> T getAll(String name, Class<T> type) throws BadRequest, 
InternalServerError {
-               return getAllInner(null, null, name, null, getClassMeta(type));
+       public <T> T get(HttpPartSchema schema, String name, T def, Class<T> 
type) throws BadRequest, InternalServerError {
+               return getInner(null, schema, name, def, getClassMeta(type));
        }
 
        /**
-        * Same as {@link #getAll(String, Class)} but allows you to override 
the part parser.
+        * Returns the specified form-data parameter value converted to a POJO 
using the specified {@link HttpPartParser}.
         *
-        * <p>
-        * This method must only be called when parsing into classes of type 
Collection or array.
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *  HttpPartParserSession parser = 
OpenApiParser.<jsf>DEFAULT</jsf>.createSession();
+        *
+        *      <jc>// Parse into a 2d long array.</jc>
+        *      <jk>long</jk>[][] myparams = formData.get(parser, schema, 
<js>"myparam"</js>, <jk>new long</jk>[][0], <jk>long</jk>[][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
         *
         * @param parser
         *      The parser to use for parsing the string value.
@@ -337,13 +504,15 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
         *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
         *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
         * @param name The parameter name.
+        * @param def The default value if the parameter was not specified or 
is <jk>null</jk>.
         * @param type The class type to convert the parameter value to.
+        * @param <T> The class type to convert the parameter value to.
         * @return The parameter value converted to the specified class type.
         * @throws BadRequest Thrown if input could not be parsed or fails 
schema validation.
         * @throws InternalServerError Thrown if any other exception occurs.
         */
-       public <T> T getAll(HttpPartParserSession parser, HttpPartSchema 
schema, String name, Class<T> type) throws BadRequest, InternalServerError {
-               return getAllInner(parser, schema, name, null, 
getClassMeta(type));
+       public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, T def, Class<T> type) throws BadRequest, InternalServerError {
+               return getInner(parser, schema, name, def, getClassMeta(type));
        }
 
        /**
@@ -393,7 +562,7 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        public <T> T get(String name, Type type, Type...args) throws 
BadRequest, InternalServerError {
                return getInner(null, null, name, null, 
this.<T>getClassMeta(type, args));
        }
-
+       
        /**
         * Same as {@link #get(String, Type, Type...)} but allows you to 
override the part parser.
         *
@@ -472,6 +641,148 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
        }
 
        /**
+        * Returns the specified form-data parameter values converted POJO 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <p>
+        * Meant to be used on multi-part parameters (e.g. 
<js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=@(1,2,3)"</js>)
+        *
+        * <p>
+        * This method must only be called when parsing into classes of type 
Collection or array.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Parse into multiple integers.</jc>
+        *      <jk>int</jk>[] myparam = formData.getAll(<js>"myparam"</js>, 
<jk>int</jk>[].<jk>class</jk>);
+        *
+        *      <jc>// Parse into multiple int arrays.</jc>
+        *      <jk>int</jk>[][] myparam = formData.getAll(<js>"myparam"</js>, 
<jk>int</jk>[][].<jk>class</jk>);
+
+        *      <jc>// Parse into multiple beans.</jc>
+        *      MyBean[] myparam = formData.getAll(<js>"myparam"</js>, 
MyBean[].<jk>class</jk>);
+        *
+        *      <jc>// Parse into multiple linked-lists of objects.</jc>
+        *      List[] myparam = formData.getAll(<js>"myparam"</js>, 
LinkedList[].<jk>class</jk>);
+        *
+        *      <jc>// Parse into multiple maps of object keys/values.</jc>
+        *      Map[] myparam = formData.getAll(<js>"myparam"</js>, 
TreeMap[].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
+        *
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T getAll(String name, Class<T> type) throws BadRequest, 
InternalServerError {
+               return getAllInner(null, null, name, getClassMeta(type));
+       }
+
+       /**
+        * Returns the specified form-data parameter values converted to POJOs 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <p>
+        * Meant to be used on multi-part parameters (e.g. 
<js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=@(1,2,3)"</js>)
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *      <jc>// Parse into multiple 2d long arrays.</jc>
+        *      <jk>long</jk>[][][] myparams = formData.getAll(schema, 
<js>"myparam"</js>, <jk>long</jk>[][][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
+        *
+        * @param schema
+        *      The schema object that defines the format of the input.
+        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
+        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
+        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T getAll(HttpPartSchema schema, String name, Class<T> type) 
throws BadRequest, InternalServerError {
+               return getAllInner(null, schema, name, getClassMeta(type));
+       }
+
+       /**
+        * Returns the specified form-data parameter values converted to POJOs 
using the specified {@link HttpPartParser}.
+        *
+        * <p>
+        * Meant to be used on multi-part parameters (e.g. 
<js>"key=1&amp;key=2&amp;key=3"</js> instead of <js>"key=@(1,2,3)"</js>)
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Pipe-delimited list of comma-delimited numbers</jc>
+        *      HttpPartSchema schema = HttpPartSchema.<jsm>create</jsm>()
+        *              .items(
+        *                      HttpPartSchema.<jsm>create</jsm>()
+        *                      .collectionFormat(<js>"pipes"</js>)
+        *                      .items(
+        *                              HttpPartSchema.<jsm>create</jsm>()
+        *                              .collectionFormat(<js>"csv"</js>)
+        *                              .type(<js>"integer"</js>)
+        *                              .format(<js>"int64"</js>)
+        *                              .minimum(<js>"0"</js>)
+        *                              .maximum(<js>"100"</js>)
+        *                              .minLength(1)
+        *                              .maxLength=(10)
+        *                      )
+        *              )
+        *              .build();
+        *
+        *      <jc>// Parse into multiple 2d long arrays.</jc>
+        *      <jk>long</jk>[][][] myparams = formData.getAll(schema, 
<js>"myparam"</js>, <jk>long</jk>[][][].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
+        *
+        * @param parser
+        *      The parser to use for parsing the string value.
+        *      <br>If <jk>null</jk>, uses the part parser defined on the 
resource/method.
+        * @param schema
+        *      The schema object that defines the format of the input.
+        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
+        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
+        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
+        * @param name The parameter name.
+        * @param type The class type to convert the parameter value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed or fails 
schema validation.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T getAll(HttpPartParserSession parser, HttpPartSchema 
schema, String name, Class<T> type) throws BadRequest, InternalServerError {
+               return getAllInner(parser, schema, name, getClassMeta(type));
+       }
+
+       /**
         * Same as {@link #get(String, Type, Type...)} except for use on 
multi-part parameters
         * (e.g. <js>"key=1&amp;key=2&amp;key=3"</js> instead of 
<js>"key=@(1,2,3)"</js>)
         *
@@ -491,7 +802,7 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T getAll(String name, Type type, Type...args) throws 
BadRequest, InternalServerError {
-               return getAllInner(null, null, name, null, 
this.<T>getClassMeta(type, args));
+               return getAllInner(null, null, name, this.<T>getClassMeta(type, 
args));
        }
 
        /**
@@ -518,11 +829,13 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T getAll(HttpPartParserSession parser, HttpPartSchema 
schema, String name, Type type, Type...args) throws BadRequest, 
InternalServerError {
-               return getAllInner(parser, schema, name, null, 
this.<T>getClassMeta(type, args));
+               return getAllInner(parser, schema, name, 
this.<T>getClassMeta(type, args));
        }
 
        /* Workhorse method */
        private <T> T getInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
+               if (parser == null)
+                       parser = req.getPartParser();
                try {
                        if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
                                OMap m = new OMap();
@@ -531,7 +844,7 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
                                        HttpPartSchema pschema = schema == null 
? null : schema.getProperty(k);
                                        ClassMeta<?> cm2 = cm.getValueType();
                                        if 
(cm.getValueType().isCollectionOrArray())
-                                               m.put(k, getAllInner(parser, 
pschema, k, null, cm2));
+                                               m.put(k, getAllInner(parser, 
pschema, k, cm2));
                                        else
                                                m.put(k, getInner(parser, 
pschema, k, null, cm2));
                                }
@@ -550,10 +863,8 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
 
        /* Workhorse method */
        @SuppressWarnings("rawtypes")
-       <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema schema, 
String name, T def, ClassMeta<T> cm) throws BadRequest, InternalServerError {
+       <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema schema, 
String name, ClassMeta<T> cm) throws BadRequest, InternalServerError {
                String[] p = get(name);
-               if (p == null)
-                       return def;
                if (schema == null)
                        schema = HttpPartSchema.DEFAULT;
                try {
@@ -575,7 +886,7 @@ public class RequestFormData extends 
LinkedHashMap<String,String[]> {
                } catch (Exception e) {
                        throw new InternalServerError(e, "Could not parse 
form-data parameter ''{0}''.", name) ;
                }
-               throw new InternalServerError("Invalid call to 
getParameters(String, ClassMeta).  Class type must be a Collection or array.");
+               throw new InternalServerError("Invalid call to getAll(String, 
ClassMeta).  Class type must be a Collection or array.");
        }
 
        private <T> T parse(HttpPartParserSession parser, HttpPartSchema 
schema, String val, ClassMeta<T> c) throws SchemaValidationException, 
ParseException {
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeader.java
similarity index 82%
copy from 
juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
copy to 
juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeader.java
index 0023f46..6f76f60 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client2/RestResponseHeader.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeader.java
@@ -10,7 +10,7 @@
 // * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either 
express or implied.  See the License for the        *
 // * specific language governing permissions and limitations under the 
License.                                              *
 // 
***************************************************************************************************************************
-package org.apache.juneau.rest.client2;
+package org.apache.juneau.rest;
 
 import static org.apache.juneau.httppart.HttpPartType.*;
 import java.lang.reflect.*;
@@ -28,17 +28,8 @@ import org.apache.juneau.utils.*;
 
 /**
  * Represents a single header on an HTTP response.
- *
- * <p>
- * An extension of an HttpClient {@link Header} that provides various support 
for converting the header to POJOs and
- * other convenience methods.
- *
- * <ul class='seealso'>
- *     <li class='jc'>{@link RestClient}
- *     <li class='link'>{@doc juneau-rest-client}
- * </ul>
  */
-public class RestResponseHeader implements Header {
+public class RequestHeader implements Header {
 
        static final Header NULL_HEADER = new Header() {
 
@@ -71,7 +62,7 @@ public class RestResponseHeader implements Header {
         * @param response The response object.
         * @param header The wrapped header.  Can be <jk>null</jk>.
         */
-       public RestResponseHeader(RestRequest request, RestResponse response, 
Header header) {
+       public RequestHeader(RestRequest request, RestResponse response, Header 
header) {
                this.request = request;
                this.response = response;
                this.header = header == null ? NULL_HEADER : header;
@@ -92,7 +83,7 @@ public class RestResponseHeader implements Header {
         *      The part schema.
         * @return This object (for method chaining).
         */
-       public RestResponseHeader schema(HttpPartSchema value) {
+       public RequestHeader schema(HttpPartSchema value) {
                this.schema = value;
                return this;
        }
@@ -101,14 +92,14 @@ public class RestResponseHeader implements Header {
         * Specifies the part parser to use for this header.
         *
         * <p>
-        * If not specified, uses the part parser defined on the client by 
calling {@link RestClientBuilder#partParser(Class)}.
+        * If not specified, uses the part parser defined on the client by 
calling {@link RestContextBuilder#partParser(Class)}.
         *
         * @param value
         *      The new part parser to use for this header.
         *      <br>If <jk>null</jk>, {@link SimplePartParser#DEFAULT} will be 
used.
         * @return This object (for method chaining).
         */
-       public RestResponseHeader parser(HttpPartParserSession value) {
+       public RequestHeader parser(HttpPartParserSession value) {
                this.parser = value == null ? SimplePartParser.DEFAULT_SESSION 
: value;
                return this;
        }
@@ -184,9 +175,9 @@ public class RestResponseHeader implements Header {
         * @param type The type to convert to.
         * @param args The type parameters.
         * @return The converted type, or <jk>null</jk> if header is not 
present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> T as(Type type, Type...args) throws RestCallException {
+       public <T> T as(Type type, Type...args) throws ParseException {
                return as(request.getClassMeta(type, args));
        }
 
@@ -198,9 +189,9 @@ public class RestResponseHeader implements Header {
         * @param type The type to convert to.
         * @param args The type parameters.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse as(Mutable<T> m, Type type, Type...args) throws 
RestCallException {
+       public <T> RestResponse as(Mutable<T> m, Type type, Type...args) throws 
ParseException {
                m.set(as(type, args));
                return response;
        }
@@ -211,9 +202,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The converted type, or <jk>null</jk> if header is not 
present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> T as(Class<T> type) throws RestCallException {
+       public <T> T as(Class<T> type) throws ParseException {
                return as(request.getClassMeta(type));
        }
 
@@ -224,9 +215,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse as(Mutable<T> m, Class<T> type) throws 
RestCallException {
+       public <T> RestResponse as(Mutable<T> m, Class<T> type) throws 
ParseException {
                m.set(as(type));
                return response;
        }
@@ -237,14 +228,10 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The converted type, or <jk>null</jk> if header is not 
present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> T as(ClassMeta<T> type) throws RestCallException {
-               try {
-                       return parser.parse(HEADER, schema, asString(), type);
-               } catch (ParseException e) {
-                       throw new RestCallException(e);
-               }
+       public <T> T as(ClassMeta<T> type) throws ParseException {
+               return parser.parse(HEADER, schema, asString(), type);
        }
 
        /**
@@ -254,9 +241,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse as(Mutable<T> m, ClassMeta<T> type) throws 
RestCallException {
+       public <T> RestResponse as(Mutable<T> m, ClassMeta<T> type) throws 
ParseException {
                m.set(as(type));
                return response;
        }
@@ -268,9 +255,9 @@ public class RestResponseHeader implements Header {
         * @param type The type to convert to.
         * @param args The type parameters.
         * @return The parsed value as an {@link Optional}, or an empty 
optional if header was not present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> Optional<T> asOptional(Type type, Type...args) throws 
RestCallException {
+       public <T> Optional<T> asOptional(Type type, Type...args) throws 
ParseException {
                return Optional.ofNullable(as(type, args));
        }
 
@@ -282,9 +269,9 @@ public class RestResponseHeader implements Header {
         * @param type The type to convert to.
         * @param args The type parameters.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse asOptional(Mutable<Optional<T>> m, Type type, 
Type...args) throws RestCallException {
+       public <T> RestResponse asOptional(Mutable<Optional<T>> m, Type type, 
Type...args) throws ParseException {
                m.set(asOptional(type, args));
                return response;
        }
@@ -295,9 +282,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The parsed value as an {@link Optional}, or an empty 
optional if header was not present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> Optional<T> asOptional(Class<T> type) throws 
RestCallException {
+       public <T> Optional<T> asOptional(Class<T> type) throws ParseException {
                return Optional.ofNullable(as(type));
        }
 
@@ -308,9 +295,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse asOptional(Mutable<Optional<T>> m, Class<T> 
type) throws RestCallException {
+       public <T> RestResponse asOptional(Mutable<Optional<T>> m, Class<T> 
type) throws ParseException {
                m.set(asOptional(type));
                return response;
        }
@@ -321,9 +308,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The parsed value as an {@link Optional}, or an empty 
optional if header was not present.
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> Optional<T> asOptional(ClassMeta<T> type) throws 
RestCallException {
+       public <T> Optional<T> asOptional(ClassMeta<T> type) throws 
ParseException {
                return Optional.ofNullable(as(type));
        }
 
@@ -334,9 +321,9 @@ public class RestResponseHeader implements Header {
         * @param <T> The type to convert to.
         * @param type The type to convert to.
         * @return The response object (for method chaining).
-        * @throws RestCallException If value could not be parsed.
+        * @throws ParseException If value could not be parsed.
         */
-       public <T> RestResponse asOptional(Mutable<Optional<T>> m, ClassMeta<T> 
type) throws RestCallException {
+       public <T> RestResponse asOptional(Mutable<Optional<T>> m, ClassMeta<T> 
type) throws ParseException {
                m.set(asOptional(type));
                return response;
        }
@@ -358,9 +345,8 @@ public class RestResponseHeader implements Header {
         *
         * @param pattern The regular expression pattern to match.
         * @return The matcher.
-        * @throws RestCallException If a connection error occurred.
         */
-       public Matcher asMatcher(Pattern pattern) throws RestCallException {
+       public Matcher asMatcher(Pattern pattern) {
                return pattern.matcher(asString());
        }
 
@@ -383,9 +369,8 @@ public class RestResponseHeader implements Header {
         * @param m The mutable to set the value in.
         * @param pattern The regular expression pattern to match.
         * @return The response object (for method chaining).
-        * @throws RestCallException If a connection error occurred.
         */
-       public RestResponse asMatcher(Mutable<Matcher> m, Pattern pattern) 
throws RestCallException {
+       public RestResponse asMatcher(Mutable<Matcher> m, Pattern pattern) {
                m.set(pattern.matcher(asString()));
                return response;
        }
@@ -407,9 +392,8 @@ public class RestResponseHeader implements Header {
         *
         * @param regex The regular expression pattern to match.
         * @return The matcher.
-        * @throws RestCallException If a connection error occurred.
         */
-       public Matcher asMatcher(String regex) throws RestCallException {
+       public Matcher asMatcher(String regex) {
                return asMatcher(regex, 0);
        }
 
@@ -432,9 +416,8 @@ public class RestResponseHeader implements Header {
         * @param m The mutable to set the value in.
         * @param regex The regular expression pattern to match.
         * @return The response object (for method chaining).
-        * @throws RestCallException If a connection error occurred.
         */
-       public RestResponse asMatcher(Mutable<Matcher> m, String regex) throws 
RestCallException {
+       public RestResponse asMatcher(Mutable<Matcher> m, String regex) {
                asMatcher(regex, 0);
                return response;
        }
@@ -457,9 +440,8 @@ public class RestResponseHeader implements Header {
         * @param regex The regular expression pattern to match.
         * @param flags Pattern match flags.  See {@link 
Pattern#compile(String, int)}.
         * @return The matcher.
-        * @throws RestCallException If a connection error occurred.
         */
-       public Matcher asMatcher(String regex, int flags) throws 
RestCallException {
+       public Matcher asMatcher(String regex, int flags) {
                return asMatcher(Pattern.compile(regex, flags));
        }
 
@@ -483,9 +465,8 @@ public class RestResponseHeader implements Header {
         * @param regex The regular expression pattern to match.
         * @param flags Pattern match flags.  See {@link 
Pattern#compile(String, int)}.
         * @return The response object (for method chaining).
-        * @throws RestCallException If a connection error occurred.
         */
-       public RestResponse asMatcher(Mutable<Matcher> m, String regex, int 
flags) throws RestCallException {
+       public RestResponse asMatcher(Mutable<Matcher> m, String regex, int 
flags) {
                asMatcher(Pattern.compile(regex, flags));
                return response;
        }
@@ -516,11 +497,11 @@ public class RestResponseHeader implements Header {
         * </p>
         *
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertExists() throws RestCallException {
+       public RestResponse assertExists() throws AssertionError {
                if (! exists())
-                       throw new RestCallException("Response did not have the 
expected header {0}.", getName());
+                       throw new BasicAssertionError("Response did not have 
the expected header {0}.", getName());
                return response;
        }
 
@@ -538,11 +519,11 @@ public class RestResponseHeader implements Header {
         *
         * @param value The value to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(String value) throws RestCallException {
+       public RestResponse assertValue(String value) throws AssertionError {
                if (! StringUtils.isEquals(value, asString()))
-                       throw new RestCallException("Response did not have the 
expected value for header {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", getName(), 
value, asString());
+                       throw new BasicAssertionError("Response did not have 
the expected value for header {0}.\n\tExpected=[{1}]\n\tActual=[{2}]", 
getName(), value, asString());
                return response;
        }
 
@@ -560,12 +541,12 @@ public class RestResponseHeader implements Header {
         *
         * @param test The predicate to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertValue(Predicate<String> test) throws 
RestCallException {
+       public RestResponse assertValue(Predicate<String> test) throws 
AssertionError {
                String text = asString();
                if (! test.test(text))
-                       throw new RestCallException("Response did not have the 
expected value for header {0}.\n\tActual=[{1}]", getName(), text);
+                       throw new BasicAssertionError("Response did not have 
the expected value for header {0}.\n\tActual=[{1}]", getName(), text);
                return response;
        }
 
@@ -583,13 +564,13 @@ public class RestResponseHeader implements Header {
         *
         * @param values The substrings to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertContains(String...values) throws 
RestCallException {
+       public RestResponse assertContains(String...values) throws 
AssertionError {
                String text = asString();
                for (String substring : values)
                        if (! StringUtils.contains(text, substring))
-                               throw new RestCallException("Response did not 
have the expected substring in header {0}.\n\tExpected=[{1}]\n\tHeader=[{2}]", 
getName(), substring, text);
+                               throw new BasicAssertionError("Response did not 
have the expected substring in header {0}.\n\tExpected=[{1}]\n\tHeader=[{2}]", 
getName(), substring, text);
                return response;
        }
 
@@ -607,9 +588,9 @@ public class RestResponseHeader implements Header {
         *
         * @param regex The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex) throws 
RestCallException {
+       public RestResponse assertMatches(String regex) throws AssertionError {
                return assertMatches(regex, 0);
        }
 
@@ -628,12 +609,12 @@ public class RestResponseHeader implements Header {
         * @param regex The pattern to test for.
         * @param flags Pattern match flags.  See {@link 
Pattern#compile(String, int)}.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(String regex, int flags) throws 
RestCallException {
+       public RestResponse assertMatches(String regex, int flags) throws 
AssertionError {
                String text = asString();
                if (! Pattern.compile(regex, flags).matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
regex, text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
regex, text);
                return response;
        }
 
@@ -655,12 +636,12 @@ public class RestResponseHeader implements Header {
         *
         * @param pattern The pattern to test for.
         * @return The response object (for method chaining).
-        * @throws RestCallException If assertion failed.
+        * @throws AssertionError If assertion failed.
         */
-       public RestResponse assertMatches(Pattern pattern) throws 
RestCallException {
+       public RestResponse assertMatches(Pattern pattern) throws 
AssertionError {
                String text = asString();
                if (! pattern.matcher(text).matches())
-                       throw new RestCallException("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
pattern.pattern(), text);
+                       throw new BasicAssertionError("Response did not match 
expected pattern in header {0}.\n\tpattern=[{1}]\n\tHeader=[{2}]", getName(), 
pattern.pattern(), text);
                return response;
        }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
index 68e8577..da94030 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestHeaders.java
@@ -165,17 +165,27 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String)} but converts the value to an 
integer.
+        * Returns the specified header value as an integer.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        * </ul>
         *
         * @param name The HTTP header name.
-        * @return The header value, or the default value if the header isn't 
present.
+        * @return The header value, or <code>0</code> value if the header 
isn't present.
         */
        public int getInt(String name) {
                return getInt(name, 0);
        }
 
        /**
-        * Same as {@link #getString(String,String)} but converts the value to 
an integer.
+        * Returns the specified header value as an integer.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        * </ul>
         *
         * @param name The HTTP header name.
         * @param def The default value to return if the header value isn't 
found.
@@ -187,7 +197,12 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String)} but converts the value to a 
boolean.
+        * Returns the specified header value as a boolean.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        * </ul>
         *
         * @param name The HTTP header name.
         * @return The header value, or the default value if the header isn't 
present.
@@ -197,7 +212,12 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #getString(String,String)} but converts the value to 
a boolean.
+        * Returns the specified header value as a boolean.
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        * </ul>
         *
         * @param name The HTTP header name.
         * @param def The default value to return if the header value isn't 
found.
@@ -227,10 +247,10 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
         * <h5 class='section'>Examples:</h5>
         * <p class='bcode w800'>
         *      <jc>// Parse into an integer.</jc>
-        *      <jk>int</jk> myheader = req.getHeader(<js>"My-Header"</js>, 
<jk>int</jk>.<jk>class</jk>);
+        *      <jk>int</jk> myheader = 
req.getHeaders().get(<js>"My-Header"</js>, <jk>int</jk>.<jk>class</jk>);
         *
         *      <jc>// Parse a UUID.</jc>
-        *      UUID myheader = req.getHeader(<js>"My-Header"</js>, 
UUID.<jk>class</jk>);
+        *      UUID myheader = req.getHeaders().get(<js>"My-Header"</js>, 
UUID.<jk>class</jk>);
         * </p>
         *
         * <ul class='notes'>
@@ -254,7 +274,59 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
        }
 
        /**
-        * Same as {@link #get(String, Class)} but allows you to override the 
part parser used.
+        * Returns all headers with the specified name converted to a POJO 
using the {@link HttpPartParser} registered with the resource.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Parse into an integer.</jc>
+        *      <jk>int</jk>[] myheaders = 
req.getHeaders().getAll(<js>"My-Header"</js>, <jk>int</jk>[].<jk>class</jk>);
+        *
+        *      <jc>// Parse a UUID.</jc>
+        *      UUID[] myheaders = 
req.getHeaders().getAll(<js>"My-Header"</js>, UUID[].<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        *      <li>
+        *              The class must be an array or collection type.
+        * </ul>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
+        *
+        * @param name The HTTP header name.
+        * @param type The class type to convert the header value to.
+        * @param <T> The class type to convert the header value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T getAll(String name, Class<T> type) throws BadRequest, 
InternalServerError {
+               return getAllInner(null, null, name, getClassMeta(type));
+       }
+
+       /**
+        * Returns the specified header value converted to a POJO using the 
specified part parser.
+        *
+        * <h5 class='section'>Examples:</h5>
+        * <p class='bcode w800'>
+        *      <jc>// Parse into an integer.</jc>
+        *      <jk>int</jk> myheader = 
req.getHeaders().get(<js>"My-Header"</js>, <jk>int</jk>.<jk>class</jk>);
+        *
+        *      <jc>// Parse a UUID.</jc>
+        *      UUID myheader = req.getHeaders().get(<js>"My-Header"</js>, 
UUID.<jk>class</jk>);
+        * </p>
+        *
+        * <ul class='notes'>
+        *      <li>
+        *              If {@code allowHeaderParams} init parameter is 
<jk>true</jk>, then first looks for {@code &HeaderName=x} in the URL query 
string.
+        * </ul>
+        *
+        * <ul class='seealso'>
+        *      <li class='jf'>{@link RestContext#REST_partParser}
+        * </ul>
         *
         * @param parser
         *      The parser to use for parsing the string header.
@@ -384,8 +456,39 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
                return getInner(parser, schema, name, null, 
this.<T>getClassMeta(type, args));
        }
 
+       /**
+        * Converts all the headers with the specified name to the specified 
type.
+        *
+        * @param parser
+        *      The parser to use for parsing the string header.
+        *      <br>If <jk>null</jk>, uses the part parser defined on the 
resource/method.
+        * @param schema
+        *      The schema object that defines the format of the input.
+        *      <br>If <jk>null</jk>, defaults to the schema defined on the 
parser.
+        *      <br>If that's also <jk>null</jk>, defaults to {@link 
HttpPartSchema#DEFAULT}.
+        *      <br>Only used if parser is schema-aware (e.g. {@link 
OpenApiParser}).
+        * @param name
+        *      The HTTP header name.
+        * @param type
+        *      The type of object to create.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        * @param args
+        *      The type arguments of the class if it's a collection or map.
+        *      <br>Can be any of the following: {@link ClassMeta}, {@link 
Class}, {@link ParameterizedType}, {@link GenericArrayType}
+        *      <br>Ignored if the main type is not a map or collection.
+        * @param <T> The class type to convert the header value to.
+        * @return The parameter value converted to the specified class type.
+        * @throws BadRequest Thrown if input could not be parsed or fails 
schema validation.
+        * @throws InternalServerError Thrown if any other exception occurs.
+        */
+       public <T> T getAll(HttpPartParserSession parser, HttpPartSchema 
schema, String name, Type type, Type...args) throws BadRequest, 
InternalServerError {
+               return getAllInner(parser, schema, name, 
this.<T>getClassMeta(type, args));
+       }
+
        /* Workhorse method */
        private <T> T getInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
+               if (parser == null)
+                       parser = req.getPartParser();
                try {
                        if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
                                OMap m = new OMap();
@@ -409,6 +512,34 @@ public class RequestHeaders extends 
TreeMap<String,String[]> {
        }
 
        /* Workhorse method */
+       @SuppressWarnings({ "rawtypes", "unchecked" })
+       <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema schema, 
String name, ClassMeta<T> cm) throws BadRequest, InternalServerError {
+               String[] p = get(name);
+               if (schema == null)
+                       schema = HttpPartSchema.DEFAULT;
+               try {
+                       if (cm.isArray()) {
+                               List c = new ArrayList();
+                               for (int i = 0; i < p.length; i++)
+                                       c.add(parse(parser, schema.getItems(), 
p[i], cm.getElementType()));
+                               return (T)toArray(c, 
cm.getElementType().getInnerClass());
+                       } else if (cm.isCollection()) {
+                               Collection c = 
(Collection)(cm.canCreateNewInstance() ? cm.newInstance() : new OList());
+                               for (int i = 0; i < p.length; i++)
+                                       c.add(parse(parser, schema.getItems(), 
p[i], cm.getElementType()));
+                               return (T)c;
+                       }
+               } catch (SchemaValidationException e) {
+                       throw new BadRequest(e, "Validation failed on header 
''{0}''. ", name);
+               } catch (ParseException e) {
+                       throw new BadRequest(e, "Could not parse header 
''{0}''.", name) ;
+               } catch (Exception e) {
+                       throw new InternalServerError(e, "Could not parse 
header ''{0}''.", name);
+               }
+               throw new InternalServerError("Invalid call to getAll(String, 
ClassMeta).  Class type must be a Collection or array.");
+       }
+
+       /* Workhorse method */
        private <T> T parse(HttpPartParserSession parser, HttpPartSchema 
schema, String val, ClassMeta<T> cm) throws SchemaValidationException, 
ParseException {
                if (parser == null)
                        parser = this.parser;
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
index 1ce2c9d..d2dae59 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestPath.java
@@ -147,7 +147,7 @@ public class RequestPath extends TreeMap<String,String> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T get(String name, Class<T> type) throws BadRequest, 
InternalServerError {
-               return getInner(null, null, name, null, 
this.<T>getClassMeta(type));
+               return getInner(null, null, name, null, getClassMeta(type));
        }
 
        /**
@@ -169,7 +169,7 @@ public class RequestPath extends TreeMap<String,String> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, Class<T> type) throws BadRequest, InternalServerError {
-               return getInner(parser, schema, name, null, 
this.<T>getClassMeta(type));
+               return getInner(parser, schema, name, null, getClassMeta(type));
        }
 
        /**
@@ -221,7 +221,7 @@ public class RequestPath extends TreeMap<String,String> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T get(String name, Type type, Type...args) throws 
BadRequest, InternalServerError {
-               return getInner(null, null, name, null, 
this.<T>getClassMeta(type, args));
+               return getInner(null, null, name, null, getClassMeta(type, 
args));
        }
 
        /**
@@ -249,11 +249,13 @@ public class RequestPath extends TreeMap<String,String> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T get(HttpPartParserSession parser, HttpPartSchema schema, 
String name, Type type, Type...args) throws BadRequest, InternalServerError {
-               return getInner(parser, schema, name, null, 
this.<T>getClassMeta(type, args));
+               return getInner(parser, schema, name, null, getClassMeta(type, 
args));
        }
 
        /* Workhorse method */
        private <T> T getInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
+               if (parser == null)
+                       parser = req.getPartParser();
                try {
                        if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
                                OMap m = new OMap();
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
index 5ade3a8..c8dfd04 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RequestQuery.java
@@ -482,7 +482,7 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T getAll(String name, Class<T> c) throws BadRequest, 
InternalServerError {
-               return getAllInner(null, null, name, null, getClassMeta(c));
+               return getAllInner(null, null, name, getClassMeta(c));
        }
 
        /**
@@ -506,7 +506,7 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T getAll(String name, Type type, Type...args) throws 
BadRequest, InternalServerError {
-               return getAllInner(null, null, name, null, 
(ClassMeta<T>)getClassMeta(type, args));
+               return getAllInner(null, null, name, 
(ClassMeta<T>)getClassMeta(type, args));
        }
 
        /**
@@ -534,7 +534,7 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
         * @throws InternalServerError Thrown if any other exception occurs.
         */
        public <T> T getAll(HttpPartParserSession parser, HttpPartSchema 
schema, String name, Type type, Type...args) throws BadRequest, 
InternalServerError {
-               return getAllInner(parser, schema, name, null, 
(ClassMeta<T>)getClassMeta(type, args));
+               return getAllInner(parser, schema, name, getClassMeta(type, 
args));
        }
 
        /**
@@ -616,6 +616,8 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
 
        /* Workhorse method */
        private <T> T getInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
+               if (parser == null)
+                       parser = req.getPartParser();
                try {
                        if (cm.isMapOrBean() && isOneOf(name, "*", "")) {
                                OMap m = new OMap();
@@ -624,7 +626,7 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
                                        HttpPartSchema pschema = schema == null 
? null : schema.getProperty(k);
                                        ClassMeta<?> cm2 = cm.getValueType();
                                        if 
(cm.getValueType().isCollectionOrArray())
-                                               m.put(k, getAllInner(parser, 
pschema, k, null, cm2));
+                                               m.put(k, getAllInner(parser, 
pschema, k, cm2));
                                        else
                                                m.put(k, getInner(parser, 
pschema, k, null, cm2));
                                }
@@ -643,10 +645,8 @@ public final class RequestQuery extends 
LinkedHashMap<String,String[]> {
 
        /* Workhorse method */
        @SuppressWarnings("rawtypes")
-       private <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, T def, ClassMeta<T> cm) throws BadRequest, 
InternalServerError {
+       private <T> T getAllInner(HttpPartParserSession parser, HttpPartSchema 
schema, String name, ClassMeta<T> cm) throws BadRequest, InternalServerError {
                String[] p = get(name);
-               if (p == null)
-                       return def;
                if (schema == null)
                        schema = HttpPartSchema.DEFAULT;
                try {
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
index f897a42..3aed6db 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestCall.java
@@ -366,4 +366,15 @@ public class RestCall {
                        return rres.getOutput();
                return null;
        }
+
+       /**
+        * Shortcut for calling <c>getRestRequest().isDebug()</c>.
+        *
+        * @return <jk>true</jk> if debug is enabled for this request.
+        */
+       public boolean isDebug() {
+               if (rreq != null)
+                       return rreq.isDebug();
+               return false;
+       }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
index 2e43461..cd4bcc4 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestContext.java
@@ -3816,22 +3816,8 @@ public final class RestContext extends BeanContext {
                                .create()
                                .append(getInstanceArrayProperty(REST_parsers, 
Parser.class, new Parser[0], resourceResolver, resource, ps))
                                .build();
-                       partSerializer =
-                               (HttpPartSerializer)
-                               SerializerGroup
-                               .create()
-                               
.append(getInstanceProperty(REST_partSerializer, HttpPartSerializer.class, 
OpenApiSerializer.class, resourceResolver, resource, ps))
-                               .build()
-                               .getSerializers()
-                               .get(0);
-                       partParser =
-                               (HttpPartParser)
-                               ParserGroup
-                               .create()
-                               .append(getInstanceProperty(REST_partParser, 
HttpPartParser.class, OpenApiParser.class, resourceResolver, resource, ps))
-                               .build()
-                               .getParsers()
-                               .get(0);
+                       partSerializer = 
getInstanceProperty(REST_partSerializer, HttpPartSerializer.class, 
OpenApiSerializer.class, resourceResolver, resource, ps);
+                       partParser = getInstanceProperty(REST_partParser, 
HttpPartParser.class, OpenApiParser.class, resourceResolver, resource, ps);
                        jsonSchemaGenerator =
                                JsonSchemaGenerator
                                .create()
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
index 43ed079..24df668 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestParamDefaults.java
@@ -258,11 +258,16 @@ class RestParamDefaults {
        static final class HeaderObject extends RestMethodParam {
                private final HttpPartParser partParser;
                private final HttpPartSchema schema;
+               private final boolean multi;
 
                protected HeaderObject(ParamInfo mpi, PropertyStore ps) {
                        super(HEADER, mpi, getName(mpi));
                        this.schema = HttpPartSchema.create(Header.class, mpi);
                        this.partParser = createPartParser(schema.getParser(), 
ps);
+                       this.multi = getMulti(mpi);
+
+                       if (multi && ! isCollection(type))
+                               throw new InternalServerError("Use of multipart 
flag on @Header parameter that's not an array or Collection on method ''{0}''", 
mpi.getMethod());
                }
 
                private static String getName(ParamInfo mpi) {
@@ -278,10 +283,18 @@ class RestParamDefaults {
                        return n;
                }
 
+               private static boolean getMulti(ParamInfo mpi) {
+                       for (Header h : mpi.getAnnotations(Header.class))
+                               if (h.multi())
+                                       return true;
+                       return false;
+               }
+
                @Override /* RestMethodParam */
                public Object resolve(RestRequest req, RestResponse res) throws 
Exception {
                        HttpPartParserSession ps = partParser == null ? 
req.getPartParser() : partParser.createPartSession(req.getParserSessionArgs());
-                       return req.getHeaders().get(ps, schema, name, type);
+                       RequestHeaders rh = req.getHeaders();
+                       return multi ? rh.getAll(ps, schema, name, type) : 
rh.get(ps, schema, name, type);
                }
        }
 
@@ -436,7 +449,7 @@ class RestParamDefaults {
        }
 
        static final class FormDataObject extends RestMethodParam {
-               private final boolean multiPart;
+               private final boolean multi;
                private final HttpPartParser partParser;
                private final HttpPartSchema schema;
 
@@ -444,9 +457,9 @@ class RestParamDefaults {
                        super(FORM_DATA, mpi, getName(mpi));
                        this.schema = HttpPartSchema.create(FormData.class, 
mpi);
                        this.partParser = createPartParser(schema.getParser(), 
ps);
-                       this.multiPart = schema.getCollectionFormat() == 
HttpPartSchema.CollectionFormat.MULTI;
+                       this.multi = getMulti(mpi) || 
schema.getCollectionFormat() == HttpPartSchema.CollectionFormat.MULTI;
 
-                       if (multiPart && ! isCollection(type))
+                       if (multi && ! isCollection(type))
                                throw new InternalServerError("Use of multipart 
flag on @FormData parameter that's not an array or Collection on method 
''{0}''", mpi.getMethod());
                }
 
@@ -463,17 +476,23 @@ class RestParamDefaults {
                        return n;
                }
 
+               private static boolean getMulti(ParamInfo mpi) {
+                       for (FormData f : mpi.getAnnotations(FormData.class))
+                               if (f.multi())
+                                       return true;
+                       return false;
+               }
+
                @Override /* RestMethodParam */
                public Object resolve(RestRequest req, RestResponse res) throws 
Exception {
                        HttpPartParserSession ps = partParser == null ? 
req.getPartParser() : partParser.createPartSession(req.getParserSessionArgs());
-                       if (multiPart)
-                               return req.getFormData().getAll(ps, schema, 
name, type);
-                       return req.getFormData().get(ps, schema, name, type);
+                       RequestFormData fd = req.getFormData();
+                       return multi ? fd.getAll(ps, schema, name, type) : 
fd.get(ps, schema, name, type);
                }
        }
 
        static final class QueryObject extends RestMethodParam {
-               private final boolean multiPart;
+               private final boolean multi;
                private final HttpPartParser partParser;
                private final HttpPartSchema schema;
 
@@ -481,9 +500,9 @@ class RestParamDefaults {
                        super(QUERY, mpi, getName(mpi));
                        this.schema = HttpPartSchema.create(Query.class, mpi);
                        this.partParser = createPartParser(schema.getParser(), 
ps);
-                       this.multiPart = schema.getCollectionFormat() == 
HttpPartSchema.CollectionFormat.MULTI;
+                       this.multi = getMulti(mpi) || 
schema.getCollectionFormat() == HttpPartSchema.CollectionFormat.MULTI;
 
-                       if (multiPart && ! isCollection(type))
+                       if (multi && ! isCollection(type))
                                throw new InternalServerError("Use of multipart 
flag on @Query parameter that's not an array or Collection on method ''{0}''", 
mpi.getMethod());
                }
 
@@ -500,12 +519,18 @@ class RestParamDefaults {
                        return n;
                }
 
+               private static boolean getMulti(ParamInfo mpi) {
+                       for (Query q : mpi.getAnnotations(Query.class))
+                               if (q.multi())
+                                       return true;
+                       return false;
+               }
+
                @Override /* RestMethodParam */
                public Object resolve(RestRequest req, RestResponse res) throws 
Exception {
                        HttpPartParserSession ps = partParser == null ? 
req.getPartParser() : partParser.createPartSession(req.getParserSessionArgs());
-                       if (multiPart)
-                               return req.getQuery().getAll(ps, schema, name, 
type);
-                       return req.getQuery().get(ps, schema, name, type);
+                       RequestQuery rq = req.getQuery();
+                       return multi ? rq.getAll(ps, schema, name, type) : 
rq.get(ps, schema, name, type);
                }
        }
 
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
index 18e5574..4efb852 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestRequest.java
@@ -1860,6 +1860,14 @@ public final class RestRequest extends 
HttpServletRequestWrapper {
                return inner;
        }
 
+       <T> ClassMeta<T> getClassMeta(Type type, Type[] args) {
+               return beanSession.getClassMeta(type, args);
+       }
+
+       <T> ClassMeta<T> getClassMeta(Class<T> type) {
+               return beanSession.getClassMeta(type);
+       }
+
        
//-----------------------------------------------------------------------------------------------------------------
        // Utility methods
        
//-----------------------------------------------------------------------------------------------------------------
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
index f76fc9e..672482a 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestResponse.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.rest;
 
 import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
 
 import java.io.*;
 import java.nio.charset.*;
@@ -88,7 +89,7 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
                        String passThroughHeaders = 
req.getHeader("x-response-headers");
                        if (passThroughHeaders != null) {
                                HttpPartParser p = context.getPartParser();
-                               OMap m = 
p.createPartSession(req.getParserSessionArgs()).parse(HttpPartType.HEADER, 
null, passThroughHeaders, context.getClassMeta(OMap.class));
+                               OMap m = 
p.createPartSession(req.getParserSessionArgs()).parse(HEADER, null, 
passThroughHeaders, context.getClassMeta(OMap.class));
                                for (Map.Entry<String,Object> e : m.entrySet())
                                        setHeaderSafe(e.getKey(), 
e.getValue().toString());
                        }
@@ -587,6 +588,65 @@ public final class RestResponse extends 
HttpServletResponseWrapper {
        }
 
        /**
+        * Sets a header on the request.
+        *
+        * @param name The header name.
+        * @param value The header value.
+        *      <ul>
+        *              <li>Can be any POJO.
+        *              <li>Converted to a string using the specified part 
serializer.
+        *      </ul>
+        * @return This object (for method chaining).
+        * @throws SchemaValidationException Header failed schema validation.
+        * @throws SerializeException Header could not be serialized.
+        */
+       public RestResponse header(String name, Object value) throws 
SchemaValidationException, SerializeException {
+               return header(null, null, name, value);
+       }
+
+       /**
+        * Sets a header on the request.
+        *
+        * @param schema
+        *      The schema to use to serialize the header, or <jk>null</jk> to 
use the default schema.
+        * @param name The header name.
+        * @param value The header value.
+        *      <ul>
+        *              <li>Can be any POJO.
+        *              <li>Converted to a string using the specified part 
serializer.
+        *      </ul>
+        * @return This object (for method chaining).
+        * @throws SchemaValidationException Header failed schema validation.
+        * @throws SerializeException Header could not be serialized.
+        */
+       public RestResponse header(HttpPartSchema schema, String name, Object 
value) throws SchemaValidationException, SerializeException {
+               return header(null, schema, name, value);
+       }
+
+       /**
+        * Sets a header on the request.
+        * @param serializer
+        *      The serializer to use to serialize the header, or <jk>null</jk> 
to use the part serializer on the request.
+        * @param schema
+        *      The schema to use to serialize the header, or <jk>null</jk> to 
use the default schema.
+        * @param name The header name.
+        * @param value The header value.
+        *      <ul>
+        *              <li>Can be any POJO.
+        *              <li>Converted to a string using the specified part 
serializer.
+        *      </ul>
+        * @return This object (for method chaining).
+        * @throws SchemaValidationException Header failed schema validation.
+        * @throws SerializeException Header could not be serialized.
+        */
+       public RestResponse header(HttpPartSerializerSession serializer, 
HttpPartSchema schema, String name, Object value) throws 
SchemaValidationException, SerializeException {
+               if (serializer == null)
+                       serializer = request.getPartSerializer();
+               setHeader(name, serializer.serialize(HEADER, schema, value));
+               return this;
+       }
+
+       /**
         * Same as {@link #setHeader(String, String)} but header is defined as 
a response part
         *
         * @param h Header to set.
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
index e924609..83b50ed 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/reshandlers/DefaultHandler.java
@@ -69,6 +69,8 @@ public class DefaultHandler implements ResponseHandler {
                        if (isThrowable) {
                                res.setHeaderSafe("Exception-Name", 
rm.getClassMeta().getName());
                                res.setHeaderSafe("Exception-Message", 
((Throwable)o).getMessage());
+                               if (req.isDebug())
+                                       ((Throwable)o).printStackTrace();
                        }
 
                        ResponseBeanPropertyMeta stm = rm.getStatusMethod();
diff --git a/pom.xml b/pom.xml
index 04fb109..1409956 100644
--- a/pom.xml
+++ b/pom.xml
@@ -40,6 +40,7 @@
                <junit.version>4.11</junit.version>
                <jaxrs.version>1.1.1</jaxrs.version>
                <servlet.version>3.1.0</servlet.version>
+               <httpcore.version>4.4.13</httpcore.version>
                <httpclient.version>4.5.6</httpclient.version>
                <jetty.version>9.4.13.v20181111</jetty.version>
                <juneau.compare.version>8.0.0</juneau.compare.version>
@@ -90,6 +91,11 @@
                        </dependency>
                        <dependency>
                                <groupId>org.apache.httpcomponents</groupId>
+                               <artifactId>httpcore</artifactId>
+                               <version>${httpcore.version}</version>
+                       </dependency>
+                       <dependency>
+                               <groupId>org.apache.httpcomponents</groupId>
                                <artifactId>httpmime</artifactId>
                                <version>${httpclient.version}</version>
                        </dependency>

Reply via email to