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 a2d9508  Add OpenAPI support to remoteable interfaces.
a2d9508 is described below

commit a2d95084b7eb836a49b97c4f3432cfc659b458d9
Author: JamesBognar <jamesbog...@apache.org>
AuthorDate: Mon Jul 16 09:21:05 2018 -0400

    Add OpenAPI support to remoteable interfaces.
---
 .../juneau/httppart/HttpPartSchemaTest_Body.java   |   5 +-
 .../java/org/apache/juneau/BeanPropertyMeta.java   |   2 +
 .../org/apache/juneau/http/annotation/Body.java    | 101 ++-----
 .../org/apache/juneau/http/annotation/Path.java    |  11 +
 .../org/apache/juneau/httppart/HttpPartSchema.java |  34 +++
 .../juneau/httppart/HttpPartSchemaBuilder.java     |  26 +-
 .../apache/juneau/internal/ReflectionUtils.java    |  29 +++
 .../jsonschema/JsonSchemaSerializerSession.java    |   8 +-
 .../apache/juneau/remoteable/RemoteMethodArg.java  | 115 ++++++--
 .../juneau/remoteable/RemoteMethodBeanArg.java     | 112 ++++++++
 ...emoteMethodArg.java => RemoteMethodReturn.java} |  81 ++++--
 .../juneau/remoteable/RemoteableMethodMeta.java    |  98 ++++---
 .../org/apache/juneau/remoteable/ReturnValue.java  |   5 +-
 .../org/apache/juneau/svl/VarResolverSession.java  |   4 +-
 juneau-doc/src/main/javadoc/overview.html          | 289 +++++++++++++++++++--
 .../rest/test/client/RequestBeanProxyTest.java     |  72 ++---
 .../rest/test/client/ThirdPartyProxyTest.java      |  16 +-
 .../apache/juneau/rest/client/NameValuePairs.java  |   2 +-
 .../org/apache/juneau/rest/client/RestCall.java    | 137 +++++++---
 .../juneau/rest/client/RestCallException.java      |   4 +-
 .../org/apache/juneau/rest/client/RestClient.java  | 122 +++++----
 .../juneau/rest/client/RestClientBuilder.java      |  44 +++-
 .../rest/client/SerializedNameValuePair.java       |   7 +-
 .../org/apache/juneau/rest/BasicRestLogger.java    |   5 +
 .../java/org/apache/juneau/rest/RestContext.java   |   3 +
 .../java/org/apache/juneau/rest/RestLogger.java    |   7 +
 26 files changed, 985 insertions(+), 354 deletions(-)

