Upgrading
Upgrading to v0.9
Breaking Changes
Queue.withRateLimit(int, double)
The withRateLimit(int limit, double period) overload on the Queue record has been removed.
Replace it with the new withRateLimit(int limit, long period, TimeUnit unit) overload:
// Before
new Queue("example-queue").withRateLimit(100, 60.0);
// After
new Queue("example-queue").withRateLimit(100, 60, TimeUnit.SECONDS);
DBOS.registerWorkflow
The static DBOS.registerWorkflow method has been removed.
Call DBOSIntegration.registerWorkflow instead, which is accessible via dbos.integration():
// Before
DBOS.registerWorkflow(name, target, method);
// After
dbos.integration().registerWorkflow(name, target, method);
DBOSIntegration.registerWorkflow
DBOSIntegration.registerWorkflow now returns a RegisteredWorkflow instead of void.
Code that discards the return value continues to compile without changes.
Code that stores the result must update its declared type from void to RegisteredWorkflow.
New Features
Step Factories
Two new mechanisms let you commit a step's database write and its DBOS checkpoint atomically, so a crash between the two can never leave them out of sync.
Step factories (non-Spring): construct a factory for your database library and pass it to DBOS.runStep:
// Plain JDBC
var factory = new JdbcStepFactory(dataSource);
dbos.runStep(factory, conn -> {
conn.prepareStatement("INSERT INTO orders ...").executeUpdate();
return orderId;
});
JdbcStepFactory, JdbiStepFactory, and JooqStepFactory are available for JDBC, JDBI 3, and jOOQ respectively.
@TransactionalStep (Spring Boot): annotate any Spring-managed method with @TransactionalStep and the transact-spring-txstep-starter module handles the rest — no factory wiring required:
@TransactionalStep
public Order createOrder(OrderRequest request) {
// Spring transaction + DBOS checkpoint committed atomically
return orderRepository.save(new Order(request));
}
See the Step Factories tutorial for setup instructions and per-library examples.
sendBulk
DBOS.sendBulk and DBOSClient.sendBulk let you send multiple workflow messages in a single batch:
dbos.sendBulk(List.of(
new SendMessage(workflowIdA, "hello", "topic"),
new SendMessage(workflowIdB, "world", "topic")));
Each message in the batch is delivered independently; messages need not share the same destination.
DBOSClient.sendBulk accepts an optional SendOptions for serialization and fork-delivery control.
Debouncer
A new Debouncer class lets you coalesce repeated workflow invocations on the same key into a single execution that uses the most recently supplied arguments.
The workflow fires after debouncePeriod of inactivity, or after an absolute debounceTimeout cap regardless of ongoing calls:
var debouncer = dbos.<String>debouncer()
.withDebounceTimeout(Duration.ofMinutes(5));
WorkflowHandle<String, Exception> handle = debouncer.debounce(
userId,
Duration.ofSeconds(60),
() -> svc.processInput(userInput));
DebouncerClient provides the same capability from external code without a running DBOS executor.
See the Debouncing reference for the full API.
Dynamic queue management
Queues can now be registered, updated, and deleted at runtime without restarting your application.
Queue configuration is persisted to the system database and survives restarts.
Register a queue after dbos.launch() using DBOS.registerQueue:
DBOS.registerQueue("pipeline-queue",
QueueOptions.setConcurrency(10).andRateLimit(100, Duration.ofSeconds(60)));
Update configuration at runtime with DBOS.updateQueue — only the fields you specify are changed:
DBOS.updateQueue("pipeline-queue", QueueOptions.setConcurrency(20));
Additional methods — DBOS.findQueue, DBOS.listQueues, and DBOS.deleteQueue — let you inspect and manage queues at runtime.
DBOSClient exposes the same operations for managing queues from outside your application.
See Queues & Concurrency for a full guide and Queues reference for the complete API.
CockroachDB support
CockroachDB is now a supported system database backend alongside PostgreSQL.
No configuration changes are required; DBOS auto-detects CockroachDB and adjusts its behaviour accordingly (for example, useListenNotify is automatically set to false).
Deprecations
Admin server
The built-in admin server is deprecated and will be removed before v1.0. Use DBOS Conductor instead.
The related configuration APIs — withAdminServer(), disableAdminServer(), enableAdminServer(), and withAdminServerPort() on DBOSConfig, and the dbos.admin-server.* properties in Spring Boot — are deprecated alongside it.
Upgrading to v0.8
DBOS Transact Java v0.8 contains several breaking changes. These changes were made to improve the developer experience as well as how DBOS Transact Java integrates into the larger Java ecosystem. This document explains how to update your existing DBOS Java app to v0.8.
Although we cannot guarantee that v0.8 will be the final release with breaking changes, our intention is to minimize or eliminate further breaking changes in DBOS Transact Java after v0.8.
DBOS Instance API
DBOS is now an instance class instead of a static utility class.
While static methods were easier to access, they are harder to test and mock.
Furthermore, a DBOS instance API fits better into Dependency Injection based Java frameworks like Spring.
Prior to v0.8, you would configure DBOS via the static configure method:
DBOSConfig dbosConfig = DBOSConfig.defaultsFromEnv("my-app");
DBOS.configure(dbosConfig);
Now, you pass the DBOSConfig instance to directly to the DBOS constructor:
DBOSConfig dbosConfig = DBOSConfig.defaultsFromEnv("my-app");
DBOS dbos = new DBOS(dbosConfig);
DBOS implements the AutoClosable interface.
This allows DBOS to work with the try-with-resources statement
or with JUnit's @AutoClose annotation.
var dbosConfig = DBOSConfig.defaultsFromEnv("my-app");
try (var dbos = new DBOS(dbosConfig)) {
Example proxy = dbos.registerProxy(Example.class, new ExampleImpl(dbos));
dbos.launch();
proxy.workflow();
}
With this change, registering DBOS proxies becomes slightly more involved.
Previously, you could register a proxy object anytime prior to calling DBOS.launch().
Now, you must construct the DBOS instance before calling registerProxy.
Like prior releases, all proxies must be registered before calling DBOS.launch().
DBOS.registerWorkflows() is now named DBOS.registerProxy.
For plugin developers, the DBOS instance is now provided as a parameter on dbosLaunched.
For more information, see the Lifecycle Listeners documentation
DBOS API Changes
Beyond the overarching change from static to instance methods, there were assorted other minor breaking changes to the DBOS API:
DBOS.registerWorkflowswas renamed toDBOS.registerProxyDBOS.registerQueuepreviously returned theQueueobject, now it is void return- Several methods with
@Nullablereturn types have been changed to returnOptionalDBOS.getWorkflowStatus()DBOS.recv()DBOS.getEvent()
- Several methods used by plugins were moved from
DBOStoDBOSIntegration. TheDBOSIntegrationinstance can be accessed viaDBOS.integration().DBOSIntegration.registerLifecycleListenerDBOSIntegration.getRegisteredWorkflowsDBOSIntegration.getRegisteredWorkflowsInstancesDBOSIntegration.startRegisteredWorkflowDBOSIntegration.getExternalStateDBOSIntegration.upsertExternalState
DBOSIntegration.startRegisteredWorkflow was previously named DBOS.startWorkflow.
There are several DBOS.startWorkflow overloads, only the one with a RegisteredWorkflow parameter was renamed and moved.
Strongly Typed Fields
In a variety of places across the public API surface area, fields have been changed to more semantically relevant types.
For example, previously we represented both timeout and deadline as Long with semantic information encoded in the field names - i.e. timeoutMs and deadlineEpochMs.
Now, we use the more semantically aligned types of Duration for timeout and Instant for deadline.
With the change in type, we also simplified the field names to drop the semantic type information.
WorkflowStatus
statusfield was previously aString, now it's aWorkflowStateenum valuenamefield was renamedworkflowNamecreatedAtandupdatedAtchanged their type fromLongtoInstanttimeoutMswas renamedtimeoutand the type changed fromLongtoDurationdeadlineEpochMswas renameddeadlineand the type changed fromLongtoInstantstartedAtEpochMswas renamedstartedAtand the type changed fromLongtoInstant
StepInfo
startedAtEpochMswas renamedstartedAtand the type was changed fromLongtoInstantcompletedAtEpochMswas renamedcompletedAtand the type was changed fromLongtoInstant
StepOptions
intervalSecondswas renamedretryIntervaland the type was changed fromDoubletoDuration
ListWorkflowsInput
withStartTimeandwithEndTimechanged parameter type fromOffsetDateTimetoInstantwithStatuseswas renamedwithStatusand the parameter type changed fromList<String>toList<WorkflowState>withWorkflowIdwas renamedwithWorkflowIds
@Step / StepOptions Changes
In previous versions, both @Step and StepOptions had a boolean retriesAllowed field.
This field has been removed.
Additionally, the default maxAttempts field of both @Step and StepOptions has been changed to 1.
To enable retries, simply set maxAttempts to a value greater than `.
Note, as covered above, the StepOptions.intervalSeconds field was renamed to retryInterval and the type was changed to Duration.
Annotations cannot use reference types like Duration so @Step still has an intervalSeconds field of type double.
@Scheduled Removed
The @Scheduled annotation has been removed. For durable scheduled code in your app, you can use the new Schedule Management Methods.
DBOSClient Changes
DBOSClient.EnqueueOptions changed the order of the parameters for the three string constructor.
Previously, the className parameter was first and the workflowName was second.
For consistency with other DBOS APIs, these two parameters have swapped position.
Across the code base, when specifying the workflow name, class name, instance name of a registered workflow, we have the parameters in that order.
Additionally, similar to DBOS changes detailed above, DBOSClient.getWorkflowStatus and DBOS.getEvent now return Optional instead of a @Nullable value;