JBoss.orgCommunity Documentation

Chapter 26. Multipart Providers

RESTEasy has rich support for the "multipart/*" and "multipart/form-data" mime types. The multipart mime format is used to pass lists of content bodies. Multiple content bodies are embedded in one message. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multipart formats, except that each inlined piece of content has a name associated with it.

RESTEasy provides a custom API for reading and writing multipart types as well as marshalling arbitrary List (for any multipart type) and Map (multipart/form-data only) objects

Classes MultipartInput and MultipartOutput provide read and write support for mime type "multipart/mixed" messages respectively. They provide for multiple part messages, in which one or more different sets of data are combined in a single body.

MultipartRelatedInput and MultipartRelatedOutput classes provide read and write support for mime type "multipart/related" messages. These are messages that contain multiple body parts that are inter-related.

MultipartFormDataInput and MultipartFormDataOutput classes provide read and write support for mine type "multipart/form-data". This type is used when returning a set of values as the the result of a user filling out a form or for uploading files.

26.1. Multipart/mixed

26.1.1. Writing multipart/mixed messages

MultipartOutput provides a set of addPart methods for registering message content and specifying special marshalling requirements. In all cases the addPart methods require an input parameter, Object and a MediaType that declares the mime type of the object. Sometimes there may be an object in which marshalling is sensitive to generic type metadata. In such cases, use an addPart method in which you declare the GenericType of the entity Object. Perhaps a file will be passed as content and it will require UTF-8 encoding. Setting input parameter, utf8Encode to true will indicate to RESTEasy to process the filename according to the character set and language encoding rules of rfc5987. This flag is only processed when mime type "multipart/form-data" is specified.

MultipartOutput automatically generates a unique message boundary identifier when it is created. Method setBoundary is provided to declare a different identifier.


public class MultipartOutput
{
   public OutputPart addPart(Object entity, MediaType mediaType);
   public OutputPart addPart(Object entity, MediaType mediaType,
        String filename);
   public OutputPart addPart(Object entity, MediaType mediaType,
        String filename, boolean utf8Encode);
   public OutputPart addPart(Object entity, GenericType<?> type,
        MediaType mediaType);
   public OutputPart addPart(Object entity, GenericType<?> type,
        MediaType mediaType, String filename);
   public OutputPart addPart(Object entity, GenericType<?> type,
        MediaType mediaType, String filename, boolean utf8Encode);
   public OutputPart addPart(Object entity, Class<?> type, Type genericType,
        MediaType mediaType);
   public OutputPart addPart(Object entity, Class<?> type, Type genericType,
        MediaType mediaType, String filename);
   public OutputPart addPart(Object entity, Class<?> type, Type genericType,
        MediaType mediaType, String filename, boolean utf8Encode);
   public List<OutputPart> getParts();
   public String getBoundary();
   public void setBoundary(String boundary);
}

Each message part registered with MultipartOutput is represented by an OutputPart object. Class MultipartOutput generates an OutputPart object for each addPart method call.


public class OutputPart {
   public OutputPart(final Object entity, final Class<?> type,
        final Type genericType, final MediaType mediaType);
   public OutputPart(final Object entity, final Class<?> type,
        final Type genericType, final MediaType mediaType,
        final String filename);
   public OutputPart(final Object entity, final Class<?> type,
        final Type genericType, final MediaType mediaType,
        final String filename, final boolean utf8Encode);
   public MultivaluedMap<String, Object> getHeaders();
   public Object getEntity();
   public Class<?> getType();
   public Type getGenericType();
   public MediaType getMediaType();
   public String getFilename();
   public boolean isUtf8Encode();
}

26.1.2. Reading multipart/mixed messages

MultipartInput and InputPart are interface classes that provide access to multipart/mixed message data. RESTEasy provides an implementation of these classes. They perform the work to retrieve message data.



package org.jboss.resteasy.plugins.providers.multipart;

import java.util.List;

public interface MultipartInput {
   List<InputPart> getParts();
   String getPreamble();
   /**
    * Call this method to delete any temporary files created from unmarshalling
    * this multipart message
    * Otherwise they will be deleted on Garbage Collection or JVM exit.
    */
   void close();
}


