KafkaException
Apache Kafka
ClassLoaders
PlainLoginModule
Java Development Kit (JDK)

KafkaException jdk.internal.loader.ClassLoaders can’t find org.apache.kafka.common.security.plain.PlainLoginModule

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

This Kafka exception usually means the client is configured to use SASL/PLAIN, but the runtime classpath does not contain the Kafka classes that implement that login module. In practice, the failure is almost always caused by a missing or mismatched Kafka client dependency rather than by the JAAS string itself.

What the Error Means

The class org.apache.kafka.common.security.plain.PlainLoginModule is part of the Kafka client libraries. When Kafka reads a SASL/PLAIN configuration like:

properties
sasl.mechanism=PLAIN
sasl.jaas.config=org.apache.kafka.common.security.plain.PlainLoginModule required username="user" password="secret";

it tries to load that class at runtime. If the class loader cannot find it, Kafka throws the exception.

So the problem is not that the class name is wrong. The problem is that the JVM cannot see the jar that contains it.

The First Thing to Check

If you are writing a Java application, verify that kafka-clients is actually present at runtime.

Maven:

xml
1<dependency>
2    <groupId>org.apache.kafka</groupId>
3    <artifactId>kafka-clients</artifactId>
4    <version>3.7.0</version>
5</dependency>

Gradle:

gradle
dependencies {
    implementation "org.apache.kafka:kafka-clients:3.7.0"
}

If the dependency is declared with the wrong scope, excluded transitively, or missing from the packaged application image, the class will not be available.

A Minimal Producer Example

The following configuration is valid only if the Kafka client jar is present:

java
1import java.util.Properties;
2import org.apache.kafka.clients.producer.KafkaProducer;
3import org.apache.kafka.clients.producer.ProducerRecord;
4
5public class ProducerExample {
6    public static void main(String[] args) {
7        Properties props = new Properties();
8        props.put("bootstrap.servers", "localhost:9092");
9        props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
10        props.put("value.serializer", "org.apache.kafka.common.serialization.StringSerializer");
11        props.put("security.protocol", "SASL_PLAINTEXT");
12        props.put("sasl.mechanism", "PLAIN");
13        props.put(
14            "sasl.jaas.config",
15            "org.apache.kafka.common.security.plain.PlainLoginModule required username=\"user\" password=\"secret\";"
16        );
17
18        try (KafkaProducer<String, String> producer = new KafkaProducer<>(props)) {
19            producer.send(new ProducerRecord<>("demo", "key", "value"));
20            producer.flush();
21        }
22    }
23}

If this fails with the class loader message, the next suspect is the runtime packaging, not the code syntax.

Common Real Causes

A frequent case is using Spring Boot or another framework where dependency versions are managed for you, then overriding one Kafka-related artifact manually. That can lead to an inconsistent classpath.

Another common case is shading or packaging a fat jar incorrectly. The dependency is present in the build but missing from the final executable artifact.

Containers also cause surprises. A local IDE run may work because the IDE includes the dependency, while the Docker image fails because the final copy step omitted some jars.

How to Debug It

Start with dependency inspection:

Maven:

bash
mvn dependency:tree | grep kafka

Gradle:

bash
./gradlew dependencies --configuration runtimeClasspath

Those commands help confirm whether kafka-clients is present and whether multiple versions are fighting each other.

If the dependency tree looks correct, inspect the actual packaged artifact or container image. The JVM only cares about what is present at runtime.

Do Not Misdiagnose JAAS Syntax

JAAS syntax errors can happen, but they usually produce different failures. If the exception explicitly says the class loader cannot find PlainLoginModule, the highest-probability diagnosis is still classpath or packaging.

That is why changing quotes, semicolons, or property names blindly often wastes time. Verify the dependency and runtime layout first.

Common Pitfalls

The biggest mistake is assuming a compile-time dependency guarantees runtime availability. In Java packaging, that is not always true.

Another problem is mixing incompatible Kafka versions across framework starters and direct dependencies. That can create subtle runtime failures even if the project compiles.

A third issue is setting sasl.mechanism=PLAIN without also including the right security protocol and credentials. That does not cause this specific class-not-found error, but it often appears immediately afterward once the classpath issue is fixed.

Summary

  • 'PlainLoginModule is provided by Kafka client libraries, not by the JDK.'
  • This exception usually means kafka-clients is missing or not packaged at runtime.
  • Check Maven or Gradle dependency trees before changing JAAS syntax.
  • Inspect the final jar or container image, not just the local IDE classpath.
  • After fixing the classpath, verify the rest of the SASL/PLAIN configuration normally.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.