Learn DBOS Java
This guide shows you how to use DBOS to build Java apps that are resilient to any failure.
1. Setting Up Your Environment
First, initialize a new project with Gradle (See the installation instructions if you do not have Gradle set up, or do not have Gradle 8 or later):
mkdir myapp && cd myapp
gradle init
When prompted, just accept the defaults for everything Gradle asks.
Then, install DBOS (plus Logback for logging) by adding the following to your app/build.gradle.kts dependencies:
dependencies {
implementation("dev.dbos:transact:0.8.0")
implementation("org.slf4j:slf4j-simple:2.0.17") // needed to see DBOS log messages
implementation("io.javalin:javalin:7.0.1") // needed for creating HTTP endpoint later in the guide
// remaining dependencies that were generated by gradle init
}
DBOS also requires a PostgreSQL database. If you don't already have PostgreSQL, you can launch it in a Docker container with this command:
docker run -d \
--name dbos-postgres \
-e POSTGRES_PASSWORD=dbos \
-p 5432:5432 \
postgres:latest
Then, set the following environment variables to your connection information (later, we'll pass them into DBOS). For example:
export PGUSER=postgres
export PGPASSWORD=dbos
export DBOS_SYSTEM_JDBC_URL=jdbc:postgresql://localhost:5432/dbos_java_starter
2. Workflows and Steps
DBOS helps you add reliability to Java programs. The key feature of DBOS is workflow methods comprised of steps. DBOS automatically provides durability by checkpointing the state of your workflows and steps to its system database. If your program crashes or is interrupted, DBOS uses this saved state to recover each of your workflows from its last completed step. Thus, DBOS makes your application resilient to any failure.
Let's create a simple DBOS program that runs a workflow of two steps.
Add the following code to your app/src/main/java/org/example/App.java file:
package org.example;
import dev.dbos.transact.DBOS;
import dev.dbos.transact.config.DBOSConfig;
import dev.dbos.transact.workflow.Workflow;
interface Example {
public void workflow();
}
class ExampleImpl implements Example {
private final DBOS dbos;
public ExampleImpl(DBOS dbos) {
this.dbos = dbos;
}
private void stepOne() {
System.out.println("Step one completed!");
}
private void stepTwo() {
System.out.println("Step two completed!");
}
@Override
@Workflow
public void workflow() {
dbos.runStep(() -> stepOne(), "stepOne");
dbos.runStep(() -> stepTwo(), "stepTwo");
}
}
public class App {
public static void main(String[] args) {
var dbosConfig = DBOSConfig.defaultsFromEnv("dbos-java-starter");
try (var dbos = new DBOS(dbosConfig)) {
Example proxy = dbos.registerProxy(Example.class, new ExampleImpl(dbos));
dbos.launch();
proxy.workflow();
}
}
}
Now, build and run this code with:
./gradlew run
Your program should print output like:
[main] INFO dev.dbos.transact.DBOS - Launching DBOS v0.8.0-m57
[main] INFO dev.dbos.transact.execution.DBOSExecutor - DBOS Executor starting
[main] INFO dev.dbos.transact.execution.DBOSExecutor - System Database: jdbc:postgresql://localhost:5432/dbos_java_starter
[main] INFO dev.dbos.transact.execution.DBOSExecutor - System Database User name: postgres
[main] INFO dev.dbos.transact.execution.DBOSExecutor - Executor ID: local
[main] INFO dev.dbos.transact.execution.DBOSExecutor - Application Version: ba1bd9eb7e0c99845dd339a348f64a42902b719fb4c553e0c49f76a63633609e
Step one completed!
Step two completed!
[main] INFO dev.dbos.transact.execution.DBOSExecutor - DBOS Executor stopping
[main] INFO dev.dbos.transact.DBOS - DBOS shut down
To reduce logging output, we are eliding com.zaxxer.hikari and dev.dbos.transact.migrations log levels below warn.
Of course, an app that runs a single workflow and exits isn't very useful in practice.
Let's convert this into a long-running HTTP server so we can trigger workflows on demand and observe DBOS's durable execution in action.
Replace the contents of app/src/main/java/org/example/App.java with:
package org.example;
import dev.dbos.transact.DBOS;
import dev.dbos.transact.config.DBOSConfig;
import dev.dbos.transact.workflow.Workflow;
import java.time.Duration;
import io.javalin.Javalin;
interface Example {
public void workflow();
}
class ExampleImpl implements Example {
private final DBOS dbos;
public ExampleImpl(DBOS dbos) {
this.dbos = dbos;
}
private void stepOne() {
System.out.println("\"%s\" Step one completed!".formatted(DBOS.workflowId()));
}
private void stepTwo() {
System.out.println("\"%s\" Step two completed!".formatted(DBOS.workflowId()));
}
@Override
@Workflow
public void workflow() {
dbos.runStep(() -> stepOne(), "stepOne");
for (int i = 0; i < 5; i++) {
System.out.println("Press Control + C to stop \"%s\"...".formatted(DBOS.workflowId()));
dbos.sleep(Duration.ofSeconds(1));
}
dbos.runStep(() -> stepTwo(), "stepTwo");
}
}
public class App {
public static void main(String[] args) {
var dbosConfig = DBOSConfig.defaultsFromEnv("dbos-java-starter");
var dbos = new DBOS(dbosConfig);
Example proxy = dbos.registerProxy(Example.class, new ExampleImpl(dbos));
Javalin.create(config -> {
config.events.serverStarting(dbos::launch);
config.events.serverStopping(dbos::shutdown);
config.routes.get("/", ctx -> {
proxy.workflow();
ctx.result("workflow executed");
});
}).start(8080);
}
}
Then, build and run this code with:
./gradlew run
Next, visit this URL: http://localhost:8080.
In your terminal, you should see an output like:
[main] INFO io.javalin.Javalin - You are running Javalin 7.0.1 (released February 28, 2026).
"477cf44d-3230-4e19-be98-1aef144a3f0c" Step one completed!
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Now, press CTRL+C stop your app. Then, run ./gradlew run again to restart it. You should see an output like:
[main] INFO io.javalin.Javalin - You are running Javalin 7.0.1 (released February 28, 2026).
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
Press Control + C to stop "477cf44d-3230-4e19-be98-1aef144a3f0c"...
"477cf44d-3230-4e19-be98-1aef144a3f0c" Step two completed!
You can see how DBOS recovers your workflow from the last completed step, executing step two without re-executing step one. Learn more about workflows, steps, and their guarantees here.
3. Queues and Parallelism
To run many functions concurrently, use DBOS queues.
To try them out, copy this code into App.java:
package org.example;
import dev.dbos.transact.DBOS;
import dev.dbos.transact.StartWorkflowOptions;
import dev.dbos.transact.config.DBOSConfig;
import dev.dbos.transact.workflow.Queue;
import dev.dbos.transact.workflow.Workflow;
import dev.dbos.transact.workflow.WorkflowHandle;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import io.javalin.Javalin;
interface Example {
void taskWorkflow(int i) throws InterruptedException;
void queueWorkflow() throws InterruptedException;
}
class ExampleImpl implements Example {
private final DBOS dbos;
private Example self;
public ExampleImpl(DBOS dbos) {
this.dbos = dbos;
}
public void setSelf(Example self) {
this.self = self;
}
@Override
@Workflow
public void taskWorkflow(int i) throws InterruptedException {
Thread.sleep(Duration.ofSeconds(5));
System.out.printf("Task %d completed!\n", i);
}
@Override
@Workflow
public void queueWorkflow() throws InterruptedException {
System.out.printf("Starting queueWorkflow!\n");
List<WorkflowHandle<Void, InterruptedException>> handles = new ArrayList<>();
var options = new StartWorkflowOptions().withQueue("example-queue");
for (int i = 0; i < 10; i++) {
final int index = i;
var handle = dbos.startWorkflow(() -> self.taskWorkflow(index), options);
handles.add(handle);
}
for (var handle : handles) {
handle.getResult();
}
System.out.printf("Successfully completed %d workflows!\n", handles.size());
}
}
public class App {
public static void main(String[] args) {
var dbosConfig = DBOSConfig.defaultsFromEnv("dbos-java-starter");
var dbos = new DBOS(dbosConfig);
ExampleImpl impl = new ExampleImpl(dbos);
Example proxy = dbos.registerProxy(Example.class, impl);
impl.setSelf(proxy);
var queue = new Queue("example-queue");
dbos.registerQueue(queue);
Javalin.create(config -> {
config.events.serverStarting(dbos::launch);
config.events.serverStopping(dbos::shutdown);
config.routes.get("/", ctx -> {
proxy.queueWorkflow();
ctx.result("workflow executed");
});
}).start(8080);
}
}
When you enqueue a function by passing new StartWorkflowOptions().withQueue("example-queue") into dbos.startWorkflow, DBOS executes it asynchronously, running it in the background without waiting for it to finish.
dbos.startWorkflow returns a handle representing the state of the enqueued function.
This example enqueues ten functions, then waits for them all to finish using .getResult() to wait for each of their handles.
Now, restart your app with:
./gradlew run
Then, visit this URL: http://localhost:8080. Wait five seconds and you should see an output like:
Starting queueWorkflow!
Task 0 completed!
Task 1 completed!
Task 2 completed!
Task 3 completed!
Task 4 completed!
Task 5 completed!
Task 6 completed!
Task 7 completed!
Task 8 completed!
Task 9 completed!
Successfully completed 10 workflows!
You can see how all ten steps run concurrently—even though each takes five seconds, they all finish at the same time. Learn more about DBOS queues here.
Congratulations! You've finished the DBOS Java guide. Next, you should:
- Learn how to add DBOS to your own application.
- Check out some example applications.