Add the kafka-basics module

This commit is contained in:
2026-08-29 09:47:25 +05:30
commit 3a682e496e
28 changed files with 1403 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
[Module README](../README.md) · [Producing →](02-producing.md)
# 1. The on-ramp, and the dependency that is not the one you remember
Under Spring Boot 3 you added `org.springframework.kafka:spring-kafka` and got a
`KafkaTemplate`. Under Boot 4 you get this:
```
No qualifying bean of type 'org.springframework.kafka.core.KafkaTemplate<java.lang.String,
com.ankurm.kafkabasics.OrderEvent>' available
```
Boot 4 split the auto-configurations out of `spring-boot-autoconfigure` into per-technology
modules. Kafka's now lives in `spring-boot-kafka`, package
`org.springframework.boot.kafka.autoconfigure`, and a bare `spring-kafka` dependency does not
bring it. The application compiles, the context starts, and there is simply no template.
```xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-kafka</artifactId>
</dependency>
```
That is the fix, and the same shape applies elsewhere: `spring-boot-starter-amqp` for RabbitMQ,
`spring-boot-starter-restclient` for `RestClient.Builder`. The rule of thumb for Boot 4 is that
if you are depending on a library directly rather than through a Boot starter, you are probably
missing its auto-configuration.
## What the starter actually gives you
| Bean | Comes from | Notes |
|---|---|---|
| `KafkaTemplate<?, ?>` | `KafkaAutoConfiguration` | typed by your injection point |
| `ProducerFactory` / `ConsumerFactory` | `KafkaAutoConfiguration` | built from `spring.kafka.*` |
| `KafkaListenerContainerFactory` | `KafkaAnnotationDrivenConfiguration` | what `@KafkaListener` binds to |
| `KafkaAdmin` | `KafkaAutoConfiguration` | creates `NewTopic` beans at startup |
`KafkaAdmin` is worth knowing about early: declare a `NewTopic` bean and Boot creates the topic
on startup with the partition count and replication factor you asked for. It will **not** change
an existing topic's partition count, so a `NewTopic` bean that disagrees with the cluster is
silently ignored rather than applied.
## Versions
Boot 4.1.1 manages Spring Kafka **4.1.1** and kafka-clients **4.2.1**. Note the second one:
Maven Central has kafka-clients 4.3.1, and overriding the managed version to reach it is the
kind of change that works until it does not. Let the BOM decide.
[Producing &rarr;](02-producing.md)

View File

@@ -0,0 +1,67 @@
[&larr; The on-ramp](01-the-on-ramp.md) &middot; [Module README](../README.md) &middot; [Serialisation &rarr;](03-serialisation.md)
# 2. Producing, and the return value everybody throws away
[`OrderProducer`](../src/main/java/com/ankurm/kafkabasics/OrderProducer.java) has three send
methods because there are exactly three useful answers to "when do I find out this failed".
```java
public void sendAndForget(OrderEvent event) {
this.template.send(TOPIC, event.orderId(), event); // returns a CompletableFuture. Ignored.
}
```
`KafkaTemplate.send` is asynchronous and returns a `CompletableFuture<SendResult<K, V>>`.
Discarding it discards the only notification you will get. The method returns normally, the
record may never reach the broker, and nothing in your logs says so — the producer's own retry
and expiry messages are at `WARN` under `org.apache.kafka`, which most applications turn down.
This is the most common way to lose messages in a Spring Kafka application, and it looks like
correct code.
```java
this.template.send(TOPIC, event.orderId(), event)
.whenComplete((result, ex) -> { /* log, meter, compensate */ });
```
Handle the future, or block on it when the caller genuinely must not proceed without a durable
write:
```java
SendResult<String, OrderEvent> result = this.template.send(record).get();
result.getRecordMetadata().partition(); // where it landed
result.getRecordMetadata().offset(); // and at what offset
```
Blocking costs a network round trip plus the replication acknowledgement, so it belongs at the
edges of a system, not inside a loop.
## Durability comes from defaults you did not set
Spring Boot sets **nothing** on the producer beyond bootstrap servers and serializers. Here is
the effective configuration, printed by
[`EffectiveConfigTest`](../src/test/java/com/ankurm/kafkabasics/EffectiveConfigTest.java) and
committed at [`docs/output/effective-config.txt`](output/effective-config.txt):
```
property kafka-clients default set by Spring Boot
acks all -
enable.idempotence true -
retries 2147483647 -
max.in.flight.requests.per.connection 5 -
delivery.timeout.ms 120000 -
```
Since Kafka 3.0 the client defaults are `acks=all` and `enable.idempotence=true`, so a stock
Boot application already has a durable, deduplicating producer. Two consequences:
- **You do not need to set `acks=all`.** It is already on.
- **An old runbook that sets `acks=1` or `retries=0` is now a downgrade.** Those lines were
written when the defaults were weaker, and deleting them makes the system safer, which is the
opposite of how configuration usually ages.
`delivery.timeout.ms` at two minutes is the one worth revisiting: it is the total budget for a
send including retries, and a request that exceeds it fails permanently. If your future never
completes, that is the clock you are waiting on.
[Serialisation &rarr;](03-serialisation.md)