package org.jboss.resteasy.plugins.providers.multipart;

import jakarta.ws.rs.core.GenericType;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.lang.reflect.Type;

/**
 * Represents one part of a multipart message.
 */
public interface InputPart {
   /**
    * If no content-type header is sent in a multipart message part
    * "text/plain; charset=ISO-8859-1" is assumed.
    *
    * This can be overwritten by setting a different String value in
    * {@link org.jboss.resteasy.spi.HttpRequest#setAttribute(String, Object)}
    * with this ("resteasy.provider.multipart.inputpart.defaultContentType")
    * String as key. It should be done in a
    * {@link jakarta.ws.rs.container.ContainerRequestFilter}.
    */
   String DEFAULT_CONTENT_TYPE_PROPERTY =
    "resteasy.provider.multipart.inputpart.defaultContentType";

   /**
    * If there is a content-type header without a charset parameter,
    * charset=US-ASCII is assumed.
    *
    * This can be overwritten by setting a different String value in
    * {@link org.jboss.resteasy.spi.HttpRequest#setAttribute(String, Object)}
    * with this ("resteasy.provider.multipart.inputpart.defaultCharset")
    * String as key. It should be done in a
    * {@link jakarta.ws.rs.container.ContainerRequestFilter}.
    */
   String DEFAULT_CHARSET_PROPERTY =
    "resteasy.provider.multipart.inputpart.defaultCharset";

   /**
    * @return headers of this part
    */
   MultivaluedMap<String, String> getHeaders();
   String getBodyAsString() throws IOException;
   <T> T getBody(Class<T> type, Type genericType) throws IOException;
   <T> T getBody(GenericType<T> type) throws IOException;

   /**
    * @return "Content-Type" of this part
    */
   MediaType getMediaType();

   /**
    * @return true if the Content-Type was resolved from the message, false if
    *         it was resolved from the server default
    */
   boolean isContentTypeFromMessage();

   /**
    * Change the media type of the body part before you extract it.
    * Useful for specifying a charset.
    * @param mediaType media type
    */
   void setMediaType(MediaType mediaType);
}}

26.1.3. Simple multipart/mixed message example

The following example shows how to read and write a simple multipart/mixed message.

The data to be transfered is a very simple class, Soup.



package org.jboss.resteasy.test.providers.multipart.resource;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlRootElement;
import jakarta.xml.bind.annotation.XmlElement;

@XmlRootElement(name = "soup")
@XmlAccessorType(XmlAccessType.FIELD)
public class Soup {
    @XmlElement
    private String id;

    public Soup(){}
    public Soup(final String id){this.id = id;}
    public String getId(){return id;}
}

This code fragment creates a multipart/mixed message passing Soup information using class, MultipartOutput.


 
      MultipartOutput multipartOutput = new MultipartOutput();
      multipartOutput.addPart(new Soup("Chicken Noodle"),
            MediaType.APPLICATION_XML_TYPE);
      multipartOutput.addPart(new Soup("Vegetable"),
            MediaType.APPLICATION_XML_TYPE);
      multipartOutput.addPart("Granny's Soups", MediaType.TEXT_PLAIN_TYPE);
 

This code fragment uses class MultipartInput to extract the Soup information provided by multipartOutput above.


            
      // MultipartInput multipartInput, the entity returned in the client in a
      // Response object or the input value of an endpoint method parameter.
      for (InputPart inputPart : multipartInput.getParts()) {
          if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) {
              Soup c = inputPart.getBody(Soup.class, null);
              String name = c.getId();
          } else {
              String s = inputPart.getBody(String.class, null);
          }
      }
 

Returning a multipart/mixed message from an endpoint can be done in two ways. MultipartOutput can be returned as the method's return object or as an entity in a Response object.



    @GET
    @Path("soups/obj")
    @Produces("multipart/mixed")
    public MultipartOutput soupsObj() {
        return multipartOutput;
    }

    @GET
    @Path("soups/resp")
    @Produces("multipart/mixed")
    public Response soupsResp() {
      return Response.ok(multipartOutput, MediaType.valueOf("multipart/mixed"))
                     .build();
    }

