Tomcat9+Java8环境下Multipart上传报MalformedStreamException求助
Let's break down the possible causes of your MalformedStreamException: Stream ended unexpectedly issue and walk through actionable fixes, since you've already tried several common troubleshooting steps.
1. Volley's SimpleMultiPartRequest has boundary handling flaws
Many third-party SimpleMultiPartRequest implementations fail to properly format multipart requests—often missing the final closing boundary or truncating file data during transmission. This leaves the backend expecting more data than it receives, triggering the stream error.
Fix: Replace your existing request with a custom, correctly implemented MultiPartRequest that properly manages boundaries and file streams:
public class CustomMultiPartRequest extends Request<String> { private final Response.Listener<String> mListener; private final File mFile; private final String mPictureId; private final String mBoundary = "apiclient-" + System.currentTimeMillis(); public CustomMultiPartRequest(String url, File file, String pictureId, Response.Listener<String> listener, Response.ErrorListener errorListener) { super(Method.POST, url, errorListener); mListener = listener; mFile = file; mPictureId = pictureId; } @Override public String getBodyContentType() { return "multipart/form-data; boundary=" + mBoundary; } @Override public byte[] getBody() throws AuthFailureError { ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); try { // Add pictureId parameter dos.writeBytes("--" + mBoundary + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"pictureId\"\r\n\r\n"); dos.writeBytes(mPictureId + "\r\n"); // Add file part (adjust Content-Type to match your image type) dos.writeBytes("--" + mBoundary + "\r\n"); dos.writeBytes("Content-Disposition: form-data; name=\"file\"; filename=\"" + mFile.getName() + "\"\r\n"); dos.writeBytes("Content-Type: image/jpeg\r\n\r\n"); FileInputStream fis = new FileInputStream(mFile); byte[] buffer = new byte[1024]; int bytesRead; while ((bytesRead = fis.read(buffer)) != -1) { dos.write(buffer, 0, bytesRead); } fis.close(); // Add closing boundary (critical for proper parsing) dos.writeBytes("\r\n--" + mBoundary + "--\r\n"); dos.flush(); return bos.toByteArray(); } catch (IOException e) { e.printStackTrace(); return null; } finally { try { dos.close(); bos.close(); } catch (IOException e) { e.printStackTrace(); } } } @Override protected Response<String> parseNetworkResponse(NetworkResponse response) { String responseString = ""; if (response.data != null) { try { responseString = new String(response.data, HttpHeaderParser.parseCharset(response.headers)); } catch (UnsupportedEncodingException e) { responseString = new String(response.data); } } return Response.success(responseString, HttpHeaderParser.parseCacheHeaders(response)); } @Override protected void deliverResponse(String response) { mListener.onResponse(response); } }
Use this custom request in your Android code:
File uploadFile = new File(submissionFile.fileLocation); CustomMultiPartRequest multiPartRequest = new CustomMultiPartRequest( Contents.API_SUBMIT_PICTURE, uploadFile, submissionFile.pictureId, response -> Log.d("logEntry", "API_SUBMIT_PICTURE: " + response), error -> { Log.e("logEntry", "API_SUBMIT_PICTURE: onErrorResponse", error); // Print full stack trace submission.failedCount++; submission.errorDetail = "API_SUBMIT_PICTURE: onErrorResponse"; } ); volleyHelper.add(multiPartRequest);
2. Tomcat's maxPostSize is truncating large requests
Tomcat 9 defaults to a maxPostSize of 2MB. Any request larger than this gets truncated, leading to an incomplete stream for the backend to parse.
Fix: Update your Tomcat/conf/server.xml to remove the POST size limit:
<Connector port="8080" protocol="HTTP/1.1" connectionTimeout="20000" redirectPort="8443" maxPostSize="-1"/> <!-- -1 means no limit -->
3. Spring's CommonsMultipartResolver is missing encoding configuration
Without a default encoding set, the resolver may struggle to parse multipart data correctly, especially if your parameters contain non-ASCII characters.
Fix: Add the defaultEncoding property to your dispatcher-servlet.xml configuration:
<bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <!-- max upload size in bytes --> <property name="maxUploadSize" value="20971520" /> <!-- 20MB --> <!-- max size of file in memory (in bytes) --> <property name="maxInMemorySize" value="1048576" /> <!-- 1MB --> <!-- Add default encoding --> <property name="defaultEncoding" value="UTF-8"/> </bean>
4. Backend request header restriction is too strict
Your headers=("content-type=multipart/*") constraint may not match the full multipart/form-data; boundary=xxx header sent by Volley, leading to unexpected parsing behavior.
Fix: Either remove the header restriction entirely, or make it more precise:
@RequestMapping(value = "/fileupload", headers = "Content-Type=multipart/form-data", method = RequestMethod.POST)
5. Android-side file read errors
If the file you're uploading is missing, corrupted, or unreadable, Volley will send an incomplete stream to the server.
Fix: Validate the file before adding it to the request:
File uploadFile = new File(submissionFile.fileLocation); if (!uploadFile.exists() || uploadFile.length() == 0) { Log.e("logEntry", "Upload file is missing or empty: " + submissionFile.fileLocation); // Handle the error appropriately (e.g., notify user) return; }
Bonus: Switch to Spring's StandardServletMultipartResolver
If CommonsMultipartResolver continues to cause issues, try Spring's native resolver (requires Servlet 3.0+, which Tomcat 9 supports):
- Update
dispatcher-servlet.xml:
<bean id="multipartResolver" class="org.springframework.web.multipart.support.StandardServletMultipartResolver"> </bean>
- Add multipart config to your
web.xmlDispatcherServlet:
<servlet> <servlet-name>dispatcher</servlet-name> <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class> <init-param> <param-name>contextConfigLocation</param-name> <param-value>/WEB-INF/dispatcher-servlet.xml</param-value> </init-param> <multipart-config> <max-file-size>20971520</max-file-size> <!-- 20MB --> <max-request-size>20971520</max-request-size> <!-- 20MB --> <file-size-threshold>1048576</file-size-threshold> <!-- 1MB --> </multipart-config> <load-on-startup>1</load-on-startup> </servlet>
内容的提问来源于stack exchange,提问作者JavaSheriff

