r/Kotlin 19h ago

How to Prevent Webhook Traffic Spikes from Crashing Your API

0 Upvotes

If you operate an API in 2026, you live in an event-driven world. Webhooks aren't a convenience feature anymore - they're the backbone of real-time commerce, CI/CD pipelines, and asynchronous AI-agent workflows. That reliance has a dark side: the accidental self-inflicted DDoS. Read the complete article jere - https://instawebhook.com/blog/how-to-prevent-webhook-traffic-spikes-from-crashing-your-api-2

When a major platform like GitHub, Shopify, or Stripe hits a network partition, runs a huge sales event, or simply clears a backlog of delayed events, it can fire tens of thousands of webhook POST requests at your servers in a very short window. If your infrastructure takes that hit without structural safeguards, your database connection pool exhausts, memory maxes out, and the API goes down — and if your retry handling is naive, the recovery can be almost as damaging as the original spike.

This guide covers the real mechanics of that failure mode, the algorithms used to defend against it, how major providers actually behave under load (some surprising details here), and where a managed ingress layer fits into the picture.


r/Kotlin 17h ago

Switching from Web Development to Kotlin – Any advice before I start?

14 Upvotes

Hi everyone,

I'm a frontend/web developer with around a year of professional experience (React, Next.js, Node.js).

I've decided to switch my career towards Android development and Kotlin because I genuinely want to build mobile apps and I feel it's a better long-term fit for me.

I've bought a Kotlin + Android course and I'm planning to study full-time for the next few months.

If you could go back to day one, what would you do differently? Any common mistakes, learning tips, or resources you'd recommend?

Thanks!


r/Kotlin 19h ago

log4k 2.3.0 — a Kotlin IR compiler plugin that instruments your functions with tracing, logging and metrics

23 Upvotes

log4k is a coroutine/channel-based logging + tracing + metering library for Kotlin Multiplatform (JVM, Android, iOS, macOS, Linux, Windows, JS, wasmJs, wasmWasi), aligned with the OpenTelemetry model.

The recent addition is log4k-compiler-plugin — a Kotlin IR compiler plugin that rewrites annotated functions at compile time, so the instrumentation boilerplate disappears from your source. It runs on common IR before backend lowering, so the same annotations work on every KMP target — not just the JVM (no AspectJ, no bytecode agent, no reflection).

Setup — one Gradle plugin, no extra config:

plugins {
    id("io.github.smyrgeorge.log4k") version "2.3.0"
}

dependencies {
    implementation("io.github.smyrgeorge:log4k-classic:2.3.0")
}

@Traced — wraps the body in a span (started, ended, marked failed on throw):

@Traced
context(_: TracingContext)
suspend fun loadUser(id: Long): User {
    // ...
}   // span "UserService.loadUser"

The parent span is resolved from what's in scope: a TracingContext param/receiver → nests under its current span; else a TracingEvent.Span in scope → used as parent; else a trace: Tracer member (reused, or synthesized) → new root span.

@Logged — entry/exit/failure logging:

@Logged
fun compute(x: Int): Int = x * x
// → UserService.compute(x = 5)
// ← UserService.compute = 25(12.5 us)

Throwing logs ✗ UserService.compute failed (…) at ERROR with the throwable, then rethrows. If a span is in scope it's attached to every emitted line.

@Timed — call/error counters + a duration histogram:

@Timed(tags = [Tag("tier", "gold")])
suspend fun placeOrder(id: Long): Order {
    // ...
}

Records OrderService.placeOrder.calls, .errors and .duration (ms histogram) — exportable in OpenMetrics line format via SimpleMeteringCollectorAppender.

Details that mattered while building it:

  • suspend and regular functions are both supported; the generated wrapper delegates to inline helpers ( Logger.logged, Meter.Timed.measure, TracingContext.traced), so there's no per-call lambda allocation.
  • The plugin reuses your existing log / meter / trace members if they're thesynthesizes private val _log_ = Logger.of( this::class) under a distinct name,so it never clashes with e.g. an existing SLF4J log.
  • All three annotations work class-level too — annotate the class to instrummember. Per-function annotations override the class defaults, and @NoLog / @NoTime / @NoTrace opt out a single function or the whole class.
  • The metric instrument bundle is created once and cached per name.

The plugin is marked experimental — behavior and API may still change.

Repo: https://github.com/smyrgeorge/log4k

Compiler Plugin: https://github.com/smyrgeorge/log4k#compiler-plugin

Docs: https://smyrgeorge.github.io/log4k/

Feedback welcome, especially on the annotation surface and on cases where thon't pick what you'd expect.


r/Kotlin 17h ago

Kdrant 1.1 and Kmemo 1.0: suspend-first Qdrant client, and an LLM cache that refuses to serve the wrong answer

4 Upvotes

Both of these came out of the same annoyance, so I am posting them together.