There is no difference in the way a client retrieves the message from the endpoint. It is done as follows.


            
      ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
      ResteasyWebTarget target = client.target(THE_URL);
      Response response = target.request().get();
      MultipartInput multipartInput = response.readEntity(MultipartInput.class);

      for (InputPart inputPart : multipartInput.getParts()) {
          if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) {
              Soup c = inputPart.getBody(Soup.class, null);
              String name = c.getId();
          } else {
              String s = inputPart.getBody(String.class, null);
          }
      }

      client.close();
 

A client sends the message, multipartOutput, to an endpoint as an entity object in an HTTP method call in this code fragment.



        ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
        ResteasyWebTarget target = client.target(SOME_URL + "/register/soups");
        Entity<MultipartOutput> entity = Entity.entity(multipartOutput,
                new MediaType("multipart", "mixed"));
        Response response = target.request().post(entity);
 

Here is the endpoint receiving the message and extracting the contents.


       
 @POST
 @Consumes("multipart/mixed")
 @Path("register/soups")
  public void registerSoups(MultipartInput multipartInput) throws IOException {

      for (InputPart inputPart : multipartInput.getParts()) {
         if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) {
                Soup c = inputPart.getBody(Soup.class, null);
                String name = c.getId();
         } else {
                String s = inputPart.getBody(String.class, null);
         }
      }
  }
 

26.1.4. Multipart/mixed message with GenericType example

This example shows how to read and write a multipart/mixed message whose content consists of a generic type, in this case a List<Soup>. The MultipartOutput and MultipartIntput methods that use GenericType parameters are used.

The multipart/mixed message is created using MultipartOutput as follows.


            
        MultipartOutput multipartOutput = new MultipartOutput();
        List<Soup> soupList = new ArrayList<Soup>();
        soupList.add(new Soup("Chicken Noodle"));
        soupList.add(new Soup("Vegetable"));
        multipartOutput.addPart(soupList, new GenericType<List<Soup>>(){},
               MediaType.APPLICATION_XML_TYPE );
        multipartOutput.addPart("Granny's Soups", MediaType.TEXT_PLAIN_TYPE);
 

The message data is extracted with MultipartInput. Note there are two MultipartInput getBody methods that can be used to retrieve data specifying GenericType. This code fragment uses the second one but shows the first one in comments.


            
   <T> T getBody(Class<T> type, Type genericType) throws IOException;
   <T> T getBody(GenericType<T> type) throws IOException;
 

            
   // MultipartInput multipartInput, the entity returned in the client in a
   // Response object or the input value of an endpoint method parameter.
   GenericType<List<Soup>> gType = new GenericType<List<Soup>>(){};

   for (InputPart inputPart : multipartInput.getParts()) {
      if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) {
         List<Soup> c = inputPart.getBody(gType);
      // List<Soup> c = inputPart.getBody(gType.getRawType(), gType.getType());
      } else {
         String s = inputPart.getBody(String.class, null);;
      }
   }
 

26.1.5. java.util.List with multipart/mixed data example

When a set of message parts are uniform they do not need to be written using MultipartOutput or read with MultipartInput. They can be sent and received as a List. RESTEasy performs the necessary work to read and write the message data.

For this example the data to be transmitted is class, ContextProvidersCustomer


            
    package org.jboss.resteasy.test.providers.multipart.resource;

    import jakarta.xml.bind.annotation.XmlAccessType;
    import jakarta.xml.bind.annotation.XmlAccessorType;
    import jakarta.xml.bind.annotation.XmlElement;
    import jakarta.xml.bind.annotation.XmlRootElement;

    @XmlRootElement(name = "customer")
    @XmlAccessorType(XmlAccessType.FIELD)
    public class ContextProvidersCustomer {
       @XmlElement
       private String name;

       public ContextProvidersCustomer() { }
       public ContextProvidersCustomer(final String name) {
          this.name = name;
      }
       public String getName() { return name;}
    }
 

