A multipart/form-data request can contain multiple sub-request bodies, each with its own separate header and body. Each sub-request body has its own separate header and body, and is typically used for file uploads. Here we use RestTemplate to send a multipart/form-data request.

RestTemplate

It’s really simple, it’s all in the code.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
package io.springcloud.test;


import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.client.MultipartBodyBuilder;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestTemplate;

public class MainTest {
    public static void main(String[] args) {

        RestTemplate restTemplate = new RestTemplate();

        HttpHeaders headers = new HttpHeaders();

        // ContentType
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);

        MultipartBodyBuilder multipartBodyBuilder = new MultipartBodyBuilder();

        // Common form parameters.
        multipartBodyBuilder.part("name", "spring cloud");
        
        // Custom ContentType.
        multipartBodyBuilder.part("compment", """
                [{"name": "open feign"}, {"name": "gateway"}]""", MediaType.APPLICATION_JSON);

        // Load a file from disk.
        Resource file1 = new FileSystemResource("C:\\logo.png");
        multipartBodyBuilder.part("avatar", file1, MediaType.IMAGE_JPEG);

        // Load the file from the classpath and add some extra request headers.
        Resource file2 = new ClassPathResource("logo.png");
        multipartBodyBuilder.part("avatar", file2, MediaType.TEXT_PLAIN)
                    .header("X-Size", "400")
                    .header("X-width", "400");

        // multipart/form-data request body
        MultiValueMap<String, HttpEntity<?>> multipartBody = multipartBodyBuilder.build();

        // The complete http request body.
        HttpEntity<MultiValueMap<String, HttpEntity<?>>> httpEntity = new HttpEntity<>(multipartBody, headers);

        ResponseEntity<String> responseEntity = restTemplate.postForEntity("http://localhost/upload", httpEntity,
                String.class);

        System.out.println(responseEntity);
    }
}

Testing

Server

A simple Controller that reads all the information submitted by the client.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package io.springboot.demo.web.controller;

import java.io.IOException;
import java.io.InputStream;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.Part;

import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.StreamUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;


import lombok.extern.slf4j.Slf4j;

@RestController
@RequestMapping("/upload")
@Slf4j
public class UploadController {

    @PostMapping(consumes = MediaType.MULTIPART_FORM_DATA_VALUE, produces = MediaType.TEXT_PLAIN_VALUE)
    public String upload(HttpServletRequest request) throws IOException, ServletException {
        
        for (Part part : request.getParts()) {
            
            String contentType = part.getContentType();
            String parameterName = part.getName();
            String fileName = part.getSubmittedFileName();
            long size = part.getSize();
            

            // Headers
            HttpHeaders headers = new HttpHeaders();
            for (String headerName : part.getHeaderNames()) {
                headers.addAll(headerName, part.getHeaders(headerName).stream().toList());
            }

            log.info("contentType={},parameterName={}, fileName={},size={},headers={}", contentType, parameterName,
                    fileName, size, headers);

            
            /**
            * Read body
            */
            try (InputStream reader = part.getInputStream()) {
                if (fileName == null) {
                    byte[] content = StreamUtils.copyToByteArray(reader);
                    log.info("content={}", new String(content));
                } else {
                    log.info("content={}", "[binary]");
                }
            }
            
            log.info("---------------------------------------------------");

        }
        return "success";
    }
}

Console Logs

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
contentType=text/plain;charset=UTF-8,parameterName=name, fileName=null,size=12,headers=[Content-Disposition:"form-data; name="name"", Content-Length:"12", Content-Type:"text/plain;charset=UTF-8"]
content=spring cloud
---------------------------------------------------
contentType=application/json,parameterName=compment, fileName=null,size=45,headers=[Content-Disposition:"form-data; name="compment"", Content-Length:"45", Content-Type:"application/json"]
content=[{"name": "open feign"}, {"name": "gateway"}]
---------------------------------------------------
contentType=image/jpeg,parameterName=avatar, fileName=logo.png,size=19825,headers=[Content-Disposition:"form-data; name="avatar"; filename="logo.png"", Content-Length:"19825", Content-Type:"image/jpeg"]
content=[binary]
---------------------------------------------------
contentType=text/plain,parameterName=avatar, fileName=logo.png,size=19825,headers=[Content-Disposition:"form-data; name="avatar"; filename="logo.png"", Content-Length:"19825", X-Size:"400", X-width:"400", Content-Type:"text/plain"]
content=[binary]
---------------------------------------------------

NoClassDefFoundError

If the client application throws a NoClassDefFoundError exception.

1
2
3
4
5
6
7
Exception in thread "main" java.lang.NoClassDefFoundError: org/reactivestreams/Publisher
    at io.springcloud.test.MainTest.main(MainTest.java:25)
Caused by: java.lang.ClassNotFoundException: org.reactivestreams.Publisher
    at java.base/jdk.internal.loader.BuiltinClassLoader.loadClass(BuiltinClassLoader.java:641)
    at java.base/jdk.internal.loader.ClassLoaders$AppClassLoader.loadClass(ClassLoaders.java:188)
    at java.base/java.lang.ClassLoader.loadClass(ClassLoader.java:520)
    ... 1 more

Then you may need to add the following dependencies to the client.

1
2
3
4
5
<!-- https://mvnrepository.com/artifact/org.reactivestreams/reactive-streams -->
<dependency>
    <groupId>org.reactivestreams</groupId>
    <artifactId>reactive-streams</artifactId>
</dependency>