i am playing with Kafka and streams technology; i have created a custom serializer and deserializer for the KStream that i will use to receive messages from a given topic.
If you call Serdes.serdeFrom(...) you get a WrappedSerde type back that is for internal usage (and WrappedSerde does not have an non-argument constructor). There is currently no API you can call to get a custom Serde. Instead, you need to implement you own Serde class and wrap you serializer and deserializer "manually".
public class EventMessageSerde implements Serde<EventMessage> {
final private JsonSerializer<EventMessage> serializer;
final private JsonDeserializer<EventMessage> deserializer;
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
serializer.configure(configs, isKey);
deserializer.configure(configs, isKey);
}
@Override
public void close() {
serializer.close();
deserializer.close();
}
@Override
public Serializer<EventMessage> serializer() {
return serializer;
}
@Override
public Deserializer<EventMessage> deserializer() {
return deserializer;
}
}
In your Properties you can set:
streamsConfiguration.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG, EventMessageSerde.class);
To complete the Matthias answer I've just coded a simple example of how to create a custom Serde (Serializer / Deserializer) within a Kafka Stream App. It's is available to clone and try in: https://github.com/Davidcorral94/Kafka-Streams-Custom-Seder
First I create two classes, one for the Serializer and another for the Deserializer. In this case I use Gson library to perform the serialization/deserialization.
public class PersonSerializer implements Closeable, AutoCloseable, Serializer<Person> {
private static final Charset CHARSET = Charset.forName("UTF-8");
static private Gson gson = new Gson();
@Override
public void configure(Map<String, ?> map, boolean b) {
}
@Override
public byte[] serialize(String s, Person person) {
// Transform the Person object to String
String line = gson.toJson(person);
// Return the bytes from the String 'line'
return line.getBytes(CHARSET);
}
@Override
public void close() {
}
}
public class PersonDeserializer implements Closeable, AutoCloseable, Deserializer<Person> {
private static final Charset CHARSET = Charset.forName("UTF-8");
static private Gson gson = new Gson();
@Override
public void configure(Map<String, ?> map, boolean b) {
}
@Override
public Person deserialize(String topic, byte[] bytes) {
try {
// Transform the bytes to String
String person = new String(bytes, CHARSET);
// Return the Person object created from the String 'person'
return gson.fromJson(person, Person.class);
} catch (Exception e) {
throw new IllegalArgumentException("Error reading bytes", e);
}
}
@Override
public void close() {
}
}
Then, I wrap both of them into a Serde to be able to use it into my Kafka Stream App.
public class PersonSerde implements Serde<Person> {
private PersonSerializer serializer = new PersonSerializer();
private PersonDeserializer deserializer = new PersonDeserializer();
@Override
public void configure(Map<String, ?> configs, boolean isKey) {
serializer.configure(configs, isKey);
deserializer.configure(configs, isKey);
}
@Override
public void close() {
serializer.close();
deserializer.close();
}
@Override
public Serializer<Person> serializer() {
return serializer;
}
@Override
public Deserializer<Person> deserializer() {
return deserializer;
}
}
Finally, you are able to use this Serde class into your Kafka Stream App with the next line:
props.put(StreamsConfig.DEFAULT_VALUE_SERDE_CLASS_CONFIG, PersonSerde.class);
This is actually working with the latest Kafka version available at this moment which is 1.0.0!
Another way is using StreamsBuilder instead of KStreamBuilder. KStreamBuilder is deprecated in 1.0.0. You can directly pass serde object using Consumed.with while creating stream. You need not to create custom Serde class in this scenario.
Serde<EventMessage> messageSerde = Serdes.serdeFrom(serializer, deserializer);
StreamsBuilder builder = new StreamsBuilder();
KStream<String, EventMessage> eventsStream = builder.stream(topic, Consumed.with(Serdes.String(), messageSerde));
You can keep StringSerde in below code instead of using messageSerde.getClass() which is failing because messageSerde is just a WrappedSerde that does not have non-argument constructor.
streamsConfiguration.put(StreamsConfig.VALUE_SERDE_CLASS_CONFIG, StringSerde.class.getName());