I build a very large POI workbook, on my web server. Holding the entire workbook in memory , will not scale for multiple concurrent requests. Is there a way i can progressively write the workbook to the servlet output stream. That should reduce the response time , as well as make the process memory efficient.
If you are about to generate Excel 2007 (xslx) then you could adapt the approach of BigGridDemo.java as described here: http://web.archive.org/web/20110821054135/http://www.realdevelopers.com/blog/code/excel
The solution is to let POI generate a container xslx as a template only and stream the actual spreadsheet data as XML into a zip output stream. Streamlining the XML generation is then up to you.
The situation has improved considerably since the rest of the answers were written - Streaming is now part of Apache Poi.
See the SXSSFWorkbook class, and the documentation here. It uses a streaming window over the sheet, flushing old rows outside the window to temporary files.
This is based on the BigGridDemo
approach used in hlg's answer, but now part of the official distribution.
Here's the example from the documentation:
public static void main(String[] args) throws Throwable {
// keep 100 rows in memory, exceeding rows will be flushed to disk
SXSSFWorkbook wb = new SXSSFWorkbook(100);
Sheet sh = wb.createSheet();
for(int rownum = 0; rownum < 1000; rownum++){
Row row = sh.createRow(rownum);
for(int cellnum = 0; cellnum < 10; cellnum++){
Cell cell = row.createCell(cellnum);
String address = new CellReference(cell).formatAsString();
cell.setCellValue(address);
}
}
// Rows with rownum < 900 are flushed and not accessible
for(int rownum = 0; rownum < 900; rownum++){
Assert.assertNull(sh.getRow(rownum));
}
// ther last 100 rows are still in memory
for(int rownum = 900; rownum < 1000; rownum++){
Assert.assertNotNull(sh.getRow(rownum));
}
FileOutputStream out = new FileOutputStream("/temp/sxssf.xlsx");
wb.write(out);
out.close();
// dispose of temporary files backing this workbook on disk
wb.dispose();
}
SXSSFWorkbook
flushes temporary data to disk behind the scenes, so this example does not keep the whole workbook in memory. –
Letterpress SXSSFWorkbook
. For more information, see the class documentation. –
Letterpress BigGridDemo
example, which became the SXSSFWorkbook
class. The workbook class is pretty general (it supports limited random access), and still allows streaming a POI document without keeping the whole thing in memory. If your use case is more specific, a custom class like yours could absolutely guarantee better performance. –
Letterpress Unfortunately, that's impossible when there's no means of sequential data. I'd suggest to look for another format, e.g. CSV or XML. Both can be written out sequentially. If it's coming from a DB, it can even be done more efficient since a decent DB has builtin facilities to efficiently export to those formats. You just have to stream the bytes from one to other side.
Did you tried with the write method direct to the HttpServletResponse.getOutputStream()?
Please take a look at the following example:
HSSFWorkbook wb = new HSSFWorkbook();
HSSFSheet sheet = wb.createSheet("new sheet");
...
OutputStream out = response.getOutputStream();
wb.write(out);
out.close();
If you use JExcel It has sample code to read stream code to and from a Servlet. http://jexcelapi.sourceforge.net/resources/faq/
The only downside to this API looks like it only supports up to Excel 2003 inclusive.
Using POI - Can you not create the file and serve the file's bytes to the servlet output stream ?
Since version 5.0.0 there is a DeferredSXSSFWorkBook class available that uses a generator function to process rows. This way you can stream directly to your HTTP Response. I took the code below from the DeferredGeneration.java example. The API is considered experimental. It works fine in my use case.
// ExcelWriter.java
public class ExcelWriter {
public static void write(OutputStream out) throws IOException {
try (DeferredSXSSFWorkbook wb = new DeferredSXSSFWorkbook()) {
DeferredSXSSFSheet sheet1 = wb.createSheet("new sheet");
// cell styles should be created outside the row generator function
CellStyle cellStyle = wb.createCellStyle();
cellStyle.setAlignment(HorizontalAlignment.CENTER);
sheet1.setRowGenerator((ssxSheet) -> {
for (int i = 0; i < 10; i++) {
Row row = ssxSheet.createRow(i);
Cell cell = row.createCell(1);
cell.setCellStyle(cellStyle);
cell.setCellValue("value " + i);
}
});
// wb.write(out);
// writeAvoidingTempFiles was added as an experimental change in POI 5.1.0
wb.writeAvoidingTempFiles(out);
// the dispose call is necessary to ensure temp files are removed
// supposedly writeAvoidingTempFiles doesn't guarantee that temp files are NOT created
wb.dispose();
}
}
}
This is how I would implement it in Quarkus 3.8.3 w/ RestEasy Reactive. Note, it is still a blocking call.
// SomeResource.java
@GET
@Path("my-endpoint/xlsx")
@Produces(MediaType.APPLICATION_OCTET_STREAM)
@ResponseHeader(name="content-disposition", value="attachment; filename=file.xlsx")
public StreamingOutput getFile() {
return new StreamingOutput() {
@Override
public void write(OutputStream out) throws IOException, WebApplicationException {
ExcelWriter.write(out);
}
};
}
© 2022 - 2025 — McMap. All rights reserved.