Configuring Kafka serializers/deserializers in Java clients

You can configure Kafka serializers/deserializers (SerDes) in your producer and consumer Java client applications.

Apicurio Registry serializer/deserializer configuration in client applications

You can configure specific client serializer/deserializer (SerDes) services and schema lookup strategies directly in a client application by using the following example constants.

The following examples show commonly used SerDes constants and configuration options.

Configuration for SerDes services

public class SerdeConfig {

   public static final String REGISTRY_URL = "apicurio.registry.url";
   public static final String ID_HANDLER = "apicurio.registry.id-handler";
  • REGISTRY_URL: The required URL of Apicurio Registry.

  • ID_HANDLER: Extends ID handling to support other ID formats and make them compatible with Apicurio Registry SerDes services. For example, you can change it from the default ID format Integer, which is also compatible with the Confluent ID format, to Long.

Configuration for SerDes lookup strategies

public class SerdeConfig {

   public static final String ARTIFACT_RESOLVER_STRATEGY = "apicurio.registry.artifact-resolver-strategy";
   public static final String SCHEMA_RESOLVER = "apicurio.registry.schema-resolver";
...
  • ARTIFACT_RESOLVER_STRATEGY: Java class that implements the artifact resolver strategy and maps between the Kafka SerDes and artifact ID. Defaults to the topic ID strategy. Only the serializer class uses this option.

  • SCHEMA_RESOLVER: Java class that implements the schema resolver. Defaults to DefaultSchemaResolver. The serializer and deserializer classes use this option.

Configuration for Kafka converters

public class SerdeBasedConverter<S, T> extends SchemaResolverConfigurer<S, T> implements Converter, Closeable {

   public static final String REGISTRY_CONVERTER_SERIALIZER_PARAM = "apicurio.registry.converter.serializer";
   public static final String REGISTRY_CONVERTER_DESERIALIZER_PARAM = "apicurio.registry.converter.deserializer";
  • REGISTRY_CONVERTER_SERIALIZER_PARAM: The required serializer to use with the Apicurio Registry Kafka converter.

  • REGISTRY_CONVERTER_DESERIALIZER_PARAM: The required deserializer to use with the Apicurio Registry Kafka converter.

Apicurio Registry serializer/deserializer configuration properties

Apicurio Registry Kafka serializers/deserializers (SerDes) support the following Java configuration properties.

SchemaResolver interface

Apicurio Registry SerDes are based on the SchemaResolver interface, which abstracts access to the registry and applies the same lookup logic for the SerDes classes of all supported formats.

Table 1. Configuration property for SchemaResolver interface
Constant Property Description Type Default

SCHEMA_RESOLVER

apicurio.registry.schema-resolver

Used by serializers and deserializers. Fully qualified Java class name that implements SchemaResolver.

String

io.apicurio.registry.resolver.DefaultSchemaResolver

Use the DefaultSchemaResolver, which provides useful features for most use cases. For some advanced use cases, you might use a custom implementation of SchemaResolver.

DefaultSchemaResolver class

You can use the DefaultSchemaResolver to configure the following features:

  • Access to the registry API

  • How to look up artifacts in the registry

  • How to write and read artifact information to and from Kafka

