sku0x20/stopgap

Built for developers who value transparency, performance, and simplicity.

Kotlin

0

942 commits

updated Sep 27, 2026

See the code

See what people are saying

README

Stopgap

Nothing is more permanent than a temporary solution. If you're building something quick, keep it cheap: minimal to put together, and just as easy to take apart later.

A small, compile-time Kotlin toolkit for building HTTP services on Helidon Core (formerly Helidon SE).

Why Stopgap

Big frameworks are convenient until you need to understand what they're doing. Startup runs through reflection and classpath scanning. The call path from socket to handler goes through layers you never wrote. A BOM decides which versions of your libraries you're allowed to use, and an "HTTP framework" ends up choosing your database access, validation, and application architecture for you.

Stopgap takes the opposite approach.

  • You own the control flow. You write main(). You build the WebServer. Nothing runs that you can't find by following calls from main().
  • Nothing is hidden. There's no runtime reflection, no proxies, and no classpath scanning. KSP generates ordinary Kotlin that you can read, set a breakpoint in, and step through.
  • An HTTP toolkit is only an HTTP toolkit. Stopgap is a thin layer over Helidon's web server. It brings no database layer, no validation, and no JSON library. You pick those yourself.
  • No BOM. Every module is published and versioned on its own. You declare Helidon yourself and choose its version.
  • Tests with as little magic as possible. Integration tests start a real server around an endpoint you construct. End-to-end tests run your actual Docker image.

Testing is first class

Stopgap came out of necessity, when even Helidon didn't provide the kind of testing support it needed. The toolkit grew from there, but testing stayed at the center, and Stopgap itself is built test-first.

No colored functions, so no test ceremony. There's no runBlocking, no runTest, no test dispatchers, and no virtual time. Endpoints are ordinary blocking code, so tests are ordinary blocking code.

Integration and end-to-end tests are kept apart. Most frameworks blur the two. The Gradle plugin sets up intTest and e2eTest as separate suites, each with its own job.

Integration tests via @WebserverTest start a minimal Helidon server with endpoint and required dependencies. As a rule of thumb, never fake what you're testing, it's integration testing, not mock testing. So the server is never mocked: requests travel over real HTTP, through the generated routing, to endpoint under test. Nothing is static, so integration tests can run in parallel. In other frameworks, int tests usually try to fit both e2e tests and integration tests, so they mostly have static state shared across tests.

End-to-end tests: docker image as a black box. The E2E suite builds your Docker image, boots it on a Docker network, and tests it from the outside. It's the exact image you deploy, JVM or native. What you test is what you ship. E2E tests usually run sequentially.

Why not Ktor?

Ktor is built on coroutines, which means colored functions: suspend spreads through your code, and you have to think about dispatchers. Stopgap runs on Helidon, whose web server was rewritten from scratch for virtual threads. You write plain blocking code, like Go, and the JVM schedules it. There's no suspend, no dispatchers, and no reactive engine converted to green threads underneath. As structured concurrency matures in Java, you can adopt it directly.

Why not http4k?

http4k is an abstraction over many servers, and you learn its model (lenses, filters). Its style is heavily functional: handlers are functions, composed with filters. Stopgap is plain, everyday Kotlin. Its tests usually call the app in memory, skipping the server and HTTP entirely. Stopgap's integration tests always go through a real server. Routing is generated at compile time as plain Kotlin you can read and step through.

Modules

Every module is optional.

ModuleWhat it gives you
irCompile-time dependency wiring with @Creates
helidon-extensions@Endpoint routing, parameter binding, content negotiation, and authentication
helidon-testJUnit extensions for in-process integration tests and Docker E2E tests
gradle-pluginThin-jar packaging, Docker image tasks, and intTest/e2eTest suites

Getting started

Requires JDK 25+ and Kotlin 2.4. See app/build.gradle.kts for a complete setup.

// settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}
// build.gradle.kts
plugins {
    id("dev.sku20.stopgap") version "3.1.0"
}

dependencies {
    implementation("dev.sku20.stopgap:ir:3.1.0")
    ksp("dev.sku20.stopgap:ir:3.1.0")
    implementation("dev.sku20.stopgap:helidon-extensions:3.1.0")
    ksp("dev.sku20.stopgap:helidon-extensions:3.1.0")

    testImplementation("dev.sku20.stopgap:helidon-test:3.1.0")
}

Examples

The app module is a working example of every feature. It's more than documentation: it's also the integration suite for Stopgap itself. Every feature in it runs through the real code generation, and its tests prove it works. The examples can't go stale, because if they break, the build fails. Tests as documentation, the way TDD intends.

License

MIT


Built for developers who value transparency, performance, and simplicity.

dependency-injection
graalvm
helidon
http
kotlin
ksp
microservices
native-image
no-reflection
rest-api
tdd
testing
virtual-threads

sku0x20/stopgap

Built for developers who value transparency, performance, and simplicity.

