One way to do it automatically is to use a try-with-resources statement. Here is one example, directly copied from the unit tests:
// write the parsed file back to a characters array
final MessageWriter<H, S, F> writer = getWriter();
final CharArrayWriter caw = new CharArrayWriter();
try (Generator generator = format == FileFormat.KVN ?
new KvnGenerator(caw, 25, "dummy.kvn", Constants.JULIAN_DAY, unitsColumn) :
new XmlGenerator(caw, XmlGenerator.DEFAULT_INDENT, "dummy.xml",
Constants.JULIAN_DAY, unitsColumn > 0,
// reparse the written file
final byte bytes = caw.toString().getBytes(StandardCharsets.UTF_8);
final DataSource source2 = new DataSource(name, () -> new ByteArrayInputStream(bytes));
final F rebuilt = getParser().parseMessage(source2);
Here, we used CharArrayWriter instead of StringBuilder, but it should work the same. The point is that the CharArrayWriter/StringBuilder is outside of the try-with-resources, but the generator is limited to the scope of the try-with-resources. This implies its close method is called automatically (even in case of exception), so the toString method called later see the closing elements.
Another point: I noticed you use the version 3.0 of the ODM, which is not yet officially published by CCSDS. This means the file produced may not be parsable by other systems.
We expect the standard to be published soon though, as we have successfully completed the test phase between Orekit and STK. I’ll make the announcement on the forum as soon as I get the information from CCSDS navigation group. Even when the file format will become official, it may take some time before other implementations are able to handle it (but of course Orekit can already do that so if it is used on both sides it will work).