Spring Boot
Kafka
Json Deserialization
Trusted Packages
Programming

Spring Boot / Kafka Json Deserialization - Trusted Packages

Master System Design with Codemia

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

Introduction

Spring Kafka can turn a JSON message body into a Java object automatically, but that convenience comes with a safety question: which classes should the consumer be allowed to create? The trusted.packages setting answers that question by restricting deserialization targets to specific package names instead of allowing arbitrary class resolution.

Why Trusted Packages Exist

JsonDeserializer can use type headers or configured target types to decide which Java class should receive the incoming JSON. If the consumer trusts every package, a malicious or misconfigured producer could try to steer deserialization toward an unexpected class. Spring Kafka therefore exposes a trusted-package list as a guardrail.

In practice, this means you should think about event contracts, not just message format. If your service only consumes com.example.orders.OrderCreatedEvent, there is usually no reason to trust unrelated packages.

The common property name in Spring Boot is spring.json.trusted.packages under the consumer properties block. A narrow configuration is the safest default.

Configuring Trusted Packages In Properties

For a Boot application, the simplest setup is property-based configuration:

properties
spring.kafka.consumer.value-deserializer=org.springframework.kafka.support.serializer.JsonDeserializer
spring.kafka.consumer.properties.spring.json.trusted.packages=com.example.events,org.acme.shared
spring.kafka.consumer.properties.spring.json.value.default.type=com.example.events.OrderCreatedEvent

The YAML form is the same setting expressed hierarchically:

yaml
1spring:
2  kafka:
3    consumer:
4      value-deserializer: org.springframework.kafka.support.serializer.JsonDeserializer
5      properties:
6        spring.json.trusted.packages: com.example.events,org.acme.shared
7        spring.json.value.default.type: com.example.events.OrderCreatedEvent

The value.default.type line is useful when the producer does not send type headers and the consumer always expects the same payload class.

Consuming A Typed Event

Assume the topic carries a simple order event:

java
1package com.example.events;
2
3public class OrderCreatedEvent {
4    private String orderId;
5    private long amountCents;
6
7    public String getOrderId() {
8        return orderId;
9    }
10
11    public void setOrderId(String orderId) {
12        this.orderId = orderId;
13    }
14
15    public long getAmountCents() {
16        return amountCents;
17    }
18
19    public void setAmountCents(long amountCents) {
20        this.amountCents = amountCents;
21    }
22}

With the trusted package configured, the listener can receive the typed object directly:

java
1import com.example.events.OrderCreatedEvent;
2import org.springframework.kafka.annotation.KafkaListener;
3import org.springframework.stereotype.Service;
4
5@Service
6public class OrderConsumer {
7
8    @KafkaListener(topics = "orders", groupId = "billing")
9    public void consume(OrderCreatedEvent event) {
10        System.out.println("Received order " + event.getOrderId());
11    }
12}

This is the ideal outcome: the consumer uses a clear event type, and deserialization is limited to the package that owns that type.

Programmatic Configuration

If you need more control, configure the deserializer as a bean. This is useful when different consumers in the same application need different target types or trust boundaries.

java
1import java.util.Map;
2import org.apache.kafka.clients.consumer.ConsumerConfig;
3import org.apache.kafka.common.serialization.StringDeserializer;
4import org.springframework.context.annotation.Bean;
5import org.springframework.kafka.core.ConsumerFactory;
6import org.springframework.kafka.core.DefaultKafkaConsumerFactory;
7import org.springframework.kafka.support.serializer.JsonDeserializer;
8
9@Bean
10ConsumerFactory<String, OrderCreatedEvent> consumerFactory() {
11    JsonDeserializer<OrderCreatedEvent> valueDeserializer =
12        new JsonDeserializer<>(OrderCreatedEvent.class);
13    valueDeserializer.addTrustedPackages("com.example.events");
14
15    return new DefaultKafkaConsumerFactory<>(
16        Map.of(
17            ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092",
18            ConsumerConfig.GROUP_ID_CONFIG, "billing"
19        ),
20        new StringDeserializer(),
21        valueDeserializer
22    );
23}

One important detail from the Spring Kafka API is that you should configure the deserializer consistently. Mixing direct setter calls with a later configure call can lead to confusing results because not every combination is applied the way people expect.

When * Is Acceptable And When It Is Not

You will often see this during local debugging:

properties
spring.kafka.consumer.properties.spring.json.trusted.packages=*

That disables the package filter and can make an early prototype work quickly. The tradeoff is that you lose an explicit safety boundary. In a production service, * is usually too broad unless the environment is tightly controlled and you fully understand the risk.

If your application consumes only a small set of event types, trusting the specific package names is both safer and easier to reason about during incident response.

Common Pitfalls

  • Using * in production because it makes deserialization errors disappear without fixing the underlying contract problem.
  • Trusting the wrong package after moving model classes into another module or namespace.
  • Forgetting that missing or mismatched type headers can still break deserialization even when the trusted package list is correct.
  • Setting a typed listener method while the producer sends a payload for a different class shape.
  • Mixing property-based and programmatic deserializer configuration without understanding which values take effect.

Summary

  • Trusted packages limit which Java packages Spring Kafka may use during JSON deserialization.
  • Narrow package lists are the right default for production consumers.
  • 'spring.json.value.default.type helps when type headers are absent and only one event type is expected.'
  • '* is convenient for local experiments but weakens the deserialization boundary.'
  • Correct deserialization still depends on aligned payloads, headers, and event classes.

Course illustration
Course illustration

All Rights Reserved.