Codync
A free, open-source 1:1 alternative to Grok Bot and Muse. Message Claude Code, Codex, Cursor and 40+ coding agents as persistent bots from your iPhone, Mac or Linux desktop. Group chats, threads, appr
A PostgreSQL-backed job scheduler for Java and Kotlin: durable steps, events and retries, with minimal dependencies, no extra infrastructure and an optional admin UI
Developer toolsJava · KotlinGitHub @bikeschedOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
The maker's introduction consists of the line above. Read the repository README for more details. Repository README ↗
Loading the full README.
Read the full README ↗A PostgreSQL-backed job scheduler for Java and Kotlin: durable steps, events and retries, with minimal dependencies, no extra infrastructure and an optional admin UI
[!NOTE] bikesched is at a super early stage (
0.2.0-SNAPSHOT) and can break at any time: APIs and the database schema may change without notice and without migrations.It is also a first experiment in building a library heavily with AI coding agents; hence the logo, a nod to Simon Willison's pelican riding a bicycle LLM benchmark.
Simon, the creator of the bikesched project: I was working on a project and looking for an open source library that does job scheduling with durable execution, has minimal dependencies and is PostgreSQL-focused, but didn't find one that fit. So instead of continuing that project, I started "bikeshedding" on a library with these properties, with AI doing much of the implementation, around the needs I'd actually run into.
bikesched stores jobs in PostgreSQL and lets every connected application instance (node) work on
them. Nodes claim due jobs with SELECT … FOR UPDATE SKIP LOCKED, so many nodes can share one
database without stepping on each other and without any coordinator besides PostgreSQL.
bikesched is inspired by some great job schedulers, like db-scheduler, JobRunr, Quartz and River, and by durable execution engines like Inngest and Restate. The goal is to keep it small and easy to drop into your app:
bikesched_*), and bikesched creates and migrates the schema on first use.One thing bikesched isn't: a workflow engine. Steps and compensation live inside a single job, so there are no process models (BPMN), no visual modelling and no orchestration across services. If that's what you're after, have a look at Camunda or similar tools.
| Module | Contents |
|---|---|
bikesched-core | the scheduler; depends on the PostgreSQL driver and the SLF4J API only; optional schema migration spec |
bikesched-jackson | JacksonJsonMapper: payloads, step results and events as records, via Jackson 3 |
bikesched-kotlin | Kotlin extensions: configuration DSL, typed handlers, kotlin.time durations |
bikesched-annotations | job handlers declared as @JobHandler methods, see Annotations |
bikesched-cron | cron expressions as CronRecurrence or as strings (schedule("0 3 * * *"), @JobHandler), via cron-utils, see Cron expressions |
bikesched-micrometer | Micrometer meters for claims, attempts and published events, see Metrics |
LISTEN/NOTIFY,
polling is only the safety netjob.postpone(…), without
using up an attemptjob.sleep(…) without holding a threadTypedJob/TypedEvent constants, next to plain string namesnullbikesched.admin()DataSource dataSource = ...; // any PostgreSQL DataSource, e.g. a HikariCP pool
try (Bikesched bikesched = Bikesched.builder(dataSource)
.nodeName("worker-1") // optional, defaults to the host name
.build()) { // migrates the schema, registers the node, starts polling
// Register a handler for a job name ...
bikesched.registerJobHandler("send-mail", job -> mailer.send(job.payload()));
// ... and schedule jobs with that name
bikesched.newJob("send-mail").payload("{\"to\": \"[email protected]\"}").schedule();
}
A handler that throws fails the attempt; the job is retried until its maximum number of attempts is
used up and then marked FAILED.
This is where bikesched stops looking like a regular job scheduler: a handler can run durable steps and wait for an event, without holding a thread or re-doing work on retries or resumes:
bikesched.registerJobHandler("order", job -> {
Order order = job.payload(Order.class);
// Creates a Stripe PaymentIntent; runs once, a retried or resumed job gets the id back instead
String paymentIntentId = job.step("create-payment-intent", String.class,
step -> stripe.createPaymentIntent(order).id());
// Suspends (no thread held) until Stripe's webhook reports on this payment intent
PaymentUpdated webhook = job.waitForEvent("await-payment", "payment-intent-updated", paymentIntentId,
PaymentUpdated.class)
.receive();
switch (webhook.status()) {
case SUCCEEDED -> job.step("mark-paid", () -> orders.markPaid(order, paymentIntentId));
case FAILED -> job.step("mark-failed", () -> orders.markFailed(order, paymentIntentId));
}
});
// in the Stripe webhook endpoint, possibly minutes or days later
bikesched.newEvent("payment-intent-updated", webhookPayload.paymentIntentId())
.payload(webhookPayload)
.publish();
A plain job scheduler would need its own tables and glue code to carry order and
paymentIntentId across that wait, to not create a second payment intent on a retry, and to
resume on another node after a crash; here it's job.step(...) and job.waitForEvent(...). See
Steps and compensation and
Events.
The documentation covers the concepts in detail:
Runnable examples with tests — a taxi booking saga with event waits and compensation, a cinema
ticket order with a StandaloneJobHandler, a webhook with a polling fallback, a provider's rate
limit with Bucket4j, chat messages deduplicated by idempotency keys, an approval with a reminder and
an escalation, a fan-out with child jobs, a recurring job on two nodes, and a Spring Boot
application whose node and handlers are wired by the starter — live in the
bikesched-examples modules.
Requirements: JDK 26 (or let Gradle provision it via the toolchain) and Docker for the tests, which run against PostgreSQL 18 with Testcontainers. The build compiles for Java 21, the oldest Java version bikesched supports (ADR 0049).
./gradlew build
The load and failure tests (database restarts, cut-off nodes,
hanging connections, throughput) take a few minutes and run on demand, before a release:
./gradlew :bikesched-stress-tests:stressTest.
To try it out locally, start PostgreSQL with Docker Compose and run the simple example, which registers a handler and runs one job:
docker compose up -d # POSTGRES_PORT=5433 docker compose up -d if 5432 is taken
./gradlew :bikesched-examples:java:runSimple # honors POSTGRES_PORT as well
All dependency and tool versions are managed in gradle/libs.versions.toml.
Releases (see the GitHub Releases) and the snapshots of main and the release branches
are published to GitHub Packages: org.bikesched:bikesched-core, bikesched-jackson,
bikesched-kotlin, bikesched-annotations, bikesched-cron, bikesched-micrometer,
bikesched-spring-boot-starter, bikesched-admin, bikesched-admin-shaded and bikesched-test,
all with the same version (0.2.0-SNAPSHOT on this branch). Reading from GitHub Packages requires a
GitHub token with the read:packages scope, even for public packages. How releases are cut:
RELEASING.md.
repositories {
maven {
url = uri("https://maven.pkg.github.com/bikesched/bikesched")
credentials {
username = providers.gradleProperty("gpr.user").get()
password = providers.gradleProperty("gpr.token").get() // token with read:packages
}
}
}
dependencies {
implementation("org.bikesched:bikesched-core:0.2.0-SNAPSHOT")
}
bikesched is licensed under the Apache License 2.0: you may use, modify and embed it, also in commercial and closed-source products. Simon, the creator of the bikesched project: it's permissive on purpose, so that anyone who runs into the same need I did can drop it into their own project, open or closed source, without license-review friction. The name and logo are trademarks, see the trademark policy for how to refer to bikesched and how to name forks.
Spring Boot auto-configuration: the node from the context, @JobHandler beans found automatically, see Spring Boot |
bikesched-admin | a small admin page and JSON API: jobs, steps, events and nodes, with retry and cancel, served by the JDK's HTTP server or any framework, see Admin UI |
bikesched-test | testing support: a test node factory, JobAwait wait helpers (Awaitility internally) and time skipping, see Testing |