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
- 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
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
- 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
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
- Already closed
- Error while closing output
- Error writing marker flag
- Not a valid input stream!
- <toString()> : Unable to read value metadata from input…
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)