01
Locate the decoding boundary
Identify the exact read operation, selected charset, and error. A filename extension or an editor’s display does not establish the byte encoding. MalformedInputException points to an invalid sequence for the decoder in use, not automatically to a damaged filesystem.
Files.readString with no charset uses UTF-8, and an explicit UTF-8 call makes that contract visible. In our fixture, the single-byte Windows-1252 representation of é was not a valid standalone UTF-8 sequence.
02
Confirm the producer’s format
Use the exporting system’s documented format, configuration, or controlled test output to establish the source charset. Do not choose Windows-1252 merely because one failing file becomes readable with it; many byte sequences can be interpreted in multiple ways.
If the producer promises UTF-8, investigate why its output violates that promise before silently accepting another charset. A truncated multibyte sequence or mixed encoding can be a different defect from an intentionally legacy export.
03
Run a controlled decoding and conversion proof
Save this fixture as EncodingProbe.java. It creates its own temporary files, writes an explicitly known source byte sequence, verifies UTF-8 rejection, decodes the known source format, and writes a separate UTF-8 copy.
The assertions check the decoded text, the converted result, and preservation of the original byte. Cleanup removes only the temporary files created by the probe. The code is a small demonstration, not a full import pipeline for unbounded files.
import java.nio.charset.Charset;
import java.nio.charset.MalformedInputException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
public class EncodingProbe {
public static void main(String[] args) throws Exception {
var directory = Files.createTempDirectory("encoding-proof-");
var source = directory.resolve("legacy.txt");
var converted = directory.resolve("utf8.txt");
try {
// The fixture producer explicitly writes Windows-1252 bytes.
Files.write(source, new byte[]{99, 97, 102, (byte) 0xe9});
boolean rejected = false;
try { Files.readString(source, StandardCharsets.UTF_8); }
catch (MalformedInputException expected) { rejected = true; }
if (!rejected) throw new AssertionError("Invalid UTF-8 accepted");
String text = Files.readString(source, Charset.forName("windows-1252"));
if (!text.equals("café")) throw new AssertionError(text);
Files.writeString(converted, text, StandardCharsets.UTF_8);
if (!Files.readString(converted, StandardCharsets.UTF_8).equals(text)) {
throw new AssertionError("Converted file mismatch");
}
if (Files.readAllBytes(source)[3] != (byte) 0xe9) {
throw new AssertionError("Source bytes changed");
}
System.out.println("invalid UTF-8 rejected; verified copy=" + text);
} finally {
Files.deleteIfExists(converted);
Files.deleteIfExists(source);
Files.deleteIfExists(directory);
}
}
}04
Verify the declared output format
Compile with an explicit source encoding so the test’s café literal is interpreted consistently. The program should report invalid UTF-8 rejected; verified copy=café after its assertions pass.
In a real import, validate representative non-ASCII characters and the receiving system’s expected format. A conversion that produces a file without throwing can still use the wrong charset and alter the meaning of the text.
javac -encoding UTF-8 -d out EncodingProbe.java
java -cp out EncodingProbe05
Keep error handling and scale appropriate
Avoid suppressing malformed sequences by silently substituting characters unless that is an explicit, acceptable data-loss policy for the application. Keep a controlled record of the source format and conversion result without logging private file contents.
The example reads a tiny fixture into memory. For large files, select a bounded streaming approach with the same deliberate encoding contract and explicit resource management. Verify failure handling before replacing or deleting original input data.
Know the limits
Reproduced with Eclipse Temurin OpenJDK 25.0.4.1+1 in a disposable Linux container with no external network. A file containing a Windows-1252 é byte failed strict UTF-8 reading. Reading with the fixture’s known source charset recovered café, and a separate UTF-8 copy round-tripped without changing the original bytes. Arbitrary charset detection, large-file streaming, and corrupted encodings were not tested.
Sources & further reading
A technical publication by CoreTrailKit.
Browse all articles