View File

@@ -0,0 +1,82 @@
[&larr; Producing](02-producing.md) &middot; [Module README](../README.md) &middot; [Keys and partitions &rarr;](04-keys-and-partitions.md)
# 3. Serialisation: two Jackson families, and the one your IDE will suggest is the wrong one
Kafka moves `byte[]`. Everything else is a `Serializer` and a `Deserializer`, and under Spring
Boot 4 there is a fork in the road that no tutorial mentions yet.
Spring Kafka 4.1 ships **two complete JSON families**:
| Class | Jackson | Mapper type |
|---|---|---|
| `JsonSerializer` / `JsonDeserializer` / `JsonSerde` | 2.x | `com.fasterxml.jackson.databind.ObjectMapper` |
| `JacksonJsonSerializer` / `JacksonJsonDeserializer` / `JacksonJsonSerde` | 3.x | `tools.jackson.databind.json.JsonMapper` |
Spring Boot 4 is a Jackson 3 application. `spring-boot-starter-jackson` brings
`tools.jackson.core:jackson-databind` 3.1.5. But the class named `JsonSerializer` — the one
every existing example configures, and the one autocomplete offers first — is the Jackson 2 one.
It does not fail at startup. It fails on the first payload containing a `java.time` value:
```
org.apache.kafka.common.errors.SerializationException: Can't serialize data
[OrderEvent[orderId=o-1, ..., placedAt=2026-08-29T10:15:30Z]] for topic [orders]
Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException:
Java 8 date/time type `java.time.Instant` not supported by default:
add Module "com.fasterxml.jackson.datatype:jackson-datatype-jsr310" to enable handling
```
The message tells you to add a Jackson 2 module, and doing so works — and leaves you running two
Jackson stacks, one for your HTTP layer and one for your messaging layer, with independent
configuration. The better fix is one word:
```yaml
spring:
kafka:
producer:
value-serializer: org.springframework.kafka.support.serializer.JacksonJsonSerializer
consumer:
value-deserializer: org.springframework.kafka.support.serializer.JacksonJsonDeserializer
```
Jackson 3 handles `java.time` with no module and no configuration, and `BigDecimal` keeps its
scale through a round trip — both asserted in
[`SerialisationTest`](../src/test/java/com/ankurm/kafkabasics/SerialisationTest.java):
```
{"orderId":"o-1","customerId":"c-1","amount":10.00,"placedAt":"2026-08-29T10:15:30Z"}
```
`amount` is `10.00`, not `10.0` and not `"10.00"`. Use `BigDecimal` for money and this survives;
use `double` and it does not.
## The type header
The three-argument `serialize(topic, headers, data)` overload writes a `__TypeId__` header
naming the class:
```java
headers.lastHeader("__TypeId__") -> "com.ankurm.kafkabasics.OrderEvent"
```
The consumer reads it and builds that class. Which is convenient and is also a remote-code-
selection primitive, so the deserializer refuses any class outside its trusted packages:
```
... is not in the trusted packages: [java.util, java.lang]
```
Three ways out, in descending order of preference:
1. `spring.json.trusted.packages: com.ankurm.kafkabasics` — an allow-list of your own packages.
2. `spring.json.value.default.type` — ignore the header, always build this class. Best when
producer and consumer are owned by different teams, because it makes the consumer's contract
its own decision rather than the producer's.
3. `spring.json.trusted.packages: "*"` — do not.
Option 2 has a second benefit worth stating plainly: the `__TypeId__` header couples the two
services by **fully qualified class name**. Renaming or moving a class in the producer breaks
every consumer that trusts the header, at runtime, with no compile-time warning. `type.mapping`
(a logical name to class map on both sides) is the version of this that survives a refactor.
[Keys and partitions &rarr;](04-keys-and-partitions.md)

