Database.save(...) closes the OutputStream passed to it, and load(...) closes the InputStream. A method shouldn't close a stream it didn't open. The caller opened it and should close it, usually with try-with-resources. As things stand:
- Writing XML to
System.out closes standard output, so anything printed afterwards is silently lost. Callers have to wrap it, e.g. new PrintStream(System.out) { public void close() {} }.
- A caller can't write anything after the database, such as a checksum or a second document, or keep using a socket or a zip entry.
- With try-with-resources, the stream gets closed twice. That's usually harmless, but it's misleading.
Where streams are closed
- XML save (
StreamFormat.None): KdbxSerializableDatabase.save closes its OutputStreamWriter, which closes the caller's stream. Then StreamFormat.None.save calls flush() on the closed stream and closes it again.
- XML load:
KdbxSerializableDatabase.load calls mapper.readValue(inputStream, ...), and Jackson closes the source by default (AUTO_CLOSE_SOURCE). Then StreamFormat.None.load closes it again.
- KDBX save and load:
KdbxStreamFormat closes the encrypting or decrypting chain, and closing the chain closes the caller's stream underneath it.
- KDB load:
KdbSerializer.createKdbDatabase closes its DigestInputStream, which closes the decrypting stream and then the caller's stream.
BasicDatabaseSerializer.Xml: Jackson's writeValue and readValue close the stream by default (AUTO_CLOSE_TARGET, AUTO_CLOSE_SOURCE).
Proposal (3.1.0)
Existing code may rely on the current behaviour, for example code that passes new FileOutputStream(...) without closing it. So save and load keep their behaviour but are deprecated, and new write and read methods do the work:
write(...), with the same signatures as save(...): writes, flushes and leaves the stream open.
read(...), with the same signatures as load(...): reads and leaves the stream open.
save is write followed by close(), and load is read followed by close(), both @Deprecated, pointing to the new methods.
How the internals stop closing the caller's stream:
- XML: flush instead of close. After
writeEndDocument(), flush the XMLStreamWriter and the OutputStreamWriter and don't close them; an OutputStreamWriter holds only a buffer, not system resources, so that's safe. Turn off Jackson's AUTO_CLOSE_SOURCE and AUTO_CLOSE_TARGET, and drop the close() calls in StreamFormat.None.
- KDBX and KDB: flushing isn't enough. These layers only finish the file when they are closed:
GZIPOutputStream writes its trailer, CipherOutputStream writes the final encrypted block, and HmacBlockOutputStream and HashedBlockOutputStream write the final empty block. Each also closes what's underneath it, and two of them are JDK classes. So the chain is still closed as now, but the caller's stream sits under a small shield at the bottom of it. The shield's close() only flushes (on output) or does nothing (on input), so the formats finish properly and the caller's stream stays open.
read leaves the stream open, but buffering layers such as the decryptor and gzip may read past the end of the database, so its position afterwards isn't defined. The javadoc should say so.
The Nx methods
saveNx and loadNx were added in March 2025 (b41301d) for the shared test code. DatabaseTestBase takes the database's load and save as functional interfaces, which can't throw checked exceptions, so the tests use KdbxDatabase::loadNx, KdbDatabase::loadNx and Database::saveNx. They're public API all the same: saveNx is on the Database interface and in AbstractDatabase, loadNx is in KdbxDatabase and KdbDatabase, and BasicDatabaseSerializer has both.
Since they exist only for the tests, deprecate all of them (no writeNx or readNx), and have the tests wrap the checked exception themselves, e.g. with a small helper in the test module.
Also
Update the examples, the readme and the tests to open streams with try-with-resources and call write and read.
2.x won't change.
Database.save(...)closes theOutputStreampassed to it, andload(...)closes theInputStream. A method shouldn't close a stream it didn't open. The caller opened it and should close it, usually with try-with-resources. As things stand:System.outcloses standard output, so anything printed afterwards is silently lost. Callers have to wrap it, e.g.new PrintStream(System.out) { public void close() {} }.Where streams are closed
StreamFormat.None):KdbxSerializableDatabase.savecloses itsOutputStreamWriter, which closes the caller's stream. ThenStreamFormat.None.savecallsflush()on the closed stream and closes it again.KdbxSerializableDatabase.loadcallsmapper.readValue(inputStream, ...), and Jackson closes the source by default (AUTO_CLOSE_SOURCE). ThenStreamFormat.None.loadcloses it again.KdbxStreamFormatcloses the encrypting or decrypting chain, and closing the chain closes the caller's stream underneath it.KdbSerializer.createKdbDatabasecloses itsDigestInputStream, which closes the decrypting stream and then the caller's stream.BasicDatabaseSerializer.Xml: Jackson'swriteValueandreadValueclose the stream by default (AUTO_CLOSE_TARGET,AUTO_CLOSE_SOURCE).Proposal (3.1.0)
Existing code may rely on the current behaviour, for example code that passes
new FileOutputStream(...)without closing it. Sosaveandloadkeep their behaviour but are deprecated, and newwriteandreadmethods do the work:write(...), with the same signatures assave(...): writes, flushes and leaves the stream open.read(...), with the same signatures asload(...): reads and leaves the stream open.saveiswritefollowed byclose(), andloadisreadfollowed byclose(), both@Deprecated, pointing to the new methods.How the internals stop closing the caller's stream:
writeEndDocument(), flush theXMLStreamWriterand theOutputStreamWriterand don't close them; anOutputStreamWriterholds only a buffer, not system resources, so that's safe. Turn off Jackson'sAUTO_CLOSE_SOURCEandAUTO_CLOSE_TARGET, and drop theclose()calls inStreamFormat.None.GZIPOutputStreamwrites its trailer,CipherOutputStreamwrites the final encrypted block, andHmacBlockOutputStreamandHashedBlockOutputStreamwrite the final empty block. Each also closes what's underneath it, and two of them are JDK classes. So the chain is still closed as now, but the caller's stream sits under a small shield at the bottom of it. The shield'sclose()only flushes (on output) or does nothing (on input), so the formats finish properly and the caller's stream stays open.readleaves the stream open, but buffering layers such as the decryptor and gzip may read past the end of the database, so its position afterwards isn't defined. The javadoc should say so.The
NxmethodssaveNxandloadNxwere added in March 2025 (b41301d) for the shared test code.DatabaseTestBasetakes the database's load and save as functional interfaces, which can't throw checked exceptions, so the tests useKdbxDatabase::loadNx,KdbDatabase::loadNxandDatabase::saveNx. They're public API all the same:saveNxis on theDatabaseinterface and inAbstractDatabase,loadNxis inKdbxDatabaseandKdbDatabase, andBasicDatabaseSerializerhas both.Since they exist only for the tests, deprecate all of them (no
writeNxorreadNx), and have the tests wrap the checked exception themselves, e.g. with a small helper in thetestmodule.Also
Update the examples, the readme and the tests to open streams with try-with-resources and call
writeandread.2.x won't change.