Quarkus Deployment
Ratchet runs on Quarkus, on the JVM and as a GraalVM native image, through the ratchet-quarkus extension. The extension wires the engine into Quarkus, so you add the extension, a store, and a datasource, then submit jobs through the usual JobSchedulerService. Ratchet's own persistence unit is supplied by the extension, so there is no persistence-unit configuration to write.
Quickstart
This is a complete dev application. In dev mode Quarkus provisions a throwaway PostgreSQL container for you, and Ratchet creates its own schema on startup, so there is nothing to install first. You need JDK 21 and a running Docker (or Podman) for Dev Services.
The extension is available from Maven Central starting with Ratchet 0.3.0.
1. Dependencies. The extension brings the engine and Hibernate ORM with it; add a store and the matching Quarkus JDBC driver.
<dependency>
<groupId>run.ratchet</groupId>
<artifactId>ratchet-quarkus</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>run.ratchet</groupId>
<artifactId>ratchet-store-postgresql</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>Add quarkus-rest too if you want the REST endpoint below. When a request has a Quarkus SecurityIdentity, Ratchet captures that principal automatically at job submission.
2. Configuration. Two lines. No datasource URL is set, so Dev Services starts a container in dev mode. auto-migrate tells Ratchet to create its tables on startup.
quarkus.datasource.db-kind=postgresql
ratchet.schema.auto-migrate=true3. A job. Any bean method is a job. Submit a method reference through JobSchedulerService.
package com.example;
import jakarta.enterprise.context.ApplicationScoped;
import org.jboss.logging.Logger;
@ApplicationScoped
public class Reports {
private static final Logger LOG = Logger.getLogger(Reports.class);
public void rebuild() {
LOG.info("report rebuilt");
}
}package com.example;
import jakarta.inject.Inject;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import run.ratchet.api.JobSchedulerService;
@Path("/reports")
public class ReportResource {
@Inject JobSchedulerService scheduler;
@Inject Reports reports;
@POST
public String schedule() {
scheduler.enqueueNow(reports::rebuild);
return "submitted";
}
}4. Allow your job classes. Ratchet refuses to run a job whose target class is not on an allowlist, and it fails startup if no allowlist is configured. Permit your own package with a PackagePrefixClassPolicy:
package com.example;
import jakarta.annotation.Priority;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.enterprise.inject.Alternative;
import jakarta.interceptor.Interceptor;
import java.util.Set;
import run.ratchet.ri.security.PackagePrefixClassPolicy;
@Alternative
@Priority(Interceptor.Priority.APPLICATION)
@ApplicationScoped
public class JobsClassPolicy extends PackagePrefixClassPolicy {
public JobsClassPolicy() {
super(Set.of("com.example"));
}
}5. Run it.
quarkus dev
# in another shell:
curl -X POST http://localhost:8080/reportsThe log shows Dev Services starting PostgreSQL, Ratchet applying its migrations, then report rebuilt when the job runs.
How the dev setup works
- Dev Services provisions a throwaway PostgreSQL container whenever no datasource URL is configured in dev and test mode. Nothing to install, and the container is discarded when you stop.
- Auto-migration runs Ratchet's bundled schema migrations on startup when
ratchet.schema.auto-migrate=true. This is a convenience for development, CI, and embedded deployments; see Schema for the production story. It is a plainratchet.*property, so it reads fromapplication.propertieswith no environment variable required. - The class allowlist is mandatory. The
PackagePrefixClassPolicyabove names the packages whose methods Ratchet will run. Without one the application fails fast at startup rather than silently rejecting every job. - The persistence unit named
ratchetis supplied by the extension and kept separate from your application's default unit, so you write no persistence-unit configuration and an application with no entities of its own still boots.
On Docker Engine 29 or newer, Dev Services may fail to negotiate the Docker API version. If you see a client version ... is too old error, create ~/.docker-java.properties with a single line api.version=1.44.
Prerequisites
- Quarkus 3.20 or later
- JDK 21 for JVM mode; a GraalVM or Mandrel distribution for native
- A SQL database for the Hibernate ORM SQL flavor: PostgreSQL 14+, MySQL 8+, Oracle, or SQL Server. On this flavor the JPA provider is Hibernate ORM; EclipseLink is a Jakarta EE feature and does not apply on Quarkus. For NoSQL, a separate
ratchet-quarkus-mongodbflavor runs on MongoDB with no JPA provider at all (see MongoDB below).
For MySQL, swap in ratchet-store-mysql and quarkus-jdbc-mysql. Oracle and SQL Server work the same way with their own store and driver artifacts.
Netty dependency alignment
Quarkus 3.20.6.2 manages Netty 4.1.130.Final. Ratchet 0.4.0 tests this platform with Netty 4.1.137.Final, which includes the upstream security fixes, and Brotli4j 1.23.0. Native builds need the matching Brotli4j Java and native libraries and Ratchet's conditional SSL compatibility code; a Netty-only override is insufficient.
An application's dependency management takes precedence over library dependencies. Upgrading Ratchet alone does not guarantee the same Netty version in your application. For a Ratchet 0.4.0 application on Quarkus 3.20.6.2, you can import Ratchet's Quarkus dependency set before the platform BOM to use the versions tested together:
<dependencyManagement>
<dependencies>
<dependency>
<groupId>run.ratchet</groupId>
<artifactId>ratchet-quarkus-parent</artifactId>
<version>0.4.0</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>io.quarkus.platform</groupId>
<artifactId>quarkus-bom</artifactId>
<version>3.20.6.2</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>This imports the full Ratchet Quarkus dependency management, including its security overrides. Check your application's resolved versions with mvn dependency:tree -Dincludes=io.netty,com.aayushatharva.brotli4j and run its integration tests. For native applications, also verify a native build because Quarkus adds native libraries during augmentation. A newer Quarkus platform may already provide the fixes; check its resolved versions before carrying these overrides forward. The SSL compatibility code disables itself when the platform already supplies the newer substitution or when the older Netty API is in use.
Moving to production
Datasource. Point the default datasource at your real database instead of Dev Services:
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=ratchet
quarkus.datasource.password=ratchet
quarkus.datasource.jdbc.url=jdbc:postgresql://db:5432/ratchetThe extension binds the ratchet unit to this datasource, scopes it to Ratchet's entities, sets database.generation=none, and keeps Ratchet's META-INF/orm.xml off the default unit.
Applications with their own entities. A named persistence unit turns off Quarkus's automatic default unit, so declare yours explicitly:
quarkus.hibernate-orm.packages=com.example.myapp
quarkus.hibernate-orm.database.generation=drop-and-createRatchet's extension defaults keep its mapping file on the ratchet unit only, so your default unit does not need a defensive mapping-files=no-file line. If your application has no entities of its own, skip this step; the ratchet unit is the only one, and the application boots without a default unit.
Schema
For development and embedded use, ratchet.schema.auto-migrate=true applies Ratchet's bundled migrations on startup, as in the Quickstart. The migrations ship with the store and run in native images as well.
For production, many teams prefer to apply schema changes out of band with their own tooling. Ratchet ships the DDL as plain SQL, so you can run it with Flyway, Liquibase, psql, or a container init script and leave auto-migrate off:
psql -U ratchet -d ratchet -f ddl/postgresql-schema.sqlSee PostgreSQL Deployment or MySQL Deployment for the schema details.
Submitting jobs
Inject JobSchedulerService and submit a method reference, as in the Quickstart. Recurring jobs, signals, and batches work the same as on a Jakarta EE server, on the JVM and in a native image. Every job target class must be permitted by the class allowlist.
Lambdas that capture arguments work too, so a job can carry parameters:
String orderId = order.id();
scheduler.enqueueNow(() -> shipping.dispatch(orderId));Captured values are persisted with the job, so they must be JSON-representable. Strings, numbers, and booleans round-trip as themselves; your own records and classes are serialized and rebuilt by JSON-B, so they need the usual JSON-B shape and must be permitted by the class allowlist. A bound method reference on an unmanaged object, new Report(id)::send, is not supported — the receiver's state has nowhere to live in the persisted job. Capture the values in a lambda instead.
MongoDB flavor
Everything above describes the SQL flavor. To run Ratchet on MongoDB instead, use the ratchet-quarkus-mongodb artifact and the Mongo store. There is no persistence unit, no orm.xml, and no schema DDL to apply, so the setup is shorter than the SQL flavor.
<dependency>
<groupId>run.ratchet</groupId>
<artifactId>ratchet-quarkus-mongodb</artifactId>
<version>0.4.0</version>
</dependency>
<dependency>
<groupId>run.ratchet</groupId>
<artifactId>ratchet-store-mongodb</artifactId>
<version>0.4.0</version>
</dependency>The flavor brings quarkus-mongodb-client with it. Point it at your database:
quarkus.mongodb.connection-string=mongodb://localhost:27017
quarkus.mongodb.database=ratchetMongo initializes its own collections and indexes, so there is no auto-migrate step. The extension forces the driver's UuidRepresentation to STANDARD at construction, so Ratchet's UUID job identifiers round-trip correctly with no configuration on your part. Submitting jobs, plus recurring, signals, and batches, is identical to the SQL flavor, and the extension runs on the JVM and as a native image the same way. See MongoDB Deployment for collection and index details.
Native image
mvn package -Pnative -Dquarkus.native.container-build=falseBuild on a host GraalVM or Mandrel. The extension registers the reflection, runtime-init, and lambda-serialization metadata the engine needs, and it includes the schema migrations in the image, so jobs and auto-migrate both work in native.
Method references and capturing lambdas both require native lambda-serialization metadata. Inline lambdas also require the bytecode of the class containing the lambda body: Ratchet reads it to resolve an expression such as () -> svc.work(arg) into a persistable invocation.
The extension registers both kinds of metadata for every class in the application index, including nested, local, and anonymous classes. Application submitters work with direct injection, Instance<JobSchedulerService>, Provider<JobSchedulerService>, inherited fields, and programmatic lookups. No submitter annotation is needed for application classes. Including their class resources increases native image size in proportion to the application's bytecode.
Submitting from dependency libraries
For indexed dependencies, the extension retains automatic discovery of classes declaring a JobSchedulerService field or method parameter, including Instance and Provider wrappers. It also honors @RegisterJobSubmitter. Dependency classes that use a lookup helper, inherit their scheduler, or contain the actual lambda in a nested class may need explicit registration.
Annotate the class that lexically contains the lambda or method reference, which can differ from the bean declaring the injected scheduler:
@RegisterJobSubmitter
public class LibrarySubmitter {
public void submit(MyJob job, String value) {
JobSchedulerService scheduler = CDI.current().select(JobSchedulerService.class).get();
scheduler.enqueueNow(() -> job.process(value));
}
}The dependency must be included in Quarkus's index for the annotation to be discovered. Both method references and inline lambdas need registration; missing class resources for an inline lambda cause IllegalStateException: Bytecode not found at submission. Job targets supplied by dependency libraries may also need reflection registration. The annotation is a no-op in JVM mode.
What the extension handles
- Starts the engine on Quarkus's
StartupEvent, after persistence is ready. Ratchet's Jakarta EE auto-start fires at static-init, which is too early on Quarkus. - Supplies the JNDI-free
StandaloneExecutorProvider, since Quarkus has no Jakarta Concurrency managed executor. - Supplies the
ratchetpersistence unit's build-time settings and binds the stores to it. - Discovers the schema-migration hook and the active store's dialect so
auto-migrateworks, and includes the migration scripts in native images. - Excludes the store's default
EntityManagerProvider, which the Quarkus provider supersedes, so an application with no entities of its own still boots. - Turns off dev-mode Hibernate validation of the
ratchetunit, whose entities are a denormalized view served by native row mappers rather than a literal image of the physical schema. - Supplies a caller-principal source backed by Quarkus
SecurityIdentity, so a job submitted during an authenticated request records that caller. It reads the identity defensively, so submitting from a background thread with no active request simply records no caller instead of failing. - Keeps Ratchet's beans from being pruned by ArC and registers the native metadata.
- Registers each job-submitting class for lambda serialization and includes its bytecode in the native image, so capturing lambdas resolve at runtime. Classes that do not inject
JobSchedulerServiceopt in with@RegisterJobSubmitter.
Differences from a Jakarta EE server
- On the SQL flavor the JPA provider is Hibernate ORM; EclipseLink is a Jakarta EE feature and is not part of the Quarkus cell. A separate
ratchet-quarkus-mongodbflavor runs Ratchet on MongoDB with no JPA provider at all. - Ratchet uses its own persistence unit rather than the container's default, as configured above.
- The standalone executor backs job execution instead of a Jakarta Concurrency managed executor.