diff --git 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
index 4b57053..088424f 100644
--- 
a/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
+++ 
b/juneau-core/juneau-core-test/src/test/java/org/apache/juneau/httppart/HttpPartSchemaTest_Body.java
@@ -78,7 +78,6 @@ public class HttpPartSchemaTest_Body {
        public static class A04 {
                public void a(
                                @Body(
-                                       required=false,
                                        description={"b3","b3"},
                                        schema=@Schema($ref="c3"),
                                        example="f2",
@@ -92,8 +91,8 @@ public class HttpPartSchemaTest_Body {
        @Test
        public void a04_basic_onParameterAndClass() throws Exception {
                HttpPartSchema s = HttpPartSchema.create().apply(Body.class, 
A04.class.getMethod("a", A02.class), 0).noValidate().build();
-               assertNull(s.getRequired());
-               
assertObjectEquals("{description:'b3\\nb3',example:'f2',schema:{'$ref':'c3'},_value:'{g2:true}'}",
 s.getApi());
+               assertTrue(s.getRequired());
+               
assertObjectEquals("{description:'b3\\nb3',example:'f2',required:true,schema:{'$ref':'c3'},_value:'{g2:true}'}",
 s.getApi());
        }
 
        @Body(
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
index b6c7770..12ad597 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/BeanPropertyMeta.java
@@ -1079,6 +1079,8 @@ public final class BeanPropertyMeta {
                        t = getMethodAnnotation(a, setter);
                if (t == null && extraKeys != null)
                        t = getMethodAnnotation(a, extraKeys);
+               if (t == null)
+                       t = ReflectionUtils.getAnnotation(a, 
typeMeta.getInnerClass());
                return t;
        }
 
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
index d281c8c..fa2b005 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Body.java
@@ -17,8 +17,6 @@ import static java.lang.annotation.RetentionPolicy.*;
 
 import java.io.*;
 import java.lang.annotation.*;
-import java.nio.charset.*;
-import java.util.logging.*;
 
 import org.apache.juneau.*;
 import org.apache.juneau.httppart.*;
@@ -45,6 +43,15 @@ import org.apache.juneau.serializer.*;
  * <p>
  * On server-side REST, this annotation can be applied to method parameters or 
parameter classes to identify them as the body of an HTTP request.
  *
+ * <p>
+ * This annotation can be applied to the following:
+ * <ul class='spaced-list'>
+ *     <li>
+ *             Parameters on a <ja>@RestMethod</ja>-annotated method.
+ *     <li>
+ *             POJO classes used as parameters on a 
<ja>@RestMethod</ja>-annotated method.
+ * </ul>
+ *
  * <h5 class='section'>Examples:</h5>
  * <p class='bcode w800'>
  *     <jc>// Used on parameter</jc>
@@ -70,90 +77,10 @@ import org.apache.juneau.serializer.*;
  *     }
  * </p>
  *
- * <p>
- * This annotation is also used for supplying swagger information about the 
body of the request.
- *
- * <h5 class='section'>Examples:</h5>
- * <p class='bcode w800'>
- *     <jc>// Normal</jc>
- *     <ja>@Body</ja>(
- *             description=<js>"Pet object to add to the store"</js>,
- *             required=<js>"true"</js>,
- *             
example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
- *     )
- * </p>
- * <p class='bcode w800'>
- *     <jc>// Free-form</jc>
- *     <ja>@Body</ja>({
- *             <js>"description: 'Pet object to add to the store',"</js>,
- *             <js>"required: true,"</js>,
- *             <js>"example: 
{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']},"</js>
- *             <js>"x-extra: 'extra field'"</js>
- *     })
- * </p>
- *
- * <p>
- * This is used to populate the auto-generated Swagger documentation and UI.
- *
- * <p>
- * This annotation can be applied to the following:
- * <ul class='spaced-list'>
- *     <li>
- *             Parameters on a <ja>@RestMethod</ja>-annotated method.
- *     <li>
- *             POJO classes used as parameters on a 
<ja>@RestMethod</ja>-annotated method.
- * </ul>
- *
- * <p>
- * Any of the following types can be used (matched in the specified order):
- * <ol class='spaced-list'>
- *     <li>
- *             {@link Reader}
- *             <br><ja>@Body</ja> annotation is optional (it's inferred from 
the class type).
- *             <br><code>Content-Type</code> is always ignored.
- *     <li>
- *             {@link InputStream}
- *             <br><ja>@Body</ja> annotation is optional (it's inferred from 
the class type).
- *             <br><code>Content-Type</code> is always ignored.
- *     <li>
- *             Any <a class='doclink' 
href='../../../../../overview-summary.html#juneau-marshall.PojoCategories'>parsable</a>
 POJO type.
- *             <br><code>Content-Type</code> is required to identify correct 
parser.
- *     <li>
- *             Objects convertible from {@link Reader} by having one of the 
following non-deprecated methods:
- *             <ul>
- *                     <li><code><jk>public</jk> T(Reader in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>create</jsm>(Reader in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>fromReader</jsm>(Reader in) {...}</code>
- *             </ul>
- *             <br><code>Content-Type</code> must not be present or match an 
existing parser so that it's not parsed as a POJO.
- *     <li>
- *             Objects convertible from {@link InputStream} by having one of 
the following non-deprecated methods:
- *             <ul>
- *                     <li><code><jk>public</jk> T(InputStream in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>create</jsm>(InputStream in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>fromInputStream</jsm>(InputStream in) {...}</code>
- *             </ul>
- *             <br><code>Content-Type</code> must not be present or match an 
existing parser so that it's not parsed as a POJO.
- *     <li>
- *             Objects convertible from {@link String} (including 
<code>String</code> itself) by having one of the following non-deprecated 
methods:
- *             <ul>
- *                     <li><code><jk>public</jk> T(String in) {...}</code> 
(e.g. {@link Integer}, {@link Boolean})
- *                     <li><code><jk>public static</jk> T 
<jsm>create</jsm>(String in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>fromString</jsm>(String in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>fromValue</jsm>(String in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>valueOf</jsm>(String in) {...}</code> (e.g. enums)
- *                     <li><code><jk>public static</jk> T 
<jsm>parse</jsm>(String in) {...}</code> (e.g. {@link Level})
- *                     <li><code><jk>public static</jk> T 
<jsm>parseString</jsm>(String in) {...}</code>
- *                     <li><code><jk>public static</jk> T 
<jsm>forName</jsm>(String in) {...}</code> (e.g. {@link Class}, {@link Charset})
- *                     <li><code><jk>public static</jk> T 
<jsm>forString</jsm>(String in) {...}</code>
- *             </ul>
- *             <br><code>Content-Type</code> must not be present or match an 
existing parser so that it's not parsed as a POJO.
- * </ol>
- *
  * <h5 class='section'>Notes:</h5>
  * <ul class='spaced-list'>
  *     <li>
- *             Annotation values are coalesced from multiple sources in the 
following order of precedence:
+ *             Swagger values are coalesced from multiple sources in the 
following order of precedence:
  *             <ol>
  *                     <li><ja>@Body</ja> annotation on parameter.
  *                     <li><ja>@Body</ja> annotation on parameter class.
@@ -225,7 +152,7 @@ import org.apache.juneau.serializer.*;
  *
  * <h5 class='section'>See Also:</h5>
  * <ul class='doctree'>
- *     <li class='link'><a class='doclink' 
href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview
 &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST 
Interfaces</a>
+ *     <li class='link'><a class='doclink' 
href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies.Body'>Overview
 &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST 
Interfaces &gt; Body</a>
  * </ul>
  */
 @Documented
@@ -233,6 +160,7 @@ import org.apache.juneau.serializer.*;
 @Retention(RUNTIME)
 @Inherited
 public @interface Body {
+
        
//=================================================================================================================
        // Attributes common to all Swagger Parameter objects
        
//=================================================================================================================
@@ -562,6 +490,11 @@ public @interface Body {
        String[] api() default {};
 
        /**
+        * TODO
+        */
+       Class<? extends HttpPartSerializer> serializer() default 
HttpPartSerializer.Null.class;
+
+       /**
         * Specifies the {@link HttpPartParser} class used for parsing values 
from strings.
         *
         * <p>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
index 7a17097..348e3ad 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/http/annotation/Path.java
@@ -399,6 +399,17 @@ public @interface Path {
        String format() default "";
 
        /**
+        * <mk>allowEmptyValue</mk> field of the Swagger <a class="doclink" 
href="https://swagger.io/specification/v2/#parameterObject";>Parameter</a> 
object.
+        *
+        * <p>
+        * Sets the ability to pass empty-valued heaver values.
+        *
+        * <p>
+        * <b>Note:</b>  This is technically only valid for either query or 
formData parameters, but support is provided anyway for backwards compatability.
+        */
+       boolean allowEmptyValue() default false;
+
+       /**
         * <mk>items</mk> field of the Swagger <a class="doclink" 
href="https://swagger.io/specification/v2/#parameterObject";>Parameter</a> 
object.
         *
         * <p>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
index c4dc13a..5dd9ff8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchema.java
@@ -115,6 +115,40 @@ public class HttpPartSchema {
        }
 
        /**
+        * Finds the schema information for the specified method return.
+        *
+        * <p>
+        * This method will gather all the schema information from the 
annotations at the following locations:
+        * <ul>
+        *      <li>The method.
+        *      <li>The method return class.
+        *      <li>The method return parent classes and interfaces.
+        * </ul>
+        *
+        * @param c
+        *      The annotation to look for.
+        *      <br>Valid values:
+        *      <ul>
+        *              <li>{@link Body}
+        *              <li>{@link Header}
+        *              <li>{@link Query}
+        *              <li>{@link FormData}
+        *              <li>{@link Path}
+        *              <li>{@link Response}
+        *              <li>{@link ResponseHeader}
+        *              <li>{@link ResponseStatus}
+        *              <li>{@link HasQuery}
+        *              <li>{@link HasFormData}
+        *      </ul>
+        * @param m
+        *      The Java method with the return type being checked.
+        * @return The schema information about the parameter.
+        */
+       public static HttpPartSchema create(Class<? extends Annotation> c, 
Method m) {
+               return create().apply(c, m).build();
+       }
+
+       /**
         * Finds the schema information for the specified class.
         *
         * <p>
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
index d3479cc..16c0dcf 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/httppart/HttpPartSchemaBuilder.java
@@ -63,10 +63,18 @@ public class HttpPartSchemaBuilder {
        }
 
        HttpPartSchemaBuilder apply(Class<? extends Annotation> c, Method m, 
int index) {
+               apply(c, m.getGenericParameterTypes()[index]);
                for (Annotation a :  m.getParameterAnnotations()[index])
                        if (c.isInstance(a))
-                               return apply(a);
-               apply(c, m.getGenericParameterTypes()[index]);
+                               apply(a);
+               return this;
+       }
+
+       HttpPartSchemaBuilder apply(Class<? extends Annotation> c, Method m) {
+               apply(c, m.getGenericReturnType());
+               Annotation a = m.getAnnotation(c);
+               if (a != null)
+                       return apply(a);
                return this;
        }
 
@@ -77,7 +85,13 @@ public class HttpPartSchemaBuilder {
                return this;
        }
 
-       HttpPartSchemaBuilder apply(Annotation a) {
+       /**
+        * Apply the specified annotation to this schema.
+        *
+        * @param a The annotation to apply.
+        * @return This object (for method chaining).
+        */
+       public HttpPartSchemaBuilder apply(Annotation a) {
                if (a instanceof Body)
                        apply((Body)a);
                else if (a instanceof Header)
@@ -105,6 +119,7 @@ public class HttpPartSchemaBuilder {
                api = AnnotationUtils.merge(api, a);
                required(HttpPartSchema.toBoolean(a.required()));
                allowEmptyValue(HttpPartSchema.toBoolean(! a.required()));
+               serializer(a.serializer());
                parser(a.parser());
                apply(a.schema());
                return this;
@@ -238,6 +253,7 @@ public class HttpPartSchemaBuilder {
                type(a.type());
                format(a.format());
                items(a.items());
+               allowEmptyValue(HttpPartSchema.toBoolean(a.allowEmptyValue()));
                collectionFormat(a.collectionFormat());
                maximum(HttpPartSchema.toNumber(a.maximum()));
                
exclusiveMaximum(HttpPartSchema.toBoolean(a.exclusiveMaximum()));
@@ -1332,7 +1348,7 @@ public class HttpPartSchemaBuilder {
         * @return This object (for method chaining).
         */
        public HttpPartSchemaBuilder serializer(Class<? extends 
HttpPartSerializer> value) {
-               if (serializer != null && serializer != 
HttpPartSerializer.Null.class)
+               if (value != null && value != HttpPartSerializer.Null.class)
                        serializer = value;
                return this;
        }
@@ -1346,7 +1362,7 @@ public class HttpPartSchemaBuilder {
         * @return This object (for method chaining).
         */
        public HttpPartSchemaBuilder parser(Class<? extends HttpPartParser> 
value) {
-               if (parser != null && parser != HttpPartParser.Null.class)
+               if (value != null && value != HttpPartParser.Null.class)
                        parser = value;
                return this;
        }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
index 2cec856..0464c3f 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/internal/ReflectionUtils.java
@@ -37,6 +37,17 @@ public final class ReflectionUtils {
        }
 
        /**
+        * Returns <jk>true</jk> if the {@link #getAnnotation(Class, Method)} 
returns a value.
+        *
+        * @param a The annotation to check for.
+        * @param m The method to check.
+        * @return <jk>true</jk> if the {@link #getAnnotation(Class, Method)} 
returns a value.
+        */
+       public static boolean hasAnnotation(Class<? extends Annotation> a, 
Method m) {
+               return getAnnotation(a, m) != null;
+       }
+
+       /**
         * Returns the specified annotation if it exists on the specified 
parameter or parameter type class.
         *
         * @param a The annotation to check for.
@@ -56,6 +67,24 @@ public final class ReflectionUtils {
        }
 
        /**
+        * Returns the specified annotation if it exists on the specified 
method or return type class.
+        *
+        * @param a The annotation to check for.
+        * @param m The method to check.
+        * @return <jk>true</jk> if the {@link #getAnnotation(Class, Method, 
int)} returns a value.
+        */
+       @SuppressWarnings("unchecked")
+       public static <T extends Annotation> T getAnnotation(Class<T> a, Method 
m) {
+               for (Annotation a2 :  m.getAnnotations())
+                       if (a.isInstance(a2))
+                               return (T)a2;
+               Type t = m.getGenericReturnType();
+               if (t instanceof Class)
+                       return getAnnotation(a, (Class<?>)t);
+               return null;
+       }
+
+       /**
         * Similar to {@link Class#getAnnotation(Class)} except also searches 
annotations on interfaces.
         *
         * @param <T> The annotation class type.
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
index c6f19db..e4fb8f8 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/jsonschema/JsonSchemaSerializerSession.java
@@ -109,12 +109,16 @@ public class JsonSchemaSerializerSession extends 
JsonSerializerSession {
                        descriptionAdded = false;
                }
 
-               if (useDef && defs.containsKey(getBeanDefId(sType)))
+               if (useDef && defs.containsKey(getBeanDefId(sType))) {
+                       pop();
                        return new ObjectMap().append("$ref", 
getBeanDefUri(sType));
+               }
 
                ObjectMap ds = 
getDefaultSchemas().get(sType.getInnerClass().getName());
-               if (ds != null && ds.containsKey("type"))
+               if (ds != null && ds.containsKey("type")) {
+                       pop();
                        return out.appendAll(ds);
+               }
 
                JsonSchemaClassMeta jscm = null;
                if (pojoSwap != null && 
pojoSwap.getClass().getAnnotation(JsonSchema.class) != null)
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
index f188334..b715b85 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
@@ -13,9 +13,14 @@
 package org.apache.juneau.remoteable;
 
 import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
 
+import java.lang.reflect.*;
+
+import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
-import org.apache.juneau.urlencoding.*;
+import org.apache.juneau.internal.*;
 
 /**
  * Represents the metadata about an annotated argument of a method on a remote 
proxy interface.
@@ -25,35 +30,99 @@ import org.apache.juneau.urlencoding.*;
  *     <li class='link'><a class='doclink' 
href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview
 &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST 
Interfaces</a>
  * </ul>
  */
-public class RemoteMethodArg {
+public final class RemoteMethodArg {
+
+       private final int index;
+       private final HttpPartType partType;
+       private final HttpPartSerializer serializer;
+       private final HttpPartSchema schema;
+       private final String name;
+       private final boolean skipIfEmpty;
 
-       /** The argument name.  Can be blank. */
-       public final String name;
+       RemoteMethodArg(int index, HttpPartType partType, HttpPartSchema 
schema) {
+               this.index = index;
+               this.partType = partType;
+               this.serializer = newInstance(HttpPartSerializer.class, schema 
== null ? null : schema.getSerializer());
+               this.schema = schema;
+               this.name = schema == null ? null : schema.getName();
+               this.skipIfEmpty = schema == null || schema.getSkipIfEmpty() == 
null ? false : schema.getSkipIfEmpty();
+       }
 
-       /** The zero-based index of the argument on the Java method. */
-       public final int index;
+       RemoteMethodArg(HttpPartType partType, HttpPartSchema schema, String 
defaultName) {
+               this.index = -1;
+               this.partType = partType;
+               this.serializer = newInstance(HttpPartSerializer.class, schema 
== null ? null : schema.getSerializer());
+               this.schema = schema;
+               this.name = StringUtils.firstNonEmpty(schema == null ? null : 
schema.getName(), defaultName);
+               this.skipIfEmpty = schema == null || schema.getSkipIfEmpty() == 
null ? false : schema.getSkipIfEmpty();
+       }
 
-       /** The value is skipped if it's null/empty. */
-       public final boolean skipIfNE;
+       /**
+        * Returns the name of the HTTP part.
+        *
+        * @return The name of the HTTP part.
+        */
+       public String getName() {
+               return name;
+       }
 
-       /** The serializer used for converting objects to strings. */
-       public final HttpPartSerializer serializer;
+       /**
+        * Returns whether the <code>skipIfEmpty</code> flag was found in the 
schema.
+        *
+        * @return <jk>true</jk> if the <code>skipIfEmpty</code> flag was found 
in the schema.
+        */
+       public boolean isSkipIfEmpty() {
+               return skipIfEmpty;
+       }
 
        /**
-        * Constructor.
+        * Returns the method argument index.
         *
-        * @param name The argument name pulled from name().
-        * @param name2 The argument name pulled from value().
-        * @param index The zero-based index of the argument on the Java method.
-        * @param skipIfNE The value is skipped if it's null/empty.
-        * @param serializer
-        *      The class to use for serializing headers, query parameters, 
form-data parameters, and path variables.
-        *      If {@link UrlEncodingSerializer}, then the url-encoding 
serializer defined on the client will be used.
+        * @return The method argument index.
         */
-       protected RemoteMethodArg(String name, String name2, int index, boolean 
skipIfNE, Class<? extends HttpPartSerializer> serializer) {
-               this.name = name.isEmpty() ? name2 : name;
-               this.index = index;
-               this.skipIfNE = skipIfNE;
-               this.serializer = newInstance(HttpPartSerializer.class, 
serializer);
+       public int getIndex() {
+               return index;
+       }
+
+       /**
+        * Returns the HTTP part type.
+        *
+        * @return The HTTP part type.  Never <jk>null</jk>.
+        */
+       public HttpPartType getPartType() {
+               return partType;
+       }
+
+       /**
+        * Returns the HTTP part serializer to use for serializing this part.
+        *
+        * @return The HTTP part serializer, or <jk>null</jk> if not specified.
+        */
+       public HttpPartSerializer getSerializer() {
+               return serializer;
+       }
+
+       /**
+        * Returns the HTTP part schema information about this part.
+        *
+        * @return The HTTP part schema information, or <jk>null</jk> if not 
found.
+        */
+       public HttpPartSchema getSchema() {
+               return schema;
+       }
+
+       static RemoteMethodArg create(Method m, int i) {
+               if (hasAnnotation(Header.class, m, i)) {
+                       return new RemoteMethodArg(i, HEADER, 
HttpPartSchema.create(Header.class, m, i));
+               } else if (hasAnnotation(Query.class, m, i)) {
+                       return new RemoteMethodArg(i, QUERY, 
HttpPartSchema.create(Query.class, m, i));
+               } else if (hasAnnotation(FormData.class, m, i)) {
+                       return new RemoteMethodArg(i, FORMDATA, 
HttpPartSchema.create(FormData.class, m, i));
+               } else if (hasAnnotation(Path.class, m, i)) {
+                       return new RemoteMethodArg(i, PATH, 
HttpPartSchema.create(Path.class, m, i));
+               } else if (hasAnnotation(Body.class, m, i)) {
+                       return new RemoteMethodArg(i, BODY, 
HttpPartSchema.create(Body.class, m, i));
+               }
+               return null;
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java
new file mode 100644
index 0000000..c682a29
--- /dev/null
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodBeanArg.java
@@ -0,0 +1,112 @@
+// 
***************************************************************************************************************************
+// * 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.remoteable;
+
+import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
+
+import java.lang.annotation.*;
+import java.lang.reflect.*;
+import java.util.*;
+
+import org.apache.juneau.*;
+import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
+
+/**
+ * Represents the metadata about an {@link RequestBean}-annotated argument of 
a method on a remote proxy interface.
+ *
+ * <h5 class='section'>See Also:</h5>
+ * <ul class='doctree'>
+ *     <li class='link'><a class='doclink' 
href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview
 &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST 
Interfaces</a>
+ * </ul>
+ */
+public final class RemoteMethodBeanArg {
+
+       private final int index;
+       private final HttpPartSerializer serializer;
+       private final Map<String,RemoteMethodArg> properties;
+
+       private RemoteMethodBeanArg(int index, Class<? extends 
HttpPartSerializer> serializer, Map<String,RemoteMethodArg> properties) {
+               this.index = index;
+               this.serializer = newInstance(HttpPartSerializer.class, 
serializer);
+               this.properties = properties;
+       }
+
+       /**
+        * Returns the index of the parameter in the method that is a request 
bean.
+        *
+        * @return The index of the parameter in the method that is a request 
bean.
+        */
+       public int getIndex() {
+               return index;
+       }
+
+       /**
+        * Returns the serializer to use for serializing parts on the request 
bean.
+        *
+        * @return The serializer to use for serializing parts on the request 
bean, or <jk>null</jk> if not defined.
+        */
+       public HttpPartSerializer getSerializer() {
+               return serializer;
+       }
+
+       /**
+        * Returns metadata on the specified property of the request bean.
+        *
+        * @param name The bean property name.
+        * @return Metadata about the bean property, or <jk>null</jk> if not 
found.
+        */
+       public RemoteMethodArg getProperty(String name) {
+               return properties.get(name);
+       }
+
+       static RemoteMethodBeanArg create(Method m, int i) {
+               Map<String,RemoteMethodArg> map = new LinkedHashMap<>();
+               if (hasAnnotation(RequestBean.class, m, i)) {
+                       RequestBean rb = getAnnotation(RequestBean.class, m, i);
+                       BeanMeta<?> bm = 
BeanContext.DEFAULT.getBeanMeta(m.getParameterTypes()[i]);
+                       for (BeanPropertyMeta bp : bm.getPropertyMetas()) {
+                               String n = bp.getName();
+                               Annotation a = bp.getAnnotation(Path.class);
+                               if (a != null) {
+                                       HttpPartSchema s = 
HttpPartSchema.create().apply(a).build();
+                                       map.put(n, new RemoteMethodArg(PATH, s, 
n));
+                               }
+                               a = bp.getAnnotation(Header.class);
+                               if (a != null) {
+                                       HttpPartSchema s = 
HttpPartSchema.create().apply(a).build();
+                                       map.put(n, new RemoteMethodArg(HEADER, 
s, n));
+                               }
+                               a = bp.getAnnotation(Query.class);
+                               if (a != null) {
+                                       HttpPartSchema s = 
HttpPartSchema.create().apply(a).build();
+                                       map.put(n, new RemoteMethodArg(QUERY, 
s, n));
+                               }
+                               a = bp.getAnnotation(FormData.class);
+                               if (a != null) {
+                                       HttpPartSchema s = 
HttpPartSchema.create().apply(a).build();
+                                       map.put(n, new 
RemoteMethodArg(FORMDATA, s, n));
+                               }
+                               a = bp.getAnnotation(Body.class);
+                               if (a != null) {
+                                       HttpPartSchema s = 
HttpPartSchema.create().apply(a).build();
+                                       map.put(n, new RemoteMethodArg(BODY, s, 
n));
+                               }
+                       }
+                       return new RemoteMethodBeanArg(i, rb.serializer(), map);
+               }
+               return null;
+       }
+}
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
similarity index 53%
copy from 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
copy to 
juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
index f188334..8604190 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodArg.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteMethodReturn.java
@@ -13,47 +13,80 @@
 package org.apache.juneau.remoteable;
 
 import static org.apache.juneau.internal.ClassUtils.*;
+import static org.apache.juneau.internal.ReflectionUtils.*;
+import static org.apache.juneau.remoteable.ReturnValue.*;
 
+import java.lang.reflect.*;
+
+import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
-import org.apache.juneau.urlencoding.*;
 
 /**
- * Represents the metadata about an annotated argument of a method on a remote 
proxy interface.
+ * Represents the metadata about the returned object of a method on a remote 
proxy interface.
  *
  * <h5 class='section'>See Also:</h5>
  * <ul class='doctree'>
  *     <li class='link'><a class='doclink' 
href='../../../../overview-summary.html#juneau-rest-client.3rdPartyProxies'>Overview
 &gt; juneau-rest-client &gt; Interface Proxies Against 3rd-party REST 
Interfaces</a>
  * </ul>
  */
-public class RemoteMethodArg {
+public final class RemoteMethodReturn {
 
-       /** The argument name.  Can be blank. */
-       public final String name;
+       private final HttpPartParser parser;
+       private final HttpPartSchema schema;
+       private final Type returnType;
+       private final ReturnValue returnValue;
 
-       /** The zero-based index of the argument on the Java method. */
-       public final int index;
+       RemoteMethodReturn(Method m, ReturnValue returnValue) {
+               this.returnType = m.getGenericReturnType();
+               if (hasAnnotation(Body.class, m)) {
+                       this.schema = HttpPartSchema.create(Body.class, m);
+                       this.parser = newInstance(HttpPartParser.class, 
schema.getParser());
+               } else {
+                       this.schema = null;
+                       this.parser = null;
+               }
+               if (returnValue == null) {
+                       if (m.getReturnType() == void.class)
+                               returnValue = NONE;
+                       else
+                               returnValue = BODY;
+               }
+               this.returnValue = returnValue;
+       }
 
-       /** The value is skipped if it's null/empty. */
-       public final boolean skipIfNE;
+       /**
+        * Returns the parser to use for parsing this part.
+        *
+        * @return The parser to use for parsing this part, or <jk>null</jk> if 
not specified.
+        */
+       public HttpPartParser getParser() {
+               return parser;
+       }
 
-       /** The serializer used for converting objects to strings. */
-       public final HttpPartSerializer serializer;
+       /**
+        * Returns schema information about the HTTP part.
+        *
+        * @return Schema information about the HTTP part, or <jk>null</jk> if 
not found.
+        */
+       public HttpPartSchema getSchema() {
+               return schema;
+       }
+
+       /**
+        * Returns the class type of the method return.
+        *
+        * @return The class type of the method return.
+        */
+       public Type getReturnType() {
+               return returnType;
+       }
 
        /**
-        * Constructor.
+        * Specifies whether the return value is the body of the request or the 
HTTP status.
         *
-        * @param name The argument name pulled from name().
-        * @param name2 The argument name pulled from value().
-        * @param index The zero-based index of the argument on the Java method.
-        * @param skipIfNE The value is skipped if it's null/empty.
-        * @param serializer
-        *      The class to use for serializing headers, query parameters, 
form-data parameters, and path variables.
-        *      If {@link UrlEncodingSerializer}, then the url-encoding 
serializer defined on the client will be used.
+        * @return The type of value returned.
         */
-       protected RemoteMethodArg(String name, String name2, int index, boolean 
skipIfNE, Class<? extends HttpPartSerializer> serializer) {
-               this.name = name.isEmpty() ? name2 : name;
-               this.index = index;
-               this.skipIfNE = skipIfNE;
-               this.serializer = newInstance(HttpPartSerializer.class, 
serializer);
+       public ReturnValue getReturnValue() {
+               return returnValue;
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
index 28516f9..ca3a71a 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/RemoteableMethodMeta.java
@@ -14,12 +14,13 @@ package org.apache.juneau.remoteable;
 
 import static org.apache.juneau.internal.ClassUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
 
-import java.lang.annotation.*;
 import java.lang.reflect.*;
 import java.util.*;
 
 import org.apache.juneau.http.annotation.*;
+import org.apache.juneau.httppart.*;
 
 /**
  * Contains the meta-data about a Java method on a remoteable interface.
@@ -36,10 +37,10 @@ public class RemoteableMethodMeta {
 
        private final String httpMethod;
        private final String url;
-       private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, 
formDataArgs, requestBeanArgs;
-       private final Integer[] otherArgs;
-       private final Integer bodyArg;
-       private final ReturnValue returnValue;
+       private final RemoteMethodArg[] pathArgs, queryArgs, headerArgs, 
formDataArgs, otherArgs;
+       private final RemoteMethodBeanArg[] requestBeanArgs;
+       private final RemoteMethodArg bodyArg;
+       private final RemoteMethodReturn methodReturn;
 
        /**
         * Constructor.
@@ -55,10 +56,10 @@ public class RemoteableMethodMeta {
                this.queryArgs = b.queryArgs.toArray(new 
RemoteMethodArg[b.queryArgs.size()]);
                this.formDataArgs = b.formDataArgs.toArray(new 
RemoteMethodArg[b.formDataArgs.size()]);
                this.headerArgs = b.headerArgs.toArray(new 
RemoteMethodArg[b.headerArgs.size()]);
-               this.requestBeanArgs = b.requestBeanArgs.toArray(new 
RemoteMethodArg[b.requestBeanArgs.size()]);
-               this.otherArgs = b.otherArgs.toArray(new 
Integer[b.otherArgs.size()]);
+               this.requestBeanArgs = b.requestBeanArgs.toArray(new 
RemoteMethodBeanArg[b.requestBeanArgs.size()]);
+               this.otherArgs = b.otherArgs.toArray(new 
RemoteMethodArg[b.otherArgs.size()]);
                this.bodyArg = b.bodyArg;
-               this.returnValue = b.returnValue;
+               this.methodReturn = b.methodReturn;
        }
 
        private static final class Builder {
@@ -68,11 +69,11 @@ public class RemoteableMethodMeta {
                        queryArgs = new LinkedList<>(),
                        headerArgs = new LinkedList<>(),
                        formDataArgs = new LinkedList<>(),
-                       requestBeanArgs = new LinkedList<>();
-               List<Integer>
                        otherArgs = new LinkedList<>();
-               Integer bodyArg;
-               ReturnValue returnValue;
+               List<RemoteMethodBeanArg>
+                       requestBeanArgs = new LinkedList<>();
+               RemoteMethodArg bodyArg;
+               RemoteMethodReturn methodReturn;
 
                Builder(String restUrl, Method m) {
                        Remoteable r = 
m.getDeclaringClass().getAnnotation(Remoteable.class);
@@ -90,50 +91,43 @@ public class RemoteableMethodMeta {
                                throw new RemoteableMetadataException(m,
                                        "Invalid value specified for 
@Remoteable.methodPaths() annotation.  Valid values are [NAME,SIGNATURE].");
 
-                       returnValue = rm == null ? ReturnValue.BODY : 
rm.returns();
+                       ReturnValue rv = m.getReturnType() == void.class ? 
ReturnValue.NONE : rm == null ? ReturnValue.BODY : rm.returns();
+
+                       methodReturn = new RemoteMethodReturn(m, rv);
 
                        url =
                                trimSlashes(restUrl)
                                + '/'
                                + (path != null ? trimSlashes(path) : 
urlEncode("NAME".equals(methodPaths) ? m.getName() : getMethodSignature(m)));
 
-                       int index = 0;
-                       for (Annotation[] aa : m.getParameterAnnotations()) {
+                       for (int i = 0; i < m.getParameterTypes().length; i++) {
+                               RemoteMethodArg rma = RemoteMethodArg.create(m, 
i);
                                boolean annotated = false;
-                               for (Annotation a : aa) {
-                                       Class<?> ca = a.annotationType();
-                                       if (ca == Path.class) {
-                                               Path p = (Path)a;
-                                               annotated = pathArgs.add(new 
RemoteMethodArg(p.name(), p.value(), index, false, p.serializer()));
-                                       } else if (ca == Query.class) {
-                                               Query q = (Query)a;
-                                               annotated = queryArgs.add(new 
RemoteMethodArg(q.name(), q.value(), index, q.skipIfEmpty(), q.serializer()));
-                                       } else if (ca == FormData.class) {
-                                               FormData f = (FormData)a;
-                                               annotated = 
formDataArgs.add(new RemoteMethodArg(f.name(), f.value(), index, 
f.skipIfEmpty(), f.serializer()));
-                                       } else if (ca == Header.class) {
-                                               Header h = (Header)a;
-                                               annotated = headerArgs.add(new 
RemoteMethodArg(h.name(), h.value(), index, h.skipIfEmpty(), h.serializer()));
-                                       } else if (ca == RequestBean.class) {
-                                               RequestBean rb = (RequestBean)a;
-                                               annotated = 
requestBeanArgs.add(new RemoteMethodArg("", "", index, false, rb.serializer()));
-                                       } else if (ca == Body.class) {
-                                               annotated = true;
-                                               if (bodyArg == null)
-                                                       bodyArg = index;
-                                               else
-                                                       throw new 
RemoteableMetadataException(m,
-                                                               "Multiple @Body 
parameters found.  Only one can be specified per Java method.");
-                                       }
+                               if (rma != null) {
+                                       annotated = true;
+                                       HttpPartType pt = rma.getPartType();
+                                       if (pt == HEADER)
+                                               headerArgs.add(rma);
+                                       else if (pt == QUERY)
+                                               queryArgs.add(rma);
+                                       else if (pt == FORMDATA)
+                                               formDataArgs.add(rma);
+                                       else if (pt == PATH)
+                                               pathArgs.add(rma);
+                                       else if (pt == BODY)
+                                               bodyArg = rma;
+                                       else
+                                               annotated = false;
+                               }
+                               RemoteMethodBeanArg rmba = 
RemoteMethodBeanArg.create(m, i);
+                               if (rmba != null) {
+                                       annotated = true;
+                                       requestBeanArgs.add(rmba);
+                               }
+                               if (! annotated) {
+                                       otherArgs.add(new RemoteMethodArg(i, 
BODY, null));
                                }
-                               if (! annotated)
-                                       otherArgs.add(index);
-                               index++;
                        }
-
-                       if (bodyArg != null && otherArgs.size() > 0)
-                               throw new RemoteableMetadataException(m,
-                                       "@Body and non-annotated parameters 
found together.  Non-annotated parameters cannot be used when @Body is used.");
                }
        }
 
@@ -196,7 +190,7 @@ public class RemoteableMethodMeta {
         *
         * @return A list of zero-indexed argument indices.
         */
-       public RemoteMethodArg[] getRequestBeanArgs() {
+       public RemoteMethodBeanArg[] getRequestBeanArgs() {
                return requestBeanArgs;
        }
 
@@ -205,7 +199,7 @@ public class RemoteableMethodMeta {
         *
         * @return A list of zero-indexed argument indices.
         */
-       public Integer[] getOtherArgs() {
+       public RemoteMethodArg[] getOtherArgs() {
                return otherArgs;
        }
 
@@ -214,7 +208,7 @@ public class RemoteableMethodMeta {
         *
         * @return A index of the argument with the {@link Body @Body} 
annotation, or <jk>null</jk> if no argument exists.
         */
-       public Integer getBodyArg() {
+       public RemoteMethodArg getBodyArg() {
                return bodyArg;
        }
 
@@ -223,7 +217,7 @@ public class RemoteableMethodMeta {
         *
         * @return Whether the method returns the HTTP response body or status 
code.
         */
-       public ReturnValue getReturns() {
-               return returnValue;
+       public RemoteMethodReturn getReturns() {
+               return methodReturn;
        }
 }
diff --git 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
index c438fb0..1c92b6b 100644
--- 
a/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
+++ 
b/juneau-core/juneau-marshall/src/main/java/org/apache/juneau/remoteable/ReturnValue.java
@@ -21,5 +21,8 @@ public enum ReturnValue {
        BODY,
 
        /** HTTP status code */
-       HTTP_STATUS;
+       HTTP_STATUS,
+
+       /** Ignore (used for void methods) */
+       NONE;
 }
diff --git 
a/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
 
b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
index fb8c6f0..ee13c93 100644
--- 
a/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
+++ 
b/juneau-core/juneau-svl/src/main/java/org/apache/juneau/svl/VarResolverSession.java
@@ -109,7 +109,7 @@ public class VarResolverSession {
                                } catch (VarResolverException e) {
                                        throw e;
                                } catch (Exception e) {
-                                       throw new VarResolverException(e, 
"Problem occurred resolving variable ''{0}''", var);
+                                       throw new VarResolverException(e, 
"Problem occurred resolving variable ''{0}'' in string ''{1}''", var, s);
                                }
                        }
                        return s;
@@ -353,7 +353,7 @@ public class VarResolverSession {
                                                        } catch 
(VarResolverException e) {
                                                                throw e;
                                                        } catch (Exception e) {
-                                                               throw new 
VarResolverException(e, "Problem occurred resolving variable ''{0}''", varType);
+                                                               throw new 
VarResolverException(e, "Problem occurred resolving variable ''{0}'' in string 
''{1}''", varType, s);
                                                        }
                                                        x = i+1;
                                                }
diff --git a/juneau-doc/src/main/javadoc/overview.html 
b/juneau-doc/src/main/javadoc/overview.html
index b89af1d..ee13f83 100644
--- a/juneau-doc/src/main/javadoc/overview.html
+++ b/juneau-doc/src/main/javadoc/overview.html
@@ -369,6 +369,12 @@
        <li><p class='toc2'><a class='doclink' 
href='#juneau-rest-client'><i>juneau-rest-client</i></a></p>
        <ol>
                <li><p><a class='doclink' 
href='#juneau-rest-client.3rdPartyProxies'>Interface Proxies Against 3rd-party 
REST Interfaces</a></p>
+               <ul>
+                       <li><p><a class='doclink' 
href='#juneau-rest-client.3rdPartyProxies.Body'>@Body</a></p>
+                       <li><p><a class='doclink' 
href='#juneau-rest-client.3rdPartyProxies.FormData'>@FormData</a></p>
+                       <li><p><a class='doclink' 
href='#juneau-rest-client.3rdPartyProxies.Query'>@Query</a></p>
+                       <li><p><a class='doclink' 
href='#juneau-rest-client.3rdPartyProxies.Header'>@Header</a></p>
+               </ul>
                <li><p><a class='doclink' href='#juneau-rest-client.SSL'>SSL 
Support</a></p>
                <li><p><a class='doclink' 
href='#juneau-rest-client.Authentication'>Authentication</a></p>
                <ol>
@@ -12791,9 +12797,9 @@
                </p>
                <ul class='spaced-list'>
                        <li>
-                               Parameters on a  {@link 
org.apache.juneau.rest.annotation.RestMethod @RestMethod}.
+                               Parameters on a <ja>@RestMethod</ja>-annotated 
method.
                        <li>
-                               POJO classes.
+                               POJO classes used as parameters on a 
<ja>@RestMethod</ja>-annotated method.
                </ul>
                <h5 class='figure'>Examples:</h5>
                <p class='bcode w800'>
@@ -12927,6 +12933,36 @@
                }
        }
                </p>
+               <p>
+                       This annotation is also used for supplying swagger 
information about the body of the request.
+                       <br>This information is used to populate the 
auto-generated Swagger documentation and UI.
+               </p>
+               <h5 class='section'>Examples:</h5>
+               <p class='bcode w800'>
+       <jc>// Normal</jc>
+       <ja>@Body</ja>(
+               description=<js>"Pet object to add to the store"</js>,
+               required=<js>"true"</js>,
+               
example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
+       )
+               </p>
+               <p class='bcode w800'>
+       <jc>// Free-form</jc>
+       <ja>@Body</ja>({
+               <js>"description: 'Pet object to add to the store',"</js>,
+               <js>"required: true,"</js>,
+               <js>"example: 
{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']},"</js>
+               <js>"x-extra: 'extra field'"</js>
+       })
+               </p>
+               <p class='bcode w800'>
+       <jc>// Localized</jc>
+       <ja>@Body</ja>(
+               description=<js>"$L{My.Localized.Description}"</js>,
+               required=<js>"true"</js>,
+               
example=<js>"{name:'Doggie',price:9.99,species:'Dog',tags:['friendly','cute']}"</js>
+       )
+               </p>
                
                <h5 class='section'>Other Notes:</h5>
                <ul class='spaced-list'>
@@ -17199,6 +17235,41 @@
                <p>
                        The Java method arguments can be annotated with any of 
the following:
                </p>
+               <ul class='doctree'>
+                       <li class='ja'>{@link 
org.apache.juneau.http.annotation.Query} - A URL query parameter.
+                       <li class='ja'>{@link 
org.apache.juneau.http.annotation.FormData} - A form-data parameter.
+                       <li class='ja'>{@link 
org.apache.juneau.http.annotation.Header} - A request header.
+                       <li class='ja'>{@link 
org.apache.juneau.http.annotation.Body} - The HTTP request body.
+               </ul>
+               <p>
+                       The return type of the Java method can be any of the 
following:
+               </p>
+               <ul class='spaced-list'>
+                       <li>
+                               <jk>void</jk> 
+                               - Don't parse any response.  
+                               <br>Note that the method will still throw a 
runtime exception if an error HTTP status is returned.
+                       <li>
+                               Any <a class='doclink' 
href='#juneau-marshall.PojoCategories'>parsable</a> POJO 
+                               - The body of the response will be converted to 
the POJO using the parser defined on the 
+                               <code>RestClient</code> based on the 
<code>Content-Type</code> of the response.
+                       <li>
+                               <code>HttpResponse</code> 
+                               - Returns the raw <code>HttpResponse</code> 
returned by the inner <code>HttpClient</code>.
+                       <li>
+                               {@link java.io.Reader} 
+                               - Returns access to the raw reader of the 
response.
+                               <br>Note that if you don't want your response 
parsed as a POJO, you'll want to get the response reader 
+                               directly.
+                       <li>
+                               {@link java.io.InputStream} 
+                               - Returns access to the raw input stream of the 
response.
+               </ul>
+               
+               <!-- === 9.1.1 - @Body 
========================================================================== -->
+               
+               <h4 class='topic' onclick='toggle(this)'><a 
href='#juneau-rest-client.3rdPartyProxies.Body' 
id='juneau-rest-client.3rdPartyProxies.Body'>9.1.1 - @Body</a></h4>
+               <div class='topic'>
                <ul class='spaced-list'>
                        <li class='ja'>
                                {@link org.apache.juneau.http.annotation.Query} 
- A URL query parameter.
@@ -17254,31 +17325,190 @@
                                        <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
                                                - Converted to a URL-encoded 
FORM post.
                                </ul>
-               </ul>
-               <p>
-                       The return type of the Java method can be any of the 
following:
-               </p>
+               </div>
+
+               <!-- === 9.1.2 - @FormData 
====================================================================== -->
+               
+               <h4 class='topic' onclick='toggle(this)'><a 
href='#juneau-rest-client.3rdPartyProxies.FormData' 
id='juneau-rest-client.3rdPartyProxies.FormData'>9.1.2 - @FormData</a></h4>
+               <div class='topic'>
                <ul class='spaced-list'>
-                       <li>
-                               <jk>void</jk> 
-                               - Don't parse any response.  
-                               <br>Note that the method will still throw a 
runtime exception if an error HTTP status is returned.
-                       <li>
-                               Any <a class='doclink' 
href='#juneau-marshall.PojoCategories'>parsable</a> POJO 
-                               - The body of the response will be converted to 
the POJO using the parser defined on the 
-                               <code>RestClient</code>.
-                       <li>
-                               <code>HttpResponse</code> 
-                               - Returns the raw <code>HttpResponse</code> 
returned by the inner <code>HttpClient</code>.
-                       <li>
-                               {@link java.io.Reader} 
-                               - Returns access to the raw reader of the 
response.
-                               <br>Note that if you don't want your response 
parsed as a POJO, you'll want to get the response reader 
-                               directly.
-                       <li>
-                               {@link java.io.InputStream} 
-                               - Returns access to the raw input stream of the 
response.
-               </ul>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Query} 
- A URL query parameter.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'><code>String</code> 
+                                               - Treated as a query string.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.FormData} 
+                               - A form-data parameter.
+                               <br>Note that this is only available if the 
HTTP method is <code>POST</code>.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Individual name-value pairs.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.Header} 
+                               - A request header.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Body} 
+                               - The HTTP request body.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text/bytes using 
the {@link org.apache.juneau.serializer.Serializer} registered 
+                                               with the 
<code>RestClient</code>.
+                                       <li class='normal'>{@link 
java.io.Reader} 
+                                               - Raw contents of reader will 
be serialized to remote resource.
+                                       <li class='normal'>{@link 
java.io.InputStream} 
+                                               - Raw contents of input stream 
will be serialized to remote resource.
+                                       <li class='normal'>{@link 
org.apache.http.HttpEntity} 
+                                               - Bypass Juneau serialization 
and pass HttpEntity directly to HttpClient.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Converted to a URL-encoded 
FORM post.
+                               </ul>
+               </div>
+
+               <!-- === 9.1.3 - @Query 
========================================================================= -->
+               
+               <h4 class='topic' onclick='toggle(this)'><a 
href='#juneau-rest-client.3rdPartyProxies.Query' 
id='juneau-rest-client.3rdPartyProxies.Query'>9.1.3 - @Query</a></h4>
+               <div class='topic'>
+               <ul class='spaced-list'>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Query} 
- A URL query parameter.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'><code>String</code> 
+                                               - Treated as a query string.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.FormData} 
+                               - A form-data parameter.
+                               <br>Note that this is only available if the 
HTTP method is <code>POST</code>.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Individual name-value pairs.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.Header} 
+                               - A request header.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Body} 
+                               - The HTTP request body.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text/bytes using 
the {@link org.apache.juneau.serializer.Serializer} registered 
+                                               with the 
<code>RestClient</code>.
+                                       <li class='normal'>{@link 
java.io.Reader} 
+                                               - Raw contents of reader will 
be serialized to remote resource.
+                                       <li class='normal'>{@link 
java.io.InputStream} 
+                                               - Raw contents of input stream 
will be serialized to remote resource.
+                                       <li class='normal'>{@link 
org.apache.http.HttpEntity} 
+                                               - Bypass Juneau serialization 
and pass HttpEntity directly to HttpClient.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Converted to a URL-encoded 
FORM post.
+                               </ul>
+               </div>
+
+               <!-- === 9.1.4 - @Header 
======================================================================== -->
+               
+               <h4 class='topic' onclick='toggle(this)'><a 
href='#juneau-rest-client.3rdPartyProxies.Header' 
id='juneau-rest-client.3rdPartyProxies.Header'>9.1.4 - @Header</a></h4>
+               <div class='topic'>
+               <ul class='spaced-list'>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Query} 
- A URL query parameter.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'><code>String</code> 
+                                               - Treated as a query string.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.FormData} 
+                               - A form-data parameter.
+                               <br>Note that this is only available if the 
HTTP method is <code>POST</code>.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Individual name-value pairs.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link 
org.apache.juneau.http.annotation.Header} 
+                               - A request header.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text using 
{@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                                       <li 
class='normal'><code>Map&lt;String,Object&gt;</code> 
+                                               - Individual name-value pairs.
+                                               <br>Values are converted to 
text using {@link 
org.apache.juneau.httppart.SimpleUonPartSerializerSession#serialize(HttpPartType,HttpPartSchema,Object)}.
+                               </ul>
+                       <li class='ja'>
+                               {@link org.apache.juneau.http.annotation.Body} 
+                               - The HTTP request body.
+                               <br>The argument can be any of the following 
types:
+                               <ul>
+                                       <li class='normal'>Any serializable 
POJO 
+                                               - Converted to text/bytes using 
the {@link org.apache.juneau.serializer.Serializer} registered 
+                                               with the 
<code>RestClient</code>.
+                                       <li class='normal'>{@link 
java.io.Reader} 
+                                               - Raw contents of reader will 
be serialized to remote resource.
+                                       <li class='normal'>{@link 
java.io.InputStream} 
+                                               - Raw contents of input stream 
will be serialized to remote resource.
+                                       <li class='normal'>{@link 
org.apache.http.HttpEntity} 
+                                               - Bypass Juneau serialization 
and pass HttpEntity directly to HttpClient.
+                                       <li class='normal'>{@link 
org.apache.juneau.rest.client.NameValuePairs} 
+                                               - Converted to a URL-encoded 
FORM post.
+                               </ul>
+               </div>
        </div>
        
        <!-- === 9.2 - SSL Support 
========================================================================== -->
@@ -22835,6 +23065,8 @@
                <h5 class='topic w800'>juneau-rest-client</h5>
                <ul class='spaced-list'>
                        <li>
+                               Remoteable interfaces support OpenAPI 
annotations.
+                       <li>
                                Made improvements to the builder API for 
defining SSL support.
                                <br>New methods added:
                                <ul class='doctree'>
@@ -22886,6 +23118,11 @@
                                        <li class='jm'>{@link 
org.apache.juneau.rest.widget.Widget#loadScriptWithVars(RestRequest,String) 
loadScriptWithVars(RestRequest,String)}
                                        <li class='jm'>{@link 
org.apache.juneau.rest.widget.Widget#loadStyleWithVars(RestRequest,String) 
loadStyleWithVars(RestRequest,String)}
                                </ul>
+                       <li>
+                               Removed the deprecated 
<code>RestCall.execute()</code> method.
+                               <br>Use {@link 
org.apache.juneau.rest.client.RestCall#run()}.
+                       <li>
+                               <code>RestCall.input(Object)</code> method 
renamed to {@link org.apache.juneau.rest.client.RestCall#body(Object)} to match 
OpenAPI terminology.
                </ul>
 
                <h5 class='topic w800'>juneau-rest-microservice</h5>
diff --git 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
index ff39f9f..7d5f064 100644
--- 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
+++ 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/RequestBeanProxyTest.java
@@ -69,7 +69,7 @@ public class RequestBeanProxyTest {
                @Query("b") String getX1();
                @Query(name="c") String getX2();
                @Query @BeanProperty("d") String getX3();
-               @Query("e") String getX4();
+               @Query(name="e",allowEmptyValue=true) String getX4();
                @Query("f") String getX5();
                @Query("g") String getX6();
                @Query("h") String getX7();
@@ -125,7 +125,7 @@ public class RequestBeanProxyTest {
                public Map<String,Object> getB() {
                        return new 
AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", 
"null");
                }
-               @Query(name="*")
+               @Query(name="*",allowEmptyValue=true)
                public Map<String,Object> getC() {
                        return new 
AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -141,17 +141,17 @@ public class RequestBeanProxyTest {
        @Test
        public void a02a_query_maps_plainText() throws Exception {
                String r = a02a.normal(new A02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void a02b_query_maps_uon() throws Exception {
                String r = a02b.normal(new A02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void a02c_query_maps_x() throws Exception {
                String r = a02b.serialized(new A02_Bean());
-               
assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",
 r);
+               assertEquals("{a:'x{a1=v1, a2=123, a3=null, 
a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
        }
 
        
//=================================================================================================================
@@ -169,7 +169,7 @@ public class RequestBeanProxyTest {
        }
 
        public static class A03_Bean {
-               @Query
+               @Query(allowEmptyValue=true)
                public NameValuePairs getA() {
                        return new 
NameValuePairs().append("a1","v1").append("a2", 123).append("a3", 
null).append("a4", "");
                }
@@ -177,7 +177,7 @@ public class RequestBeanProxyTest {
                public NameValuePairs getB() {
                        return new 
NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
                }
-               @Query(name="*")
+               @Query(name="*",allowEmptyValue=true)
                public NameValuePairs getC() {
                        return new 
NameValuePairs().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -198,12 +198,12 @@ public class RequestBeanProxyTest {
        @Test
        public void a03b_query_nameValuePairs_on() throws Exception {
                String r = a03b.normal(new A03_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a1:'v1',a2:'\\'123\\'',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'\\'123\\'',c4:''}",
 r);
        }
        @Test
        public void a03c_query_nameValuePairs_x() throws Exception {
                String r = a03b.serialized(new A03_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",
 r);
        }
 
        
//=================================================================================================================
@@ -283,7 +283,7 @@ public class RequestBeanProxyTest {
                public List<Object> getX2() {
                        return new 
AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
                }
-               @Query("d")
+               @Query(name="d",allowEmptyValue=true)
                public List<Object> getX3() {
                        return new AList<>();
                }
@@ -299,7 +299,7 @@ public class RequestBeanProxyTest {
                public Object[] getX6() {
                        return new Object[]{"foo", "", "true", "123", "null", 
true, 123, null};
                }
-               @Query("h")
+               @Query(name="h",allowEmptyValue=true)
                public Object[] getX7() {
                        return new Object[]{};
                }
@@ -325,7 +325,7 @@ public class RequestBeanProxyTest {
        @Test
        public void a06c_query_collections_x() throws Exception {
                String r = a06b.serialized(new A06_Bean());
-               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}",
 r);
+               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}",
 r);
        }
 
        
//=================================================================================================================
@@ -373,7 +373,7 @@ public class RequestBeanProxyTest {
                public String getX3() {
                        return "d1";
                }
-               @FormData("e")
+               @FormData(name="e",allowEmptyValue=true)
                public String getX4() {
                        return "";
                }
@@ -433,7 +433,7 @@ public class RequestBeanProxyTest {
                public Map<String,Object> getB() {
                        return new 
AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", 
"null");
                }
-               @FormData(name="*")
+               @FormData(name="*",allowEmptyValue=true)
                public Map<String,Object> getC() {
                        return new 
AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -449,17 +449,17 @@ public class RequestBeanProxyTest {
        @Test
        public void c02a_formData_maps_plainText() throws Exception {
                String r = c02a.normal(new C02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void c02b_formData_maps_uon() throws Exception {
                String r = c02b.normal(new C02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void c02c_formData_maps_x() throws Exception {
                String r = c02b.serialized(new C02_Bean());
-               
assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",
 r);
+               assertEquals("{a:'x{a1=v1, a2=123, a3=null, 
a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
        }
 
        
//=================================================================================================================
@@ -591,7 +591,7 @@ public class RequestBeanProxyTest {
                public List<Object> getX2() {
                        return new 
AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
                }
-               @FormData("d")
+               @FormData(name="d",allowEmptyValue=true)
                public List<Object> getX3() {
                        return new AList<>();
                }
@@ -607,7 +607,7 @@ public class RequestBeanProxyTest {
                public Object[] getX6() {
                        return new Object[]{"foo", "", "true", "123", "null", 
true, 123, null};
                }
-               @FormData("h")
+               @FormData(name="h",allowEmptyValue=true)
                public Object[] getX7() {
                        return new Object[]{};
                }
@@ -633,7 +633,7 @@ public class RequestBeanProxyTest {
        @Test
        public void c06c_formData_collections_x() throws Exception {
                String r = c06b.serialized(new C06_Bean());
-               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}",
 r);
+               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}",
 r);
        }
 
 
@@ -682,7 +682,7 @@ public class RequestBeanProxyTest {
                public String getX3() {
                        return "d1";
                }
-               @Header("e")
+               @Header(name="e",allowEmptyValue=true)
                public String getX4() {
                        return "";
                }
@@ -742,7 +742,7 @@ public class RequestBeanProxyTest {
                public Map<String,Object> getB() {
                        return new 
AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", 
"null");
                }
-               @Header(name="*")
+               @Header(name="*",allowEmptyValue=true)
                public Map<String,Object> getC() {
                        return new 
AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -758,17 +758,17 @@ public class RequestBeanProxyTest {
        @Test
        public void e02a_header_maps_plainText() throws Exception {
                String r = e02a.normal(new E02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'true',b2:'123',b3:'null',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void e02b_header_maps_uon() throws Exception {
                String r = e02b.normal(new E02_Bean());
-               
assertEquals("{a1:'v1',a2:'123',a4:'',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
+               
assertEquals("{a:'(a1=v1,a2=123,a3=null,a4=\\'\\')',b1:'\\'true\\'',b2:'\\'123\\'',b3:'\\'null\\'',c1:'v1',c2:'123',c4:''}",
 r);
        }
        @Test
        public void e02c_header_maps_x() throws Exception {
                String r = e02b.serialized(new E02_Bean());
-               
assertEquals("{a1:'xv1x',a2:'x123x',a4:'xx',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}",
 r);
+               assertEquals("{a:'x{a1=v1, a2=123, a3=null, 
a4=}x',b1:'xtruex',b2:'x123x',b3:'xnullx',c1:'xv1x',c2:'x123x',c4:'xx'}", r);
        }
 
        
//=================================================================================================================
@@ -850,7 +850,7 @@ public class RequestBeanProxyTest {
                public List<Object> getX2() {
                        return new 
AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
                }
-               @Header("d")
+               @Header(name="d",allowEmptyValue=true)
                public List<Object> getX3() {
                        return new AList<>();
                }
@@ -866,7 +866,7 @@ public class RequestBeanProxyTest {
                public Object[] getX6() {
                        return new Object[]{"foo", "", "true", "123", "null", 
true, 123, null};
                }
-               @Header("h")
+               @Header(name="h",allowEmptyValue=true)
                public Object[] getX7() {
                        return new Object[]{};
                }
@@ -892,7 +892,7 @@ public class RequestBeanProxyTest {
        @Test
        public void e04c_header_collections_x() throws Exception {
                String r = e04b.serialized(new E04_Bean());
-               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'fooXXtrueX123XnullXtrueX123Xnull',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'fooXXtrueX123XnullXtrueX123Xnull',h:''}",
 r);
+               
assertEquals("{a:'fooXXtrueX123XnullXtrueX123Xnull',b:'fooXXtrueX123XnullXtrueX123Xnull',c:'foo||true|123|null|true|123|null',d:'',f:'fooXXtrueX123XnullXtrueX123Xnull',g:'foo||true|123|null|true|123|null',h:''}",
 r);
        }
 
        
//=================================================================================================================
@@ -940,7 +940,7 @@ public class RequestBeanProxyTest {
                public String getX3() {
                        return "d1";
                }
-               @Path("e")
+               @Path(name="e",allowEmptyValue=true)
                public String getX4() {
                        return "";
                }
@@ -992,7 +992,7 @@ public class RequestBeanProxyTest {
        }
 
        public static class G02_Bean {
-               @Path
+               @Path(name="*",allowEmptyValue=true)
                public Map<String,Object> getA() {
                        return new 
AMap<String,Object>().append("a1","v1").append("a2", 123).append("a3", 
null).append("a4", "");
                }
@@ -1000,7 +1000,7 @@ public class RequestBeanProxyTest {
                public Map<String,Object> getB() {
                        return new 
AMap<String,Object>().append("b1","true").append("b2", "123").append("b3", 
"null");
                }
-               @Path(name="*")
+               @Path(name="*",allowEmptyValue=true)
                public Map<String,Object> getC() {
                        return new 
AMap<String,Object>().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -1044,7 +1044,7 @@ public class RequestBeanProxyTest {
        }
 
        public static class G03_Bean {
-               @Path
+               @Path(name="*",allowEmptyValue=true)
                public NameValuePairs getA() {
                        return new 
NameValuePairs().append("a1","v1").append("a2", 123).append("a3", 
null).append("a4", "");
                }
@@ -1052,7 +1052,7 @@ public class RequestBeanProxyTest {
                public NameValuePairs getB() {
                        return new 
NameValuePairs().append("b1","true").append("b2", "123").append("b3", "null");
                }
-               @Path(name="*")
+               @Path(name="*",allowEmptyValue=true)
                public NameValuePairs getC() {
                        return new 
NameValuePairs().append("c1","v1").append("c2", 123).append("c3", 
null).append("c4", "");
                }
@@ -1108,7 +1108,7 @@ public class RequestBeanProxyTest {
                public List<Object> getX2() {
                        return new 
AList<>().append("foo").append("").append("true").append("123").append("null").append(true).append(123).append(null);
                }
-               @Path("d")
+               @Path(name="d",allowEmptyValue=true)
                public List<Object> getX3() {
                        return new AList<>();
                }
@@ -1124,7 +1124,7 @@ public class RequestBeanProxyTest {
                public Object[] getX6() {
                        return new Object[]{"foo", "", "true", "123", "null", 
true, 123, null};
                }
-               @Path("h")
+               @Path(name="h",allowEmptyValue=true)
                public Object[] getX7() {
                        return new Object[]{};
                }
@@ -1150,7 +1150,7 @@ public class RequestBeanProxyTest {
        @Test
        public void g04c_path_collections_x() throws Exception {
                String r = g04b.serialized(new G04_Bean());
-               
assertEquals("echoPath/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull//NULL/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull//NULL",
 r);
+               
assertEquals("echoPath/fooXXtrueX123XnullXtrueX123Xnull/fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null//NULL/fooXXtrueX123XnullXtrueX123Xnull/foo||true|123|null|true|123|null//NULL",
 r);
        }
 
        
//=================================================================================================================
diff --git 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
index dc9b589..2fe57e3 100644
--- 
a/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
+++ 
b/juneau-microservice/juneau-microservice-test/src/test/java/org/apache/juneau/rest/test/client/ThirdPartyProxyTest.java
@@ -2151,7 +2151,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanPath6 {
-                       @Path
+                       @Path("*")
                        Map<String,Object> getX();
                }
 
@@ -2161,7 +2161,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanPath7 {
-                       @Path
+                       @Path("*")
                        ABean getX();
                }
 
@@ -2260,7 +2260,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanQuery6 {
-                       @Query
+                       @Query("*")
                        Map<String,Object> getX();
                }
 
@@ -2270,7 +2270,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanQuery7 {
-                       @Query
+                       @Query("*")
                        ABean getX();
                }
 
@@ -2369,7 +2369,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanFormData6 {
-                       @FormData
+                       @FormData("*")
                        Map<String,Object> getX();
                }
 
@@ -2379,7 +2379,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanFormData7 {
-                       @FormData
+                       @FormData("*")
                        ABean getX();
                }
 
@@ -2478,7 +2478,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanHeader6 {
-                       @Header
+                       @Header("*")
                        Map<String,Object> getX();
                }
 
@@ -2488,7 +2488,7 @@ public class ThirdPartyProxyTest extends RestTestcase {
                );
 
                public static interface ReqBeanHeader7 {
-                       @Header
+                       @Header("*")
                        ABean getX();
                }
 
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
index 66c637d..ceb375c 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/NameValuePairs.java
@@ -28,7 +28,7 @@ import org.apache.juneau.urlencoding.*;
  *
  * <p>
  * Instances of this method can be passed directly to the {@link 
RestClient#doPost(Object, Object)} method or
- * {@link RestCall#input(Object)} methods to perform URL-encoded form posts.
+ * {@link RestCall#body(Object)} methods to perform URL-encoded form posts.
  *
  * <h5 class='section'>Example:</h5>
  * <p class='bcode'>
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
index eb574ae..7c1e145 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCall.java
@@ -93,6 +93,7 @@ public final class RestCall extends BeanSession implements 
Closeable {
        private boolean hasInput;  // input() was called, even if it's setting 
'null'.
        private Serializer serializer;
        private Parser parser;
+       private HttpPartParser partParser;
        private URIBuilder uriBuilder;
        private NameValuePairs formData;
 
@@ -115,6 +116,7 @@ public final class RestCall extends BeanSession implements 
Closeable {
                this.retryInterval = client.retryInterval;
                this.serializer = client.serializer;
                this.parser = client.parser;
+               this.partParser = client.getPartParser();
                uriBuilder = new URIBuilder(uri);
        }
 
@@ -207,16 +209,19 @@ public final class RestCall extends BeanSession 
implements Closeable {
        public RestCall query(String name, Object value, boolean skipIfEmpty, 
HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException {
                if (serializer == null)
                        serializer = client.getPartSerializer();
-               if (! ("*".equals(name) || isEmpty(name))) {
+               boolean isMulti = isEmpty(name) || "*".equals(name) || value 
instanceof NameValuePairs;
+               if (! isMulti) {
                        if (value != null && ! (ObjectUtils.isEmpty(value) && 
skipIfEmpty))
                                try {
                                        uriBuilder.addParameter(name, 
serializer.createSession(null).serialize(HttpPartType.QUERY, schema, value));
-                               } catch (SerializeException | 
SchemaValidationException e) {
-                                       throw new RestCallException(e);
+                               } catch (SchemaValidationException e) {
+                                       throw new RestCallException(e, 
"Validation error on request query parameter ''{0}''=''{1}''", name, value);
+                               } catch (SerializeException e) {
+                                       throw new RestCallException(e, 
"Serialization error on request query parameter ''{0}''", name);
                                }
                } else if (value instanceof NameValuePairs) {
                        for (NameValuePair p : (NameValuePairs)value)
-                               query(p.getName(), p.getValue(), skipIfEmpty, 
SimpleUonPartSerializer.DEFAULT, schema);
+                               query(p.getName(), p.getValue(), skipIfEmpty, 
serializer, schema);
                } else if (value instanceof Map) {
                        for (Map.Entry<String,Object> p : ((Map<String,Object>) 
value).entrySet())
                                query(p.getKey(), p.getValue(), skipIfEmpty, 
serializer, schema);
@@ -327,7 +332,8 @@ public final class RestCall extends BeanSession implements 
Closeable {
                        formData = new NameValuePairs();
                if (serializer == null)
                        serializer = client.getPartSerializer();
-               if (! ("*".equals(name) || isEmpty(name))) {
+               boolean isMulti = isEmpty(name) || "*".equals(name) || value 
instanceof NameValuePairs;
+               if (! isMulti) {
                        if (value != null && ! (ObjectUtils.isEmpty(value) && 
skipIfEmpty))
                                formData.add(new SerializedNameValuePair(name, 
value, serializer, schema));
                } else if (value instanceof NameValuePairs) {
@@ -341,11 +347,11 @@ public final class RestCall extends BeanSession 
implements Closeable {
                        return formData(name, toBeanMap(value), skipIfEmpty, 
serializer, schema);
                } else if (value instanceof Reader) {
                        contentType("application/x-www-form-urlencoded");
-                       input(value);
+                       body(value);
                } else if (value instanceof CharSequence) {
                        try {
                                
contentType("application/x-www-form-urlencoded");
-                               input(new StringEntity(value.toString()));
+                               body(new StringEntity(value.toString()));
                        } catch (UnsupportedEncodingException e) {}
                } else {
                        throw new FormattedRuntimeException("Invalid name 
''{0}'' passed to formData(name,value,skipIfEmpty) for data type ''{1}''", 
name, getReadableClassNameForObject(value));
@@ -440,14 +446,17 @@ public final class RestCall extends BeanSession 
implements Closeable {
                String path = uriBuilder.getPath();
                if (serializer == null)
                        serializer = client.getPartSerializer();
-               if (! ("*".equals(name) || isEmpty(name))) {
+               boolean isMulti = isEmpty(name) || "*".equals(name) || value 
instanceof NameValuePairs;
+               if (! isMulti) {
                        String var = "{" + name + "}";
                        if (path.indexOf(var) == -1)
                                throw new RestCallException("Path variable 
{"+name+"} was not found in path.");
                        try {
                                uriBuilder.setPath(path.replace(var, 
serializer.createSession(null).serialize(HttpPartType.PATH, schema, value)));
-                       } catch (SerializeException | SchemaValidationException 
e) {
-                               throw new RestCallException(e);
+                       } catch (SchemaValidationException e) {
+                               throw new RestCallException(e, "Validation 
error on request path parameter ''{0}''=''{1}''", name, value);
+                       } catch (SerializeException e) {
+                               throw new RestCallException(e, "Serialization 
error on request path parameter ''{0}''", name);
                        }
                } else if (value instanceof NameValuePairs) {
                        for (NameValuePair p : (NameValuePairs)value)
@@ -501,11 +510,9 @@ public final class RestCall extends BeanSession implements 
Closeable {
        /**
         * Sets the input for this REST call.
         *
-        * TODO - Describe allowed input if serializer not defined.
-        *
         * @param input
-        *      The input to be sent to the REST resource (only valid for PUT 
and POST) requests. <br>
-        *      Can be of the following types:
+        *      The input to be sent to the REST resource (only valid for PUT 
and POST) requests.
+        *      <br>Can be of the following types:
         *      <ul class='spaced-list'>
         *              <li>
         *                      {@link Reader} - Raw contents of {@code Reader} 
will be serialized to remote resource.
@@ -522,7 +529,7 @@ public final class RestCall extends BeanSession implements 
Closeable {
         * @return This object (for method chaining).
         * @throws RestCallException If a retry was attempted, but the entity 
was not repeatable.
         */
-       public RestCall input(final Object input) throws RestCallException {
+       public RestCall body(Object input) throws RestCallException {
                this.input = input;
                this.hasInput = true;
                this.formData = null;
@@ -530,6 +537,32 @@ public final class RestCall extends BeanSession implements 
Closeable {
        }
 
        /**
+        * Same as {@link #body(Object)} but allows you to specify a part 
serializer to use to serialize the body.
+        *
+        * @param input
+        *      The input to be sent to the REST resource (only valid for PUT 
and POST) requests. <br>
+        * @param partSerializer
+        *      The part serializer to use to serialize the body of the request.
+        * @param schema
+        *      The schema information about the part being serialized.
+        * @return This object (for method chaining).
+        * @throws RestCallException
+        */
+       public RestCall body(Object input, HttpPartSerializer partSerializer, 
HttpPartSchema schema) throws RestCallException {
+               try {
+                       if (partSerializer != null)
+                               
body(partSerializer.serialize(HttpPartType.BODY, schema, input));
+                       else
+                               body(input);
+               } catch (SchemaValidationException e) {
+                       throw new RestCallException(e, "Validation error on 
request body.");
+               } catch (SerializeException e) {
+                       throw new RestCallException(e, "Serialization error on 
request body.");
+               }
+               return this;
+       }
+
+       /**
         * Specifies the serializer to use on this call.
         *
         * <p>
@@ -557,7 +590,6 @@ public final class RestCall extends BeanSession implements 
Closeable {
                return this;
        }
 
-
        
//--------------------------------------------------------------------------------
        // HTTP headers
        
//--------------------------------------------------------------------------------
@@ -584,12 +616,15 @@ public final class RestCall extends BeanSession 
implements Closeable {
        public RestCall header(String name, Object value, boolean skipIfEmpty, 
HttpPartSerializer serializer, HttpPartSchema schema) throws RestCallException {
                if (serializer == null)
                        serializer = client.getPartSerializer();
-               if (! ("*".equals(name) || isEmpty(name))) {
+               boolean isMulti = isEmpty(name) || "*".equals(name) || value 
instanceof NameValuePairs;
+               if (! isMulti) {
                        if (value != null && ! (ObjectUtils.isEmpty(value) && 
skipIfEmpty))
                                try {
                                        request.setHeader(name, 
serializer.createSession(null).serialize(HttpPartType.HEADER, schema, value));
-                               } catch (SerializeException | 
SchemaValidationException e) {
-                                       throw new RestCallException(e);
+                               } catch (SchemaValidationException e) {
+                                       throw new RestCallException(e, 
"Validation error on request header parameter ''{0}''=''{1}''", name, value);
+                               } catch (SerializeException e) {
+                                       throw new RestCallException(e, 
"Serialization error on request header parameter ''{0}''", name);
                                }
                } else if (value instanceof NameValuePairs) {
                        for (NameValuePair p : (NameValuePairs)value)
@@ -1419,16 +1454,6 @@ public final class RestCall extends BeanSession 
implements Closeable {
        }
 
        /**
-        * @return The HTTP response code.
-        * @throws RestCallException
-        * @deprecated Use {@link #run()}.
-        */
-       @Deprecated
-       public int execute() throws RestCallException {
-               return run();
-       }
-
-       /**
         * Method used to execute an HTTP response where you're only interested 
in the HTTP response code.
         *
         * <p>
@@ -1882,7 +1907,7 @@ public final class RestCall extends BeanSession 
implements Closeable {
                BeanContext bc = parser;
                if (bc == null)
                        bc = BeanContext.DEFAULT;
-               return getResponse(bc.getClassMeta(type));
+               return getResponseInner(null, null, bc.getClassMeta(type));
        }
 
        /**
@@ -1975,7 +2000,37 @@ public final class RestCall extends BeanSession 
implements Closeable {
                BeanContext bc = parser;
                if (bc == null)
                        bc = BeanContext.DEFAULT;
-               return (T)getResponse(bc.getClassMeta(type, args));
+               return (T)getResponseInner(null, null, bc.getClassMeta(type, 
args));
+       }
+
+       /**
+        * Same as {@link #getResponse(Type, Type...)} but allows you to 
specify a part parser to use for parsing the response.
+        *
+        * @param <T> The class type of the object to create.
+        * @param partParser
+        *      The part parser.
+        *      <br>Can be <jk>null</jk>.
+        * @param schema
+        *      The schema information about the body of the response.
+        *      <br>Can be <jk>null</jk>.
+        * @param type
+        *      The object type 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.
+        * @return The parsed object.
+        * @throws ParseException
+        *      If the input contains a syntax error or is malformed, or is not 
valid for the specified type.
+        * @throws IOException If a connection error occurred.
+        * @see BeanSession#getClassMeta(Class) for argument syntax for maps 
and collections.
+        */
+       public <T> T getResponse(HttpPartParser partParser, HttpPartSchema 
schema, Type type, Type...args) throws IOException, ParseException {
+               BeanContext bc = parser;
+               if (bc == null)
+                       bc = BeanContext.DEFAULT;
+               return (T)getResponseInner(partParser, schema, 
bc.getClassMeta(type, args));
        }
 
        /**
@@ -2039,8 +2094,11 @@ public final class RestCall extends BeanSession 
implements Closeable {
                return getResponsePojoRest(ObjectMap.class);
        }
 
-       <T> T getResponse(ClassMeta<T> type) throws IOException, ParseException 
{
+       <T> T getResponseInner(HttpPartParser partParser, HttpPartSchema 
schema, ClassMeta<T> type) throws IOException, ParseException {
                try {
+                       if (partParser == null)
+                               partParser = this.partParser;
+
                        Class<?> ic = type.getInnerClass();
 
                        if (ic.equals(HttpResponse.class))
@@ -2050,6 +2108,17 @@ public final class RestCall extends BeanSession 
implements Closeable {
                        if (ic.equals(InputStream.class))
                                return (T)getInputStream();
 
+                       connect();
+                       Header h = response.getFirstHeader("Content-Type");
+                       MediaType mt = MediaType.forString(h == null ? null : 
h.getValue());
+
+                       if ((isEmpty(mt) || 
mt.toString().equals("text/plain"))) {
+                               if (type.hasStringTransform())
+                                       return 
type.getStringTransform().transform(getResponseAsString());
+                               if (partParser != null)
+                                       return 
partParser.createSession(null).parse(HttpPartType.BODY, schema, 
getResponseAsString(), type);
+                       }
+
                        if (parser != null) {
                                try (Closeable in = parser.isReaderParser() ? 
getReader() : getInputStream()) {
                                        return parser.parse(in, type);
@@ -2062,10 +2131,6 @@ public final class RestCall extends BeanSession 
implements Closeable {
                        if (type.hasInputStreamTransform())
                                return 
type.getInputStreamTransform().transform(getInputStream());
 
-                       MediaType mt = getMediaType();
-                       if ((isEmpty(mt) || mt.toString().equals("text/plain")) 
&& type.hasStringTransform())
-                               return 
type.getStringTransform().transform(getResponseAsString());
-
                        throw new ParseException(
                                "Unsupported media-type in request header 
''Content-Type'': ''{0}''\n\tSupported media-types: {1}",
                                getResponseHeader("Content-Type"), parser == 
null ? null : parser.getMediaTypes()
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
index 4801bc4..e5bf627 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestCallException.java
@@ -94,7 +94,7 @@ public final class RestCallException extends IOException {
         * @throws IOException
         */
        public RestCallException(String msg, HttpResponse response) throws 
ParseException, IOException {
-               super(format("{0}\n{1}\nstatus='{2}'\nResponse: \n{3}", msg, 
response.getStatusLine().getStatusCode(), 
EntityUtils.toString(response.getEntity(), UTF8)));
+               super(format("{0}\n{1}\nstatus=''{2}''\nResponse: \n{3}", msg, 
response.getStatusLine().getStatusCode(), 
EntityUtils.toString(response.getEntity(), UTF8)));
        }
 
        /**
@@ -107,7 +107,7 @@ public final class RestCallException extends IOException {
         * @param response The response from the server.
         */
        public RestCallException(int responseCode, String responseMsg, String 
method, URI url, String response) {
-               super(format("HTTP method '{0}' call to '{1}' caused response 
code '{2},{3}'.\nResponse: \n{4}", method, url, responseCode, responseMsg, 
response));
+               super(format("HTTP method ''{0}'' call to ''{1}'' caused 
response code ''{2}, {3}''.\nResponse: \n{4}", method, url, responseCode, 
responseMsg, response));
                this.responseCode = responseCode;
                this.responseStatusMessage = responseMsg;
                this.response = response;
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
index 1d44d8c..37ca742 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClient.java
@@ -14,6 +14,8 @@ package org.apache.juneau.rest.client;
 
 import static org.apache.juneau.internal.ReflectionUtils.*;
 import static org.apache.juneau.internal.StringUtils.*;
+import static org.apache.juneau.httppart.HttpPartType.*;
+import static org.apache.juneau.remoteable.ReturnValue.*;
 
 import java.io.*;
 import java.lang.reflect.*;
@@ -29,7 +31,6 @@ import org.apache.http.client.utils.*;
 import org.apache.http.entity.*;
 import org.apache.http.impl.client.*;
 import org.apache.juneau.*;
-import org.apache.juneau.http.annotation.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.internal.*;
 import org.apache.juneau.json.*;
@@ -231,13 +232,34 @@ public class RestClient extends BeanContext implements 
Closeable {
        public static final String RESTCLIENT_parser = PREFIX + "parser.o";
 
        /**
+        * Configuration property:  Part parser.
+        *
+        * <h5 class='section'>Property:</h5>
+        * <ul>
+        *      <li><b>Name:</b>  <js>"RestClient.partParser.o"</js>
+        *      <li><b>Data type:</b>  <code>Class&lt;? <jk>implements</jk> 
HttpPartParser&gt;</code> or {@link HttpPartParser}.
+        *      <li><b>Default:</b>  {@link OpenApiPartParser};
+        *      <li><b>Methods:</b>
+        *              <ul>
+        *                      <li class='jm'>{@link 
RestClientBuilder#partParser(Class)}
+        *                      <li class='jm'>{@link 
RestClientBuilder#partParser(HttpPartParser)}
+        *              </ul>
+        * </ul>
+        *
+        * <h5 class='section'>Description:</h5>
+        * <p>
+        * The parser to use for parsing POJOs from form data, query 
parameters, headers, and path variables.
+        */
+       public static final String RESTCLIENT_partParser = PREFIX + 
"partParser.o";
+
+       /**
         * Configuration property:  Part serializer.
         *
         * <h5 class='section'>Property:</h5>
         * <ul>
-        *      <li><b>Name:</b>  <js>"RestClient.urlEncodingSerializer.o"</js>
+        *      <li><b>Name:</b>  <js>"RestClient.partSerializer.o"</js>
         *      <li><b>Data type:</b>  <code>Class&lt;? <jk>implements</jk> 
HttpPartSerializer&gt;</code> or {@link HttpPartSerializer}.
-        *      <li><b>Default:</b>  {@link SimpleUonPartSerializer};
+        *      <li><b>Default:</b>  {@link OpenApiPartSerializer};
         *      <li><b>Methods:</b>
         *              <ul>
         *                      <li class='jm'>{@link 
RestClientBuilder#partSerializer(Class)}
@@ -385,6 +407,7 @@ public class RestClient extends BeanContext implements 
Closeable {
        private final boolean keepHttpClientOpen, debug;
        private final UrlEncodingSerializer urlEncodingSerializer;  // Used for 
form posts only.
        private final HttpPartSerializer partSerializer;
+       private final HttpPartParser partParser;
        private final String rootUrl;
        private volatile boolean isClosed = false;
        private final StackTraceElement[] creationStack;
@@ -481,7 +504,8 @@ public class RestClient extends BeanContext implements 
Closeable {
                }
 
                this.urlEncodingSerializer = new 
SerializerBuilder(ps).build(UrlEncodingSerializer.class);
-               this.partSerializer = 
getInstanceProperty(RESTCLIENT_partSerializer, HttpPartSerializer.class, 
SimpleUonPartSerializer.class, true, ps);
+               this.partSerializer = 
getInstanceProperty(RESTCLIENT_partSerializer, HttpPartSerializer.class, 
OpenApiPartSerializer.class, true, ps);
+               this.partParser = getInstanceProperty(RESTCLIENT_partParser, 
HttpPartParser.class, OpenApiPartParser.class, true, ps);
                this.executorService = 
getInstanceProperty(RESTCLIENT_executorService, ExecutorService.class, null);
 
                RestCallInterceptor[] rci = 
getInstanceArrayProperty(RESTCLIENT_interceptors, RestCallInterceptor.class, 
new RestCallInterceptor[0]);
@@ -584,14 +608,14 @@ public class RestClient extends BeanContext implements 
Closeable {
         * @throws RestCallException If any authentication errors occurred.
         */
        public RestCall doPut(Object url, Object o) throws RestCallException {
-               return doCall("PUT", url, true).input(o);
+               return doCall("PUT", url, true).body(o);
        }
 
        /**
         * Same as {@link #doPut(Object, Object)} but don't specify the input 
yet.
         *
         * <p>
-        * You must call either {@link RestCall#input(Object)} or {@link 
RestCall#formData(String, Object)}
+        * You must call either {@link RestCall#body(Object)} or {@link 
RestCall#formData(String, Object)}
         * to set the contents on the result object.
         *
         * @param url
@@ -636,14 +660,14 @@ public class RestClient extends BeanContext implements 
Closeable {
         * @throws RestCallException If any authentication errors occurred.
         */
        public RestCall doPost(Object url, Object o) throws RestCallException {
-               return doCall("POST", url, true).input(o);
+               return doCall("POST", url, true).body(o);
        }
 
        /**
         * Same as {@link #doPost(Object, Object)} but don't specify the input 
yet.
         *
         * <p>
-        * You must call either {@link RestCall#input(Object)} or {@link 
RestCall#formData(String, Object)} to set the
+        * You must call either {@link RestCall#body(Object)} or {@link 
RestCall#formData(String, Object)} to set the
         * contents on the result object.
         *
         * <h5 class='section'>Notes:</h5>
@@ -710,7 +734,7 @@ public class RestClient extends BeanContext implements 
Closeable {
         */
        public RestCall doFormPost(Object url, Object o) throws 
RestCallException {
                return doCall("POST", url, true)
-                       .input(o instanceof HttpEntity ? o : new 
RestRequestEntity(o, urlEncodingSerializer));
+                       .body(o instanceof HttpEntity ? o : new 
RestRequestEntity(o, urlEncodingSerializer));
        }
 
        /**
@@ -774,7 +798,7 @@ public class RestClient extends BeanContext implements 
Closeable {
                        if (method != null && uri != null) {
                                rc = doCall(method, uri, content != null);
                                if (content != null)
-                                       rc.input(new StringEntity(content));
+                                       rc.body(new StringEntity(content));
                                if (h != null)
                                        for (Map.Entry<String,Object> e : 
h.entrySet())
                                                rc.header(e.getKey(), 
e.getValue());
@@ -818,7 +842,7 @@ public class RestClient extends BeanContext implements 
Closeable {
        public RestCall doCall(HttpMethod method, Object url, Object content) 
throws RestCallException {
                RestCall rc = doCall(method.name(), url, method.hasContent());
                if (method.hasContent())
-                       rc.input(content);
+                       rc.body(content);
                return rc;
        }
 
@@ -1015,45 +1039,45 @@ public class RestClient extends BeanContext implements 
Closeable {
                                                        
rc.serializer(serializer).parser(parser);
 
                                                        for (RemoteMethodArg a 
: rmm.getPathArgs())
-                                                               rc.path(a.name, 
args[a.index], a.serializer, null);
+                                                               
rc.path(a.getName(), args[a.getIndex()], a.getSerializer(), a.getSchema());
 
                                                        for (RemoteMethodArg a 
: rmm.getQueryArgs())
-                                                               
rc.query(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+                                                               
rc.query(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), a.getSerializer(), 
a.getSchema());
 
                                                        for (RemoteMethodArg a 
: rmm.getFormDataArgs())
-                                                               
rc.formData(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+                                                               
rc.formData(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), 
a.getSerializer(), a.getSchema());
 
                                                        for (RemoteMethodArg a 
: rmm.getHeaderArgs())
-                                                               
rc.header(a.name, args[a.index], a.skipIfNE, a.serializer, null);
+                                                               
rc.header(a.getName(), args[a.getIndex()], a.isSkipIfEmpty(), 
a.getSerializer(), a.getSchema());
 
-                                                       if (rmm.getBodyArg() != 
null)
-                                                               
rc.input(args[rmm.getBodyArg()]);
+                                                       RemoteMethodArg ba = 
rmm.getBodyArg();
+                                                       if (ba != null)
+                                                               
rc.body(args[ba.getIndex()], ba.getSerializer(), ba.getSchema());
 
                                                        if 
(rmm.getRequestBeanArgs().length > 0) {
                                                                BeanSession bs 
= createBeanSession();
-                                                               for 
(RemoteMethodArg rma : rmm.getRequestBeanArgs()) {
-                                                                       
BeanMap<?> bm = bs.toBeanMap(args[rma.index]);
+                                                               for 
(RemoteMethodBeanArg rmba : rmm.getRequestBeanArgs()) {
+                                                                       
BeanMap<?> bm = bs.toBeanMap(args[rmba.getIndex()]);
 
                                                                        for 
(BeanPropertyValue bpv : bm.getValues(false)) {
                                                                                
BeanPropertyMeta pMeta = bpv.getMeta();
                                                                                
Object val = bpv.getValue();
-
-                                                                               
Path p = pMeta.getAnnotation(Path.class);
-                                                                               
if (p != null)
-                                                                               
        rc.path(getName(p.name(), p.value(), pMeta), val, 
getPartSerializer(p.serializer(), rma.serializer), null);
-
-                                                                               
if (val != null) {
-                                                                               
        Query q1 = pMeta.getAnnotation(Query.class);
-                                                                               
        if (q1 != null)
-                                                                               
                rc.query(getName(q1.name(), q1.value(), pMeta), val, 
q1.skipIfEmpty(), getPartSerializer(q1.serializer(), rma.serializer), null);
-
-                                                                               
        FormData f1 = pMeta.getAnnotation(FormData.class);
-                                                                               
        if (f1 != null)
-                                                                               
                rc.formData(getName(f1.name(), f1.value(), pMeta), val, 
f1.skipIfEmpty(), getPartSerializer(f1.serializer(), rma.serializer), null);
-
-                                                                               
        org.apache.juneau.http.annotation.Header h1 = 
pMeta.getAnnotation(org.apache.juneau.http.annotation.Header.class);
-                                                                               
        if (h1 != null)
-                                                                               
                rc.header(getName(h1.name(), h1.value(), pMeta), val, 
h1.skipIfEmpty(), getPartSerializer(h1.serializer(), rma.serializer), null);
+                                                                               
RemoteMethodArg a = rmba.getProperty(pMeta.getName());
+                                                                               
if (a != null) {
+                                                                               
        HttpPartType pt = a.getPartType();
+                                                                               
        if (pt == PATH)
+                                                                               
                rc.path(a.getName(), val, 
ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), 
a.getSchema());
+                                                                               
        if (val != null) {
+                                                                               
                if (pt == QUERY) {
+                                                                               
                        rc.query(a.getName(), val, a.isSkipIfEmpty(), 
ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), 
a.getSchema());
+                                                                               
                } else if (pt == FORMDATA) {
+                                                                               
                        rc.formData(a.getName(), val, a.isSkipIfEmpty(), 
ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), 
a.getSchema());
+                                                                               
                } else if (pt == HEADER) {
+                                                                               
                        rc.header(a.getName(), val, a.isSkipIfEmpty(), 
ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), 
a.getSchema());
+                                                                               
                } else if (pt == HttpPartType.BODY) {
+                                                                               
                        rc.body(val, 
ObjectUtils.firstNonNull(a.getSerializer(), rmba.getSerializer()), 
a.getSchema());
+                                                                               
                }
+                                                                               
        }
                                                                                
}
                                                                        }
                                                                }
@@ -1062,12 +1086,16 @@ public class RestClient extends BeanContext implements 
Closeable {
                                                        if 
(rmm.getOtherArgs().length > 0) {
                                                                Object[] 
otherArgs = new Object[rmm.getOtherArgs().length];
                                                                int i = 0;
-                                                               for (Integer 
otherArg : rmm.getOtherArgs())
-                                                                       
otherArgs[i++] = args[otherArg];
-                                                               
rc.input(otherArgs);
+                                                               for 
(RemoteMethodArg a : rmm.getOtherArgs())
+                                                                       
otherArgs[i++] = args[a.getIndex()];
+                                                               
rc.body(otherArgs);
                                                        }
 
-                                                       if (rmm.getReturns() == 
ReturnValue.HTTP_STATUS) {
+                                                       RemoteMethodReturn rmr 
= rmm.getReturns();
+                                                       if 
(rmr.getReturnValue() == NONE) {
+                                                               rc.run();
+                                                               return null;
+                                                       } else if 
(rmr.getReturnValue() == HTTP_STATUS) {
                                                                
rc.ignoreErrors();
                                                                int returnCode 
= rc.run();
                                                                Class<?> rt = 
method.getReturnType();
@@ -1076,13 +1104,13 @@ public class RestClient extends BeanContext implements 
Closeable {
                                                                if (rt == 
Boolean.class || rt == boolean.class)
                                                                        return 
returnCode < 400;
                                                                throw new 
RestCallException("Invalid return type on method annotated with 
@RemoteableMethod(returns=HTTP_STATUS).  Only integer and booleans types are 
valid.");
+                                                       } else {
+                                                               Object v = 
rc.getResponse(rmr.getParser(), rmr.getSchema(), method.getGenericReturnType());
+                                                               if (v == null 
&& method.getReturnType().isPrimitive())
+                                                                       v = 
ClassUtils.getPrimitiveDefault(method.getReturnType());
+                                                               return v;
                                                        }
 
-                                                       Object v = 
rc.getResponse(method.getGenericReturnType());
-                                                       if (v == null && 
method.getReturnType().isPrimitive())
-                                                               v = 
ClassUtils.getPrimitiveDefault(method.getReturnType());
-                                                       return v;
-
                                                } catch (RestCallException e) {
                                                        // Try to throw 
original exception if possible.
                                                        
e.throwServerException(interfaceClass.getClassLoader());
@@ -1126,6 +1154,10 @@ public class RestClient extends BeanContext implements 
Closeable {
                return partSerializer;
        }
 
+       HttpPartParser getPartParser() {
+               return partParser;
+       }
+
        URI toURI(Object url) throws URISyntaxException {
                if (url instanceof URI)
                        return (URI)url;
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
index 0ec6db9..4c0b45b 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/RestClientBuilder.java
@@ -1113,6 +1113,46 @@ public class RestClientBuilder extends 
BeanContextBuilder {
        }
 
        /**
+        * Configuration property:  Part parser.
+        *
+        * <p>
+        * The parser to use for parsing POJOs from form data, query 
parameters, headers, and path variables.
+        *
+        * <h5 class='section'>See Also:</h5>
+        * <ul>
+        *      <li class='jf'>{@link RestClient#RESTCLIENT_partParser}
+        * </ul>
+        *
+        * @param value
+        *      The new value for this setting.
+        *      <br>The default value is {@link OpenApiPartParser}.
+        * @return This object (for method chaining).
+        */
+       public RestClientBuilder partParser(Class<? extends HttpPartParser> 
value) {
+               return set(RESTCLIENT_partParser, value);
+       }
+
+       /**
+        * Configuration property:  Part parser.
+        *
+        * <p>
+        * Same as {@link #partParser(Class)} but takes in a parser instance.
+        *
+        * <h5 class='section'>See Also:</h5>
+        * <ul>
+        *      <li class='jf'>{@link RestClient#RESTCLIENT_partParser}
+        * </ul>
+        *
+        * @param value
+        *      The new value for this setting.
+        *      <br>The default value is {@link OpenApiPartParser}.
+        * @return This object (for method chaining).
+        */
+       public RestClientBuilder partParser(HttpPartParser value) {
+               return set(RESTCLIENT_partParser, value);
+       }
+
+       /**
         * Configuration property:  Part serializer.
         *
         * <p>
@@ -1125,7 +1165,7 @@ public class RestClientBuilder extends BeanContextBuilder 
{
         *
         * @param value
         *      The new value for this setting.
-        *      <br>The default value is {@link SimpleUonPartSerializer}.
+        *      <br>The default value is {@link OpenApiPartSerializer}.
         * @return This object (for method chaining).
         */
        public RestClientBuilder partSerializer(Class<? extends 
HttpPartSerializer> value) {
@@ -1145,7 +1185,7 @@ public class RestClientBuilder extends BeanContextBuilder 
{
         *
         * @param value
         *      The new value for this setting.
-        *      <br>The default value is {@link SimpleUonPartSerializer}.
+        *      <br>The default value is {@link OpenApiPartSerializer}.
         * @return This object (for method chaining).
         */
        public RestClientBuilder partSerializer(HttpPartSerializer value) {
diff --git 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
index 5adca48..1b4fb8c 100644
--- 
a/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
+++ 
b/juneau-rest/juneau-rest-client/src/main/java/org/apache/juneau/rest/client/SerializedNameValuePair.java
@@ -13,6 +13,7 @@
 package org.apache.juneau.rest.client;
 
 import org.apache.http.*;
+import org.apache.juneau.*;
 import org.apache.juneau.httppart.*;
 import org.apache.juneau.serializer.*;
 import org.apache.juneau.urlencoding.*;
@@ -64,8 +65,10 @@ public final class SerializedNameValuePair implements 
NameValuePair {
        public String getValue() {
                try {
                        return 
serializer.createSession(null).serialize(HttpPartType.FORMDATA, schema, value);
-               } catch (SerializeException | SchemaValidationException e) {
-                       throw new RuntimeException(e);
+               } catch (SchemaValidationException e) {
+                       throw new FormattedRuntimeException(e, "Validation 
error on request form-data parameter ''{0}''=''{1}''", name, value);
+               } catch (SerializeException e) {
+                       throw new FormattedRuntimeException(e, "Serialization 
error on request form-data parameter ''{0}''", name);
                }
        }
 }
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
index c76d33a..918c4ea 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/BasicRestLogger.java
@@ -52,6 +52,11 @@ public class BasicRestLogger implements RestLogger {
                return logger;
        }
 
+       @Override /* RestLogger */
+       public void setLevel(Level level) {
+               getLogger().setLevel(level);
+       }
+       
        /**
         * Log a message to the logger.
         *
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 eb74228..857a332 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
@@ -26,6 +26,7 @@ import java.nio.charset.*;
 import java.util.*;
 import java.util.concurrent.*;
 import java.util.concurrent.atomic.*;
+import java.util.logging.*;
 
 import javax.activation.*;
 import javax.servlet.*;
@@ -2925,6 +2926,8 @@ public final class RestContext extends BeanContext {
                        staticFileResponseHeaders = 
getMapProperty(REST_staticFileResponseHeaders, Object.class);
 
                        logger = getInstanceProperty(REST_logger, resource, 
RestLogger.class, NoOpRestLogger.class, true, this);
+                       if (debug)
+                               logger.setLevel(Level.FINE);
 
                        varResolver = builder.varResolverBuilder
                                .vars(
diff --git 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
index ad2a4c9..0f0f074 100644
--- 
a/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
+++ 
b/juneau-rest/juneau-rest-server/src/main/java/org/apache/juneau/rest/RestLogger.java
@@ -40,6 +40,13 @@ public interface RestLogger {
        public interface Null extends RestLogger {}
 
        /**
+        * Sets the logging level for this logger.
+        *
+        * @param level The new level.
+        */
+       public void setLevel(Level level);
+
+       /**
         * Log a message to the logger.
         *
         * @param level The log level.

Reply via email to