Acquiring a data key
DataKey

State- vs. Input validation:

In your constructor you do something not quite intelligent. You first set all the fields and then check them for integrity. 

IMO you should check the input data for validity before using it with something. This allows you to extract a so-called "leading guard clause". Some condition before a block of code makes it fail.

In fact you could extract the validation from the DataKey to the Builder while you're at it. Compare:

private DataKey(Builder builder) {
    if(!builder.isValid()){
         throw new IllegalStateException(INVALID_BUILDER_MESSAGE);
    }
    //keep doing the stuff you do ;)
}


Or even sooner:

public DataKey build(){
    if(!this.isValid()){
        throw new IllegalStateException(INVALID_BUILDER_MESSAGE);
    }
    return DataKey(this);
}


then you could remove the validation "clutter" from your DataKey constructor. And your isValid() method would look like:

private boolean isValid() {
    return !(userId == 0 &amp;&amp; uuid != null 
      &amp;&amp; uuid.isEmpty() &amp;&amp; deviceId != null
      &amp;&amp; deviceId.isEmpty());
}


If-Statement vs Ternary operator:


    if (userId != 0) {
        this.flow = FlowEnum.USERFLOW;
    }
    else {
        this.flow = FlowEnum.DEVICEFLOW;
    }



You can shortcut this drastically by using a ternary operator. IMO this also makes your intent way more clear:

this.flow = (userId == 0) ? FlowEnum.DEVICEFLOW : FlowEnum.USERFLOW;


You can shorten this even further by adding a static import to your class:

import static fully.qualified.FlowEnum.*;


and then you get:

 this.flow = (userId == 0) ? DEVICEFLOW : USERFLOW;


But that one is personal preference, some people even see this as light obfuscation.

While we're at your enum. Currently it's no more than a glorified boolean. I will assume there could be more Flows, because if not you could just do:

this.isUserFlow = userId != 0;