A good approach to do multipart file upload in Android
Categories:
Robust Multipart File Uploads in Android: A Comprehensive Guide

Learn how to implement reliable multipart file uploads in Android using HttpURLConnection, handling large files, progress updates, and error scenarios.
Uploading files from an Android device to a server is a common requirement for many applications. While various libraries simplify this task, understanding the underlying mechanics of multipart/form-data requests is crucial for robust implementation, especially when dealing with large files, network fluctuations, and progress tracking. This article will guide you through building a reliable multipart file upload mechanism using standard Java APIs, focusing on HttpURLConnection
and best practices for Android development.
Understanding Multipart/form-data
Multipart/form-data is an HTTP content type that allows clients to send multiple types of data (e.g., text fields, binary files) in a single request. Each part of the data is separated by a unique boundary string. This format is essential for file uploads as it allows the server to distinguish between different form fields and the actual file content.
flowchart TD A[Android App] --> B{HTTP POST Request} B --"Content-Type: multipart/form-data"--> C[Server] C --"Parses Boundary"--> D[Extracts Form Fields] C --"Extracts File Data"--> E[Saves File] D & E --> F[Server Response] F --> A
Multipart File Upload Process Flow
Implementing the Upload Logic
The core of the upload process involves constructing the HTTP request with the correct headers and body. We'll use HttpURLConnection
for this, as it's a lightweight and efficient option built into the Android SDK. The process involves setting up the connection, writing the multipart data (text fields and file content) to the output stream, and then reading the server's response.
import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Map;
public class MultipartUploader {
private static final String LINE_FEED = "\r\n";
private HttpURLConnection httpConn;
private String charset;
private DataOutputStream outputStream;
private String boundary;
public MultipartUploader(String requestURL, String charset) throws Exception {
this.charset = charset;
boundary = "----" + System.currentTimeMillis();
URL url = new URL(requestURL);
httpConn = (HttpURLConnection) url.openConnection();
httpConn.setUseCaches(false);
httpConn.setDoOutput(true); // indicates POST method
httpConn.setDoInput(true);
httpConn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + boundary);
httpConn.setRequestProperty("User-Agent", "AndroidMultipartUploader");
outputStream = new DataOutputStream(httpConn.getOutputStream());
}
public void addFormField(String name, String value) throws Exception {
outputStream.writeBytes("--" + boundary + LINE_FEED);
outputStream.writeBytes("Content-Disposition: form-data; name=\"" + name + "\"" + LINE_FEED);
outputStream.writeBytes("Content-Type: text/plain; charset=" + charset + LINE_FEED);
outputStream.writeBytes(LINE_FEED);
outputStream.writeBytes(value + LINE_FEED);
outputStream.flush();
}
public void addFilePart(String fieldName, File uploadFile) throws Exception {
String fileName = uploadFile.getName();
outputStream.writeBytes("--" + boundary + LINE_FEED);
outputStream.writeBytes("Content-Disposition: form-data; name=\"" + fieldName + "\"; filename=\"" + fileName + "\"" + LINE_FEED);
outputStream.writeBytes("Content-Type: " + HttpURLConnection.guessContentTypeFromName(fileName) + LINE_FEED);
outputStream.writeBytes("Content-Transfer-Encoding: binary" + LINE_FEED);
outputStream.writeBytes(LINE_FEED);
outputStream.flush();
FileInputStream inputStream = new FileInputStream(uploadFile);
byte[] buffer = new byte[4096];
int bytesRead = -1;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
outputStream.flush();
inputStream.close();
outputStream.writeBytes(LINE_FEED);
}
public String finish() throws Exception {
outputStream.writeBytes(LINE_FEED);
outputStream.writeBytes("--" + boundary + "--" + LINE_FEED);
outputStream.flush();
outputStream.close();
int status = httpConn.getResponseCode();
if (status == HttpURLConnection.HTTP_OK) {
BufferedReader reader = new BufferedReader(new InputStreamReader(httpConn.getInputStream()));
StringBuilder response = new StringBuilder();
String line;
while ((line = reader.readLine()) != null) {
response.append(line);
}
reader.close();
httpConn.disconnect();
return response.toString();
} else {
throw new Exception("Server returned non-OK status: " + status);
}
}
}
Basic MultipartUploader class using HttpURLConnection
addFilePart
method. You can update a UI element (like a ProgressBar
) by calculating the percentage of bytes written and dispatching updates to the main thread.Integrating with Android and Asynchronous Execution
Network operations, including file uploads, must be performed on a background thread to prevent blocking the UI and causing Application Not Responding (ANR) errors. Android provides several mechanisms for this, such as AsyncTask
(deprecated but still seen), ExecutorService
, or Kotlin Coroutines. For simplicity, we'll illustrate with a basic Thread
and Handler
for UI updates.
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
public class UploadManager {
private static final String UPLOAD_URL = "http://your-server.com/upload"; // Replace with your server URL
private Handler mainHandler = new Handler(Looper.getMainLooper());
public interface UploadCallback {
void onUploadSuccess(String response);
void onUploadFailure(String error);
void onUploadProgress(int progress);
}
public void uploadFile(final File fileToUpload, final Map<String, String> formFields, final UploadCallback callback) {
new Thread(new Runnable() {
@Override
public void run() {
try {
MultipartUploader uploader = new MultipartUploader(UPLOAD_URL, "UTF-8");
for (Map.Entry<String, String> entry : formFields.entrySet()) {
uploader.addFormField(entry.getKey(), entry.getValue());
}
uploader.addFilePart("file", fileToUpload);
final String response = uploader.finish();
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onUploadSuccess(response);
}
});
} catch (final Exception e) {
mainHandler.post(new Runnable() {
@Override
public void run() {
callback.onUploadFailure(e.getMessage());
}
});
}
}
}).start();
}
// Example usage in an Activity/Fragment
public void initiateUpload(File selectedFile) {
Map<String, String> fields = new HashMap<>();
fields.put("description", "My awesome photo");
fields.put("userId", "123");
uploadFile(selectedFile, fields, new UploadCallback() {
@Override
public void onUploadSuccess(String response) {
Toast.makeText(null, "Upload successful: " + response, Toast.LENGTH_LONG).show();
}
@Override
public void onUploadFailure(String error) {
Toast.makeText(null, "Upload failed: " + error, Toast.LENGTH_LONG).show();
}
@Override
public void onUploadProgress(int progress) {
// Update UI progress bar here
}
});
}
}
Android integration with background thread and UI updates
android.permission.INTERNET
permission to your AndroidManifest.xml
file. For accessing files, you might also need READ_EXTERNAL_STORAGE
and WRITE_EXTERNAL_STORAGE
(for older Android versions) or use the Storage Access Framework for modern Android versions.Error Handling and Best Practices
Robust error handling is critical for any network operation. This includes catching IOException
for network issues, handling non-200 HTTP status codes from the server, and providing meaningful feedback to the user. Additionally, consider these best practices:
- Retry Mechanism: Implement a retry logic with exponential backoff for transient network errors.
- Cancellation: Provide a way for users to cancel ongoing uploads.
- Background Services: For long-running uploads, especially when the app might be in the background, consider using a
Foreground Service
to ensure the upload completes and to display a persistent notification. - Security: Always use HTTPS for all network communications to protect sensitive data during transfer.
- File Paths: Be careful with file paths. On Android, use
Context.getFilesDir()
orContext.getExternalFilesDir()
for app-specific storage, or the Storage Access Framework for user-selected files.
sequenceDiagram participant App as Android App participant Uploader as MultipartUploader participant Server as Remote Server App->>Uploader: init(URL, charset) loop Add Form Fields App->>Uploader: addFormField(name, value) end App->>Uploader: addFilePart(fieldName, file) Uploader->>Server: HTTP POST Request (multipart/form-data) Server-->>Uploader: HTTP Response (Status Code) alt Success (200 OK) Uploader->>Server: Read Response Body Server-->>Uploader: Response Data Uploader->>App: onUploadSuccess(response) else Failure (Non-200 Status or Exception) Uploader->>App: onUploadFailure(error) end
Sequence Diagram of Multipart File Upload