Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE

1. Overview

Using a Base64 encoded string is a widely adopted method for storing Universally Unique Identifiers (UUIDs). This provides a more compact result compared to the standard UUID string representation. In this article, we’ll explore various approaches for encoding UUIDs as Base64 strings.

2.  Encode using byte[] and Base64.Encoder

We’ll start with the most straightforward approach to encoding by using byte[] and Base64.Encoder.

2.1. Encoding

We’ll create an array of bytes from our UUID bits. For this purpose, we’ll take the most significant bits and least significant bits from our UUID and place them in our array at positions 0-7 and 8-15, respectively:

byte[] convertToByteArray(UUID uuid) {
    byte[] result = new byte[16];

    long mostSignificantBits = uuid.getMostSignificantBits();
    fillByteArray(0, 8, result, mostSignificantBits);

    long leastSignificantBits = uuid.getLeastSignificantBits();
    fillByteArray(8, 16, result, leastSignificantBits);

    return result;
}

In the filling method, we move bits to our array, converting them into bytes and shifting by 8 bits in each iteration:

void fillByteArray(int start, int end, byte[] result, long bits) {
    for (int i = start; i < end; i++) {
        int shift = i * 8;
        result[i] = (byte) ((int) (255L & bits >> shift));
    }
}

In the next step, we’ll Base64.Encoder from JDK to encode our byte array into a string:

UUID originalUUID = UUID.fromString("cc5f93f7-8cf1-4a51-83c6-e740313a0c6c");

@Test
void givenEncodedString_whenDecodingUsingBase64Decoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw==";
    byte[] uuidBytes = convertToByteArray(originalUUID);
    String encodedUUID = Base64.getEncoder().encodeToString(uuidBytes);
    assertEquals(expectedEncodedString, encodedUUID);
}

As we can see, the obtained value is exactly what we expected.

2.2. Decoding

To decode a UUID from a Base64 encoded string, we can perform the opposite actions in the following manner:

@Test
public void givenEncodedString_whenDecodingUsingBase64Decoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw==";
    byte[] decodedBytes = Base64.getDecoder().decode(expectedEncodedString);
    UUID uuid = convertToUUID(decodedBytes);
}

Firstly, we used Base64.Decoder to obtain a byte array from our encoded string and call our conversion method to make a UUID from this array:

UUID convertToUUID(byte[] src) {
    long mostSignificantBits = convertBytesToLong(src, 0);
    long leastSignificantBits = convertBytesToLong(src, 8);

    return new UUID(mostSignificantBits, leastSignificantBits);
}

We convert parts of our array to the most and least significant bits long representation and make UUID using them.

The conversion method is following:

long convertBytesToLong(byte[] uuidBytes, int start) {
    long result = 0;

    for(int i = 0; i < 8; i++) {
        int shift = i * 8;
        long bits = (255L & (long)uuidBytes[i + start]) << shift;
        long mask = 255L << shift;
        result = result & ~mask | bits;
    }

    return result;
}

In this method, we go through the bytes array, convert each of them to bits, and move them into our result.

As we can see, the final result of the decoding will match the original UUID we used for encoding.

3. Encode using ByteBuffer and Base64.getUrlEncoder()

Using the standard functionality from JDK, we can simplify the code written above.

3.1. Encoding

Using a ByteBuffer, we can make the process of transforming our UUID into a byte array in just a few lines of code:

ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
byteBuffer.putLong(originalUUID.getMostSignificantBits());
byteBuffer.putLong(originalUUID.getLeastSignificantBits());

We created a buffer wrapping a byte array and put the most and least significant bits from our UUID.

For encoding purposes, we’ll use Base64.getUrlEncoder() this time:

String encodedUUID = Base64.getUrlEncoder().encodeToString(byteBuffer.array());

As a result, we created a Base64-encoded UUID in 4 lines of code:

