JEP 401 landed in JDK 28 preview. `LocalDate` and the primitive wrappers become value classes when the flag is on. `==` stops comparing identity, starts comparing fields. No compile error, just different behavior at the same line.
Kyryl
@code-with-kyryl.bsky.social
Senior Java Engineer; all my links: https://code-with-kyryl.carrd.co/
Canva rebuilt session revocation without a shared cache tier: 16-byte records in S3, sliced into 30-minute objects across a 12h window, gateways rebuild a local sorted-array index. Cache memory down 87.5%. Third team I have tracked landing on this shape this month.
Anthropic's own study: 97% of Claude Code's per-call permission prompts got rubber-stamped. Auto mode caught 89% of harmful actions in testing. Humans clicking through prompts caught 13.6%. That is why it is becoming the default on Aug 14.
Cross-team API changes are the real latency in microservices, and it is measured in weeks, not milliseconds. The bottleneck was never the wire.
Two services writing to the same table is a distributed monolith wearing a microservice badge. Every cost of the split, none of the isolation.
1/ You set Kafka to exactly-once, and a payment still got processed twice.
Your Kafka retention.ms says 1 hour. The data is still on disk 6 hours later. Not a bug. Kafka deletes whole segments, never individual records.
Adding one consumer to a Kafka group can freeze every consumer for seconds. That is eager rebalancing, and it is still the default. Here is what actually happens, and the one config that fixes it.
CDC turns your database into an event source. And it is the cleanest way to share data without sharing a schema. No dual-write, no "remember to also emit an event" discipline spread across every service.
The database went down for ten minutes. The restart storm it triggered lasted forty. One `/actuator/health` endpoint wired to both the liveness and readiness probe. That is the whole bug.
CREATE INDEX without CONCURRENTLY locks out writes for the entire build. On a large live table that is not a migration. It is a multi-minute write outage for everything touching it.
A path variable in your metrics tag will quietly bankrupt your Prometheus backend. It passes code review. It compiles. It works, for a while.
`@Transactional(readOnly = true)` gets slapped on every query method as a habit, treated as a marker for the next reader. It is not decorative. It changes Hibernate's flush mode.
Called my own `@Async` method from inside the same class. No error. No async. Ran inline like a plain method call.
Retry aspect and `@Transactional` aspect on the same method. No `@Order` on either. Spring is free to nest them any way it likes, and it does not ask you first.
Prototype bean field-injected into a singleton. Created once. Spring resolves the constructor arg one time, the singleton holds that instance forever. State leaks between requests until someone finally traces it back.
A test with a 30-argument constructor call. Somewhere in there, the one field under test. Good luck spotting it without counting positions.
Good enough integration tests were fine until AI started writing them. The authoring cost is gone, so the bar moves from "good enough" to exact.
`@Enumerated(ORDINAL)` is the JPA default, and the default silently corrupts data. It stores the enum's position: 0, 1, 2. Add a constant mid-list later and every old row decodes wrong. No error. No failed query.
Every team builds the same cost bot. A Lambda on a Cost Explorer cron, a CloudTrail lookup, a Slack ping for whoever tagged the runaway resource. It works. You just maintain every line of it yourself. AWS just shipped the managed version.
Every `POST /search` you have shipped is a safe, idempotent read wearing a write's costume. You used POST because GET will not carry a body and your filters were too big for the URL. HTTP finally has the right verb. It is QUERY.
I created three Kafka quizzes and added them to my tech blog, one per level. A straightforward way to test your knowledge and see where the gaps actually are. 👇
`UserService` + `UserServiceImpl`. One interface, one impl, forever. The reason this pattern exists died around Spring Boot 2.0, and most of us never updated the habit.
Your event fires. Your listener queries the DB for the entity just saved. It finds nothing. Add a log line — it starts working. Remove it — breaks again. That's not a race condition. That's `@EventListener`.
MapStruct's default config quietly ships bugs. Add a field to a DTO, forget to map it, it's null in prod. The compiler knew and said nothing, because the default reporting policy is WARN. Three things I put on every mapper.
Confluence docs for your service are outdated the moment you push the code. Not a discipline problem. Structural. There's no mechanism keeping external docs in sync with code.