Runs, controls, and monitors Java applications as Windows services.
Java Service Steward is a Windows service host for Java applications, written in Rust. It registers a Java application as a Windows service, launches the JVM, keeps a loopback control channel to it, restarts it when it exits unexpectedly or stops answering, rotates its log, and answers Service Control Manager requests such as stop, pause, resume, and custom control codes. It ships as one executable plus a small Java 8 compatible JAR: no native DLL, no JNI, no installer.
It reads the widely deployed wrapper.conf configuration format and follows
the same command-line conventions (-c, -i, -t, -p, -r, -d, and so
on), so an existing configuration keeps working with a different host
underneath it. Java Service Steward targets 64-bit Windows, ships as a single
executable plus one JAR, and is permissively licensed (Apache-2.0 OR MIT), so
anybody can deploy, embed, or redistribute it without publishing their own
code.
Pre-1.0. The first public release was 0.3.0; the current version is the latest section of CHANGELOG.md. The configuration format, command line, log format, and supervision semantics are meant for production use; the Rust and Java APIs may still change between minor versions, and every incompatible change is recorded in CHANGELOG.md.
| Area | Supported | Not supported |
|---|---|---|
| Launchers | SimpleApp, StartStopApp, JarApp, selected through wrapper.java.mainclass (the launcher aliases used by existing configuration files are recognized) |
The bridge JAR and native library of the original product |
| Application API | io.github.jayyanez.jss.bridge.Steward and ServiceListener: start, stop, restart, log, control events, start wait hints |
The original product's Java API; applications that import it need a source-level migration |
| Diagnostics | Thread dumps (-d, control code 255, CTRL_BREAK or jcmd), on-demand HPROF heap dumps (--heapdump, control code 254) |
JMX, remote control |
| Logging | LPTM log records, console format, SIZE/WRAPPER/JVM roll modes, size limit, archive count, per-level filtering |
Syslog |
| Supervision | Startup, ping, and shutdown timeouts; restart throttling; exit-code actions; output filters (RESTART, SHUTDOWN, DUMP, GC, PAUSE, RESUME); pause/resume; Job Object clean-up of child processes |
Unix daemons, named-pipe backends |
| Editions | The Community Edition feature set, property by property | Features that exist only in the Standard or Professional editions of the original product |
The complete property-by-property table is in docs/compatibility.md.
-
Download
java-service-steward-<version>-windows-x64.zipfrom the releases page and check it against theSHA256SUMSfile published next to it. -
Unpack it. The zip contains
wrapper.exe,wrapper.jar, the license files, andexamples/wrapper.conf.example. -
Copy
examples/wrapper.conf.exampletowrapper.confnext towrapper.exeand edit the Java command, the classpath, and the main class. -
Run the application in the current console first:
wrapper.exe -c wrapper.conf
Press
Ctrl+Cto stop it. The log is written to the path named bywrapper.logfile. -
Install and control it as a Windows service from an elevated shell:
wrapper.exe -i wrapper.conf # install wrapper.exe -t wrapper.conf # start wrapper.exe -p wrapper.conf # stop wrapper.exe -r wrapper.conf # remove
wrapper.exe -it wrapper.confinstalls and starts in one step,-qprints the service state, and-a/-epause and resume a pausable service.
wrapper.exe --help prints the complete command and property reference; no
configuration file is read for that command.
Every release also ships a CycloneDX SBOM and a Sigstore build-provenance
attestation; gh attestation verify <zip> --owner jayyanez proves that a
download was built by this repository's release workflow. See
docs/release.md. New tagged Windows
releases use optional Authenticode signing; existing 0.3.2 and older downloads
remain unsigned. See code signing.
A minimal wrapper.conf:
#encoding=UTF-8
set.APP_HOME=C:/example/app
wrapper.java.command=%JAVA_HOME%/bin/java.exe
wrapper.java.mainclass=io.github.jayyanez.jss.bridge.SimpleApp
wrapper.java.classpath.1=wrapper.jar
wrapper.java.classpath.2=%APP_HOME%/lib/*
wrapper.java.additional.1=-Xmx512m
wrapper.app.parameter.1=com.example.Main
wrapper.logfile=../logs/wrapper.log
wrapper.ntservice.name=example-service
wrapper.ntservice.displayname=Example ServiceRules worth knowing:
- A relative configuration path, and every relative path inside the file
(classpath entries, log file, PID files, working directory), is resolved
from the directory that contains
wrapper.exe, not from the caller's current directory. This keeps service start-up independent of how the Service Control Manager launches the process. set.NAME=valuedefines an environment variable for the rest of the file and for the JVM;set.default.NAME=valuedefines it only when it is not already set.%NAME%expands an environment variable, including one defined withset., anywhere in a value.- Numbered properties (
wrapper.java.classpath.<n>,wrapper.java.additional.<n>,wrapper.app.parameter.<n>, and others) are read in order from 1 and stop at the first missing index. An explicitly empty entry keeps its index and produces no argument. - Properties given on the command line as
name=valueoverride the file, which is convenient for one-off runs such aswrapper.exe -c wrapper.conf wrapper.debug=true.
The fully commented examples/wrapper.conf.example
covers the timeouts, filters, logging, and service properties, and the
project's own jss.* extensions. The prefix tells you what is portable:
wrapper.* properties belong to the shared configuration format, while
jss.* properties (all optional, all with defaults) exist only in Java Service
Steward; see docs/compatibility.md.
An installation that already runs from a wrapper.conf can switch host in
place. Neither wrapper.conf nor the service's registered ImagePath needs
to change: the executable keeps the name wrapper.exe, the bridge keeps the
name wrapper.jar, and the launcher class names accepted in
wrapper.java.mainclass are recognized and mapped to the bundled launchers.
- Stop the service.
- Back up the current
wrapper.exe,wrapper.jar, and the native DLL next to them. - Copy the new
wrapper.exeover the old one, and the newwrapper.jarover the JAR named bywrapper.java.classpath. - Optionally delete the old native DLL. It is never loaded.
- Start the service.
Rollback is the reverse: stop the service and restore the backed-up files.
The JAR and DLL of the original product are not supported: when the configured
classpath does not contain the bundled wrapper.jar, the wrapper refuses to
launch Java and says so in the log. Applications that only run a main
method need no code change. Applications that implemented the original
product's listener interface must be ported to
io.github.jayyanez.jss.bridge.Steward and ServiceListener; this is a
source change, described in docs/migration.md together
with the table of properties that are supported, accepted and ignored, or
rejected.
wrapper.exe -d wrapper.confasks the running service for a thread dump. It is captured as ordinaryjvm <n>records in the configured log file, with the normal format and rotation.sc.exe control <service> 255does the same without the wrapper.wrapper.exe --heapdump wrapper.confasks for an HPROF heap dump written next to the log file, or tojss.heapdump.directory. This is a Java Service Steward extension on control code 254. It needs ajcmdmatching the configured Java runtime; without one the request is rejected with an actionable log message and the service keeps running.- A JVM started with
-Xrscannot receive theCTRL_BREAKthread-dump signal. The defaultjss.threaddump.method=AUTOnotices-Xrsand usesjcmd Thread.printinstead, so existing configurations need no change.
Details, including what a heap dump contains and how to protect it, are in docs/diagnostics.md.
Requirements: Rust 1.88 or newer (rust-toolchain.toml selects the stable
channel with rustfmt and clippy), and a JDK 8 or newer on PATH to build
wrapper.jar, which is compiled with --release 8. Production machines only
need a Java runtime.
cargo build --release # target\release\wrapper.exe
./scripts/build-java-bridge.ps1 # target\release\wrapper.jar
./scripts/build-release.ps1 # fmt, clippy, tests, both artifacts, version checksThe crate is also on crates.io:
cargo install java-service-steward builds wrapper.exe from the released
sources. It does not build wrapper.jar; take it from the release zip or run
scripts/build-java-bridge.ps1 from the crate sources with a JDK on PATH.
The integration tests compile small synthetic Java applications and run real
JVMs, so they need a JDK on PATH (javac, jar, jcmd, and jlink are
looked up there). Without a JDK those tests skip; set
JSS_REQUIRE_JAVA_TESTS=1 to make them fail instead, which is what CI does:
cargo fmt --check
cargo clippy --all-targets --all-features -- -D warnings
$env:JSS_REQUIRE_JAVA_TESTS = "1"
cargo test --all-targets --all-featuresCI runs this on windows-latest against Temurin 8, 21, and 25, plus a
Rust 1.88 build check and a license and advisory audit; see
.github/workflows/ci.yml. The release procedure
is in docs/release.md.
Please read SECURITY.md before deploying. Two points deserve
attention up front: the service runs as LocalSystem unless
wrapper.ntservice.account names a dedicated account, and the control channel
between wrapper.exe and the JVM is a loopback-only TCP socket protected by a
random key generated for each launch. Vulnerabilities should be reported
privately as described in that file.
Contributions are welcome. CONTRIBUTING.md explains the
development workflow, the provenance rules that keep the project independent,
and the Developer Certificate of Origin: every commit must carry a
Signed-off-by line (git commit -s).
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or https://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or https://opensource.org/licenses/MIT)
at your option. Third-party components and their licenses are listed in THIRD_PARTY_NOTICES.md.
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in this project by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.
The release workflow signs wrapper.exe through Microsoft Azure Artifact
Signing under the verified publisher Jay Yanez, unless the maintainer
deliberately disables release signing. Ordinary CI/local builds remain
unsigned, and manual validation does not publish a release. Existing 0.3.2
and older release assets are unchanged; the first signed public package needs
a new version. wrapper.jar is not JAR-signed. Authenticode, ZIP checksums,
and Sigstore provenance are separate verification mechanisms, and signing
does not guarantee that every SmartScreen reputation prompt disappears.
See the signing and validation procedure.
Privacy: this program will not transfer any information to other networked systems unless specifically requested by the user or the person installing or operating it.
Java Service Steward is an independent project. It is not affiliated with,
endorsed by, or sponsored by Tanuki Software, Ltd. "Java Service Wrapper" and
"Tanuki Software" are names of Tanuki Software, Ltd. and are used here only to
describe compatibility with the wrapper.conf configuration format and
command-line conventions of that product. Java Service Steward does not
contain, bundle, or require any Tanuki Software code or binaries.
How the project was built without reference to third-party source code is described in docs/provenance.md.