Pluggable serializers for CorDapp checkpoints

If a CorDapp encounters an exception during the checkpoint process, it may need a custom serializer to make a class serializable.

Checkpoint serializers follow the same rules as normal pluggable serializers.

In addition they need to implement net.corda.core.serialization.CheckpointCustomSerializer.

Create a class that claims to implement the Map interface, but does not behave correctly:

@CordaSerializable
public final class BrokenMapImpl<K,V> extends HashMap<K,V> {
    @Override
    public V put(K key, V value) {
        throw new RuntimeException("Broken on purpose");
    }
}

This class will not checkpoint correctly because the put method is broken.

Here is a flow for testing the behaviour of this map:

@InitiatingFlow
@StartableByRPC
public class BrokenMapFlow extends FlowLogic<Integer> {

    @Suspendable
    @Override
    public Integer call() throws FlowException {

        // Something to store in our map
        HashMap<String, Integer> inputValues = new HashMap<>();
        inputValues.put("Key", 5);

        // This map won't serialize correctly
        BrokenMapImpl<String, Integer> brokenMap = new BrokenMapImpl<>();
        brokenMap.putAll(inputValues);

        // Force a checkpoint
        sleep(Duration.ofMinutes(5));

        // Output a value from the map
        getLogger().info("Flow completed successfully");
        getLogger().info("Result is " + brokenMap.get("Key"));
        return brokenMap.get("Key");
    }
}

At this point, there is a flow that will not deserialize from checkpoint correctly. It will throw an exception when trying to rebuild BrokenMapImpl.

Adding this implementation of CheckpointCustomSerializer will fix the issue. It will be found at startup and registered with the system. When the test flow reaches a checkpoint, this will transform BrokenMapImpl into a HashMap for storage and then convert back to BrokenMapImpl when the flow resumes.

public class BrokenMapSerializer implements CheckpointCustomSerializer<BrokenMapImpl, HashMap> {

    // Convert to a HashMap for storage
    @Override
    public HashMap toProxy(BrokenMapImpl brokenMap) {
        return new HashMap(brokenMap);
    }

    // Convert back to BrokenMapImpl on resume
    @Override
    public BrokenMapImpl fromProxy(HashMap hashMap) {
        BrokenMapImpl brokenMap = new BrokenMapImpl();
        brokenMap.putAll(hashMap);
        return brokenMap;
    }
}

Was this page helpful?

Thanks for your feedback!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.

We're sorry this page wasn't helpful. Let us know how we can make it better!

Chat with us

Chat with us on our #docs channel on slack. You can also join a lot of other slack channels there and have access to 1-on-1 communication with members of the R3 team and the online community.

Create an issue

Create a new GitHub issue in this repository - submit technical feedback, draw attention to a potential documentation bug, or share ideas for improvement and general feedback.

Propose documentation improvements directly

Help us to improve the docs by contributing directly. It's simple - just fork this repository and raise a PR of your own - R3's Technical Writers will review it and apply the relevant suggestions.