  • Fall-back options for deserializers

Configuration for registry API access options

The DefaultSchemaResolver provides the following properties to configure access to the core registry API:

Table 2. Configuration properties for access to registry API
Constant Property Description Type Default

REGISTRY_URL

apicurio.registry.url

Used by serializers and deserializers. URL to access the registry API.

String

None

AUTH_TOKEN_ENDPOINT

apicurio.registry.auth.service.token.endpoint

Used by serializers and deserializers. URL of the token endpoint.

String

None

AUTH_CLIENT_ID

apicurio.registry.auth.client.id

Used by serializers and deserializers. Client ID to access the authentication service. Required when accessing a secure registry by using the OAuth client credentials flow.

String

None

AUTH_CLIENT_SECRET

apicurio.registry.auth.client.secret

Used by serializers and deserializers. Client secret to access the authentication service. Required when accessing a secure registry by using the OAuth client credentials flow.

String

None

AUTH_USERNAME

apicurio.registry.auth.username

Used by serializers and deserializers. Username to access the registry. Required when accessing a secure registry by using HTTP basic authentication.

String

None

AUTH_PASSWORD

apicurio.registry.auth.password

Used by serializers and deserializers. Password to access the registry. Required when accessing a secure registry by using HTTP basic authentication.

String

None

TLS_KEYSTORE_LOCATION

apicurio.registry.tls.keystore.location

Used by serializers and deserializers. The location of the key store file for mutual TLS (mTLS). Can be a file path or classpath resource.

String

None

TLS_KEYSTORE_PASSWORD

apicurio.registry.tls.keystore.password

Used by serializers and deserializers. The password for the key store file. Required when using JKS or PKCS12 key stores.

String

None

TLS_KEYSTORE_TYPE

apicurio.registry.tls.keystore.type

Used by serializers and deserializers. The type of key store. Valid values are JKS, PKCS12, or PEM.

String

JKS

TLS_CLIENT_CERTIFICATE

apicurio.registry.tls.client-certificate

Used by serializers and deserializers. The PEM client certificate content or file path for mutual TLS (mTLS).

String

None

TLS_CLIENT_KEY

apicurio.registry.tls.client-key

Used by serializers and deserializers. The PEM client private key content or file path for mutual TLS (mTLS).

String

None

TLS security for the registry connection

When Apicurio Registry is served over HTTPS with certificates that the default Java trust store does not trust, you can configure a trust store for the SerDes HTTP client. You set the following properties directly as Kafka producer or consumer properties:

Table 3. Configuration properties for TLS security
Configuration property Description Default

apicurio.registry.tls.truststore.location

Path to the trust store file. With the PEM type, this is the path to a PEM certificate file.

None

apicurio.registry.tls.truststore.password

Password for the trust store. Required for the JKS and PKCS12 types. Not used for the PEM type.

None

apicurio.registry.tls.truststore.type

Type of the trust store: JKS, PKCS12, or PEM.

JKS

apicurio.registry.tls.certificates

Comma-separated list of PEM certificate file paths, or inline PEM certificate content. When the value contains -----BEGIN CERTIFICATE-----, Apicurio Registry treats it as inline content; otherwise, as file paths.

None

apicurio.registry.tls.trust-all

Disables all certificate verification. Do not use this option in production.

false

apicurio.registry.tls.verify-host

Verifies the server host name against the certificate. Disable this option only when necessary, for example, when connecting by IP address.

true

The following example configures a producer with a PKCS12 trust store:

apicurio.registry.url=https://registry.example.com/apis/registry/v3
apicurio.registry.tls.truststore.location=/etc/certs/truststore.p12
apicurio.registry.tls.truststore.password=changeit
apicurio.registry.tls.truststore.type=PKCS12

Alternatively, provide one or more PEM certificate files without a password or type:

apicurio.registry.tls.certificates=/etc/certs/ca1.pem,/etc/certs/ca2.pem
These properties configure server trust only. Client certificate (mutual TLS) configuration is not available through the SerDes properties.

HTTP proxy for the registry connection

To reach Apicurio Registry through an HTTP proxy, set the apicurio.registry.proxy.host and apicurio.registry.proxy.port properties. For proxies that require authentication, also set apicurio.registry.proxy.username and apicurio.registry.proxy.password.

OpenTelemetry distributed tracing

The SerDes libraries support optional OpenTelemetry trace context propagation. When enabled, W3C trace context headers (traceparent and tracestate) are injected into all HTTP requests to Apicurio Registry, allowing schema resolution and registration calls to appear as spans in your distributed traces alongside your Kafka producer and consumer operations.

To enable this feature, set the following configuration property:

apicurio.registry.otel.enabled=true

The opentelemetry-api library must be on the classpath. When you initialize an OpenTelemetry SDK in your application (for example, by using the OpenTelemetry Java agent or the Quarkus OpenTelemetry extension), the SerDes HTTP calls participate in the active trace. If no OpenTelemetry SDK is configured, the behavior is a no-op and the SerDes add no trace headers.

Configuration for registry lookup options

The DefaultSchemaResolver uses the following properties to configure how to look up artifacts in Apicurio Registry.

Table 4. Configuration properties for registry artifact lookup
Constant Property Description Type Default

ARTIFACT_RESOLVER_STRATEGY

apicurio.registry.artifact-resolver-strategy

Used by serializers only. Fully qualified Java class name that implements ArtifactReferenceResolverStrategy and maps each Kafka message to an ArtifactReference (groupId, artifactId, and version). For example, the default strategy uses the topic name as the schema artifactId.

String

io.apicurio.registry.serde.strategy.TopicIdStrategy

EXPLICIT_ARTIFACT_GROUP_ID

apicurio.registry.artifact.group-id

Used by serializers only. Sets the groupId used for querying or creating an artifact. Overrides the groupId returned by the ArtifactResolverStrategy.

String

None

EXPLICIT_ARTIFACT_ID

apicurio.registry.artifact.artifact-id

Used by serializers only. Sets the artifactId used for querying or creating an artifact. Overrides the artifactId returned by the ArtifactResolverStrategy.

String

None

EXPLICIT_ARTIFACT_VERSION

apicurio.registry.artifact.version

Used by serializers only. Sets the artifact version used for querying or creating an artifact. Overrides the version returned by the ArtifactResolverStrategy.

String

None

FIND_LATEST_ARTIFACT

apicurio.registry.find-latest

Used by serializers only. Specifies whether the serializer tries to find the latest artifact in the registry for the corresponding group ID and artifact ID.

boolean

false

AUTO_REGISTER_ARTIFACT

apicurio.registry.auto-register

Used by serializers only. Specifies whether the serializer tries to create an artifact in the registry. The JSON Schema serializer does not support this feature.

boolean, boolean String

false

DEREFERENCE_SCHEMA

apicurio.registry.dereference-schema

Used by serializers and deserializers. Configures the SerDes to dereference the schema. After the schema is registered, this property instructs the SerDes to request the dereferenced schema from the server. It also instructs the serializer to dereference the schema before registering it in Apicurio Registry, but this is only supported for Avro.

boolean

false

AUTO_REGISTER_ARTIFACT_IF_EXISTS

apicurio.registry.auto-register.if-exists

Used by serializers only. Configures the behavior of the client when a conflict occurs while creating an artifact because the artifact already exists. Available values are FAIL, CREATE_VERSION, or FIND_OR_CREATE_VERSION.

String

FIND_OR_CREATE_VERSION

CHECK_PERIOD_MS

apicurio.registry.check-period-ms

Used by serializers and deserializers. Specifies how long to cache artifacts before auto-eviction (milliseconds). If set to zero, artifacts are fetched every time.

java.time.Duration, non-negative Number, or integer String

30000

RETRY_BACKOFF_MS

apicurio.registry.retry-backoff-ms

Used by serializers and deserializers. If the SerDes cannot retrieve a schema from the registry, they can retry a number of times. This configuration option controls the delay between the retry attempts (milliseconds).

java.time.Duration, non-negative Number, or integer String

300

RETRY_COUNT

apicurio.registry.retry-count

Used by serializers and deserializers. If the SerDes cannot retrieve a schema from the registry, they can retry a number of times. This configuration option controls the number of retry attempts.

non-negative Number, or integer String

3

FAULT_TOLERANT_REFRESH

apicurio.registry.fault-tolerant-refresh

Used by serializers and deserializers. If true, the SerDes log exceptions instead of throwing them when an error occurs while refreshing a schema in the cache. This is useful for production situations where a stale schema is better than completely failing schema resolution. This does not affect retry attempts, because the SerDes attempt retries before considering this flag.

boolean

false

BACKGROUND_REFRESH_ENABLED

apicurio.registry.background-refresh-enabled

Used by serializers and deserializers. If true, enables background refresh of expired cache entries following the "stale-while-revalidate" pattern. When an entry expires and a stale value exists, the cache returns the stale value immediately (non-blocking) and triggers an asynchronous refresh in the background. This prevents latency spikes in high-concurrency scenarios where multiple threads would otherwise block waiting for cache refresh. If no stale value exists (first fetch), the cache falls back to synchronous refresh behavior. When a background refresh fails, the cache logs errors and continues to serve stale values (similar to FAULT_TOLERANT_REFRESH behavior). This option complements FAULT_TOLERANT_REFRESH, which handles refresh failures rather than refresh latency.

boolean

false

BACKGROUND_REFRESH_EXECUTOR_THREADS

apicurio.registry.background-refresh.executor-threads

Used by serializers and deserializers when BACKGROUND_REFRESH_ENABLED is enabled. Specifies the number of threads in the background refresh executor pool. The executor uses a fixed thread pool with daemon threads to prevent blocking application shutdown. A value of 2-4 threads is typically sufficient for most workloads.

non-negative Number, or integer String

2

BACKGROUND_REFRESH_TIMEOUT_MS

apicurio.registry.background-refresh.timeout-ms

Used by serializers and deserializers when BACKGROUND_REFRESH_ENABLED is enabled. Specifies the maximum time (milliseconds) to wait for a background refresh operation to complete before timing out. If a background refresh exceeds this timeout, it is interrupted and the cache continues to serve the stale value until the next refresh attempt.

java.time.Duration, non-negative Number, or integer String

30000

USE_ID

apicurio.registry.use-id

Used by serializers and deserializers. Configures the SerDes to use the specified IdOption as the identifier for artifacts. Options are globalId and contentId. Instructs the serializer to write the specified ID to Kafka, and instructs the deserializer to use this ID to find the schema.

String

contentId

CANONICALIZE

apicurio.registry.canonicalize

Used by serializers only. Specifies whether the serializer canonicalizes schema content when looking up or creating an artifact version in the registry by content. When set to true, semantically equivalent schemas with different formatting match. When false, schema content is compared as-is. NOTE: In versions before 3.1.0, canonicalization was always attempted as a fallback. Starting with 3.1.0, you must explicitly set this property to true if you want canonicalized lookups.

boolean

false

Configuration to read and write registry artifacts in Kafka

The DefaultSchemaResolver uses the following properties to configure how artifact information is written to and read from Kafka.

Table 5. Configuration properties to read/write artifact information in Kafka
Constant Property Description Type Default

ENABLE_HEADERS

apicurio.registry.headers.enabled

Used by serializers and deserializers. Configures the SerDes to read and write the artifact identifier in Kafka message headers instead of in the message payload.

boolean

false

HEADERS_HANDLER

apicurio.registry.headers.handler

Used by serializers and deserializers. Fully qualified Java class name that implements HeadersHandler and writes and reads the artifact identifier to and from the Kafka message headers.

String

io.apicurio.registry.serde.headers.DefaultHeadersHandler

ID_HANDLER

apicurio.registry.id-handler

Used by serializers and deserializers. Fully qualified Java class name of a class that implements IdHandler and writes and reads the artifact identifier to and from the message payload. Defaults to a 4-byte format that includes the contentId in the message payload.

String

io.apicurio.registry.serde.Default4ByteIdHandler

Configuration for deserializer fall-back options

The DefaultSchemaResolver uses the following property to configure a fall-back provider for all deserializers.

Table 6. Configuration property for deserializer fall-back provider
Constant Property Description Type Default

FALLBACK_ARTIFACT_PROVIDER

apicurio.registry.fallback.provider

Used by deserializers only. Sets a custom implementation of FallbackArtifactProvider for resolving the artifact used for deserialization. FallbackArtifactProvider configures a fallback artifact to fetch from the registry if the lookup fails.

String

io.apicurio.registry.serde.fallback.DefaultFallbackArtifactProvider

The DefaultFallbackArtifactProvider uses the following properties to configure deserializer fall-back options:

Table 7. Configuration properties for deserializer fall-back options
Constant Property Description Type Default

FALLBACK_ARTIFACT_ID

apicurio.registry.fallback.artifact-id

Used by deserializers only. Sets the artifactId used as fallback for resolving the artifact used for deserialization.

String

None

FALLBACK_ARTIFACT_GROUP_ID

apicurio.registry.fallback.group-id

Used by deserializers only. Sets the groupId used as fallback for resolving the group used for deserialization.

String

None

FALLBACK_ARTIFACT_VERSION

apicurio.registry.fallback.version

Used by deserializers only. Sets the version used as fallback for resolving the artifact used for deserialization.

String

None

You can configure application properties as Java system properties or include them in the Quarkus application.properties file. For more details, see the Quarkus documentation.

Client serializer/deserializer types

When you use schemas in your Kafka client applications, you must choose which specific schema type to use, depending on your use case. Apicurio Registry provides SerDe Java classes for Apache Avro, JSON Schema, and Google Protobuf.

You can also use Kafka to implement custom serializer and deserializer classes, and use Apicurio Registry functions through the Apicurio Registry REST Java client.

Kafka application configuration for serializers/deserializers

To use the SerDe classes that Apicurio Registry provides in your Kafka application, set the correct configuration properties. The following Avro examples show how to configure a serializer in a Kafka producer application and how to configure a deserializer in a Kafka consumer application.

The following example shows a serializer configuration in a Kafka producer:

// Create the Kafka producer
private static Producer<Object, Object> createKafkaProducer() {
    Properties props = new Properties();

    // Configure standard Kafka settings
    props.putIfAbsent(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, SERVERS);
    props.putIfAbsent(ProducerConfig.CLIENT_ID_CONFIG, "Producer-" + TOPIC_NAME);
    props.putIfAbsent(ProducerConfig.ACKS_CONFIG, "all");

    // Use Apicurio Registry-provided Kafka serializer for Avro
    props.putIfAbsent(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
    props.putIfAbsent(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, AvroKafkaSerializer.class.getName());

    // Configure the Apicurio Registry location
    props.putIfAbsent(SerdeConfig.REGISTRY_URL, REGISTRY_URL);

    // Register the schema artifact if not found in the registry.
    props.putIfAbsent(SerdeConfig.AUTO_REGISTER_ARTIFACT, Boolean.TRUE);

    // Create the Kafka producer
    Producer<Object, Object> producer = new KafkaProducer<>(props);
    return producer;
}

The following example shows a deserializer configuration in a Kafka consumer:

// Create the Kafka consumer
private static KafkaConsumer<Long, GenericRecord> createKafkaConsumer() {
    Properties props = new Properties();

    // Configure standard Kafka settings
    props.putIfAbsent(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, SERVERS);
    props.putIfAbsent(ConsumerConfig.GROUP_ID_CONFIG, "Consumer-" + TOPIC_NAME);
    props.putIfAbsent(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, "true");
    props.putIfAbsent(ConsumerConfig.AUTO_COMMIT_INTERVAL_MS_CONFIG, "1000");
    props.putIfAbsent(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest");

    // Use Apicurio Registry-provided Kafka deserializer for Avro
    props.putIfAbsent(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
    props.putIfAbsent(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, AvroKafkaDeserializer.class.getName());

    // Configure the Apicurio Registry location
    props.putIfAbsent(SerdeConfig.REGISTRY_URL, REGISTRY_URL);

    // No other configuration needed because the schema globalId the deserializer uses is sent
    // in the payload. The deserializer extracts the globalId and uses it to look up the schema
    // from the registry.

    // Create the Kafka consumer
    KafkaConsumer<Long, GenericRecord> consumer = new KafkaConsumer<>(props);
    return consumer;
}
Additional resources

Avro SerDes configuration

You can configure the Kafka client serializer and deserializer (SerDes) classes for Apache Avro by using the configuration options in this section.

Apicurio Registry provides the following Kafka client SerDes classes for Avro:

  • io.apicurio.registry.serde.avro.AvroKafkaSerializer

  • io.apicurio.registry.serde.avro.AvroKafkaDeserializer

Avro serializer configuration

You can configure the Avro serializer class with the following options:

  • Apicurio Registry URL

  • Artifact resolver strategy

  • ID location

  • ID encoding

  • Avro datum provider

  • Avro encoding

ID location

The serializer passes the unique ID of the schema as part of the Kafka message so that consumers can use the correct schema for deserialization. The ID can be in the message payload or in the message headers. The default location is the message payload. To send the ID in the message headers, set the following configuration property:

props.putIfAbsent(SerdeConfig.ENABLE_HEADERS, "true")

The property name is apicurio.registry.headers.enabled.

ID encoding

You can customize how the SerDes encode the schema ID when they pass it in the Kafka message body. Set the apicurio.registry.id-handler configuration property to a class that implements the io.apicurio.registry.serde.IdHandler interface. Apicurio Registry provides the following implementations:

  • io.apicurio.registry.serde.Default4ByteIdHandler: Stores the ID as a 4-byte integer

  • io.apicurio.registry.serde.Legacy8ByteIdHandler: Stores the ID as an 8-byte long

Apicurio Registry represents the schema ID as a long, but for legacy reasons, or for compatibility with other registries or SerDe classes, you might want to use 4 bytes when sending the ID.

Avro datum provider

Avro provides different datum writers and readers to write and read data. Apicurio Registry supports three different types:

  • Generic

  • Specific

  • Reflect

The Apicurio Registry AvroDatumProvider class abstracts which type is used. The default is DefaultAvroDatumProvider.

You can set the following configuration options:

  • apicurio.registry.avro-datum-provider: Specifies a fully qualified Java class name of the AvroDatumProvider implementation, for example io.apicurio.registry.serde.avro.ReflectAvroDatumProvider

  • apicurio.registry.use-specific-avro-reader: Specifies whether to use a specific type when you use DefaultAvroDatumProvider. Set to true to enable this option.

Avro encoding

When you use Avro to serialize data, you can use the Avro binary encoding format to ensure that the data is encoded in as efficient a format as possible. Avro also supports encoding the data as JSON, which you can use to inspect the payload of each message, for example, for logging or debugging.

You can set the Avro encoding by configuring the apicurio.registry.avro.encoding property with a value of JSON or BINARY. The default is BINARY.

Avro writer schema validation

Before writing a record, the Avro serializer verifies that the schema resolved from Apicurio Registry structurally matches the schema of the record being serialized. The schemas are compared by Avro parsing canonical form, so differences in properties that do not affect the binary encoding, such as connect.* metadata added by Kafka Connect, are tolerated. A structural mismatch, for example a stale latest artifact version when apicurio.registry.find-latest is enabled without apicurio.registry.auto-register, would otherwise silently write field values under the wrong fields. When a mismatch is detected, serialization fails with a descriptive error. Validation applies when the serialized object carries its own schema, as Avro generic and specific containers do, or when the resolved schema is a record and the configured datum provider can derive the object’s schema, as with the reflect provider. Plain Java collections without an attached schema are not validated.

You can disable this validation by setting the apicurio.registry.avro.validate-writer-schema property to false. The default is true.

Avro deserializer configuration

You must configure the Avro deserializer class to match the following configuration settings of the serializer:

  • Apicurio Registry URL

  • ID encoding

  • Avro datum provider

  • Avro encoding

See the serializer section for these configuration options. The property names and values are the same.

The following options are not required when configuring the deserializer:

  • Artifact resolver strategy

  • ID location

The deserializer class can determine the values for these options from the message. The strategy is not required because the serializer is responsible for sending the ID as part of the message.

The deserializer determines the ID location by checking for the magic byte at the start of the message payload. If that byte is found, the deserializer reads the ID from the message payload by using the configured handler. If the magic byte is not found, the deserializer reads the ID from the message headers.

Avro SerDes and artifact references

When you work with Avro messages and a schema with nested records, Apicurio Registry registers a new artifact for each nested record. For example, the following TradeKey schema includes a nested Exchange schema:

{
  "namespace": "com.kubetrade.schema.trade",
  "type": "record",
  "name": "TradeKey",
  "fields": [
    {
      "name": "exchange",
      "type": "com.kubetrade.schema.common.Exchange"
    },
    {
      "name": "key",
      "type": "string"
    }
  ]
}

The nested Exchange schema is defined as follows:

{
  "namespace": "com.kubetrade.schema.common",
  "type": "enum",
  "name": "Exchange",
  "symbols" : ["GEMINI"]
}

When you use these schemas with Avro SerDes, Apicurio Registry creates two artifacts, one for the TradeKey schema and one for the Exchange schema. Whenever a message that uses the TradeKey schema is serialized or deserialized, both schemas are retrieved, allowing you to split your definitions into different files.

Custom logical type conversions for the Avro Kafka Connect converter

The io.apicurio.registry.utils.converter.AvroConverter Kafka Connect converter, provided by the apicurio-registry-utils-converter artifact, converts Kafka Connect logical types such as Decimal, Date, Time, and Timestamp to Avro logical types. You can register custom conversions for additional logical types, for example, Debezium micro-precision and nano-precision timestamp types, which have no built-in Avro mapping.

To add a custom conversion, implement the io.apicurio.registry.utils.converter.avro.ToAvroLogicalTypeConverter interface with the following methods:

  • kafkaConnectLogicalTypeName returns the Kafka Connect schema name to match, for example, io.debezium.time.MicroTimestamp.

  • avroLogicalType returns the Avro logical type to apply.

  • convert converts the field value.

Register the implementation through the Java ServiceLoader mechanism: package it in a JAR file with a META-INF/services/io.apicurio.registry.utils.converter.avro.ToAvroLogicalTypeConverter provider file that contains the implementation class name, and place the JAR file on the Kafka Connect worker classpath together with the converter. A custom conversion that declares the same Kafka Connect type name as a built-in conversion replaces the built-in conversion. Conversions apply in the Kafka Connect to Avro direction only.

JSON Schema SerDes configuration

You can configure the Kafka client serializer and deserializer (SerDes) classes for JSON Schema by using the configuration options in this section.

Apicurio Registry provides the following Kafka client SerDes classes for JSON Schema:

  • io.apicurio.registry.serde.jsonschema.JsonSchemaKafkaSerializer

  • io.apicurio.registry.serde.jsonschema.JsonSchemaKafkaDeserializer

Unlike Apache Avro, JSON Schema is not a serialization technology, but is instead a validation technology. As a result, configuration options for JSON Schema are different. For example, no encoding option exists, because data is always encoded as JSON.

JSON Schema serializer configuration

You can configure the JSON Schema serializer class as follows:

  • Apicurio Registry URL

  • Artifact resolver strategy

  • Schema validation

The only non-standard configuration property is JSON Schema validation, which is enabled by default. You can disable this by setting apicurio.registry.serde.validation-enabled to "false". For example:

props.putIfAbsent(SerdeConfig.VALIDATION_ENABLED, Boolean.FALSE)

JSON Schema deserializer configuration

You can configure the JSON Schema deserializer class as follows:

  • Apicurio Registry URL

  • Schema validation

  • Class for deserializing data

You must provide the location of Apicurio Registry so that the schema can be loaded. The other configuration is optional.

Deserializer validation only works if the serializer passes the global ID in the Kafka message, which only happens when you enable validation in the serializer.

Custom ObjectMapper

The JSON Schema serializer and deserializer classes provide a setObjectMapper method that you can use to supply a custom Jackson ObjectMapper, for example, to register additional Jackson modules or custom serializers. You must call setObjectMapper before you call configure, and instantiate the SerDes class yourself instead of naming it in the producer or consumer properties:

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

JsonSchemaKafkaSerializer<MessageBean> serializer = new JsonSchemaKafkaSerializer<>();
serializer.setObjectMapper(mapper);
serializer.configure(configProps, false);

KafkaProducer<String, MessageBean> producer =
        new KafkaProducer<>(configProps, new StringSerializer(), serializer);

When you do not set a custom mapper, the SerDes create a default ObjectMapper that ignores unknown properties during deserialization and omits null values during serialization.

JSON Schema SerDes and artifact references

The JSON Schema SerDes cannot discover the schema from the message payload, so you must register the schema artifact beforehand, and this also applies to artifact references.

If the $ref value is a URL, the SerDes try to resolve the referenced schema by using that URL. Validation then works as usual: the SerDes validate the data against the main schema and validate the nested value against the nested schema. The SerDes also support referencing artifacts in Apicurio Registry.

For example, the following citizen.json schema references the city.json schema:

{
 "$id": "https://example.com/citizen.schema.json",
 "$schema": "http://json-schema.org/draft-07/schema#",
 "title": "Citizen",
 "type": "object",
 "properties": {
   "firstName": {
     "type": "string",
     "description": "The citizen's first name."
   },
   "lastName": {
     "type": "string",
     "description": "The citizen's last name."
   },
   "age": {
     "description": "Age in years which must be equal to or greater than zero.",
     "type": "integer",
     "minimum": 0
   },
   "city": {
     "$ref": "city.json"
   }
 }
}

The city.json schema is defined as follows:

{
 "$id": "https://example.com/city.schema.json",
 "$schema": "http://json-schema.org/draft-07/schema#",
 "title": "City",
 "type": "object",
 "properties": {
   "name": {
     "type": "string",
     "description": "The city's name."
   },
   "zipCode": {
     "type": "integer",
     "description": "The zip code.",
     "minimum": 0
   }
 }
}

In this example, a given citizen has a city. Apicurio Registry creates a citizen artifact with a reference to the city artifact by using the name city.json. In the SerDes, when the citizen schema is fetched, the city schema is also fetched because it is referenced from the citizen schema. When you serialize or deserialize data, the SerDes use the reference name to resolve the nested schema, allowing validation against the citizen schema and the nested city schema.

Protobuf SerDes configuration

You can configure the Kafka client serializer and deserializer (SerDes) classes for Google Protobuf by using the configuration options in this section.

Apicurio Registry provides the following Kafka client SerDes classes for Protobuf:

  • io.apicurio.registry.serde.protobuf.ProtobufKafkaSerializer

  • io.apicurio.registry.serde.protobuf.ProtobufKafkaDeserializer

Protobuf serializer configuration

You can configure the Protobuf serializer class as follows:

  • Apicurio Registry URL

  • Artifact resolver strategy

  • ID location

  • ID encoding

  • Schema validation

Protobuf deserializer configuration

You must configure the Protobuf deserializer class to match the following configuration settings in the serializer:

  • Apicurio Registry URL

  • ID encoding

The configuration property names and values are the same as for the serializer.

The following options are not required when configuring the deserializer:

  • Artifact resolver strategy

  • ID location

The deserializer class can determine the values for these options from the message. The strategy is not required because the serializer is responsible for sending the ID as part of the message.

The deserializer determines the ID location by checking for the magic byte at the start of the message payload. If that byte is found, the deserializer reads the ID from the message payload by using the configured handler. If the magic byte is not found, the deserializer reads the ID from the message headers.

By default, the Protobuf deserializer deserializes to a DynamicMessage instance. To deserialize to your generated Protobuf message class instead, set the apicurio.registry.deserializer.value.return-class property to the generated class.

Deserializer fallback parsing

By default, a schema resolution failure causes deserialization to fail. When you configure a specific return class, you can enable an opt-in fallback that parses the message payload directly with the generated class when schema lookup or parsing fails, for example, during a registry outage or when a transitive import is missing:

apicurio.protobuf.fallback.on-schema-error=true
apicurio.registry.deserializer.value.return-class=com.example.MyProtobufMessage

The fallback never applies when the deserializer returns DynamicMessage instances. When the fallback activates, the deserializer logs a warning that includes the original error.

Confluent interoperability

By default, the Protobuf SerDes use the Apicurio Registry wire format, which writes a type reference into the message payload. You can configure the following options to exchange Protobuf messages with clients that use the Confluent wire format, which writes a message-index list instead:

Table 8. Configuration properties for Confluent interoperability
Configuration property Description Default

apicurio.registry.serde.send-type-ref

Serializer. Writes the Apicurio Registry type reference into the payload.

true

apicurio.registry.serde.send-indexes

Serializer. Writes the Confluent-style message-index list into the payload.

false

apicurio.registry.serde.read-type-ref

Deserializer. Reads the Apicurio Registry type reference from the payload.

true

apicurio.registry.serde.read-indexes

Deserializer. Reads the Confluent-style message-index list from the payload.

false

To produce messages that Confluent consumers can read, disable the type reference and enable message indexes on the serializer:

apicurio.registry.serde.send-type-ref=false
apicurio.registry.serde.send-indexes=true

To consume messages in the Confluent format, mirror the settings on the deserializer, and set the return class so that the deserializer produces your generated message type:

apicurio.registry.serde.read-type-ref=false
apicurio.registry.serde.read-indexes=true
apicurio.registry.deserializer.value.return-class=com.example.MyProtobufMessage
Enable the message indexes and disable the type reference together. The two payload prefixes are not compatible with each other. The default values preserve the Apicurio Registry wire format, so existing producer and consumer pairs require no changes.

Protobuf SerDes and artifact references

When you use a complex Protobuf message with an import statement, Apicurio Registry stores the imported Protobuf messages as separate artifacts. When Apicurio Registry gets the main schema to check a Protobuf message, it also retrieves the referenced schemas so that the full message schema can be checked and serialized.

For example, the following table_info.proto schema file includes the imported mode.proto schema file:

syntax = "proto3";
package sample;
option java_package = "io.api.sample";
option java_multiple_files = true;

import "sample/mode.proto";

message TableInfo {

 int32 winIndex = 1;
 Mode mode = 2;
 int32 min = 3;
 int32 max = 4;
 string id = 5;
 string dataAdapter = 6;
 string schema = 7;
 string selector = 8;
 string subscription_id = 9;
}

The mode.proto schema file is defined as follows:

syntax = "proto3";
package sample;
option java_package = "io.api.sample";
option java_multiple_files = true;

enum Mode {

MODE_UNKNOWN = 0;
RAW = 1;
MERGE = 2;
DISTINCT = 3;
COMMAND = 4;
}

In this example, Apicurio Registry stores two Protobuf artifacts, one for TableInfo and one for Mode. However, because Mode is part of TableInfo, whenever TableInfo is fetched to check a message in the SerDes, Mode is also returned as an artifact referenced by TableInfo.