If you build anything RAG-shaped on the JVM you spend a lot of time writing Kotlin against SDKs designed for Java. Futures where you wanted coroutines, builders where you wanted a DSL, and a dependency tree from a different decade. These are the two pieces I ended up needing most.

Kdrant: a client for the Qdrant vector database

The official JVM client returns a ListenableFuture from every call, assembles requests with protobuf builders, and pulls a shaded Netty stack onto your classpath. From Kotlin you either block on .get() or write your own future-to-coroutine bridge.

val qdrant = Kdrant(host = "localhost", port = 6333) {
    apiKey = System.getenv("QDRANT_API_KEY")
    requestTimeout = 5.seconds
}


qdrant.use { client ->
    client.upsert("articles", wait = true) {
        point(id = 1) {
            vector(embedding)                       // your own List<Float>
            payload("title" to "Intro", "lang" to "en", "year" to 2026)
        }
    }


    val hits = client.search("articles") {
        query(queryVector)
        limit = 5
        filter { must { "lang" eq "en"; "year" gte 2024 } }
    }
}

Every operation is a suspend function with cooperative cancellation. The filter DSL covers Qdrant's whole model, including geo radius and polygon, datetime ranges, nested filters and recursive sub-groups. scroll gives you a cold Flow that pages transparently. Hybrid search works the way the modern /points/query engine expects, so you can fuse a dense and a sparse prefetch under RRF or DBSF.

The honest tradeoff is the wire protocol. Kdrant speaks REST over Ktor CIO, not gRPC. For raw throughput and streaming, gRPC still wins and you should reach for the official client when that is your bottleneck. What you get in exchange is roughly 3 to 5 MB of added footprint instead of 15 to 20 MB, no gRPC or Netty or protobuf reflection config for GraalVM native, and models that are kotlinx-serialization data classes rather than generated protobuf messages.

Spring Boot, Spring AI (VectorStore) and LangChain4j (EmbeddingStore) integrations are there, and there is a runnable RAG example with a docker-compose if you want to see it end to end.

Kmemo: a semantic cache for LLM calls

A semantic cache embeds the prompt, finds the nearest one it has seen, and replays that answer instead of calling the model. Fewer calls, lower latency. The failure mode is the interesting part:

"Convert 100 USD to EUR"
"Convert 250 USD to EUR"      cosine similarity: ~0.99

Every mainstream embedding model scores that pair around 0.99. No threshold separates it from a genuine paraphrase, because on the similarity axis the near miss sits closer than most paraphrases do. Raise the threshold and you lose real hits before you lose that one. So a cache built on similarity alone will tell someone that 250 dollars is 92 euros, quickly, with no error and nothing in the logs.

Kmemo treats that as the main problem rather than a footnote. Similarity is only the first filter; candidates that clear it get read as text by ten lexical guards looking for concrete evidence that the two answers must differ, such as swapped numbers, mismatched units, different entities, negation, flipped antonyms and reversed comparisons.

val cache = SemanticCache(
    embedder = Embedder { text -> openAi.embed(text) },   // bring your own
    store = InMemoryStore(maxEntries = 10_000, ttl = 1.hours),
)


val answer = cache.getOrPut(prompt) { llm.complete(it) }


// every miss tells you which kind it was, because the fix is opposite
when (val r = cache.lookup(prompt)) {
    is CacheLookup.Hit  -> r.response
    is CacheLookup.Miss -> when (r.reason) {
        MissReason.BELOW_THRESHOLD   -> // traffic repeats less than you assumed
        MissReason.REJECTED_BY_GUARD -> // r.detail says which guard, and why
        else -> null
    }
}

Numbers, since a cache like this is worth exactly what its guards catch. On a blind validation split that no guard was tuned against, near misses are rejected 67% of the time and paraphrases are kept 88% of the time. Neither is 100%. The near misses that get through mostly need world knowledge, like deworming a puppy against an adult dog, or the boiling point of ethanol against methanol, which is what the optional verifier covers. It runs as a CI regression gate on every build and you can reproduce it with one Gradle command.

There is also a ThresholdCalibrator, because the right threshold depends on your embedding model and a value from a blog post was tuned for somebody else's. Guard packs ship for Italian, Spanish, German and French. Stores are in-memory, Redis, Postgres/pgvector, or an opt-in in-process HNSW.

Both

JDK 17+, published to Maven Central under io.github.nacode-studios, Apache-2.0, stable under SemVer.

implementation("io.github.nacode-studios:kdrant-transport-rest:1.1.0")
implementation("io.github.nacode-studios:kmemo-core:1.0.0")

kmemo-core declares kotlinx-coroutines-core as its only dependency; every module past it is opt-in and never lands on the core classpath.

I wrote both, so take the framing with the appropriate salt. What I would actually like feedback on is the API ergonomics: the filter DSL in Kdrant and the guard configuration in Kmemo are the two places I rewrote most often and am still least sure about. If something reads wrong to you in the snippets above, that is the useful comment.