Комментарии 6
Это все конечно замечательно, но что мешало обновить версию зависимости, в которой dispose делается автоматически?
Да я не знал, что у меня вообще есть проблема в проде - диск тек тихо, а узнал по инциденту, а не по changelog’у POI. «Просто обновись» работает, когда заранее знаешь, что искать
Плюс апгрейд POI в живом проекте — не однострочник: поведение форматтеров/стилей, конфликты версий с другими либами. Защитный try/finally + ArchUnit + тест на tmp-файлы дают гарантию здесь и сейчас, независимо от версии.
Но большое спасибо за наводку, посмотрю свежие версии либы!
Это поведение по доке, а не баг библиотеки.
Немного странное поведение, ИМХО. Насколько я вижу в https://poi.apache.org/apidocs/dev/org/apache/poi/xssf/streaming/SXSSFWorkbook.html#close-- написано, что
Once this has been called, no further operations, updates or reads should be performed on the Workbook.
Т. е. смысла держать временные файлы нет, ведь исходный том уже протух и при его повторной обработке всё начнётся с самого начала.
Да, тут как раз и прикол, что дока сама себе противоречит. С одной стороны после close() с воркбуком работать уже нельзя. С другой - tmp-файлы спокойно остаются лежать. По-факту это баг, по доке это фича.
В общем, починил у себя через dispose() и докинул в functional-тесты прямо проверку этого контракта. Получилось четыре теста:
с dispose() после успешной записи - tmp-файлов нет
с dispose() в finally после исключения - tmp-файлов нет
без dispose() после успешной записи - tmp-файл остался
без dispose() после исключения - tmp-файл остался
Последние два и есть пруф, что без dispose() файлы реально текут. Если в будущей версии POI причешут поведение и close() сам начнёт чистить - эти два теста покраснеют, и сразу станет видно, что костыль с dispose() уже не нужен
Вод код тестов
@Test
void withoutDisposeShouldKeepPoiTempFilesAfterSuccessfulClose(@TempDir Path poiTempDir) throws IOException {
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiTempDir.toFile()));
// window=1 POI флашит каждую строку на диск, tmp-файл гарантирован
try (SXSSFWorkbook wb = new SXSSFWorkbook(1)) {
wb.setCompressTempFiles(true);
var sheet = wb.createSheet("contract-doc");
for (int i = 0; i < 100; i++) {
sheet.createRow(i).createCell(0).setCellValue("row " + i);
}
wb.write(new ByteArrayOutputStream());
} // try-with-resources вызвал close() но НЕ dispose()
assertPoiSheetFilesPresent(poiTempDir); // tmp-файл всё ещё на диске
}
@Test
void withoutDisposeShouldKeepPoiTempFilesAfterFailedWrite(@TempDir Path poiTempDir) throws IOException {
TempFile.setTempFileCreationStrategy(new DefaultTempFileCreationStrategy(poiTempDir.toFile()));
SXSSFWorkbook wb = new SXSSFWorkbook(1);
try (OutputStream failing = new OutputStream() {
@Override public void write(int b) throws IOException { throw new IOException("boom"); }
}) {
wb.setCompressTempFiles(true);
var sheet = wb.createSheet("contract-doc");
for (int i = 0; i < 100; i++) {
sheet.createRow(i).createCell(0).setCellValue("row " + i);
}
assertThatThrownBy(() -> wb.write(failing)).isInstanceOf(IOException.class);
} finally {
wb.close(); // close без dispose — симулируем «забытый» dispose
}
assertPoiSheetFilesPresent(poiTempDir);
}Спасибо за уточнение, докинул дополнительно 2 тесты, которые воспроизводят проблему тоже

Шёл за утечкой памяти, нашёл утечку диска: SXSSFWorkbook без dispose() в Apache POI