In this code fragment the client creates and sends a list of ContextProvidersCustomers.


            
      List<ContextProvidersCustomer> customers =
            new ArrayList<ContextProvidersCustomer>();
      customers.add(new ContextProvidersCustomer("Bill"));
      customers.add(new ContextProvidersCustomer("Bob"));

      Entity<ContextProvidersCustomer> entity = Entity.entity(customers,
        new MediaType("multipart", "mixed"));

      Client client = ClientBuilder.newClient();
      WebTarget target = client.target(SOME_URL);
      Response response = target.request().post(entity);
 

The endpoint receives the list, alters the contents and returns a new list.


            
   @POST
   @Consumes("multipart/mixed")
   @Produces(MediaType.APPLICATION_XML)
   @Path("post/list")
   public List<ContextProvidersName> postList(
         List<ContextProvidersCustomer> customers) throws IOException {

      List<ContextProvidersName> names = new ArrayList<ContextProvidersName>();

      for (ContextProvidersCustomer customer : customers) {
         names.add(new ContextProvidersName("Hello " + customer.getName()));
      }
      return names;
   }
 

The client receives the altered message data and processes it.


            
    Response response = target.request().post(entity);
    List<ContextProvidersCustomer> rtnList =
      response.readEntity(new GenericType<List<ContextProvidersCustomer>>(){});
        :
        :
 

26.3. Multipart/form-data

The MultiPart/Form-Data mime type is used in sending form data (rfc2388). It can include data generated by user input, information that is typed, or included from files that the user has selected. "multipart/form-data" is often found in web application HTML Form documents and is generally used to upload files. The form-data format is the same as other multi-part formats, except that each inlined piece of content has a name associated with it.

26.3.1. Multipart/form-data with EntityPart's

In Jakarta RESTful Web Services 3.1 the jakarta.ws.rs.core.EntityPart API was introduced. This can be used to read and write multipart/form-data with a standardized API.

The content of the EntityPart has two definable size limits. These can be set as system properties, servlet context properties or a MicroProfile Config compatible value.

Table 26.1. 
Property NameDescriptionDefault ValueExample
dev.resteasy.entity.memory.thresholdThe threshold to use for the amount of data to store in memory for entities.5MB"512MB" is equivalent to 512 Megabytes
dev.resteasy.entity.file.thresholdThe threshold to use for the amount of data that can be stored in a file for entities. If the threshold is reached an IllegalStateException will be thrown. A value of -1 means no limit. 50MB"1GB" is equivalent to 1 Gigabyte


You can read, write and inject (with @FormParam) an EntityPart in the following forms;

  • EntityPart
  • List<EntityPart>
  • InputStream

Example reading data:


try (Client client = ClientBuilder.newClient()) {
    final List<EntityPart> multipart = new ArrayList<>();
    multipart.add(
            EntityPart.withName("content")
                    .content("Example Content")
                    .mediaType(MediaType.TEXT_PLAIN_TYPE)
                    .build()
    );
    try (
            Response response = client.target(INSTANCE.configuration().baseUriBuilder().path("test/injected"))
                    .request(MediaType.MULTIPART_FORM_DATA_TYPE)
                    .post(Entity.entity(new GenericEntity<>(multipart) {
                    }, MediaType.MULTIPART_FORM_DATA))
    ) {
        Assert.assertEquals(Response.Status.OK, response.getStatusInfo());
        final List<EntityPart> entityParts = response.readEntity(new GenericType<>() {
        });
    }
}
               

Example receiving and writing data.


@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.MULTIPART_FORM_DATA)
@Path("/injected")
public List<EntityPart> injected(@FormParam("content") final String string,
                         @FormParam("content") final EntityPart entityPart,
                         @FormParam("content") final InputStream in) throws IOException {
    final List<EntityPart> multipart = new ArrayList<>();
    multipart.add(
            EntityPart.withName("received-entity-part")
                    .content(entityPart.getContent(String.class))
                    .mediaType(entityPart.getMediaType())
                    .fileName(entityPart.getFileName().orElse(null))
                    .build()
    );
    multipart.add(
            EntityPart.withName("received-input-stream")
                    .content(MultipartEntityPartProviderTest.toString(in).getBytes(StandardCharsets.UTF_8))
                    .mediaType(MediaType.APPLICATION_OCTET_STREAM_TYPE)
                    .build()
    );
    multipart.add(
            EntityPart.withName("received-string")
                    .content(string)
                    .mediaType(MediaType.TEXT_PLAIN_TYPE)
                    .build()
    );
    return multipart;
}
            

