Apache Kafka
Avro
Java
Programming Errors
Data Serialization

Apache Kafka and Avro org.apache.avro.generic.GenericData$Record cannot be cast to com.harmeetsingh13.java.Customer

Master System Design with Codemia

Enhance your system design skills with over 120 practice problems, detailed solutions, and hands-on exercises.

Introduction

This cast error means the consumer received a generic Avro record, but the code expected a generated specific Avro class such as Customer. The fix is to make the deserializer and the consumer type agree on whether records should be handled as GenericRecord or as generated SpecificRecord classes.

Why the Cast Fails

Avro has two common reading modes:

  • generic mode, which returns GenericRecord
  • specific mode, which returns generated Java classes

If your code says this:

java
Customer customer = (Customer) record.value();

but the deserializer actually returned GenericData.Record, the cast fails immediately. The problem is not Java casting syntax. The problem is a mismatch in Avro deserialization mode.

The Consumer Must Match the Deserializer

If you want specific Avro classes, the consumer configuration needs the specific-reader setting enabled.

java
1Properties props = new Properties();
2props.put("bootstrap.servers", "localhost:9092");
3props.put("group.id", "customers");
4props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
5props.put("value.deserializer", "io.confluent.kafka.serializers.KafkaAvroDeserializer");
6props.put("schema.registry.url", "http://localhost:8081");
7props.put("specific.avro.reader", "true");
8
9KafkaConsumer<String, Customer> consumer = new KafkaConsumer<>(props);

With that setup, record.value() can be a Customer instance, assuming the schema and generated class align.

A Working Specific-Record Example

java
1import org.apache.kafka.clients.consumer.ConsumerRecord;
2import org.apache.kafka.clients.consumer.ConsumerRecords;
3import org.apache.kafka.clients.consumer.KafkaConsumer;
4import java.time.Duration;
5import java.util.List;
6import java.util.Properties;
7
8public class CustomerConsumer {
9    public static void main(String[] args) {
10        Properties props = new Properties();
11        props.put("bootstrap.servers", "localhost:9092");
12        props.put("group.id", "customers");
13        props.put("key.deserializer", "org.apache.kafka.common.serialization.StringDeserializer");
14        props.put("value.deserializer", "io.confluent.kafka.serializers.KafkaAvroDeserializer");
15        props.put("schema.registry.url", "http://localhost:8081");
16        props.put("specific.avro.reader", "true");
17
18        try (KafkaConsumer<String, Customer> consumer = new KafkaConsumer<>(props)) {
19            consumer.subscribe(List.of("customer-topic"));
20
21            while (true) {
22                ConsumerRecords<String, Customer> records = consumer.poll(Duration.ofMillis(100));
23                for (ConsumerRecord<String, Customer> record : records) {
24                    Customer customer = record.value();
25                    System.out.println(customer.getName());
26                }
27            }
28        }
29    }
30}

The important line is the specific.avro.reader setting. Without it, many setups return a generic record instead.

If You Intend to Use Generic Records

Then the code should say so explicitly and stop trying to cast:

java
1import org.apache.avro.generic.GenericRecord;
2
3KafkaConsumer<String, GenericRecord> consumer = new KafkaConsumer<>(props);
4GenericRecord value = record.value();
5System.out.println(value.get("name"));

This is a legitimate design when:

  • you do not want generated classes
  • you consume multiple schemas dynamically
  • the topic contains heterogeneous Avro records

The mistake is mixing generic consumption with specific-record casting.

Schema and Code Generation Still Matter

Even with specific.avro.reader=true, the generated Java class must match the schema used by the topic. If the namespace or record name does not match, the specific reader may still fail to produce the class you expect.

So there are really two alignment requirements:

  1. generic versus specific reader mode
  2. generated class matching the writer schema name and namespace

If either of those is wrong, the runtime behavior will not match the Java types in your consumer code, even if the topic data itself is otherwise valid Avro.

Common Pitfalls

The biggest mistake is setting the consumer type parameter to Customer but forgetting to enable the specific Avro reader. Generic return values and specific type parameters do not magically reconcile themselves.

Another mistake is generating classes from one schema version while consuming data written with a mismatched name or namespace. Specific Avro relies on schema identity, not just field similarity.

A third issue is mixing generic and specific approaches in the same code path. Pick one mode per consumption path and keep the types honest.

Summary

  • The cast error means you received GenericRecord but expected a generated Avro class.
  • Use specific.avro.reader=true when you want specific Avro classes from the deserializer.
  • Keep the consumer generic type aligned with the deserializer mode.
  • If you want dynamic handling, consume GenericRecord and stop casting to Customer.
  • Schema name and namespace still need to match the generated specific class.

Course illustration
Course illustration

All Rights Reserved.