WinRM Java Client 2.2.00
-
Home
- Reference
Migrating from winrm4j
winrm4j[1] has served Java projects well, but it is dormant — the last release (0.12.3) dates back to August 2021 and the last commit to March 2023 — and its Apache CXF / Java 8-era stack has aged poorly: on modern JDKs it needs manually added JAXB and JAX-WS dependencies, and its ServiceLoader-based XML factory lookup is a known source of classpath conflicts. This page maps the winrm4j API to the WinRM Java Client so an existing code base can switch in one sitting.
Why migrate
- Zero runtime dependencies. winrm4j pulls in the Apache CXF stack — dozens of jars, roughly 10 MB — plus whatever JAXB/JAX-WS additions your JDK requires. This client is a single jar of a few hundred kB that speaks WS-Management over the JDK's own HTTP and XML APIs.
- Modern JDKs, no workarounds. The library targets Java 11 and runs on any later JDK — no
jakarta/javaxjuggling, no JAXB add-ons. - Immune to JAXP conflicts by construction. It uses the JDK-default XML factories directly, so another library's
ServiceLoader-registered XML implementation cannot break it — a classic winrm4j/CXF failure mode. - Actively maintained, with releases published on Maven Central.
- Features winrm4j never had: WQL / WMI queries[2], file transfers[3] through the WinRM channel, standard input[4],
Process-style streaming[5] of live output, and a full-featured command-line client[6].
The five-minute version
// winrm4j
WinRmTool tool = WinRmTool.Builder.builder("server.example.com", "DOMAIN\\Administrator", "password")
.authenticationScheme(AuthSchemes.NTLM)
.port(5985)
.useHttps(false)
.build();
WinRmToolResponse response = tool.executeCommand("ipconfig /all");
System.out.println(response.getStdOut());
System.out.println(response.getStatusCode());
becomes:
// WinRM Java Client
try (WinRMClient client = WinRMClient.builder("server.example.com")
.credentials("DOMAIN\\Administrator", "password".toCharArray())
.build()) {
CommandResult result = client.command("ipconfig /all").execute();
System.out.println(result.stdout());
System.out.println(result.exitCode());
}
NTLM over HTTP on port 5985 is the default on both sides, so nothing needs to be spelled out. Two shape differences are visible immediately:
- The client is
AutoCloseableand meant for try-with-resources — it authenticates once and runs any number of commands and queries over the same connection, where winrm4j re-created a shell (and re-authenticated) on everyexecuteCommand(...)call. - The password is a
char[], not aString; the builder takes the array as-is, without copying it.
Option mapping
Builder options
winrm4j has two builders — WinRmTool.Builder (high level) and WinRmClientBuilder (low level) — with largely overlapping options. Both map to the single WinRMClient.builder(hostname)[7]:
| winrm4j | WinRM Java Client |
|---|---|
WinRmTool.Builder.builder(address, username, password) |
WinRMClient.builder(hostname).credentials(username, password) — password as char[] |
builder(address, domain, username, password) |
credentials("DOMAIN\\user", password) — the domain rides in the user name |
useHttps(true) |
https() |
port(int) |
port(int) |
authenticationScheme(AuthSchemes.NTLM) |
authentication(AuthScheme.NTLM) — the default; several schemes form an ordered fallback list (Authentication[8]) |
authenticationScheme(AuthSchemes.KERBEROS) |
authentication(AuthScheme.KERBEROS) — requires https() (see behavioral differences) |
authenticationScheme(AuthSchemes.BASIC) |
authentication(AuthScheme.BASIC) — over HTTPS (see behavioral differences) |
disableCertificateChecks(true) |
trustAllCertificates() |
sslContext(SSLContext) |
sslContext(SSLContext) — hostname verification stays on |
hostnameVerifier(...), sslSocketFactory(...) |
none — hostname verification is all or nothing: on with sslContext(...), off (together with certificate validation) with trustAllCertificates(). There is no custom-verifier hook, so the certificate must identify the hostname you connect by (TLS / HTTPS[9]) |
operationTimeout(long) (milliseconds) |
timeout(Duration) — different semantics, see behavioral differences |
connectionTimeout(long), connectionRequestTimeout(long), receiveTimeout(Long) |
none — the single timeout(Duration) is a wall-clock deadline covering all of it |
retriesForConnectionFailures(int) |
retries(int, Duration) — opt-in; see behavioral differences |
failureRetryPolicy(...), retryReceiveAfterOperationTimeout(...) |
none — see behavioral differences |
workingDirectory(String) |
workingDirectory(String) — on the command, not the client (command options[10]) |
environment(Map<String, String>) |
environment(String, String) — on the command, once per variable |
requestNewKerberosTicket(boolean) |
none — Kerberos logs in with the password by default; ticketCache(Path) points at an existing ticket cache instead |
context(WinRmClientContext) |
none needed — there is no CXF Bus to share or shut down; build clients freely and close() them |
locale(Locale) |
none — the WSMan locale is not configurable |
allowChunking(boolean), payloadEncryptionMode(...) |
none — see behavioral differences for encryption |
Running commands
| winrm4j | WinRM Java Client |
|---|---|
tool.executeCommand(String) |
client.command(commandLine).execute() |
tool.executeCommand(String, Writer out, Writer err) |
command(...).onStdout(chunk -> ...).onStderr(chunk -> ...).execute() — or start() for a java.lang.Process-style handle (streaming[5]) |
tool.executePs(String) |
client.powerShell(script).execute() — also base64-encoded (-EncodedCommand), so no quoting or escaping (Running PowerShell[11]) |
tool.executePs(List<String>) |
powerShell(String.join("\n", lines)).execute() — winrm4j joined the lines for you; here the script is just a string |
tool.executeCommand(List<String>) |
one command(...) per command line — or join with " & " for cmd.exe semantics |
Reading the result
winrm4j WinRmToolResponse |
CommandResult[12] |
|---|---|
getStdOut() |
stdout() |
getStdErr() |
stderr() |
getStatusCode() |
exitCode() |
Failures surface through the unchecked WinRMClientException[13] hierarchy instead of winrm4j's SOAPFaultException / RuntimeException mix — authentication rejections, WSMan faults (with the fault code and detail as fields), and timeouts each have their own type. See Timeouts and Errors[14].
Users of the lower-level WinRmClient / createShell() / ShellCommand API map the same way: one WinRMClient plays both roles — createShell() + ShellCommand.execute(cmd, out, err) becomes command(cmd).onStdout(...).onStderr(...).execute(), and the shell reuse that createShell() provided is automatic (one client keeps one remote shell alive across commands).
Behavioral differences
Beyond the API shapes, a few runtime behaviors differ deliberately. Worth reading before flipping the switch:
- Payload encryption is always on over HTTP. In winrm4j, NTLM message encryption is a configurable
PayloadEncryptionMode(OFF/OPTIONAL/REQUIRED) that only appeared in its final 0.12.x releases. Here, HTTP always uses NTLM message encryption — there is no unencrypted mode and nothing to configure, and hosts that require encryption (AllowUnencrypted=false, the Windows default) work out of the box. - Basic needs HTTPS, but the client does not enforce it. winrm4j offers
AuthSchemes.BASIC, which here maps toauthentication(AuthScheme.BASIC). This client has no Basic message protection: it accepts the scheme over both transports, so usehttps()with it — without TLS, the credential and payload travel in the clear and the client will not stop you. The host must have theBasicsetting enabled on the WinRM service and be reachable over HTTPS (Preparing the Windows Host[15]). NTLM remains the recommended scheme. - Kerberos requires HTTPS. winrm4j runs Kerberos over plain HTTP; this client refuses at
build(), because it does not implement Kerberos message encryption — without TLS the payload would travel unprotected. Connect withhttps()and by the FQDN the KDC knows (Authentication[8]). - TLS certificates are validated by default, including hostname verification — like winrm4j (
disableCertificateChecksexists on both sides), but worth re-checking if your winrm4j setup disabled checks and you want to stop doing that: pointsslContext(...)at a trust store containing the host certificate instead (TLS / HTTPS[9]). - Command output is UTF-8, not code page 437. winrm4j hardcodes
WINRS_CODEPAGE=437(US-OEM), which mangles any non-ASCII output on non-English hosts. This client creates the remote shell with code page 65001 (UTF-8), so accented and non-Latin output decodes correctly whatever the remote locale — no configuration needed (Character encoding[16]). - Timeout semantics. winrm4j's
operationTimeoutis the WSManReceivepolling timeout (how long each poll waits for output), and separate CXF settings govern connect/receive at the HTTP level. Here a singletimeout(Duration)(default 30 s) is a wall-clock deadline for the whole operation withexecute(), and an inactivity timeout withstart()(Timeouts and Errors[14]). - No silent retries. winrm4j retries connection failures once by default and can be told to retry
Receiveafter an operation timeout. Here nothing is retried unless you opt in withretries(int, Duration), and the policy is deliberately narrow: only attempts that provably never reached the server (TCP connect, DNS, TLS handshake, the authentication handshake) are retried, preserving at-most-once execution for non-idempotent commands. A request that was actually sent is never replayed. - One shell per client. winrm4j creates and tears down a remote shell for every
executeCommand(...). This client creates the shell on the first command and reuses it, which is faster — and is whyworkingDirectory(...)andenvironment(...)are per-command options that take effect on the client's first command (command options[10]).
What you gain
Once on the fluent API, features winrm4j never offered are one call away:
- WQL / WMI queries —
client.wql("SELECT Name, State FROM Win32_Service").execute(), with typed rows and streaming (WQL Queries[2]). - File transfers —
upload(Path...)copies local scripts to the host through the WinRM channel itself (no SMB, no port 445) before the command runs (File Transfers[3]). - Standard input —
stdin(...)feeds a remote command its input, with real EOF semantics (Standard input[4]). - Live streaming —
start()returns ajava.lang.Process-shapedRemoteProcess[17] whose output is consumed while the command runs, with bounded memory (Streaming the output[5]). - A real CLI — the standalone jar runs commands, PowerShell, WQL queries, and an interactive remote shell from the terminal (Command-Line Client[6]).
See also
- Installation[18] — coordinates and supported JDKs
- Remote Commands[19] — the complete command API
- Authentication[8] — NTLM and Kerberos details
- Timeouts and Errors[14] — timeout semantics and the exception surface
- [1] https://github.com/cloudsoft/winrm4j
- [2] wql.html
- [3] file-transfers.html
- [4] commands.html#standard-input
- [5] commands.html#streaming-the-output
- [6] cli.html
- [7] apidocs/org/metricshub/winrm/WinRMClient.html
- [8] authentication.html
- [9] tls.html
- [10] commands.html#command-options
- [11] commands.html#running-powershell
- [12] apidocs/org/metricshub/winrm/CommandResult.html
- [13] apidocs/org/metricshub/winrm/exceptions/WinRMClientException.html
- [14] timeouts-and-errors.html
- [15] preparing-the-host.html
- [16] commands.html#character-encoding
- [17] apidocs/org/metricshub/winrm/RemoteProcess.html
- [18] installation.html
- [19] commands.html
