AlgorithmOutput sizeSpeedUse caseRecommendation
MD5128 bits (16 bytes)Very fastNon-security checksums⚠️ Broken for security — collisions found
SHA-1160 bits (20 bytes)FastLegacy systems⚠️ Deprecated — collision attacks known
SHA-256256 bits (32 bytes)FastGeneral security, HMAC, signatures✅ Default choice
SHA-512512 bits (64 bytes)Fast on 64-bitHigh-security contexts✅ Prefer on 64-bit hardware
SHA3-256256 bitsModeratePost-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())); }
PitfallFix
Using MD5 or SHA-1 for password storageNever — use PBKDF2, bcrypt, or Argon2 instead
Sharing MessageDigest across threadsCreate 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 bytesAlways use StandardCharsets.UTF_8
Hashing Object.toString() outputHash meaningful serialised content, not the default object representation
Using MD5 to detect malicious tamperingUse HMAC-SHA256 — MD5 alone provides no authentication