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 2022 — 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/javax juggling, 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 queries, file transfers, remote file reads and listings, standard input, Process-style streaming, and a full CLI: see What you gain.

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 this client's default, so nothing needs to be spelled out. Two shape differences are visible immediately:

  • The client is AutoCloseable and 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 every executeCommand(...) call.
  • The password is a char[], not a String; 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)[2]:

winrm4j WinRM Java Client
WinRmTool.Builder.builder(address, username, password) WinRMClient.builder(hostname).credentials(username, password) — a bare host name or IP, not a URL or host:port (use https() and port(int)); 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[3])
authenticationScheme(AuthSchemes.KERBEROS) authentication(AuthScheme.KERBEROS) — requires https() (see behavioral differences)
authenticationScheme(AuthSchemes.SPNEGO) authentication(AuthScheme.KERBEROS) — always SPNEGO (Negotiate) here, and requires https(); credential delegation, automatic in winrm4j's SPNEGO, is opt-in with allowDelegation() (Credential delegation[4])
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[5])
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; retries(1, Duration.ofSeconds(5)) is the closest equivalent of winrm4j's default (one retry, 5-second pause); see behavioral differences
a custom failureRetryPolicy(...), retryReceiveAfterOperationTimeout(...) none — see behavioral differences
workingDirectory(String) workingDirectory(String) — on the command, not the client (command options[6])
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[7])
tool.executePs(String) client.powerShell(script).execute() — also base64-encoded (-EncodedCommand), so no quoting or escaping (Running PowerShell[8])
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[9]
getStdOut() stdout()
getStdErr() stderr()
getStatusCode() exitCode()

Failures surface through the unchecked WinRMClientException[10] hierarchy instead of winrm4j's SOAPFaultException / RuntimeException mix — authentication rejections, WSMan faults (with getters for the fault code and detail), and timeouts each have their own type. See Timeouts and Errors[11].

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:

  • NTLM 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, NTLM over HTTP always uses message encryption — nothing to configure — and hosts that require encryption (AllowUnencrypted=false, the Windows default) work out of the box.
  • Basic: use it with https() and a bare local account name. The client accepts Basic over plain HTTP too, where the credential and payload travel in the clear, and Windows accepts Basic only for local accounts named without a DOMAIN\/MACHINE\ prefix (Authentication[12]). 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 with https() and by the FQDN the KDC knows (Authentication[3]).
  • 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[13]).
  • The user profile is not loaded by default. winrm4j hardcodes WINRS_NOPROFILE=FALSE, so its commands see the user's profile: %APPDATA%, the user's HKEY_CURRENT_USER hive. This client does not load it unless the builder calls loadUserProfile() (Loading the user profile[14]).
  • Timeout semantics. winrm4j's operationTimeout is the WSMan Receive polling timeout (how long each poll waits for output), and separate CXF settings govern connect/receive at the HTTP level. Here a single timeout(Duration) (default 30 s) is a wall-clock deadline for the whole operation with execute(), and an inactivity timeout with start() (Timeouts and Errors[11]).
  • No silent retries. winrm4j retries connection failures once by default and can be told to retry Receive after an operation timeout. Here nothing is retried unless you opt in with retries(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 may have run is never replayed: the only replays are of a command the host refused before running it (its shell was reaped, or the quota was full; see Shell reuse[15]).
  • One shell for many commands. winrm4j creates and tears down a remote shell for every executeCommand(...). This client creates the shell on the first command and reuses it, for 10 commands by default (Shell reuse[15]), which is faster — and is why workingDirectory(...) and environment(...) are per-command options that take effect on the client's first command, then on every shell that replaces it (command options[6]).

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[16]).
  • 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[17]).
  • Remote files — client.file(path) reads a file (whole, a byte range, a tail, or streamed), downloads it to a local file, returns its properties, or lists a directory with filters evaluated on the host (Remote Files[18]).
  • Standard input — stdin(...) feeds a remote command its input, with real EOF semantics (Standard input[19]).
  • Live streaming — start() returns a java.lang.Process-shaped RemoteProcess[20] whose output is consumed while the command runs, with bounded memory (Streaming the output[7]).
  • A real CLI — the standalone jar runs commands and WQL queries, opens an interactive remote shell, and lists, reads and downloads remote files (ls, stat, cat, get) from the terminal (Command-Line Client[21]).

See also

winrm4j migration cloudsoft cxf WinRmTool WinRmClientBuilder executePs executeCommand winrm wql wmi windows ntlm kerberos java remote management
Links:
  • [1] https://github.com/cloudsoft/winrm4j
  • [2] apidocs/org/metricshub/winrm/WinRMClient.html
  • [3] authentication.html
  • [4] authentication.html#credential-delegation
  • [5] tls.html
  • [6] commands.html#command-options
  • [7] commands.html#streaming-the-output
  • [8] commands.html#running-powershell
  • [9] apidocs/org/metricshub/winrm/CommandResult.html
  • [10] apidocs/org/metricshub/winrm/exceptions/WinRMClientException.html
  • [11] timeouts-and-errors.html
  • [12] authentication.html#basic
  • [13] commands.html#character-encoding
  • [14] commands.html#loading-the-user-profile
  • [15] commands.html#shell-reuse
  • [16] wql.html
  • [17] file-transfers.html
  • [18] files.html
  • [19] commands.html#standard-input
  • [20] apidocs/org/metricshub/winrm/RemoteProcess.html
  • [21] cli.html
  • [22] installation.html
  • [23] commands.html
Searching...
No results.