This page covers Vidocq Runtime usage beyond the hello world: the application entry
point, packaging via the vidocq-runtime-maven-plugin (jlink, jpackage, Docker),
configuration via https://microprofile.io/specifications/microprofile-config/, observability (https://microprofile.io/specifications/microprofile-health/, https://microprofile.io/specifications/microprofile-metrics/,
https://microprofile.io/specifications/microprofile-open-api/), profiles, and AOT preparation.
The application entry point — @VidocqMain
The official application main is a trampoline: it contains nothing but
Vidocq.run(…).
@VidocqMain
public final class App implements VidocqApp {
public static void main(String[] args) {
Vidocq.run(App.class, args); // no business logic before this line
}
@Override
public int run(String... args) throws Exception {
// runs AFTER boot, inside the Vauban class loader, with transformed classes
Vidocq.waitForExit(); // block for a server application
return 0;
}
}
Why a trampoline? Because Vidocq does not let the application main "own" the runtime:
Vidocq.run() re-resolves the application into a child module layer defined by the
Vauban class loader, where every class flows through the load-time transformations
(client-proxy weaving…) at definition. This is what makes a plain IDE launch —
right-click → Run, everything on the module path, no special configuration — behave
exactly like a production launch: same layer, same transformed classes, no
instrumentation agent, no JVM warning.
The contract (deliberately identical to Quarkus' @QuarkusMain):
-
no business logic before
Vidocq.run(…)— the trampoline class is loaded by the JVM’s boot layer while the application runs in the Vauban layer; code executed beforerun()lives in the wrong class loader and sees untransformed classes; -
application logic that must run after boot goes into a
VidocqApppassed toVidocq.run(Class, args)— the class is re-loaded through the layer and instantiated there (as a CDI bean when it is one); its return value is the exit code; -
a plain server without post-boot logic just calls
Vidocq.run(args).
Vidocq.run() picks the application modules of the boot layer automatically (the
caller’s module, every non-runtime module carrying META-INF/vauban-beans.list, and the
modules owning the beans that vidocq:generate scanned from dependencies); override
with -Dvidocq.app.modules=<name,name> or an explicit -Dvidocq.app.path=<archives>.
A legacy main that boots the runtime directly still works — unwoven beans are then
covered by the load-time weaving agent (with the JDK’s dynamic-agent warning) and the
log suggests migrating to the trampoline.
One more benefit: applications never declare generated providers in
module-info.java. The annotation processors emit standard META-INF/services files
(_VaubanComponents, Cassini CassiniAdapter`/`CassiniRoutes…), and the Vauban
layer promotes them to synthetic provides when it resolves the module — your
module-info.java stays free of any generated class name.
custom JUL log handlers declared in the application’s logging.properties must
live on the module path — LogManager instantiates handlers through the system class
loader, which cannot see the layer.
|
Packaging with vidocq-runtime-maven-plugin
The Maven plugin exposes three packaging targets. See the dedicated page for the full inventory and the internal JLINK.md details.
| Goal | Output | Typical startup |
|---|---|---|
|
|
~4 s |
|
|
~1 s (CDS) |
|
|
~50 MB image |
Quick start (from vidocq-runtime-cassini-rest-example):
cd vidocq-runtime-examples/vidocq-runtime-cassini-rest-example
./mvnw -ntp package -DskipTests
./target/dist/bin/todo-app
jlink and jpackage require every dependency to be a named module. When some third-party jars ship without module-info.class, run vidocq:modularize beforehand: it generates their descriptors into target/vidocq-modularized/, where dev, jlink and package pick them up automatically. See the vidocq:modularize goal.
Multi-module applications (scanned dependencies)
Hexagonal and multi-module applications typically keep their business and adapter modules
spec-only — Jakarta EE / MicroProfile APIs only, no Vidocq dependency, no Vidocq APT — and
concentrate everything runtime-specific in a small main module. That main module asks
vidocq:generate to scan the app modules and produce the CDI/REST glue for them:
<execution>
<id>generate</id>
<goals><goal>generate</goal></goals>
<configuration>
<scanDependencies>
<scanDependency>com.acme:my-rest-adapter</scanDependency>
<scanDependency>com.acme:my-domain</scanDependency>
</scanDependencies>
</configuration>
</execution>
Two rules for each scanned module’s module-info.java — and remember that the Java module
system only lets a module opens (or exports) packages it owns: you cannot open another module’s package from
the main module, which is why the single-module opens shown in
Getting started must move into the module that contains the classes.
module com.acme.rest.adapter {
exports com.acme.rest; // resource classes
exports com.acme.rest.dto; // JSON-B reads records via their public accessors
requires jakarta.cdi;
requires jakarta.ws.rs;
// The consuming runtime generates/reflects the CDI plumbing for these beans.
// Unqualified `opens` names no runtime module: the module stays spec-only,
// and classpath-based runtimes ignore it entirely.
opens com.acme.rest;
}
|
New in 0.3.0-SNAPSHOT
The classes that
No extra configuration is needed beyond |
|
On 0.2.0 (released)
Vidocq 0.2.0 leaves the generated classes in the main module’s
|
MicroProfile configuration
Vidocq Runtime implements https://microprofile.io/specifications/microprofile-config/. Source hierarchy, highest to lowest priority:
| Ordinal | Source | Description |
|---|---|---|
400 |
|
|
300 |
|
Environment variables ( |
250 |
|
|
100 |
|
classpath |
ExternalFileConfigSource lets you override a deployed jlink/jpackage binary without rebuilding, simply by editing the conf/vidocq.properties file next to the launcher. A boot log line traces the effective load.
Health
@Liveness
@ApplicationScoped
public class DatabaseHealth implements HealthCheck {
@Inject DataSource ds;
@Override
public HealthCheckResponse call() {
try (Connection c = ds.getConnection()) {
return HealthCheckResponse.up("database");
} catch (SQLException e) {
return HealthCheckResponse.down("database");
}
}
}
Endpoints exposed by vidocq-runtime-knock-health-extension: /health, /health/live, /health/ready, /health/started (see TCK).
Metrics
https://microprofile.io/specifications/microprofile-metrics/ is shipped by the vidocq-runtime-dirac-metrics-extension extension (127 MicroProfile Metrics TCK tests green). Endpoints: /metrics, /metrics/{scope} and /metrics/{scope}/{name}.
OpenAPI
The vidocq-runtime-grimm-openapi-extension extension serves the OpenAPI document, generated from Jakarta REST + MicroProfile OpenAPI annotations, at /openapi; the companion vidocq-runtime-grimm-openapi-ui-extension serves Swagger UI at /openapi/ui.
Profiles
MicroProfile convention: prefix a key with %<profile>. to scope it.
vidocq.http.port=8080
%dev.vidocq.http.port=8081
%prod.vidocq.http.port=80
Activate via the -Dvidocq.profile=dev system property (there is no environment-variable equivalent).
Docker deployment
Minimal distroless image produced by vidocq:docker:
./mvnw -ntp package -Dvidocq.docker.build=true
docker run --rm -p 8080:8080 \
-e VIDOCQ_CONFIG_DIR=/etc/myapp \
-v $(pwd)/conf:/etc/myapp:ro \
example/my-app:1.0.0
The distroless/base-debian12:nonroot base image has no shell, no package manager — minimal attack surface. The nonroot user (UID 65532) is enforced.
AOT — GraalVM and Leyden
-
Leyden CDS — supported out of the box. Static Class-File API codegen produces bytecode that archives perfectly.
-
GraalVM native-image — Vidocq Runtime’s zero-reflection + zero-proxy strategy minimises
reachability-metadata. Seevidocq/JLINK.mdfor runtime prerequisites (logging.properties, named Java modules, serialised records).
Next steps
-
Reference — exhaustive config keys, plugin properties
-
Internals — boot sequence
-
Maven plugin — goal details