@Test
public void givenUUID_whenEncodingUsingByteBufferAndBase64UrlEncoder_thenGiveExpectedEncodedString() {
    String expectedEncodedString = "zF-T94zxSlGDxudAMToMbA==";
    ByteBuffer byteBuffer = ByteBuffer.wrap(new byte[16]);
    byteBuffer.putLong(originalUUID.getMostSignificantBits());
    byteBuffer.putLong(originalUUID.getLeastSignificantBits());
    String encodedUUID = Base64.getUrlEncoder().encodeToString(byteBuffer.array());
    assertEquals(expectedEncodedString, encodedUUID);
}

3.2. Decoding

We can perform the opposite operation using ByteBuffer and Base64.UrlDecoder():

@Test
void givenEncodedString_whenDecodingUsingByteBufferAndBase64UrlDecoder_thenGiveExpectedUUID() {
    String expectedEncodedString = "zF-T94zxSlGDxudAMToMbA==";
    byte[] decodedBytes = Base64.getUrlDecoder().decode(expectedEncodedString);
    ByteBuffer byteBuffer = ByteBuffer.wrap(decodedBytes);
    long mostSignificantBits = byteBuffer.getLong();
    long leastSignificantBits = byteBuffer.getLong();
    UUID uuid = new UUID(mostSignificantBits, leastSignificantBits);
    assertEquals(originalUUID, uuid);
}

As we can see, we successfully decoded the expected UUID from the encoded string.

4. Reduce the Length of an Encoded UUID

As we saw in previous sections, Base64, by default, contains “==” on the end. To save a few more bytes, we can trim this ending.
For this purpose, we can configure our encoder to not add the padding:

String encodedUUID = 
  Base64.getUrlEncoder().withoutPadding().encodeToString(byteBuffer.array());

assertEquals(expectedEncodedString, encodedUUID);

As a result, we can see the encoded string without extra characters. There’s no need to change our decoder since it will work with both variants of the encoded string in the same way.

5. Encode Using Conversion Utils and Codec Utils From Apache Commons

In this section, we’ll use uuidToByteArray from Apache Commons Conversion utils to make an array of UUID bytes. Also, we’ll use encodeBase64URLSafeString from Apache Commons Base64 utils.

5.1. Dependencies

To demonstrate this encoding approach, we’ll use the Apache Commons Lang library. Let’s add its dependency to our pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.14.0</version>
</dependency>

Another dependency we’ll use is a commons-codec:

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.16.0</version>
</dependency>

5.2. Encoding

We’ll encode the UUID in just two lines of code:

@Test
void givenUUID_whenEncodingUsingApacheUtils_thenGiveExpectedEncodedString() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw";
    byte[] bytes = Conversion.uuidToByteArray(originalUUID, new byte[16], 0, 16);
    String encodedUUID = encodeBase64URLSafeString(bytes);
    assertEquals(expectedEncodedString, encodedUUID);
}

As we can see, the result is already trimmed and doesn’t contain a pending ending.

5.3. Decoding

We’ll make a reverse operation calling Base64.decodeBase64() and Conversion.byteArrayToUuid() from Apache Commons:

@Test
void givenEncodedString_whenDecodingUsingApacheUtils_thenGiveExpectedUUID() {
    String expectedEncodedString = "UUrxjPeTX8xsDDoxQOfGgw";
    byte[] decodedBytes = decodeBase64(expectedEncodedString);
    UUID uuid = Conversion.byteArrayToUuid(decodedBytes, 0);
    assertEquals(originalUUID, uuid);
}

We successfully obtained the original UUID.

6. Conclusion

UUID is a widely used data type, and one of the approaches to encode it is by using Base64. In this article, we explored a few methods to encode UUID into Base64.

As usual, the full source code can be found over on GitHub.

Course – LS – All

Get started with Spring and Spring Boot, through the Learn Spring course:

>> CHECK OUT THE COURSE
res – REST with Spring (eBook) (everywhere)
Subscribe
Notify of
guest
0 Comments
Inline Feedbacks
View all comments