Currently multipart/form-data requests only work in the provided servlet containers. The servlet's multipart-config must be configured as well. If using one of the RESTEasy Undertow embedded servers or the resteasy-servlet-initializer, then annotate the application with @MultipartConfig. For other servers specify the configuration. Below is an example web.xml configuration.


<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="https://jakarta.ee/xml/ns/jakartaee"
   xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
   xsi:schemaLocation="https://jakarta.ee/xml/ns/jakartaee https://jakarta.ee/xml/ns/jakartaee/web-app_5_0.xsd"
   version="5.0">
    <servlet>
        <servlet-name>RESTEasy</servlet-name>
        <servlet-class>org.jboss.resteasy.plugins.server.servlet.HttpServlet30Dispatcher</servlet-class>
        <init-param>
            <param-name>jakarta.ws.rs.core.Application</param-name>
            <param-value>dev.resteasy.resource.EntityApplication</param-value>
        </init-param>
        <async-supported>true</async-supported>
        <multipart-config>
            <max-file-size>-1</max-file-size>
            <max-request-size>-1</max-request-size>
            <file-size-threshold>0</file-size-threshold>
            <location></location>
        </multipart-config>
    </servlet>
    <servlet-mapping>
        <servlet-name>RESTEasy</servlet-name>
        <url-pattern>/*</url-pattern>
    </servlet-mapping>
</web-app>

               

26.3.2. Writing multipart/form-data messages

Form data consists of key/value pairs. RESTEasy provides class MultipartFormDataOutput to assist the user in specifying the required information and generating a properly formatted message. It is a subclass of MultipartOutput. And as with multipart/mixed data sometimes there may be marshalling which is sensitive to generic type metadata, in those cases use the methods containing input parameter GenericType.


         
package org.jboss.resteasy.plugins.providers.multipart;

public class MultipartFormDataOutput extends MultipartOutput
{
    public OutputPart addFormData(String key, Object entity,
            MediaType mediaType)
    public OutputPart addFormData(String key, Object entity, GenericType type,
            MediaType mediaType)
    public OutputPart addFormData(String key, Object entity, Class type,
            Type genericType, MediaType mediaType)
    public Map<String, OutputPart> getFormData()
    public Map<String, List<OutputPart>> getFormDataMap()
}

26.3.3. Reading multipart/form-data messages

MultipartFormDataInput is an interface class that provides access to multipart/form-data message data. It is a subclass of MultipartInput. RESTEasy provides an implementation of this class. It performs the work to retrieve message data.


         
package org.jboss.resteasy.plugins.providers.multipart;

import java.io.IOException;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

import jakarta.ws.rs.core.GenericType;

public interface MultipartFormDataInput extends MultipartInput {
   /**
    * @return A parameter map containing a list of values per name.
    */
   Map<String, List<InputPart>> getFormDataMap();
   <T> T getFormDataPart(String key, Class<T> rawType, Type genericType)
         throws IOException;
   <T> T getFormDataPart(String key, GenericType<T> type) throws IOException;
}

26.3.4. Simple multipart/form-data message example

The following example show how to read and write a simple multipart/form-data message.

The multipart/mixed message is created on the clientside using the MultipartFormDataOutput object. One piece of form data to be transfered is a very simple class, ContextProvidersName.


         
package org.jboss.resteasy.test.providers.multipart.resource;

import jakarta.xml.bind.annotation.XmlAccessType;
import jakarta.xml.bind.annotation.XmlAccessorType;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;

@XmlRootElement(name = "name")
@XmlAccessorType(XmlAccessType.FIELD)
public class ContextProvidersName {
   @XmlElement
   private String name;

   public ContextProvidersName() {}
   public ContextProvidersName(final String name) {this.name = name;}
   public String getName() {return name;}
}
 