View File

@@ -0,0 +1,51 @@
[&larr; Serialisation](03-serialisation.md) &middot; [Module README](../README.md) &middot; [Consuming &rarr;](05-consuming.md)
# 4. Keys and partitions: the ordering guarantee in disguise
Kafka orders records **within a partition**. Not within a topic. So the key is not a label — it
decides which records are ordered with respect to each other, and it is the most consequential
line in a producer.
Same key, same partition, forever:
```
key partition murmur2 & 0x7fffffff % 3 Math.abs(murmur2) % 3
o-1 0 0 0
o-2 0 0 2
o-3 1 1 1
o-4 1 1 1
o-5 2 2 0
o-6 1 1 1
```
From [`docs/output/key-to-partition.txt`](output/key-to-partition.txt), produced against a real
broker by
[`KeysAndPartitionsTest`](../src/test/java/com/ankurm/kafkabasics/KeysAndPartitionsTest.java).
The default partitioner is `murmur2` of the **serialized key bytes**, masked positive, modulo
the partition count. Note the third and fourth columns: `& 0x7fffffff` and `Math.abs` disagree
on two of six keys, because clearing the sign bit is not the same number as negating it. If you
reimplement the partitioner to predict placement — for a test, for a migration, for a routing
table — `Math.abs` gives you the right answer about half the time, which is the worst available
failure mode.
## Three consequences
**A null key is not a key.** Records without one are spread across partitions by the sticky
partitioner, so nothing about their relative order is guaranteed. If two events describe the same
entity and you did not key them, they can be processed out of order by different consumers.
**Adding partitions repartitions every key.** The modulus changes, so `o-2` moves. Anything
relying on per-key ordering loses it across the resize for records still in flight. Pick the
partition count with room to grow; changing it later is a data-ordering event, not a capacity
knob.
**Key cardinality is your parallelism ceiling.** Keying by `customerId` when one customer is 40%
of traffic gives you a hot partition that no amount of consumer scaling fixes, because one
partition is consumed by exactly one member of a group.
Choosing the key is choosing what must stay ordered. Order events by `orderId` if operations on
one order must not overtake each other; by `customerId` if that is true across a customer's
orders. Those are different systems.
[Consuming &rarr;](05-consuming.md)

View File

@@ -0,0 +1,59 @@
[&larr; Keys and partitions](04-keys-and-partitions.md) &middot; [Module README](../README.md) &middot; [Acknowledgement &rarr;](06-acknowledgement.md)
# 5. Consuming
```java
@KafkaListener(topics = "orders", groupId = "orders-basic")
public void onOrder(@Payload OrderEvent event,
@Header(KafkaHeaders.RECEIVED_PARTITION) int partition,
@Header(KafkaHeaders.OFFSET) long offset) { ... }
```
There is no poll loop, no offset commit and no rebalance listener, because the container owns
all three. That is the actual value Spring Kafka adds over the plain client, and it is worth
knowing what it is doing on your behalf — see [chapter 6](06-acknowledgement.md).
## The single most common "my listener never fires"
`auto.offset.reset` decides where a consumer group with **no committed offset** starts. The
kafka-clients default is `latest`: skip everything already in the topic. So a brand-new group
attached to a topic full of records reads nothing, and the listener you just wrote never runs.
```yaml
spring:
kafka:
consumer:
auto-offset-reset: earliest
```
Boot does not change this default; the value in this module's `application.yaml` does. It only
applies when there is no committed offset — once the group has committed once, this setting is
irrelevant, which is why the symptom disappears the first time it works and never comes back.
The other reliable cause is a `groupId` mismatch between the annotation and
`spring.kafka.consumer.group-id`. The annotation wins, and a typo there creates a brand-new group
that then hits the paragraph above.
## Concurrency and what it can and cannot buy
`spring.kafka.listener.concurrency` creates that many consumers in the group, each on its own
thread. The ceiling is the partition count: with 3 partitions, `concurrency: 10` gives you three
working consumers and seven idle ones. There is no configuration that makes two threads consume
one partition, because that would break the ordering guarantee from
[chapter 4](04-keys-and-partitions.md).
## Headers worth knowing
| Constant | What it carries |
|---|---|
| `KafkaHeaders.RECEIVED_KEY` | the producer's key, through the key deserializer |
| `KafkaHeaders.RECEIVED_PARTITION` | which partition this came from |
| `KafkaHeaders.OFFSET` | the offset within that partition |
| `KafkaHeaders.RECEIVED_TIMESTAMP` | broker or producer timestamp, per topic config |
| `__TypeId__` | the class name the producer wrote — see [chapter 3](03-serialisation.md) |
Declare the key as `required = false` unless you are certain every record has one. A `null` key
on a `required = true` header parameter fails the conversion, and the failure arrives as a
deserialization-time error rather than as a null.
[Acknowledgement &rarr;](06-acknowledgement.md)

