44. Converting between bytes and hex-encoded strings
Converting bytes to hexadecimal (and vice versa) is a common operation in applications that manipulate fluxes of files/messages, perform encoding/decoding tasks, process images, and so on.
A Java byte is a number in the [-128, +127] range and is represented using 1 signed byte (8 bits). A hexadecimal (base 16) is a system based on 16 digits (0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, and F). In other words, those 8 bits of a byte value accommodate exactly 2 hexadecimal characters in the range 00 to FF. The decimal <-> binary <-> hexadecimal mapping is resumed in the following figure:
Figure 2.27: Decimal to binary to hexadecimal conversion
For instance, 122 in binary is 01111010. Since 0111 is in hexadecimal 7, and 1010 is A, this results in 122 being 7A in hexadecimal (also written as 0x7A).
How about a negative byte? We know from the previous chapter that Java represents a negative number as two’s complement of the positive number. This means that -122 in binary is 10000110 (retain the first 7 bits of positive 122 = 1111010, flip(1111010) = 0000101, add(0000001) = 00000110, and append sign bit 1, 10000110) and in hexadecimal, is 0x86.
Converting a negative number to hexadecimal can be done in several ways, but we can easily obtain the lower 4 bits as 10000110 & 0xF = 0110, and the higher four bits as (10000110>> 4) & 0xF = 1000 & 0xF = 1000 (here, the 0xF (binary, 1111) mask is useful only for negative numbers). Since, 0110 = 6 and 1000 = 8, we see that 10000110 is in hexadecimal 0x86.
If you need a deep coverage of bits manipulation in Java or you simply face issues in understanding the current topic, then please consider the book The Complete Coding Interview Guide in Java, especially Chapter 9.
So, in code lines, we can rely on this simple algorithm and Character.forDigit(int d, int r)
, which returns the character representation for the given digit (d
) in the given radix (r
):
public static String byteToHexString(byte v) {
int higher = (v >> 4) & 0xF;
int lower = v & 0xF;
String result = String.valueOf(
new char[]{
Character.forDigit(higher, 16),
Character.forDigit(lower, 16)}
);
return result;
}
There are many other ways to solve this problem (in the bundled code, you can see another flavor of this solution). For example, if we know that the Integer.toHexString(int n)
method returns a string that represents the unsigned integer in base 16 of the given argument, then all we need is to apply the 0xFF (binary, 11111111) mask for negatives as:
public static String byteToHexString(byte v) {
return Integer.toHexString(v & 0xFF);
}
If there is an approach that we should avoid, then that is the one based on String.format()
. The String.format("%02x ", byte_nr)
approach is concise but very slow!
How about the reverse process? Converting a given hexadecimal string (for instance, 7d, 09, and so on) to a byte is quite easy. Just take the first (d1
) and second (d2
) character of the given string and apply the relation, (byte) ((d1 << 4) + d2)
:
public static byte hexToByte(String s) {
int d1 = Character.digit(s.charAt(0), 16);
int d2 = Character.digit(s.charAt(1), 16);
return (byte) ((d1 << 4) + d2);
}
More examples are available in the bundled code. If you rely on third-party libraries, then check Apache Commons Codec (Hex.encodeHexString()
), Guava (BaseEncoding
), Spring Security (Hex.encode()
), Bouncy Castle (Hex.toHexString()
), and so on.
JDK 17+
Starting with JDK 17, we can use the java.util.HexFormat
class. This class has plenty of static methods for handling hexadecimal numbers, including String toHexDigits(byte value)
and byte[]parseHex(CharSequence string)
. So, we can convert a byte to a hexadecimal string as follows:
public static String byteToHexString(byte v) {
HexFormat hex = HexFormat.of();
return hex.toHexDigits(v);
}
And, vice versa as follows:
public static byte hexToByte(String s) {
HexFormat hex = HexFormat.of();
return hex.parseHex(s)[0];
}
In the bundled code, you can also see the extrapolation of these solutions for converting an array of bytes (byte[]
) to a String
, and vice versa.