The client creates and sends the message as follows:


         
      MultipartFormDataOutput output = new MultipartFormDataOutput();
      output.addFormData("bill", new ContextProvidersCustomer("Bill"),
         MediaType.APPLICATION_XML_TYPE);
      output.addFormData("bob", "Bob", MediaType.TEXT_PLAIN_TYPE);

      Entity<MultipartFormDataOutput> entity = Entity.entity(output,
          new MediaType("multipart", "related"));

      Client client = ClientBuilder.newClient();
      WebTarget target = client.target(SOME_URL);
      Response response = target.request().post(entity);
 

The endpoint receives the message and processes it.


         
   @POST
   @Consumes("multipart/form-data")
   @Produces(MediaType.APPLICATION_XML)
   @Path("post/form")
   public Response postForm(MultipartFormDataInput input)
         throws IOException {

      Map<String, List<InputPart>> map = input.getFormDataMap();
      List<ContextProvidersName> names = new ArrayList<ContextProvidersName>();

      for (Iterator<String> it = map.keySet().iterator(); it.hasNext(); ) {
         String key = it.next();
         InputPart inputPart = map.get(key).iterator().next();
         if (MediaType.APPLICATION_XML_TYPE.equals(inputPart.getMediaType())) {
            names.add(new ContextProvidersName(inputPart.getBody(
                  ContextProvidersCustomer.class, null).getName()));
         } else {
            names.add(new ContextProvidersName(inputPart.getBody(
                  String.class, null)));
         }
      }
      return Response.ok().build();
   }
 

26.3.5. java.util.Map with multipart/form-data

When the data of a multipart/form-data message is uniform it does not need to be written in a MultipartFormDataOutput object. It can be sent and received as a java.util.Map object. RESTEasy performs the necessary work to read and write the message data, however the Map object must declare the type it is unmarshalling via the generic parameters in the Map type declaration.

Here is an example of a client creating and sending a multipart/form-data message.


            
      Map<String, ContextProvidersCustomer> customers =
              new HashMap<String, ContextProvidersCustomer>();
      customers.put("bill", new ContextProvidersCustomer("Bill"));
      customers.put("bob", new ContextProvidersCustomer("Bob"));

      Entity<Map<String, ContextProvidersCustomer>> entity =
        Entity.entity(customers, new MediaType("multipart", "form-data"));

      Client client = ClientBuilder.newClient();
      WebTarget target = client.target(SOME_URL);
      Response response = target.request().post(entity)
 

This is the endpoint the client above is calling. It receives the message and processes it.


            
 @POST
 @Consumes("multipart/form-data")
 @Produces(MediaType.APPLICATION_XML)
 @Path("post/map")
 public Response postMap(Map<String, ContextProvidersCustomer> customers)
         throws IOException {

   List<ContextProvidersName> names = new ArrayList<ContextProvidersName>();
   for (Iterator<String> it = customers.keySet().iterator(); it.hasNext(); ) {
       String key = it.next();
       ContextProvidersCustomer customer = customers.get(key);
       names.add(new ContextProvidersName(key + ":" + customer.getName()));
   }
   return Response.ok().build();
 }
 

26.3.6. Multipart/form-data java.util.Map as method return type

A java.util.Map object representing a multipart/form-data message can be returned from an endpoint as long as the message data is uniform, however the endpoint method MUST be annotated with @PartType which declares the media type of the Map entries and the Map object must declare the type it is unmarshalling via the generic parameters in the Map type declaration. RESTEasy requires this information so it can generate the message properly.

Here is an example of an endpoint returning a Map of ContextProvidersCustomer to the client.


            
   @GET
   @Produces("multipart/form-data")
   @PartType("application/xml")
   @Path("get/map")
   public Map<String, ContextProvidersCustomer> getMap() {

      Map<String, ContextProvidersCustomer> map =
          new HashMap<String, ContextProvidersCustomer>();
      map.put("bill", new ContextProvidersCustomer("Bill"));
      map.put("bob", new ContextProvidersCustomer("Bob"));
      return map;
   }
 

