Diagnostic and configuration analysis toolkit for JBoss Web Server / Apache Tomcat.
Status: Under active development as part of Google Summer of Code 2026.
jws-diag is a read-only CLI that helps SREs and support engineers quickly understand and validate a Tomcat/JWS installation without modifying any files or sending network requests.
| Command | Description |
|---|---|
jws-diag summary |
Show installed versions, JVM info, OS/container signals, native library status |
jws-diag config |
Parse and display effective connector, TLS, proxy, and executor configuration |
jws-diag validate |
Run diagnostic rules and report findings as INFO/WARN/ERROR |
jws-diag bundle |
Generate a redacted .tar.gz support bundle for safe sharing |
Requirements: JDK 11+, Maven 3.6+
git clone https://github.com/web-servers/jws-diag.git
cd jws-diag
mvn package -qThe self-contained jar is at target/jws-diag-0.1.0-SNAPSHOT.jar.
jws-diag auto-discovers CATALINA_HOME through five sources (in priority order):
--catalina-homeCLI flagCATALINA_HOME/CATALINA_BASEenvironment variables- systemd override files (
/etc/sysconfig/tomcat,/etc/default/tomcat) - Well-known paths (
/opt/rh/jws*/root/usr/share/tomcat,/usr/share/tomcat) - Running process detection via
/proc/*/cmdline
If Tomcat is installed at a standard path, no flags are required.
# Auto-discover Tomcat
java -jar target/jws-diag-0.1.0-SNAPSHOT.jar summary
# Explicit path
java -jar target/jws-diag-0.1.0-SNAPSHOT.jar summary --catalina-home /opt/tomcat
# JSON output
java -jar target/jws-diag-0.1.0-SNAPSHOT.jar config --format jsonTomcat 10.1.49 | JWS 6.1.0
CATALINA_HOME: /opt/rh/jws6/root/usr/share/tomcat
CATALINA_BASE: /opt/rh/jws6/root/etc/tomcat
JVM: 17.0.10 (Red Hat, Inc.) | OS: RHEL 9.3 (x86_64)
Container: Podman (detected via /run/.containerenv)
Native: APR 1.7.2 | OpenSSL 3.0.9
PID: 12345
Connector :8080 [HTTP/1.1]
protocol: HTTP/1.1 (explicit)
sslEnabled: false (default)
maxThreads: 200 (default)
connectionTimeout: 20000 (explicit)
maxConnections: 8192 (default)
compression: off (default)
Connector :8443 [HTTP/1.1] ★ SSL
maxThreads: 150 (explicit)
connectionTimeout: 60000 (default)
SSLHostConfig:
protocols: TLSv1.2+TLSv1.3
Certificate:
keystoreFile: conf/localhost-rsa.jks
keystoreType: JKS (default)
keystorePass: ***REDACTED***
Executor "tomcatThreadPool"
maxThreads: 150 (explicit)
minSpareThreads: 4 (explicit)
threadPriority: 5 (default)
maxIdleTime: 60000 (default)
namePrefix: catalina-exec-
Host: localhost
appBase: webapps (explicit)
autoDeploy: true (explicit)
unpackWARs: true (explicit)
Valve: AccessLog [org.apache.catalina.valves.AccessLogValve]
directory: logs
pattern: %h %l %u %t "%r" %s %b
Each attribute is annotated (explicit) when set in server.xml or (default) when
the Tomcat compiled-in default was applied. Passwords and secrets are always ***REDACTED***.
{
"schemaVersion": "1.0",
"shutdownPort": 8005,
"services": [{
"name": "Catalina",
"connectors": [{
"port": 8080,
"protocol": { "value": "HTTP/1.1", "explicit": true },
"sslEnabled": { "value": false, "explicit": false },
"maxThreads": { "value": 200, "explicit": false },
"connectionTimeout": { "value": 20000, "explicit": true }
}],
"executors": [{
"name": "tomcatThreadPool",
"maxThreads": { "value": 150, "explicit": true },
"minSpareThreads": { "value": 4, "explicit": true },
"threadPriority": { "value": 5, "explicit": false },
"maxIdleTime": { "value": 60000, "explicit": false }
}]
}]
}The { "value": ..., "explicit": true/false } pattern makes it easy for CI pipelines
and automation to distinguish operator-set values from Tomcat defaults.
Options go after the command name: jws-diag config --format json, not jws-diag --format json config.
| Option | Description |
|---|---|
-h, --help |
Show help and exit |
-V, --version |
Show version and exit |
Every command accepts:
| Option | Description |
|---|---|
-f, --format HUMAN|JSON |
Output format (default: HUMAN). Values are case-insensitive, so json works. |
JSON output carries a per-command schemaVersion. See docs/json-output.md for what a version change means and how to consume the output safely.
summary, config, validate, diff and logs accept --all, which scans /proc and reports on every running instance. Each instance's paths come from its own process, so --all cannot be combined with that command's path options (--catalina-home, --catalina-base, --left, --right, --log-file). The combination is rejected and exits 3.
Show installed versions, JVM info, OS and container detection, and native library status.
| Option | Description |
|---|---|
--catalina-home <path> |
Path to CATALINA_HOME. Overrides auto-detection. |
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to CATALINA_HOME. |
--all |
Report every running instance. |
Parse server.xml and show the effective connector, TLS, proxy and executor configuration, with each value marked (explicit) or (default).
| Option | Description |
|---|---|
--catalina-home <path> |
Path to CATALINA_HOME. Overrides auto-detection. |
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to CATALINA_HOME. |
--all |
Report every running instance. |
Run diagnostic rules against the configuration and report INFO, WARN and ERROR findings.
| Option | Description |
|---|---|
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to the CATALINA_BASE environment variable. There is no auto-detection. |
--all |
Validate every running instance. |
Compare the effective server.xml configuration of two installations.
| Option | Description |
|---|---|
--left <path> |
First CATALINA_BASE directory, or its server.xml |
--right <path> |
Second CATALINA_BASE directory, or its server.xml |
--all |
Compare every running instance against the one with the lowest PID. Needs at least two running instances. |
Give either --left and --right together, or --all.
Scan a Tomcat log for known error patterns such as OutOfMemoryError, BindException and stuck threads.
| Option | Description |
|---|---|
--log-file <path> |
Log file to scan. Default: CATALINA_BASE/logs/catalina.out. |
--catalina-home <path> |
Path to CATALINA_HOME. Overrides auto-detection. |
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to CATALINA_HOME. |
--all |
Scan every running instance's log. |
With --all, the log file is found per instance: CATALINA_OUT from that process's environment, then logs/catalina.out, then the newest catalina.*.log. An instance whose CATALINA_OUT points at a stream such as /dev/stdout is skipped with the reason.
Show the mod_cluster or mod_proxy_cluster listener configuration from server.xml. A server.xml without one is a normal result and exits 0.
| Option | Description |
|---|---|
--catalina-home <path> |
Path to CATALINA_HOME |
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to CATALINA_HOME. |
List every running Tomcat or JWS instance found in /proc. Finding none is a normal result and exits 0. There are no options besides --format.
Write a redacted .tar.gz support bundle with configuration files, a version manifest and logs.
| Option | Description |
|---|---|
--catalina-base <path> |
Path to CATALINA_BASE. Defaults to the CATALINA_BASE environment variable. |
--catalina-home <path> |
Path to CATALINA_HOME. Defaults to the CATALINA_HOME environment variable, then to CATALINA_BASE. |
--output-dir <dir> |
Where to write the archive. Default: the current directory. |
--staging-dir <dir> |
Where to assemble the bundle before archiving. Default: a temporary directory, removed afterwards. |
--redaction-level DEFAULT|STRICT |
STRICT also masks IP addresses, hostnames and environment variable values. Default: DEFAULT. |
With --format json, bundle prints the absolute archive path and the number of files that could not be collected.
Placeholders in server.xml are resolved in this order:
- JVM system properties (
-Dkey=value) CATALINA_BASE/conf/catalina.properties- Environment variables (
${env.VAR_NAME}) ${VAULT::block::attr::}tokens — preserved as-is (resolved at runtime by tomcat-vault)- Unresolved
${...}— kept verbatim in output
Attributes absent from server.xml receive their Tomcat 10.1.x compiled-in defaults:
| Attribute | Default | Element |
|---|---|---|
maxThreads |
200 | Connector |
connectionTimeout |
60000 ms | Connector |
maxConnections |
8192 | Connector |
protocol |
HTTP/1.1 |
Connector |
SSLEnabled |
false |
Connector |
compression |
off |
Connector |
secretRequired |
true |
AJP Connector |
autoDeploy |
true |
Host |
unpackWARs |
true |
Host |
keystoreType |
JKS |
SSLHostConfig |
maxThreads |
200 | Executor |
minSpareThreads |
25 | Executor |
threadPriority |
5 | Executor |
maxIdleTime |
60000 ms | Executor |
- Read-only: No file writes, no process signals, no network calls
- Secrets redacted: Attribute names matching
*password*,*secret*,*credential*→***REDACTED*** - VAULT tokens preserved:
${VAULT::...}passed through opaque — never resolved or redacted - Minimal privileges: Runs as the invoking user; no privilege escalation required
jws-diag summary detects APR/OpenSSL native library configuration by:
- Scanning
CATALINA_BASE/conf/server.xmlforAprLifecycleListenerorOpenSSLLifecycleListener - Scanning
CATALINA_HOME/lib/for versioned jars (tomcat-native-*.jar,openssl-*.jar) - Selecting the highest semver when multiple versions are present
Note: listener presence indicates the library is configured, not confirmed loaded at runtime.
Run the full test suite and generate a coverage report:
mvn testThe HTML report is written to target/site/jacoco/index.html. Current
instruction coverage on main is 89%.
To enforce the coverage floor (required in CI):
mvn verifymvn verify fails the build if instruction coverage drops below 80%.
The floor is defined in the JaCoCo check execution in pom.xml.
Every command uses the same exit code contract. Codes 0 to 2 describe what the tool found. Code 3 describes the tool failing to look at all, so a CI gate can tell "your configuration has a problem" apart from "jws-diag could not run".
| Code | Meaning |
|---|---|
0 |
Ran successfully; nothing noteworthy found |
1 |
Ran successfully; warning level findings, a non-empty difference, or a partially collected result |
2 |
Ran successfully; error level findings |
3 |
The tool itself failed: installation not found, file unreadable, malformed XML, or bad arguments |
Per command:
| Command | 0 |
1 |
2 |
3 |
|---|---|---|---|---|
summary |
all instances reported | some instances skipped | not used | cannot resolve or read installation, or every instance skipped |
config |
all instances reported | some instances skipped | not used | cannot resolve or parse server.xml, or every instance skipped |
validate |
no findings above INFO | WARN findings, or some instances skipped | ERROR findings | cannot resolve CATALINA_BASE, server.xml missing or unparseable, --all with --catalina-base, or every instance skipped |
diff |
configurations identical | configurations differ, or some instances skipped | not used | cannot resolve or parse either side, or every instance skipped |
logs |
no matches above INFO | WARN matches, or some instances skipped | ERROR matches | cannot resolve or read the log file, or every instance skipped |
modcluster |
configuration shown, or none present | not used | not used | cannot resolve or parse server.xml |
instances |
instances listed, or none running | not used | not used | not used |
bundle |
bundle written complete | bundle written, some files skipped | not used | cannot resolve paths or write the archive |
For --all, a run where some instances were skipped is incomplete, so it exits at least
1. A run where every instance was skipped examined nothing, so it exits 3.
Absence of optional configuration is a result, not a failure: modcluster with no
mod_cluster listener and instances with nothing running both exit 0.
See CONTRIBUTING.md for the development workflow, code standards, and PR process.