A successful Base64 decode does not necessarily mean that an input follows your application's preferred textual format. If encoded values are compared, deduplicated or used as cache identifiers, accepting multiple spellings can produce surprising behavior. This tutorial enforces the standard alphabet with canonical padding by decoding and comparing a re-encoding.
Prerequisites are JDK 21 and no external libraries. The example accepts complete strings, limits their encoded length, and returns bytes. It does not treat those bytes as UTF-8 or as a trusted file format.
Complete example
Save as Canonical64.java:
import java.util.Base64;
public class Canonical64 {
static byte[] decode(String text) {
if (text == null || text.length() > 4096)
throw new IllegalArgumentException("missing or oversized");
byte[] bytes = Base64.getDecoder().decode(text);
if (!Base64.getEncoder().encodeToString(bytes).equals(text))
throw new IllegalArgumentException("noncanonical encoding");
return bytes;
}
public static void main(String[] args) {
if (decode("Zg==")[0] != 102 || decode("").length != 0)
throw new AssertionError();
int rejected = 0;
for (String s : new String[]{"Zg", "Zh==", "Zg==\n", "@@", "A".repeat(4097)}) {
try { decode(s); throw new AssertionError("accepted: " + s); }
catch (IllegalArgumentException expected) { rejected++; }
}
System.out.println("canonical=2 rejected=" + rejected);
}
}
Run and expected output
java Canonical64.java
canonical=2 rejected=5
The first accepted string represents the byte value 102. The second accepted string represents an empty byte array. The rejected fixtures cover omitted padding, a noncanonical final quantum, an embedded newline, invalid characters and an input above the configured limit.
The basic decoder performs decoding and rejects invalid input under its rules. Re-encoding adds the application's stricter textual policy. Instead of trying to maintain a separate collection of padding and alphabet checks, the program asks the matching encoder to produce the one representation it accepts.
Edge cases and troubleshooting
Empty data is intentionally valid here. Add a minimum decoded-length rule if your field must contain a digest, nonce or other fixed-size value. The 4096-character bound is a tutorial policy; choose a limit from the actual field contract. Also bound the request body before it becomes a Java string, since this function cannot undo an earlier oversized allocation.
Do not trim or remove whitespace before validation if the goal is to reject noncanonical input. Such normalization would conceal the distinction the boundary is intended to enforce. If an upstream producer emits URL-safe or MIME Base64, agree on that format explicitly and use its matching encoder and decoder; this example deliberately requires basic padded Base64.
Canonical encoding provides a consistent representation, not authentication or encryption. Validate decoded length and content separately. The local tests verify the accepted representation and rejection fixtures; they do not test a network protocol or process sensitive data.
Primary reference
Java documents its basic, URL-safe and MIME Base64 variants. Continue with the Java category.