Skip to content
Apache Kafka kf security 4 min read

Securing Spring Kafka Clients

Connecting a Spring Boot application to a hardened Kafka cluster means moving beyond the default PLAINTEXT listener and speaking SASL_SSL: TLS for transport encryption plus SASL for authentication. Spring for Apache Kafka exposes every underlying client property through spring.kafka.*, so the entire handshake is declarative. The hard part in production is not the configuration itself but keeping credentials out of source control. This page shows a complete, working setup and the secrets-management practices that keep it safe.

How the security protocol fits together

Kafka clients pick a security protocol that combines two independent concerns. Choosing the wrong combination is the most common cause of broker connection failures, so it helps to be explicit about what each one buys you.

ProtocolEncryption (TLS)Authentication (SASL)Typical use
PLAINTEXTNoNoLocal dev only
SSLYesVia mutual TLS (client cert)Cert-based identity
SASL_PLAINTEXTNoYesInside a trusted network
SASL_SSLYesYesStandard for managed/production clusters

For managed services (Confluent Cloud, Amazon MSK, Aiven) and any internet-facing broker, SASL_SSL is the expected choice. The client encrypts the channel with TLS, validates the broker certificate against a truststore, and then authenticates with a SASL mechanism such as PLAIN, SCRAM-SHA-512, or OAUTHBEARER.

Configuring application.yml

Spring Boot maps the well-known consumer/producer/admin settings to first-class keys and passes anything else through spring.kafka.properties. The security.protocol, SASL mechanism, and JAAS login module belong under the shared properties block so they apply to producers, consumers, and the admin client alike.

spring:
  kafka:
    bootstrap-servers: ${KAFKA_BOOTSTRAP_SERVERS}
    security:
      protocol: SASL_SSL
    ssl:
      trust-store-location: file:${KAFKA_TRUSTSTORE_PATH}
      trust-store-password: ${KAFKA_TRUSTSTORE_PASSWORD}
      trust-store-type: PKCS12
    properties:
      sasl.mechanism: SCRAM-SHA-512
      sasl.jaas.config: >-
        org.apache.kafka.common.security.scram.ScramLoginModule required
        username="${KAFKA_USERNAME}"
        password="${KAFKA_PASSWORD}";
      # Verify the broker hostname against its certificate (leave enabled in prod)
      ssl.endpoint.identification.algorithm: https
    consumer:
      group-id: orders-service
      auto-offset-reset: earliest
    producer:
      acks: all

A few details matter for correctness:

  • trust-store-location must use the file: (or classpath:) resource prefix. A bare path is silently ignored.
  • The sasl.jaas.config value is a single line terminated by a semicolon. YAML’s >- folded scalar lets you wrap it for readability while collapsing newlines into spaces.
  • For PLAIN instead of SCRAM, swap the login module to org.apache.kafka.common.security.plain.PlainLoginModule.

Never set ssl.endpoint.identification.algorithm to an empty string to “fix” a TLS error. Disabling hostname verification opens you to man-in-the-middle attacks. Fix the certificate or truststore instead.

Keeping secrets out of source

The YAML above contains no literal passwords — every sensitive value is an ${ENV_VAR} placeholder resolved at startup. Spring’s relaxed binding reads these from environment variables, so the same artifact runs unchanged across environments. Inject them from your platform’s secret store rather than committing them.

For Kubernetes, mount secrets as environment variables:

env:
  - name: KAFKA_PASSWORD
    valueFrom:
      secretKeyRef:
        name: kafka-credentials
        key: password

For HashiCorp Vault, Spring Cloud Vault fetches secrets at boot and exposes them as ordinary properties, so ${KAFKA_PASSWORD} resolves transparently:

spring:
  config:
    import: "vault://secret/orders-service"
  cloud:
    vault:
      uri: ${VAULT_ADDR}
      authentication: KUBERNETES
      kubernetes:
        role: orders-service

Verifying the connection

A tiny ApplicationRunner confirms authentication works without waiting for real traffic. It asks the admin client for the cluster ID — a request that only succeeds once the SASL_SSL handshake completes.

import org.apache.kafka.clients.admin.AdminClient;
import org.springframework.boot.ApplicationRunner;
import org.springframework.context.annotation.Bean;
import org.springframework.kafka.core.KafkaAdmin;

@Bean
ApplicationRunner verifyKafka(KafkaAdmin kafkaAdmin) {
    return args -> {
        try (var admin = AdminClient.create(kafkaAdmin.getConfigurationProperties())) {
            String clusterId = admin.describeCluster().clusterId().get();
            System.out.println("Connected to Kafka cluster: " + clusterId);
        }
    };
}

Output:

Connected to Kafka cluster: 7vQ2k9xLToK1mF8aPzN0wg

If credentials are wrong you will instead see an org.apache.kafka.common.errors.SaslAuthenticationException; a bad truststore surfaces as an SSLHandshakeException. These distinct errors make it easy to tell an authentication problem from an encryption problem.

Best Practices

  • Use SASL_SSL for every non-local environment and prefer SCRAM-SHA-512 or OAUTHBEARER over PLAIN, which sends the password (TLS-wrapped but still recoverable broker-side).
  • Resolve all credentials from environment variables or a secret manager (Vault, AWS Secrets Manager, Kubernetes Secrets); never commit passwords or keystores to Git.
  • Keep ssl.endpoint.identification.algorithm set to https so the client verifies the broker’s hostname.
  • Distribute only the truststore (public CA chain) to clients; reserve keystores for mutual-TLS scenarios and rotate both on a schedule.
  • Scope each service to its own SCRAM user with least-privilege ACLs so a leaked credential cannot read or write unrelated topics.
  • Add a startup health check (as shown above) so misconfiguration fails fast and loudly rather than at first message send.
Last updated June 1, 2026
Was this helpful?