Every admin console ends up with a file upload — Excel imports, avatar pictures, PDF attachments. Implementing multipart requests by hand in OkHttp or RestTemplate means building MultipartBody pieces, computing Content-Type boundaries, and leaking HTTP plumbing into service code. In JQuickCurl you simply keep curl's -F file=@/path/to/file syntax: the upload command is the curl command.
We'll cover:
- Single-file upload.
- Multiple files in one request.
- Mixing files with plain form fields.
- Dynamic file paths through variables.
- A runnable example against httpbin's echo endpoint.
The curl Syntax Refresher
# one file
curl -X POST https://api.example.com/upload -F 'file=@./photo.png'
# two files under the SAME field name
curl -X POST https://api.example.com/upload \
-F 'files=@./a.pdf' -F 'files=@./b.pdf'
# a file plus regular form fields
curl -X POST https://api.example.com/upload \
-F 'userId=123' \
-F 'note=hello' \
-F 'file=@./photo.png'
The magic marker is @: key=@path means this value is a file on disk; key=value without @ is a plain text field. JQuickCurl's parser understands exactly this convention and builds the corresponding multipart body over OkHttp.
Example 1: Single-File Upload
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
public interface UploadApi {
@JCurlCommand("curl -X POST 'https://httpbin.org/post' "
+ "-F 'avatar=@./profile.png'")
String uploadAvatar(JQuickCurlReq request);
}
JQuickCurlReq req = new JQuickCurlReq();
String result = JCurlInvoker.createProxy(UploadApi.class).uploadAvatar(req);
System.out.println(result); // httpbin echoes back the "files" section
httpbin's /post returns "files": {"avatar": "<binary content>"}, which proves the multipart body really was transmitted.
Example 2: Multiple Files Under One Field
Uploading several files at once simply repeats -F with the same key:
public interface UploadApi {
@JCurlCommand("curl -X POST 'https://httpbin.org/post' "
+ "-F 'documents=@./report-q1.pdf' "
+ "-F 'documents=@./report-q2.pdf'")
String uploadQuarterlyReports(JQuickCurlReq request);
}
The same pattern works for a set of related files such as images or logs. For truly arbitrary numbers of files, pair the -F command with a small loop that builds several commands or pick a fixed upper bound per call.
Example 3: File + Ordinary Form Fields (The Importer)
Real import endpoints rarely want a bare file — they want metadata alongside it. Combine fields and files freely:
public interface UploadApi {
@JCurlCommand("curl -X POST 'https://httpbin.org/post' "
+ "-F 'uploader=${uploader}' "
+ "-F 'description=${description}' "
+ "-F 'spreadsheet=@./products.xlsx'")
String importSpreadsheet(JQuickCurlReq request);
}
JQuickCurlReq req = new JQuickCurlReq();
req.put("uploader", "jane.doe");
req.put("description", "monthly product refresh");
req.put("spreadsheet", "./products.xlsx");
String out = api.importSpreadsheet(req);
The server receives form fields uploader and description plus file spreadsheet — everything curl's multipart encoding supports.
Example 4: Dynamic File Paths
Hard-coding a path in the annotation works, but real applications pick the file at runtime. Because -F values participate in variable substitution, the path can come from JQuickCurlReq:
@JCurlCommand("curl -X POST 'https://httpbin.org/post' "
+ "-F 'file=@${filePath}'")
String uploadFromPath(JQuickCurlReq request);
JQuickCurlReq req = new JQuickCurlReq();
req.put("filePath", "downloads/today/report.xlsx");
api.uploadFromPath(req);
Path tips: use forward slashes for portability; on Windows, backslashes inside a Java annotation string must be escaped (
\\\\) or prefer/. Relative paths resolve against the process's working directory.
Complete Runnable Demo
import com.github.paohaijiao.anno.JCurlCommand;
import com.github.paohaijiao.domain.req.JQuickCurlReq;
import com.github.paohaijiao.executor.JCurlInvoker;
public interface MultiUploadApi {
@JCurlCommand("curl -X POST 'https://httpbin.org/post' "
+ "-F 'owner=${owner}' "
+ "-F 'primary=@./pom.xml' "
+ "-F 'secondary=@./README.md'")
String uploadPair(JQuickCurlReq request);
}
class UploadDemo {
public static void main(String[] args) {
MultiUploadApi api = JCurlInvoker.createProxy(MultiUploadApi.class);
JQuickCurlReq req = new JQuickCurlReq();
req.put("owner", "team-core");
String result = api.uploadPair(req);
System.out.println(result);
// Look for "form": { "owner": "team-core" } and
// "files": { "primary": ..., "secondary": ... } in the echo.
}
}
Run it from the project root so pom.xml and README.md exist — the demo then needs no extra setup.
When Multipart Is the Wrong Tool
- Large files / resumable uploads: multipart isn't streaming-friendly for gigabytes; consider chunked or presigned-URL uploads.
-
Tiny key/value pairs: plain JSON or
--data-urlencodeis lighter than multipart. - Files already on a server: pass a URL and let the backend fetch it instead.
Summary
JQuickCurl lets your team keep using the file-upload syntax they already know from curl and Postman. -F 'key=@path' for files, -F 'key=value' for fields, repeated -F options for multi-file uploads, and ${path} variables for runtime-selected files. The multipart boundary choreography stays inside the library.
That covers sending bytes up; Post 10 flips the direction and downloads remote files straight to local disk with curl's --output flag.
Top comments (0)