Kotlin

0

942 commits

updated Sep 27, 2026

See the code

See what people are saying

README

Stopgap

Nothing is more permanent than a temporary solution. If you're building something quick, keep it cheap: minimal to put together, and just as easy to take apart later.

A small, compile-time Kotlin toolkit for building HTTP services on Helidon Core (formerly Helidon SE).

Why Stopgap

Big frameworks are convenient until you need to understand what they're doing. Startup runs through reflection and classpath scanning. The call path from socket to handler goes through layers you never wrote. A BOM decides which versions of your libraries you're allowed to use, and an "HTTP framework" ends up choosing your database access, validation, and application architecture for you.

Stopgap takes the opposite approach.

  • You own the control flow. You write main(). You build the WebServer. Nothing runs that you can't find by following calls from main().
  • Nothing is hidden. There's no runtime reflection, no proxies, and no classpath scanning. KSP generates ordinary Kotlin that you can read, set a breakpoint in, and step through.
  • An HTTP toolkit is only an HTTP toolkit. Stopgap is a thin layer over Helidon's web server. It brings no database layer, no validation, and no JSON library. You pick those yourself.
  • No BOM. Every module is published and versioned on its own. You declare Helidon yourself and choose its version.
  • Tests with as little magic as possible. Integration tests start a real server around an endpoint you construct. End-to-end tests run your actual Docker image.

Testing is first class

Stopgap came out of necessity, when even Helidon didn't provide the kind of testing support it needed. The toolkit grew from there, but testing stayed at the center, and Stopgap itself is built test-first.

No colored functions, so no test ceremony. There's no runBlocking, no runTest, no test dispatchers, and no virtual time. Endpoints are ordinary blocking code, so tests are ordinary blocking code.

Integration and end-to-end tests are kept apart. Most frameworks blur the two. The Gradle plugin sets up intTest and e2eTest as separate suites, each with its own job.

Integration tests via @WebserverTest start a minimal Helidon server with endpoint and required dependencies. As a rule of thumb, never fake what you're testing, it's integration testing, not mock testing. So the server is never mocked: requests travel over real HTTP, through the generated routing, to endpoint under test. Nothing is static, so integration tests can run in parallel. In other frameworks, int tests usually try to fit both e2e tests and integration tests, so they mostly have static state shared across tests.

End-to-end tests: docker image as a black box. The E2E suite builds your Docker image, boots it on a Docker network, and tests it from the outside. It's the exact image you deploy, JVM or native. What you test is what you ship. E2E tests usually run sequentially.

Why not Ktor?

Ktor is built on coroutines, which means colored functions: suspend spreads through your code, and you have to think about dispatchers. Stopgap runs on Helidon, whose web server was rewritten from scratch for virtual threads. You write plain blocking code, like Go, and the JVM schedules it. There's no suspend, no dispatchers, and no reactive engine converted to green threads underneath. As structured concurrency matures in Java, you can adopt it directly.

Why not http4k?

http4k is an abstraction over many servers, and you learn its model (lenses, filters). Its style is heavily functional: handlers are functions, composed with filters. Stopgap is plain, everyday Kotlin. Its tests usually call the app in memory, skipping the server and HTTP entirely. Stopgap's integration tests always go through a real server. Routing is generated at compile time as plain Kotlin you can read and step through.

Modules

Every module is optional.

ModuleWhat it gives you
irCompile-time dependency wiring with @Creates
helidon-extensions@Endpoint routing, parameter binding, content negotiation, and authentication
helidon-testJUnit extensions for in-process integration tests and Docker E2E tests
gradle-pluginThin-jar packaging, Docker image tasks, and intTest/e2eTest suites

Getting started

Requires JDK 25+ and Kotlin 2.4. See app/build.gradle.kts for a complete setup.

// settings.gradle.kts
pluginManagement {
    repositories {
        gradlePluginPortal()
        mavenCentral()
    }
}
// build.gradle.kts
plugins {
    id("dev.sku20.stopgap") version "3.1.0"
}

dependencies {
    implementation("dev.sku20.stopgap:ir:3.1.0")
    ksp("dev.sku20.stopgap:ir:3.1.0")
    implementation("dev.sku20.stopgap:helidon-extensions:3.1.0")
    ksp("dev.sku20.stopgap:helidon-extensions:3.1.0")

    testImplementation("dev.sku20.stopgap:helidon-test:3.1.0")
}

Examples

The app module is a working example of every feature. It's more than documentation: it's also the integration suite for Stopgap itself. Every feature in it runs through the real code generation, and its tests prove it works. The examples can't go stale, because if they break, the build fails. Tests as documentation, the way TDD intends.

License

MIT


Built for developers who value transparency, performance, and simplicity.

dependency-injection
graalvm
helidon
http
kotlin
ksp
microservices
native-image
no-reflection
rest-api
tdd
testing
virtual-threads

Languages

Kotlin

99.5%