Documenting the steps to accept multipart/form-data submission of a file over JAX-RS, in current versions of WebSphere Liberty.

(This is an update to JAX-RS in WebSphere to accept uploaded files.)

References

Approach

Note, first, that this is again using a Liberty-specific implementation and class. However,

  1. It’s not dependent on the internal, third party library Liberty uses for JAX-RS (previously Apache Wink, now Apache CXF)
  2. It’s using an approach that hopefully will be somewhat similar to what is now in Jakarta Rest 3.1 (March, 2022), not yet available in Liberty, but maybe imminently?

Maven dependency

<dependency>
    <groupId>com.ibm.websphere.appserver.api</groupId>
    <artifactId>com.ibm.websphere.appserver.api.jaxrs20</artifactId>
    <version>1.1.68</version>
    <scope>provided</scope>
</dependency>

Liberty server.xml

I’m currently using feature webProfile-8.0, which includes the necessary subfeatures. Or see the list of Liberty features if you’re being more specific.

Java Resource

Method signature to accept multipart/form-data and have access to the right kinds of objects to process the uploaded file (along with other, non-file “parts”):

import javax.activation.DataHandler;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.Response.Status;
import com.ibm.websphere.jaxrs20.multipart.IAttachment
...
@POST
@Consumes(MediaType.MULTIPART_FORM_DATA)
public Response submitFile(
    List<IAttachment> attachments) throws IOException

Note that we’re submitting both text-field values and a file, and only 1 file at a time. Thus, some of the simple logic below was sufficient.

Note also, some sample IAttachments from when 2 text fields and a file are submitted:

contentType: text/plain, Content-Disposition: form-data; name="recordNumber"
contentType: text/plain, Content-Disposition: form-data; name="plateNumber"
contentType: application/pdf, Content-Disposition: form-data; name="document"; filename="file.pdf"
{
...
    File file = null;
    Map<String, String> params = new HashMap<>();

    for (IAttachment attachment : attachments) {

        if (attachment == null) {
            log.warn("Empty attachment found");
            continue;
        }

        DataHandler dataHandler = attachment.getDataHandler();

        String attachmentName = dataHandler.getName();

        if (attachmentName == null) {
            log.warn("Nameless attachment found");
            continue;
        }

        String contentDisposition = attachment.getHeader("Content-Disposition");

        log.debug("attachmentName: {}, contentType: {}, Content-Disposition: {}",
       	          attachmentName, attachment.getContentType(), contentDisposition);

        // look for "filename=" to determine files vs. parameters
        if (contentDisposition.toLowerCase().contains("filename=")) {

            try {
                ... 
                file = yourCodeToMakeAFileFromInputStream(dataHandler.getInputStream());
            }

            catch (IOException e) {
                log.error("Failed to make File from InputStream: " + e.toString());
                return Response.status(Status.INTERNAL_SERVER_ERROR).
                                entity("Unable to save file").build();
            }
        }

        else {

            try {
                String value = 
                    new String(dataHandler.getInputStream().readAllBytes(), StandardCharsets.UTF_8);
                log.debug("part {}={}", attachmentName, value);
                params.put(attachmentName, value);
            }

            catch (IOException e) {
                String msg = "Invalid text value submitted for " + attachmentName;
                log.error("{}: {}", msg, e.toString());

                return Response.status(Status.BAD_REQUEST).entity(msg).build();
            }
        }

    }

    if (file == null) {
        String msg = "No file parameter was submitted.";
        log.warn(msg);
        return Response.status(Status.BAD_REQUEST).entity(msg).build();
    }

    return yourCodeToProcessTheFileAndTextFields(file, params);
}