1. Introduction

In this article, we’ll see how we can serialize and deserialize java.sql.Blob using Jackson. The java.sql.Blob represents a Binary Large Object (Blob) in Java, which can store large amounts of binary data. When dealing with JSON serialization and deserialization using Jackson, handling Blob objects can be tricky since Jackson does not support them directly. However, we can create custom serializers and deserializers to handle Blob objects.

We’ll start with setting up the environment and a simple example. Further along, we’ll quickly show how we can implement a custom serializer and deserialize for Blob data type. Finally, we’ll verify our approach with the tests using our simple example use case.

2. Dependency and Example Setup

Firstly, let’s ensure we have the necessary jackson-databind dependency in our pom.xml:

<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.12.3</version>
</dependency>

We’ll next demonstrate how to integrate Blob fields within a typical POJO, highlighting the need for custom serialization and deserialization. Let’s create a simple User POJO that contains an ID, name, and profilePicture which is of type Blob:

public class User {
    private int id;
    private String name;
    private Blob profilePicture;
    //Constructor 
    // Getters and setters
}

We’ll later use this User class to demonstrate the custom serializing and deserializing involving a Blob field.

3. Defining Blob Serializer

Let’s define a serializer that will convert the profilePicture attribute of the User to a Base64-encoded binary string:

@JacksonStdImpl
public class SqlBlobSerializer extends JsonSerializer<Blob> {
    @Override
    public void serialize(Blob value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        try {
            byte[] blobBytes = value.getBytes(1, (int) value.length());
            gen.writeBinary(blobBytes);
        } catch (Exception e) {
            throw new IOException("Failed to serialize Blob", e);
        }
    }
}

Importantly*, @JacksonStdImpl i*ndicates that this class is a standard implementation of a serializer that Jackson can use. It’s a marker annotation typically used for built-in serializers and deserializers in Jackson.

*Our SqlBlobSerializer extends JsonSerialzier, a generic class provided by Jackson for defining custom serializers*. We override the serialize method passing the Blob object to be serialized as well as JsonGenerator and SerializerProvider. JsonGenerator is used to generate the resulting JSON content whereas SerializerProvider is used to provide serializers for serializing the objects

Essentially, the serialize method converts the Blob into a byte array using getBytes(). It then writes the byte array as a Base64-encoded binary string using  gen.writeBinary()

5.  Defining Blob Deserializer

Let’s now define a deserializer, which can convert a Base64 encoded string to a Blob using Jackson:

@JacksonStdImpl
public class SqlBlobDeserializer extends JsonDeserializer<Blob> {
    @Override
    public Blob deserialize(JsonParser p, DeserializationContext ctxt) throws IOException {
        try {
            byte[] blobBytes = p.getBinaryValue();
            return new SerialBlob(blobBytes);
        } catch (Exception e) {
            throw new IOException("Failed to deserialize Blob", e);
        }
    }
}

Here, SqlBlobDeserializer extends JsonDeserializer, a generic class provided by Jackson for defining custom deserializers*. We then override the deserialize method from the JsonDeserializer passing JsonParser which is the parser used to read JSON content.* Additionally, we pass the DeserializationContext that can be used to access information about the deserialization process.

*Essentially, the SqlBlobDeserializer retrieves binary data into byte[] from the JSON using getBinaryValue().* It then converts the byte array into a SerialBlob object that is an implementation of java.sql.Blob.

6. Registering the Custom Serializer and Deserializer

Now that we have a BlobSerializer and BlobDeserializer, the next step is to register them both with Jackson. Registering custom serializers and deserializers with Jackson means configuring the Jackson ObjectMapper to use specific classes for converting certain types of Java objects to and from JSON.  Let’s create a SimpleModule next and  add our blobSerializer and blobDeserializer to this module:

SimpleModule module = new SimpleModule();
module.addSerializer(Blob.class, new SqlBlobSerializer());
module.addDeserializer(Blob.class, new SqlBlobDeserializer());

Next, let’s create an ObjectMapper, and register this module to it:

ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(module);

Essentially, by registering a specific module to the ObjectMapper, we make sure that it knows how to handle non-standard types during JSON processing. In this case, we are ensuring our ObjectMapper knows how to handle Blob type using our custom serializer and deserializer.

7. Unit Test

Finally, let’s see our registered serializer and deserializer in action by writing some unit tests. Let’s test the BlobSerializer first*:*

@Test
public void givenUserWithBlob_whenSerialize_thenCorrectJsonDataProduced() throws Exception {
    User user = new User();
    user.setId(1);
    user.setName("Test User");
    //sample blob data from byte[] 
    byte[] profilePictureData = "example data".getBytes();
    Blob profilePictureBlob = new SerialBlob(profilePictureData);
    user.setProfilePicture(profilePictureBlob);

    String json = mapper.writeValueAsString(user);
    String expectedJson = "{\"id\":1,\"name\":\"Test User\",\"profilePicture\":\"ZXhhbXBsZSBkYXRh\"}";
    assertEquals(expectedJson, json);
}

The test verifies that the serialized JSON string matches the expected JSON format. Specifically, the profilePicture field in the JSON is expected to be a base64-encoded string representing the Blob data.

Next, let’s write a test for BlobDeserializer:

@Test
public void givenUserJsonWithBlob_whenDeserialize_thenCorrectDataRecieved() throws Exception {
    String json = "{\"id\":1,\"name\":\"Test User\",\"profilePicture\":\"ZXhhbXBsZSBkYXRh\"}";
    User deserializedUser = mapper.readValue(json, User.class);
    assertEquals(1, deserializedUser.getId());
    assertEquals("John Doe", deserializedUser.getName());

    byte[] expectedProfilePictureData = "example data".getBytes();
    Blob deserializedProfilePictureBlob = deserializedUser.getProfilePicture();
    byte[] deserializedData = deserializedProfilePictureBlob.getBytes(1, (int) deserializedProfilePictureBlob.length());
    assertArrayEquals(expectedProfilePictureData, deserializedData);
}

Here, the Blob data is expected to match the original byte data for the string “example data”. This test ensures that the custom SqlBlobDeserialiser correctly converts the base64-encoded string back into a Blob object, preserving the original binary data within the User object.

8. Conclusion

In this article, we demonstrated how to effectively serialize and deserialize java.sql.Blob objects using the Jackson library in Java. We created custom serializers and deserializers to handle the binary data within the Blob objects, converting them to and from base64-encoded strings in JSON format.

As always, the full implementation of this article can be found over on GitHub.