I want to share this familiar but cryptic error I experienced when opening certain PDFs which were streamed from a Java web application.
We were a bit puzzled by the error because although those PDF files appeared broken in Adobe Reader, we were able to open them just fine in Chrome PDF viewer. Furthermore, we only got the error for certain PDF images. Adobe Reader was able to render other images that we streamed from the same app just fine.
It turned out the error was because in the java application, we were dumping the content to the output stream all at once, without including the ‘Content-Length’ header. Below snippets show the simplified part of the codes before applying the fix.
OutputStream out = null; response.setContentType("application/pdf"); try { out = response.getOutputStream(); out.write(documentBean.getDocumentContentBytes()); } finally { if (out != null) { out.flush(); out.close(); } }
As you can see, the above codes grab the HTTP output stream and write all the bytes at once. However, it does not include the ‘Content-Length’ header.
So, don’t forget the ‘Content-Length’ header when returning PDF content to the browser.
OutputStream out = null; response.setContentType("application/pdf"); response.setContentLength(documentBean.getDocumentContentBytes().length); try { out = response.getOutputStream(); out.write(documentBean.getDocumentContentBytes()); } finally { if (out != null) { out.flush(); out.close(); } }
Another way to avoid corrupting the PDF files is to use a buffer. In our case, we made changes to use both the buffer and also include the header, since we already know the content length from the bytes.
response.setContentType(documentBean.getMimeType()); response.setContentLength(documentBean.getDocumentContentBytes().length); try (InputStream docImage = new BufferedInputStream(new ByteArrayInputStream(documentBean.getDocumentContentBytes())); OutputStream outStream = response.getOutputStream()) { byte[] readingBuffer = new byte[100 * 1024]; int read = 0; while ((read = docImage.read(readingBuffer, 0, readingBuffer.length)) != -1) { outStream.write(readingBuffer, 0, read); outStream.flush(); } } catch (IOException ex) { logger.error("An error occurred while writing document image to stream.", ex); }
That’s it for this post.
Easily test sending and receiving email locally using MailHog
Migrating from Oracle to Azure SQL caveat – java.sql.Date does not represent time.
Notes on the three programming paradigms
Encryption in Java with JCA and Bouncy Castle API.
Building backend API with Spring data, Hibernate, JPA.
Neo4j slow query caused SSLException: SSL peer shut down incorrectly