| Algorithm | Output size | Speed | Use case | Recommendation |
| MD5 | 128 bits (16 bytes) | Very fast | Non-security checksums | ⚠️ Broken for security — collisions found |
| SHA-1 | 160 bits (20 bytes) | Fast | Legacy systems | ⚠️ Deprecated — collision attacks known |
| SHA-256 | 256 bits (32 bytes) | Fast | General security, HMAC, signatures | ✅ Default choice |
| SHA-512 | 512 bits (64 bytes) | Fast on 64-bit | High-security contexts | ✅ Prefer on 64-bit hardware |
| SHA3-256 | 256 bits | Moderate | Post-quantum resistance | ✅ Future-proof |
Obtain a MessageDigest instance via getInstance(algorithm), feed bytes in with update(), and call digest() to get the raw byte array.
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class HashExample {
public static byte[] sha256(String input) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
return md.digest(input.getBytes(StandardCharsets.UTF_8));
}
/** Convert raw bytes to lowercase hex string */
public static String toHex(byte[] bytes) {
StringBuilder sb = new StringBuilder(bytes.length * 2);
for (byte b : bytes) {
sb.append(String.format("%02x", b));
}
return sb.toString();
}
public static void main(String[] args) throws Exception {
String input = "Hello, World!";
byte[] hash = sha256(input);
System.out.println(toHex(hash));
// dffd6021bb2bd5b0af676290809ec3a53191dd81c7f70a4b28688a362182986d
}
}
MessageDigest instances are not thread-safe. Always call getInstance() per thread or per operation — do not share a single instance across threads.
Java 17 introduced java.util.HexFormat, which replaces the manual format loop:
import java.util.HexFormat;
import java.security.MessageDigest;
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] hash = md.digest("hello".getBytes(StandardCharsets.UTF_8));
// Java 17+
String hex = HexFormat.of().formatHex(hash);
System.out.println(hex);
// 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
// Upper-case with delimiter
String upper = HexFormat.ofDelimiter(":").withUpperCase().formatHex(hash);
System.out.println(upper); // 2C:F2:4D:BA:...
For large files, feed the bytes in chunks using update(byte[]) to avoid loading the entire file into memory.
import java.io.*;
import java.nio.file.*;
import java.security.*;
public static String hashFile(Path path, String algorithm) throws Exception {
MessageDigest md = MessageDigest.getInstance(algorithm);
try (InputStream is = Files.newInputStream(path)) {
byte[] buffer = new byte[8192];
int bytesRead;
while ((bytesRead = is.read(buffer)) != -1) {
md.update(buffer, 0, bytesRead);
}
}
return HexFormat.of().formatHex(md.digest());
}
// Usage
String sha256 = hashFile(Path.of("/data/archive.zip"), "SHA-256");
System.out.println("SHA-256: " + sha256);
String md5 = hashFile(Path.of("/data/archive.zip"), "MD5");
System.out.println("MD5 : " + md5);
Alternatively, use DigestInputStream to wrap an existing stream:
MessageDigest md = MessageDigest.getInstance("SHA-256");
try (DigestInputStream dis = new DigestInputStream(
Files.newInputStream(Path.of("/data/file.bin")), md)) {
// read all bytes — digest is updated automatically
dis.transferTo(OutputStream.nullOutputStream());
}
String hex = HexFormat.of().formatHex(md.digest());
You can call update() multiple times before the final digest() — useful for computing a hash over data that arrives in parts.
MessageDigest md = MessageDigest.getInstance("SHA-256");
// Hash three parts as one logical message
md.update("part1".getBytes(StandardCharsets.UTF_8));
md.update("part2".getBytes(StandardCharsets.UTF_8));
md.update("part3".getBytes(StandardCharsets.UTF_8));
byte[] hash = md.digest(); // resets the digest for reuse
// Equivalent single call
byte[] combined = "part1part2part3".getBytes(StandardCharsets.UTF_8);
byte[] expected = MessageDigest.getInstance("SHA-256").digest(combined);
System.out.println(MessageDigest.isEqual(hash, expected)); // true
Use MessageDigest.isEqual(a, b) for constant-time comparison. Comparing with Arrays.equals() or == is vulnerable to timing attacks when used for security checks.
Java 9 added the SHA-3 family (SHA3-224, SHA3-256, SHA3-384, SHA3-512), which uses the Keccak sponge construction — a fundamentally different algorithm from SHA-2.
// SHA3-256
byte[] hash = MessageDigest.getInstance("SHA3-256")
.digest("data".getBytes(StandardCharsets.UTF_8));
System.out.println(HexFormat.of().formatHex(hash));
// List all available digest algorithms
for (Provider p : Security.getProviders()) {
p.getServices().stream()
.filter(s -> "MessageDigest".equals(s.getType()))
.forEach(s -> System.out.println(s.getAlgorithm()));
}
| Pitfall | Fix |
| Using MD5 or SHA-1 for password storage | Never — use PBKDF2, bcrypt, or Argon2 instead |
Sharing MessageDigest across threads | Create a new instance per thread/call |
Comparing digests with == or equals() | Use MessageDigest.isEqual() for constant-time comparison |
| Forgetting to specify charset when converting String to bytes | Always use StandardCharsets.UTF_8 |
Hashing Object.toString() output | Hash meaningful serialised content, not the default object representation |
| Using MD5 to detect malicious tampering | Use HMAC-SHA256 — MD5 alone provides no authentication |