pentaho/pentaho-kettle · error · IOException

Already closed

Error message

Already closed

What it means

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.

Solutions

  1. Close the stream exactly once - prefer try-with-resources so ownership is unambiguous
  2. Track closed state in calling code with a boolean or null out the reference after closing
  3. Catch IOException around close() and ignore if caused by IllegalStateException 'Already closed' when double-close is unavoidable

Example fix

// before
stream.close();
stream.close(); // throws
// after
try (RepoClientOutputConverter stream = createStream( repo, metaLoader )) {
  stream.write( data );
} // single, automatic close
Defensive patterns

Strategy: try-catch

Validate before calling

boolean isOpen( RepoClientOutputConverter s ) { return s != null && !s.isClosed(); } // track closed state at call site

Type guard

if ( stream != null && !alreadyClosed ) { stream.close(); alreadyClosed = true; }

Try / catch

try {
  stream.close();
} catch ( IOException e ) {
  if ( !( e.getCause() instanceof IllegalStateException ) ) {
    throw e; // swallow only 'Already closed' double-close
  }
}

Prevention

When it happens

Trigger: 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.

Common situations: Manual close followed by try-with-resources; defensive close in finally blocks; framework shutdown hooks closing streams that application code already closed.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of pentaho/pentaho-kettle@f3058517a1 (2026-09-13). Data as JSON: /api/errors/ae657fe8d39a427f. Report an issue: GitHub.

Appendix: source

Thrown at plugins/repo-vfs/repo-vfs-pur/src/main/java/org/pentaho/di/plugins/repofvs/pur/converter/RepoClientOutputConverter.java:91

    public static RepoMetaOutputStream createTransMetaOutputStream( Repository repo, RepositoryFile file ) {
      return new RepoMetaOutputStream( repo, file, xmlIn -> new TransMeta( xmlIn, repo, false, null, null ) );
    }


    public static RepoMetaOutputStream createJobMetaOutputStream( Repository repo, RepositoryFile file ) {
      return new RepoMetaOutputStream( repo, file, xmlIn -> new JobMeta( xmlIn, repo, null ) );
    }

    RepoMetaOutputStream( Repository repo, RepositoryFile file, MetaLoader metaLoader ) {
      this.repo = repo;
      this.file = file;
      this.metaLoader = metaLoader;
    }

    @Override
    public void close() throws IOException {
      if ( closed ) {
        throw new IOException( new IllegalStateException( "Already closed" ) );
      }
      closed = true;
      saveToRepository();
      super.close();
    }

    private void saveToRepository() throws IOException {
      try ( ByteArrayInputStream bais = new ByteArrayInputStream( toByteArray() ) ) {
        var meta = metaLoader.loadMeta( bais );
        String filePath = file.getPath();
        String fileName = FilenameUtils.getName( filePath );
        String parentPath = FilenameUtils.getFullPath( filePath );
        meta.setFilename( fileName );
        meta.setName( FilenameUtils.getBaseName( fileName ) );
        meta.setRepositoryDirectory( repo.findDirectory( parentPath ) );
        repo.save( meta, null, null );
      } catch ( KettleException e ) {
        log.error( "Error saving file {}", file.getPath(), e );

View on GitHub (pinned to f3058517a1)