{"record":{"id":"ae657fe8d39a427f","repo":"pentaho/pentaho-kettle","slug":"already-closed","errorCode":null,"errorMessage":"Already closed","messagePattern":"Already closed","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"plugins/repo-vfs/repo-vfs-pur/src/main/java/org/pentaho/di/plugins/repofvs/pur/converter/RepoClientOutputConverter.java","lineNumber":91,"sourceCode":"    public static RepoMetaOutputStream createTransMetaOutputStream( Repository repo, RepositoryFile file ) {\n      return new RepoMetaOutputStream( repo, file, xmlIn -> new TransMeta( xmlIn, repo, false, null, null ) );\n    }\n\n\n    public static RepoMetaOutputStream createJobMetaOutputStream( Repository repo, RepositoryFile file ) {\n      return new RepoMetaOutputStream( repo, file, xmlIn -> new JobMeta( xmlIn, repo, null ) );\n    }\n\n    RepoMetaOutputStream( Repository repo, RepositoryFile file, MetaLoader metaLoader ) {\n      this.repo = repo;\n      this.file = file;\n      this.metaLoader = metaLoader;\n    }\n\n    @Override\n    public void close() throws IOException {\n      if ( closed ) {\n        throw new IOException( new IllegalStateException( \"Already closed\" ) );\n      }\n      closed = true;\n      saveToRepository();\n      super.close();\n    }\n\n    private void saveToRepository() throws IOException {\n      try ( ByteArrayInputStream bais = new ByteArrayInputStream( toByteArray() ) ) {\n        var meta = metaLoader.loadMeta( bais );\n        String filePath = file.getPath();\n        String fileName = FilenameUtils.getName( filePath );\n        String parentPath = FilenameUtils.getFullPath( filePath );\n        meta.setFilename( fileName );\n        meta.setName( FilenameUtils.getBaseName( fileName ) );\n        meta.setRepositoryDirectory( repo.findDirectory( parentPath ) );\n        repo.save( meta, null, null );\n      } catch ( KettleException e ) {\n        log.error( \"Error saving file {}\", file.getPath(), e );","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/pentaho/pentaho-kettle/blob/f3058517a153da500bf4551f46d79b91bf8ec552/plugins/repo-vfs/repo-vfs-pur/src/main/java/org/pentaho/di/plugins/repofvs/pur/converter/RepoClientOutputConverter.java#L73-L109","documentation":"RepoClientOutputConverter.close() throws IOException wrapping an IllegalStateException when close() is called on an already-closed stream. Closing twice is treated as a programming error rather than idempotent, unlike many stream implementations.","triggerScenarios":"Calling close() a second time on the same RepoClientOutputConverter; try-with-resources nesting that closes an underlying stream already closed; closing explicitly and then letting try-with-resources close again.","commonSituations":"Manual close followed by try-with-resources; defensive close in finally blocks; framework shutdown hooks closing streams that application code already closed.","solutions":["Close the stream exactly once - prefer try-with-resources so ownership is unambiguous","Track closed state in calling code with a boolean or null out the reference after closing","Catch IOException around close() and ignore if caused by IllegalStateException 'Already closed' when double-close is unavoidable"],"exampleFix":"// before\nstream.close();\nstream.close(); // throws\n// after\ntry (RepoClientOutputConverter stream = createStream( repo, metaLoader )) {\n  stream.write( data );\n} // single, automatic close","handlingStrategy":"try-catch","validationCode":"boolean isOpen( RepoClientOutputConverter s ) { return s != null && !s.isClosed(); } // track closed state at call site","typeGuard":"if ( stream != null && !alreadyClosed ) { stream.close(); alreadyClosed = true; }","tryCatchPattern":"try {\n  stream.close();\n} catch ( IOException e ) {\n  if ( !( e.getCause() instanceof IllegalStateException ) ) {\n    throw e; // swallow only 'Already closed' double-close\n  }\n}","preventionTips":["Use try-with-resources so close() runs exactly once","Never close manually and also via try-with-resources","Null out stream references after explicit close to prevent accidental reuse"],"tags":["io","stream","double-close"],"backgroundTag":"invalid-state-transition","analyzedSha":"f3058517a153da500bf4551f46d79b91bf8ec552","analyzedAt":"2026-09-13T14:04:16.340Z","contentChangedAt":"2026-09-13T14:04:16.340Z","schemaVersion":2},"datasetVersion":"2026-09-20T23:17:15.980Z"}