View File

@@ -0,0 +1,79 @@
[&larr; Consuming](05-consuming.md) &middot; [Module README](../README.md) &middot; [Testing &rarr;](07-testing.md)
# 6. Acknowledgement, and a property that is not where you look for it
The container's default `AckMode` is **`BATCH`**: commit the offsets of the whole `poll()` batch
after the listener has returned for every record in it. Confirmed at runtime by
[`EffectiveConfigTest`](../src/test/java/com/ankurm/kafkabasics/EffectiveConfigTest.java):
```
=== listener container ===
ackMode BATCH
groupId orders-basic
```
That gives at-least-once delivery. A crash after processing and before the commit redelivers the
batch, which is why your listener must be idempotent — and why
[the error-handling article](https://ankurm.com/spring-kafka-4-1-error-handling-dlt-retry-topics/)
is a necessary sequel rather than an optional one.
## Where `enable.auto.commit` actually lives
This one is worth the detour, because every debugging session about commits starts in the wrong
place.
- The **kafka-clients default** for `enable.auto.commit` is `true`.
- **Spring Boot does not set it.** It is absent from `ConsumerFactory.getConfigurationProperties()`,
before the containers start and after.
- **`ConsumerFactory.isAutoCommit()` returns `true`** on a stock Boot 4.1 application, because it
reads that same absent key and falls back to the client default.
- And yet **no consumer auto-commits**, because `ListenerConsumer.determineAutoCommit` checks
whether the factory config contains the key and, when it does not, calls
`setProperty("enable.auto.commit", "false")` on the **per-container `Properties`** handed to
`createConsumer`.
So the shared factory never learns, its public accessor answers the opposite of the truth, and
the real value lives in an override map you cannot reach from application code. All four of
those statements are asserted in the test, including the counter-intuitive one:
```java
assertThat(this.consumerFactory.isAutoCommit()).isTrue();
```
If you ever need to know whether a running consumer auto-commits, read the consumer's own
metrics or its startup log line, not the factory.
## Manual acknowledgement
```yaml
spring:
kafka:
listener:
ack-mode: MANUAL
```
```java
@KafkaListener(topics = "orders")
void onOrder(OrderEvent event, Acknowledgment acknowledgment) {
process(event);
acknowledgment.acknowledge();
}
```
Nothing commits until `acknowledge()` runs, which is what you want when the work must be durable
before the offset moves. Two things to know:
- Asking for `MANUAL` while auto-commit is genuinely enabled is an
`IllegalStateException` at container start. It does not happen on a stock configuration —
despite `isAutoCommit()` returning `true` — because `determineAutoCommit` sets the container
property to `false` before the check runs. [`ManualAckTest`](../src/test/java/com/ankurm/kafkabasics/ManualAckTest.java)
pins that down, because it is exactly the kind of interaction that would otherwise be a
surprise in production.
- **An `Acknowledgment` you forget to call stalls the partition.** Not immediately — the
container keeps polling until `max.poll.records` of un-acknowledged records accumulate. So the
symptom is a consumer that works for a while and then stops, which reads like a broker problem.
`MANUAL_IMMEDIATE` commits synchronously on the consumer thread instead of at the end of the
batch. It is slower and it is the right choice when redelivery is genuinely expensive.
[Testing &rarr;](07-testing.md)

View File

@@ -0,0 +1,84 @@
[&larr; Acknowledgement](06-acknowledgement.md) &middot; [Module README](../README.md)
# 7. Testing without installing Kafka
Two options, both real brokers, and the choice is less obvious than it looks.
## `@EmbeddedKafka` — a real broker inside the JVM
```java
@SpringBootTest
@EmbeddedKafka(topics = "orders", partitions = 3)
class KeysAndPartitionsTest { ... }
```
`spring-kafka-test` starts `EmbeddedKafkaKraftBroker` — the actual Apache Kafka broker classes,
in KRaft mode, in-process. No ZooKeeper, no container, no daemon. It binds a random port and
exposes it as `${spring.embedded.kafka.brokers}`:
```yaml
spring:
kafka:
bootstrap-servers: ${spring.embedded.kafka.brokers}
```
It starts in about three seconds and needs nothing installed, which is why every transcript in
this module came from it and why `./scripts/run-all.sh` works on a machine with no Docker.
## Testcontainers — the image you actually deploy
```java
@TestConfiguration(proxyBeanMethods = false)
public class TestcontainersConfiguration {
@Bean
@ServiceConnection
KafkaContainer kafkaContainer() {
return new KafkaContainer(DockerImageName.parse("apache/kafka:4.1.0"));
}
}
```
`@ServiceConnection` registers the container's bootstrap servers as the application's, which
removes the `@DynamicPropertySource` block that older examples all carry.
**Two coordinates changed recently and both will bite you:**
```xml
<!-- NOT org.testcontainers:kafka, which stopped at 1.21.4 -->
<dependency>
<groupId>org.testcontainers</groupId>
<artifactId>testcontainers-kafka</artifactId>
<scope>test</scope>
</dependency>
```
Testcontainers 2.x prefixed every module artifact with `testcontainers-`, and Boot 4.1.1 imports
`testcontainers-bom` 2.0.5, which manages only the new names. Using the old coordinate fails
with a Maven error that does not mention the rename:
```
'dependencies.dependency.version' for org.testcontainers:kafka:jar is missing
```
The class moved too: use `org.testcontainers.kafka.KafkaContainer` (Apache Kafka, KRaft), not
the older `org.testcontainers.containers.KafkaContainer` (Confluent images, ZooKeeper).
## Which to use
| | `@EmbeddedKafka` | Testcontainers |
|---|---|---|
| startup | ~3s | ~10s, plus image pull |
| needs Docker | no | yes |
| broker version | the client library's | whatever image you name |
| TLS, SASL, quotas, partitions | not modelled | real |
Use `@EmbeddedKafka` for the bulk of a suite and Testcontainers for the handful of tests where
the difference between "the broker classes" and "the broker you deploy" matters. The
[`TestcontainersConfiguration`](../src/test/java/com/ankurm/kafkabasics/TestcontainersConfiguration.java)
in this module is compiled but not exercised by `run-all.sh`, because the machine that
regenerates `docs/output/` has no Docker daemon &mdash; which is itself the argument for keeping
both paths.
[Module README](../README.md)

View File

@@ -0,0 +1,71 @@
=== producer ===
property kafka-clients default set by Spring Boot
--------------------------------------------------------------------------------------------------------
acks all -
enable.idempotence true -
retries 2147483647 -
max.in.flight.requests.per.connection 5 -
linger.ms 5 -
batch.size 16384 -
compression.type none -
delivery.timeout.ms 120000 -
=== consumer ===
property kafka-clients default set by Spring Boot
--------------------------------------------------------------------------------------------------------
auto.offset.reset latest earliest
enable.auto.commit true -
max.poll.records 500 -
max.poll.interval.ms 300000 -
session.timeout.ms 45000 -
heartbeat.interval.ms 3000 -
isolation.level read_uncommitted read_uncommitted
partition.assignment.strategy [class org.apache.kafka.clients.consumer.RangeAssignor, class org.apache.kafka.clients.consumer.CooperativeStickyAssignor] -
=== listener container ===
=== listener container ===
ackMode BATCH
groupId orders-basic
concurrency
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.828 s -- in com.ankurm.kafkabasics.EffectiveConfigTest
[INFO] Running com.ankurm.kafkabasics.SerialisationTest
JacksonJsonSerializer -> {"orderId":"o-1","customerId":"c-1","amount":10.00,"placedAt":"2026-08-29T10:15:30Z"}
[INFO] Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.227 s -- in com.ankurm.kafkabasics.SerialisationTest
[INFO] Running com.ankurm.kafkabasics.KeysAndPartitionsTest
=== key -> partition, 3 partitions ===
key partition murmur2 & 0x7fffffff % 3 Math.abs(murmur2) % 3
o-1 0 0 0
o-2 0 0 2
o-3 1 1 1
o-4 1 1 1
o-5 2 2 0
o-6 1 1 1
2026-08-29T09:44:16.725+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-1 partition=0 offset=0
2026-08-29T09:44:16.726+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-2 partition=0 offset=1
2026-08-29T09:44:16.726+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-1 partition=0 offset=2
2026-08-29T09:44:16.726+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-3 partition=1 offset=0
2026-08-29T09:44:16.726+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-4 partition=1 offset=1
2026-08-29T09:44:16.727+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-6 partition=1 offset=2
2026-08-29T09:44:16.727+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-5 partition=2 offset=0
2026-08-29T09:44:16.746+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=e-1 partition=0 offset=3
2026-08-29T09:44:16.749+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=e-2 partition=1 offset=3
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.532 s -- in com.ankurm.kafkabasics.KeysAndPartitionsTest
[INFO] Running com.ankurm.kafkabasics.ManualAckTest
Bootstrap metadata: BootstrapMetadata(records=[ApiMessageAndVersion(FeatureLevelRecord(name='metadata.version', featureLevel=30) at version 0), ApiMessageAndVersion(FeatureLevelRecord(name='eligible.leader.replicas.version', featureLevel=1) at version 0), ApiMessageAndVersion(FeatureLevelRecord(name='group.version', featureLevel=1) at version 0), ApiMessageAndVersion(FeatureLevelRecord(name='share.version', featureLevel=1) at version 0), ApiMessageAndVersion(FeatureLevelRecord(name='streams.version', featureLevel=1) at version 0), ApiMessageAndVersion(FeatureLevelRecord(name='transaction.version', featureLevel=2) at version 0)], metadataVersionLevel=30, source=format command)
Formatting metadata directory /tmp/kafka-14668657738428164299/combined_0_0 with metadata.version 4.3-IV0.
2026-08-29T09:44:17.569+05:30 INFO 67 --- [kafka-basics] [ main] com.ankurm.kafkabasics.ManualAckTest : Starting ManualAckTest using Java 25.0.4.1 with PID 67 (started by tender-adoring-cori in /tmp/work/smd/kafka-basics)
2026-08-29T09:44:17.570+05:30 INFO 67 --- [kafka-basics] [ main] com.ankurm.kafkabasics.ManualAckTest : The following 1 profile is active: "test"
2026-08-29T09:44:17.780+05:30 INFO 67 --- [kafka-basics] [ main] com.ankurm.kafkabasics.ManualAckTest : Started ManualAckTest in 0.503 seconds (process running for 6.368)
2026-08-29T09:44:18.617+05:30 INFO 67 --- [kafka-basics] [ntainer#0-0-C-1] com.ankurm.kafkabasics.OrderConsumer : received orderId=o-1 partition=0 offset=0
[INFO] Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.919 s -- in com.ankurm.kafkabasics.ManualAckTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 8, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 9.869 s
[INFO] Finished at: 2026-08-29T09:44:20+05:30
[INFO] ------------------------------------------------------------------------

View File

@@ -0,0 +1,7 @@
key partition murmur2 & 0x7fffffff % 3 Math.abs(murmur2) % 3
o-1 0 0 0
o-2 0 0 2
o-3 1 1 1
o-4 1 1 1
o-5 2 2 0
o-6 1 1 1

View File

@@ -0,0 +1 @@
JacksonJsonSerializer -> {"orderId":"o-1","customerId":"c-1","amount":10.00,"placedAt":"2026-08-29T10:15:30Z"}

View File

@@ -0,0 +1,4 @@
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 3.828 s -- in com.ankurm.kafkabasics.EffectiveConfigTest
Tests run: 4, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.227 s -- in com.ankurm.kafkabasics.SerialisationTest
Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.532 s -- in com.ankurm.kafkabasics.KeysAndPartitionsTest
Tests run: 1, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 1.919 s -- in com.ankurm.kafkabasics.ManualAckTest