Java workloads¶
How it works¶
Brewlet lets your team ship Java application code independently of
platform-managed runtime infrastructure. You publish JARs and an explicit launch
descriptor as an OCI artifact, without bundling a JDK. The platform approves and
provisions a Java runtime image; your Kubernetes Deployment
selects its runtime ID. Brewlet runs your declared Java command with application
files mounted read-only under /app.
Dependencies can ship with your application or optionally come from an approved
managed bundle. Release-time composition combines your
thin JAR with the locked dependency set into a new, verifiable application
artifact. Compatible library updates can preserve your application JAR, but
still require testing. The
optional Maven plugin prepares
Java-specific inputs; the brewlet CLI itself does not resolve Maven
dependencies or construct your classpath.
Updates run in new task instances, not by patching a live JVM. See the architecture overview and launch contract for ownership, execution and rollout details. The runtime root is read-only too; temporary files and caches need approved writable volumes.
Tested runtimes¶
Brewlet's tests run Java on unmodified distroless Java 21 images (arm64, using
/usr/bin/java) and on Eclipse Temurin 21 images. The exact images are listed
in the upstream runtime list and
rotation runtime list. They are test inputs, not a security
recommendation; use the runtime images your platform team approves.
The Java example fixture, including the Maven producer, runs daily against
main in the Java example workflow. It does not gate
pull requests or releases; Python is the only qualification fixture (see
verification).
Ask your platform team for the runtime ID, supported platforms and Java
executable path (see the
platform handoff).
java-21 below is an example ID chosen by an administrator; Brewlet has no
built-in runtime catalog.
Build the payload¶
This complete thin-JAR example uses the small Java test application and its
separate greeting dependency from the Brewlet repository. You need Git, Maven
and JDK 21 or later:
WORK="$(mktemp -d)"
git clone --quiet --depth 1 --branch v0.8.1 https://github.com/microsoft/brewlet.git "$WORK/brewlet"
PROJECT="$WORK/brewlet/integration-tests/fixtures/java"
mvn -B -q -f "$PROJECT/dependency/pom.xml" install
mvn -B -q -f "$PROJECT/bom/pom.xml" install
mvn -B -q -f "$PROJECT/app/pom.xml" package
mkdir "$WORK/application" "$WORK/dependencies"
chmod 0755 "$WORK/application" "$WORK/dependencies"
install -m 0644 "$PROJECT/app/target/fixture-java-2.0.0.jar" "$WORK/application/app.jar"
install -m 0644 "$PROJECT/dependency/target/greeting-2.0.0.jar" "$WORK/dependencies/greeting-2.0.0.jar"
The resulting layout is /app/app.jar plus /app/lib/greeting-2.0.0.jar.
Do not package a JDK with the application.
Launch descriptor¶
Save this as $WORK/launch.json. It names the application's main class and
the Java executable path in the selected runtime image:
{
"schemaVersion": 1,
"process": {
"entrypoint": [
"/usr/bin/java"
],
"args": [
"--class-path",
"/app/app.jar:/app/lib/*",
"sh.brewlet.fixture.Main"
],
"workingDir": "/app"
}
}
The classpath wildcard is a Java argument, not a shell expansion. Brewlet does
not generate JVM options. The optional top-level runtimeImages list restricts
which runtime IDs may run the artifact; the Deployment must still name one.
Package¶
Files must be world-readable and directories searchable (see producer filesystem requirements, which also covers macOS). Replace the registry with your own:
brewlet build registry.example.com/apps/java-demo:v1 \
--platform linux/arm64 \
--launch "$WORK/launch.json" \
--layer "dependency=$WORK/dependencies:/app/lib" \
--layer "app=$WORK/application:/app"
brewlet push registry.example.com/apps/java-demo:v1 --push-result "$WORK/push.json"
This puts dependencies in their own layer but does not publish a shared, independently managed layer set. For that, see dependency layer sets.
Instead of the manual steps above, the optional Maven plugin can generate the Java arguments and dependency layers, or consume a verified layer set. Use plugin version 0.8.1 with the 0.8.1 CLI. The plugin does not apply Kubernetes resources.
Native deployment¶
Use the digest from push.json. The following is a template: replace the
image digest, runtime ID, namespace and architecture with values from your
platform team. Resource sizes are examples, not JVM sizing recommendations.
apiVersion: apps/v1
kind: Deployment
metadata:
name: java-demo
namespace: apps
spec:
replicas: 1
selector:
matchLabels: {app: java-demo}
template:
metadata:
labels: {app: java-demo}
annotations:
brewlet.sh/runtime-image: java-21
spec:
runtimeClassName: brewlet
nodeSelector: {kubernetes.io/arch: arm64}
containers:
- name: app
image: registry.example.com/apps/java-demo@sha256:<application-digest>
securityContext: {readOnlyRootFilesystem: true}
resources:
requests: {cpu: 100m, memory: 64Mi}
limits: {cpu: "1", memory: 512Mi}
readinessProbe:
httpGet: {path: /, port: 8000}
Registry authentication, default non-root identity, capabilities and startup tolerations follow the common workload rules. Mount writable volumes if the application or JVM needs temporary storage.
Verify¶
After applying the manifest:
kubectl -n apps rollout status deployment/java-demo --timeout=5m
kubectl -n apps exec deployment/java-demo -- /usr/bin/java \
--class-path '/app/app.jar:/app/lib/*' sh.brewlet.fixture.Main --probe "two words"
kubectl -n apps port-forward deployment/java-demo 18080:8000
In another terminal, curl --fail http://127.0.0.1:18080/ should report
language: java, the actual runtime and dependency: shared-contract.
The Exec report also includes --probe and two words as distinct arguments.
To see which runtime image generation the Pod actually uses, check the
actual runtime binding; rollout
success alone does not tell you. Stop the port-forward when finished.
Pitfalls and limits¶
- A thin JAR needs its external dependency files; an executable (fat) JAR can instead use explicit
-jararguments. Brewlet does not unpack nested libraries or discover a main class. - JPMS, agents, AppCDS and JVM tuning are your choices, written explicitly in
launch.json(or generated by the Maven plugin). Runtime rotation must stay compatible with them; it does not update dependency copies under/app. - Requests and limits are not inferred heap settings. Leave headroom for non-heap/native allocations and writable runtime data where needed.
- Native libraries (JNI) and prebuilt archives such as AppCDS are platform-specific; test them on every platform you declare.
See resources, instrumentation and troubleshooting for the common operational boundaries.