{"record":{"id":"049238a504d112d9","repo":"apache/iceberg","slug":"unclosed-output-stream-created-by-049238","errorCode":null,"errorMessage":"Unclosed output stream created by:\n\t{}","messagePattern":"Unclosed output stream created by:\n\t(.+?)","errorType":"console","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"core/src/main/java/org/apache/iceberg/hadoop/HadoopStreams.java","lineNumber":206,"sourceCode":"      // Need to check the interrupted flag to detect failed object upload\n      // and propagate the error up.\n      if (Thread.interrupted()\n          && \"org.apache.hadoop.fs.s3a.S3ABlockOutputStream\"\n              .equals(stream.getWrappedStream().getClass().getName())) {\n        throw new IOException(\n            \"S3ABlockOutputStream failed to upload object after stream was closed\");\n      }\n    }\n\n    @SuppressWarnings({\"checkstyle:NoFinalizer\", \"Finalize\", \"deprecation\"})\n    @Override\n    protected void finalize() throws Throwable {\n      super.finalize();\n      if (!closed) {\n        close(); // releasing resources is more important than printing the warning\n        String trace =\n            Joiner.on(\"\\n\\t\").join(Arrays.copyOfRange(createStack, 1, createStack.length));\n        LOG.warn(\"Unclosed output stream created by:\\n\\t{}\", trace);\n      }\n    }\n  }\n\n  private static class WrappedSeekableInputStream extends FSInputStream\n      implements DelegatingInputStream {\n    private final SeekableInputStream inputStream;\n\n    private WrappedSeekableInputStream(SeekableInputStream inputStream) {\n      this.inputStream = inputStream;\n    }\n\n    @Override\n    public void seek(long pos) throws IOException {\n      inputStream.seek(pos);\n    }\n\n    @Override","sourceCodeStart":188,"sourceCodeEnd":224,"githubUrl":"https://github.com/apache/iceberg/blob/86d9c8fc543e7c56c9f624eb725f76c9baff9570/core/src/main/java/org/apache/iceberg/hadoop/HadoopStreams.java#L188-L224","documentation":"HadoopStreams wraps Hadoop FSDataOutputStream as Iceberg PositionOutputStream. If the stream is finalized without being closed, finalize() closes it and logs a WARN with the creation-time stack trace, signaling a leaked output handle (possibly also incomplete/abandoned writes).","triggerScenarios":"Code obtains table.io().newOutputFile(...).create(...) and never calls close() — e.g. writer objects not closed on exception paths, or output streams stored and abandoned after task failure.","commonSituations":"Custom file-writing code without try-with-resources; exceptions between create and close; task cancellation leaving writers open; leaked metadata/data writers on commit failure paths.","solutions":["Close output streams in try-with-resources or finally blocks.","Use Iceberg writers (FileAppender/DataWriter) which manage close semantics and call close() on completion or in finally.","Follow the creation stack trace in the WARN to locate the leaking open call.","On failed writes, delete the orphan output file after closing."],"exampleFix":"// before\nPositionOutputStream out = output.create();\nout.write(data);\n// missing close on exception path\n// after\ntry (PositionOutputStream out = output.create()) {\n  out.write(data);\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try (PositionOutputStream out = outputFile.create()) {\n  out.write(bytes);\n} // closed even on exception; orphan cleanup on failure if needed","preventionTips":["Use try-with-resources for every output stream/writer.","Close Iceberg writers (DataWriter/FileAppender) in finally or via try-with-resources.","Delete orphan output files when a write fails after creation.","Trace any 'Unclosed output stream' WARN via its creation stack."],"tags":["resource-leak","stream","hadoop"],"backgroundTag":"resource-leak","analyzedSha":"86d9c8fc543e7c56c9f624eb725f76c9baff9570","analyzedAt":"2026-09-12T00:46:39.097Z","contentChangedAt":"2026-09-12T00:46:39.097Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}