The client would retrieve the data as follows.


            
      Client client = ClientBuilder.newClient();
      WebTarget target = client.target(SOME_URL);
      Response response = target.request().get();
      MultipartFormDataInput entity = response.readEntity(
            MultipartFormDataInput.class);
      client.close();

      ContextProvidersCustomer bill = entity.getFormDataPart("bill",
            ContextProvidersCustomer.class, null);
      ContextProvidersCustomer bob = entity.getFormDataPart("bob",
            ContextProvidersCustomer.class, null);
 

26.3.7. @MultipartForm and POJOs

If you have exact knowledge of the multipart/form-data packets, they can be mapped to and from a POJO class using the annotation @org.jboss.resteasy.annotations.providers.multipart.MultipartForm and the Jakarta RESTful Web Services @FormParam annotation. Simply define a POJO with at least a default constructor and annotate its fields and/or properties with @FormParams. These @FormParams must also be annotated with @org.jboss.resteasy.annotations.providers.multipart.PartType if doing output. For example:


            public class CustomerProblemForm {
    @FormParam("customer")
    @PartType("application/xml")
    private Customer customer;

    @FormParam("problem")
    @PartType("text/plain")
    private String problem;

    public Customer getCustomer() { return customer; }
    public void setCustomer(Customer cust) { this.customer = cust; }
    public String getProblem() { return problem; }
    public void setProblem(String problem) { this.problem = problem; }
}

After defining the POJO class it can be used to represent multipart/form-data. Here's an example of sending a CustomerProblemForm using the RESTEasy client framework:


            
@Path("portal")
public interface CustomerPortal {

   @Path("issues/{id}")
   @Consumes("multipart/form-data")
   @PUT
   public void putProblem(@MultipartForm CustomerProblemForm,
                          @PathParam("id") int id) {
      CustomerPortal portal = ProxyFactory.create(
            CustomerPortal.class, "http://example.com");
      CustomerProblemForm form = new CustomerProblemForm();
      form.setCustomer(...);
      form.setProblem(...);

      portal.putProblem(form, 333);
   }
}

Note that the @MultipartForm annotation was used to tell RESTEasy that the object has a @FormParam and that it should be marshalled from that. The same object can be used to receive multipart data. Here is an example of the server side counterpart of our customer portal.


            @Path("portal")
public class CustomerPortalServer {

    @Path("issues/{id})
    @Consumes("multipart/form-data")
    @PUT
    public void putIssue(@MultipartForm CustoemrProblemForm,
                         @PathParam("id") int id) {
       ... write to database...
    }
}

In addition to the XML data format, JSON formatted data can be used to represent POJO classes. To achieve this, plug in a JSON provider into your project. For example, add the RESTEasy Jackson2 Provider into the project's dependency scope:


            
<dependency>
    <groupId>org.jboss.resteasy</groupId>
    <artifactId>resteasy-jackson2-provider</artifactId>
    <version>${resteasy.ver}</version>
</dependency>

Now an ordinary POJO class can be written, which Jackson2 will automatically serialize/deserialize into JSON format:


            
public class JsonUser {
   private String name;

   public JsonUser() {}
   public JsonUser(final String name) { this.name = name; }
   public String getName() { return name; }
   public void setName(String name) { this.name = name; }
}

The resource class can be written like this:


            
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;
import org.jboss.resteasy.annotations.providers.multipart.PartType;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.FormParam;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;

@Path("/")
public class JsonFormResource {

    public JsonFormResource() {
    }

    public static class Form {

    @FormParam("user")
    @PartType("application/json")
    private JsonUser user;

    public Form() {
    }

    public Form(final JsonUser user) {
    this.user = user;
    }

    public JsonUser getUser() {
      return user;
    }
}

