A Kafka producer or consumer pointed at OCI Streaming fails at startup. It logs SaslAuthenticationException: Authentication failed, or Connection to node -1 ... could not be established. Broker may not be available, or it connects and then reports that the topic is not in metadata. Nearly every case comes down to five things: the bootstrap endpoint, the SASL username string, the auth token, the network path, or the stream name.
Symptom to cause
| Client error | Likely cause | Fix |
|---|---|---|
SaslAuthenticationException: Authentication failed | Wrong username format, wrong stream pool OCID, revoked or mistyped auth token, missing IAM policy | Rebuild the username string, create a new token, check the policy |
Authentication failed even though the credentials look right | Two streams with the same name in the same compartment (in different pools) | Use unique stream names, or the correct pool OCID |
Broker may not be available / timeouts | Wrong region endpoint, port 9092 blocked, private stream pool reached from outside its VCN | Use the pool's bootstrap server, open egress on 9092, check the route |
Topic ... not present in metadata / UNKNOWN_TOPIC_OR_PARTITION | Stream doesn't exist in that pool, or it isn't ACTIVE | Create the stream in the pool you authenticate against |
RecordTooLargeException | Message or batch larger than the service accepts | Cap max.request.size at 1 MB and keep messages small |
| Random disconnects under load | Connections recycled by the service's load balancing | Set retries on the producer |
Check 1: the stream pool and its bootstrap server
Every Kafka connection authenticates against a stream pool, not a single stream. Find the pool and read its Kafka settings:
# List stream pools in a compartment
oci streaming admin stream-pool list \
--compartment-id ocid1.compartment.oc1..aaaa... \
--query 'data[].{name:name, id:id, state:"lifecycle-state"}' --output table
# Show the pool's Kafka settings and endpoint details
oci streaming admin stream-pool get \
--stream-pool-id ocid1.streampool.oc1.iad.aaaa...
For a public pool, the bootstrap server is the regional endpoint on port 9092, for example streaming.us-ashburn-1.oci.oraclecloud.com:9092. If the pool has a private endpoint, use the bootstrap server shown in the pool's Kafka connection settings in the Console. Don't build it by hand. The pool's region must match the endpoint region. A client using the Phoenix endpoint for an Ashburn pool fails.
Then confirm the stream exists in that pool and is ACTIVE:
oci streaming admin stream list \
--compartment-id ocid1.compartment.oc1..aaaa... \
--stream-pool-id ocid1.streampool.oc1.iad.aaaa... \
--query 'data[].{name:name, state:"lifecycle-state", partitions:partitions}' --output table
The Kafka topic name is the stream name.
Check 2: the SASL username string
OCI Streaming uses SASL/PLAIN over TLS (SASL_SSL). Oracle documents the username as:
<tenancyName>/<domain>/<username>/<streamPoolOcid>
Oracle's own client examples also show the shorter <tenancyName>/<username>/<streamPoolOcid> form. Which one works depends on how your tenancy and users are set up (identity domains, legacy IDCS federation). If one form fails with a known-good token, try the other before you change anything else.
Get the exact pieces rather than typing them from memory:
# Tenancy name (not the OCID)
oci iam tenancy get --tenancy-id "$OCI_TENANCY" --query 'data.name' --raw-output
# Your user name as OCI stores it (often an email address)
oci iam user get --user-id ocid1.user.oc1..aaaa... --query 'data.name' --raw-output
Common mistakes:
- Using the tenancy OCID instead of the tenancy name.
- Using the stream OCID instead of the stream pool OCID.
- Leaving out the domain segment for a user in a non-default identity domain.
- A trailing space or newline in the username or token, picked up from a secret store or environment file.
Tip
Test the string with
kcatbefore you touch the application. Ifkcatcan list metadata, the problem is in the application's configuration.
kcat -L \
-b streaming.us-ashburn-1.oci.oraclecloud.com:9092 \
-X security.protocol=SASL_SSL \
-X sasl.mechanisms=PLAIN \
-X sasl.username='mytenancy/Default/svc-kafka@example.com/ocid1.streampool.oc1.iad.aaaa...' \
-X sasl.password='<auth-token>'
Check 3: the auth token and IAM policy
The password is an auth token for the user, not the Console password and not an API signing key.
oci iam auth-token list --user-id ocid1.user.oc1..aaaa... \
--query 'data[].{desc:description, state:"lifecycle-state", created:"time-created"}' --output table
A token is shown only once, when you create it. If nobody knows where the value came from, create a new one, update the secret, and delete the old one after the clients switch over. Each user can have only a few auth tokens, so check the current limit and delete unused ones first.
Warning
Deleting an auth token immediately breaks every client still using it. Roll the new token out to every producer and consumer first, then delete the old one.
The user, or a group they belong to, needs permission on the stream. Least-privilege statements look like this:
Allow group KafkaProducers to use stream-push in compartment Streaming
Allow group KafkaConsumers to use stream-pull in compartment Streaming
stream-push covers producing (PutMessages). stream-pull covers consuming, including cursors and consumer group commits. Admin tooling that lists or creates topics through the Kafka Admin API needs more, typically manage stream-family scoped to the right compartment. A missing policy usually shows up as an authentication failure, not a separate authorization error.
To rule out Kafka-client problems, test the same identity with the native API:
EP=$(oci streaming admin stream get --stream-id ocid1.stream.oc1.iad.aaaa... \
--query 'data."messages-endpoint"' --raw-output)
CURSOR=$(oci streaming stream cursor create-cursor \
--stream-id ocid1.stream.oc1.iad.aaaa... --partition 0 --type TRIM_HORIZON \
--endpoint "$EP" --query 'data.value' --raw-output)
oci streaming stream message get --stream-id ocid1.stream.oc1.iad.aaaa... \
--cursor "$CURSOR" --endpoint "$EP" --limit 5
If this works with the CLI profile of the same user but Kafka fails, the problem is the username string or the token.
Check 4: the network path
# Can we reach the broker port at all?
nc -vz streaming.us-ashburn-1.oci.oraclecloud.com 9092
# Does TLS complete?
openssl s_client -connect streaming.us-ashburn-1.oci.oraclecloud.com:9092 -brief </dev/null
| Client location | What to check |
|---|---|
| OCI instance in a private subnet, public pool | NAT gateway or service gateway route, egress rule for TCP 9092 |
| OCI instance, private pool | Same VCN or a routed/peered VCN, NSG on the pool's private endpoint allows TCP 9092 from the client |
| On-premises | Firewall egress to 9092, proxy that doesn't intercept TLS, FastConnect/VPN routes for private pools |
| OKE pod | Node subnet egress, pod network policy, DNS resolution of the bootstrap name |
A private pool's endpoint can't be reached from the internet. A laptop on the corporate VPN without a route to that VCN times out. It won't get an authentication error.
Check 5: client settings that fit the service
Oracle's recommended Java settings:
properties.put("bootstrap.servers", "streaming.us-ashburn-1.oci.oraclecloud.com:9092");
properties.put("security.protocol", "SASL_SSL");
properties.put("sasl.mechanism", "PLAIN");
properties.put("sasl.jaas.config",
"org.apache.kafka.common.security.plain.PlainLoginModule required "
+ "username=\"mytenancy/Default/svc-kafka@example.com/ocid1.streampool.oc1.iad.aaaa...\" "
+ "password=\"" + authToken + "\";");
// Producer
properties.put("retries", 5); // transient errors and connection recycling
properties.put("max.request.size", 1024 * 1024); // 1 MB
// Consumer
properties.put("max.partition.fetch.bytes", 1024 * 1024);
Streaming doesn't implement every Kafka feature. Kafka Streams, log compaction, transactions, idempotent producers and dynamic partition addition aren't supported. A client with enable.idempotence=true or a transactional ID fails or misbehaves. Recent Kafka Java clients turn idempotence on by default, so set enable.idempotence=false explicitly. Check the current Streaming limits for per-partition throughput and message size before you size partitions.
Prevent it next time
- Store the full SASL username and the token together in OCI Vault. Load both from there and trim whitespace.
- Use a dedicated service user per application, with
stream-pushorstream-pullscoped to the stream's compartment. - Track auth token age and rotate tokens on a schedule, with an overlap window so clients never lose access.
- Keep stream names unique per compartment, so the Kafka-side name always maps to one stream.
- Add a
kcat -Lsmoke test to the deployment pipeline that runs against the real pool before the app starts.