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, with the
policy of Vauban’s Java SE launcher: every module moves except the
Vidocq bricks, the platform and Jakarta modules, automatic modules, modules with a javax.,
sun. or com.sun. package, and what those kept modules read. The caller’s module always
moves. CDI-agnostic libraries move too, so Vauban places the client proxies that must live in
their packages, with zero opens. Override with -Dvidocq.app.modules=<name,name> or an
explicit -Dvidocq.app.path=<archives>.
NEW When the selection finds no application module to move, the application stays in the boot
layer, and there Vidocq cannot give itself access to the VidocqApp class it has to instantiate. The boot
now fails with a message that names the class, its package and the two fixes, instead of a bare
IllegalAccessException: add exports <package> to io.vidocq.runtime.core; to the application’s
module-info.java, or make the class a CDI bean (@ApplicationScoped or @Dependent), which the
container creates itself (Vidocq/vidocq#201). In the application layer, the usual case, Vidocq adds that
export by itself.
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 vauban:modularize beforehand: it generates their descriptors into target/vauban-modularized/, where dev, jlink and package pick them up automatically. See the vauban: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
The classes that
No configuration is needed: see
which jars |
|
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.
Dev console NEW
While you develop, run mvn vidocq:dev: it adds the console itself when the application has vidocq-runtime-chappe-webserver-extension, and never packages it. The application logs Vidocq dev console: http://127.0.0.1:8888/, a read-only page with the startup report of the running boot and the live values of the extensions — the connections of each Mansart pool, the heap, the threads, the garbage collectors. It is on in a dev launch only, on the loopback address. See Dev console; an extension adds its own panel as Writing a dev console panel describes.
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