    @PUT
    @Path("form/class")
    @Consumes("multipart/form-data")
    public String putMultipartForm(@MultipartForm Form form) {
         return form.getUser().getName();
    }
}

In the code shown above, it can be seen the PartType of JsonUser is marked as "application/json", and it's included in the "@MultipartForm Form" class instance.

To send the request to the resource method, send the JSON formatted data that corresponds with the JsonUser class. The easiest way to do this is to use a proxy class that has the same definition of the resource class. Here is the sample code of the proxy class that is corresponding with the JsonFormResource class:


            
import org.jboss.resteasy.annotations.providers.multipart.MultipartForm;

import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.PUT;
import jakarta.ws.rs.Path;

@Path("/")
public interface JsonForm {

@PUT
@Path("form/class")
@Consumes("multipart/form-data")
  String putMultipartForm(@MultipartForm JsonFormResource.Form form);
}

And then use the proxy class above to send the request to the resource method correctly. Here is the sample code:


            
ResteasyClient client = (ResteasyClient)ClientBuilder.newClient();
...
JsonForm proxy = client.target("your_request_url_address")
                       .proxy(JsonForm.class);
String name = proxy.putMultipartForm(new JsonFormResource
                   .Form(new JsonUser("bill")));
...

If the client side has the Jackson2 provider included, the request will be marshaled correctly. The JsonUser data will be converted into JSON format and sent to the server side. Hand-crafted JSON data can be use in the request and sent to the server side. It must be made sure the request data is in the correct form.

26.4. Note about multipart parsing and working with other frameworks

There are many frameworks doing multipart parsing automatically with the help of filters and interceptors, like org.jboss.seam.web.MultipartFilter in Seam and org.springframework.web.multipart.MultipartResolver in Spring, however these incoming multipart request stream can be parsed only once. RESTEasy users working with multipart should make sure that nothing parses the stream before RESTEasy gets it.

26.5. Overwriting the default fallback content type for multipart messages

By default if no Content-Type header is present in a part "text/plain; charset=us-ascii" is used as the fallback. This is the value defined by the MIME RFC, however some web clients, and web browsers do not send Content-Type headers for all fields in a multipart/form-data request. They send them only for the file parts. This can cause character encoding and unmarshalling errors on the server side. To correct this there is an option to define another, non-rfc compliant fallback value. This can be done dynamically per request with the filter facility of Jakarta RESTful Web Services 3.0. In the following example we will set "*/*; charset=UTF-8" as the new default fallback:



import org.jboss.resteasy.plugins.providers.multipart.InputPart;

@Provider
public class InputPartDefaultCharsetOverwriteContentTypeCharsetUTF8
   implements ContainerRequestFilter {

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException
   {
      requestContext.setProperty(InputPart.DEFAULT_CONTENT_TYPE_PROPERTY, "*/*; charset=UTF-8");
   }
}

26.6. Overwriting the content type for multipart messages

Using attribute, InputPart.DEFAULT_CONTENT_TYPE_PROPERTY and a filter enables the setting of a default Content-Type, It is also possible to override the Content-Type by setting a different media type with method InputPart.setMediaType(). Here is an example:



@POST
@Path("query")
@Consumes(MediaType.MULTIPART_FORM_DATA)
@Produces(MediaType.TEXT_PLAIN)
public Response setMediaType(MultipartInput input) throws IOException
{
    List<InputPart> parts = input.getParts();
    InputPart part = parts.get(0);
    part.setMediaType(MediaType.valueOf("application/foo+xml"));
    String s = part.getBody(String.class, null);
    ...
}

26.7. Overwriting the default fallback charset for multipart messages

Sometimes, a part may have a Content-Type header with no charset parameter. If the InputPart.DEFAULT_CONTENT_TYPE_PROPERTY property is set and the value has a charset parameter, that value will be appended to an existing Content-Type header that has no charset parameter. It is also possible to specify a default charset using the constant InputPart.DEFAULT_CHARSET_PROPERTY (actual value "resteasy.provider.multipart.inputpart.defaultCharset"):


import org.jboss.resteasy.plugins.providers.multipart.InputPart;

@Provider
public class InputPartDefaultCharsetOverwriteContentTypeCharsetUTF8
   implements ContainerRequestFilter {

   @Override
   public void filter(ContainerRequestContext requestContext) throws IOException
   {
      requestContext.setProperty(InputPart.DEFAULT_CHARSET_PROPERTY, "UTF-8");
   }
}

If both InputPart.DEFAULT_CONTENT_TYPE_PROPERTY and

InputPart.DEFAULT_CHARSET_PROPERTY are set, then the value of

InputPart.DEFAULT_CHARSET_PROPERTY will override any charset in the value of

InputPart.DEFAULT_CONTENT_TYPE_PROPERTY.