<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Deniss Larka</title>
    <description>The latest articles on DEV Community by Deniss Larka (@deniss_larka).</description>
    <link>https://dev.to/deniss_larka</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3977066%2F1e65bada-d789-45f3-8a83-fec9a67ccd4a.png</url>
      <title>DEV Community: Deniss Larka</title>
      <link>https://dev.to/deniss_larka</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/deniss_larka"/>
    <language>en</language>
    <item>
      <title>The JDK's forgotten JMX protocol</title>
      <dc:creator>Deniss Larka</dc:creator>
      <pubDate>Sat, 11 Jul 2026 15:36:29 +0000</pubDate>
      <link>https://dev.to/deniss_larka/the-jdks-forgotten-jmx-protocol-4pci</link>
      <guid>https://dev.to/deniss_larka/the-jdks-forgotten-jmx-protocol-4pci</guid>
      <description>&lt;p&gt;Every Java engineer who has connected JConsole — or JDK Mission Control — to a server in&lt;br&gt;
another network segment knows the ritual. Open the JMX port. Discover that RMI quietly opened a &lt;em&gt;second&lt;/em&gt; port — random by&lt;br&gt;
default. Pin it with a system property nobody remembers without searching. File a firewall&lt;br&gt;
ticket for both. Wait.&lt;/p&gt;

&lt;p&gt;What fewer people know: the JMX specification shipped with a second remote transport that has&lt;br&gt;
none of these problems. One socket, one port, TLS underneath if you want it. It's called JMXMP —&lt;br&gt;
the JMX Messaging Protocol. It lost for the least mysterious reason in software — RMI shipped&lt;br&gt;
by default, JMXMP was a separate download, and defaults win — and its reference implementation&lt;br&gt;
has been effectively abandoned since around 2008. Yet it never quite died. Code that refuses&lt;br&gt;
to die usually knows something.&lt;/p&gt;

&lt;p&gt;I didn't set out to resurrect it. I fell into it.&lt;/p&gt;
&lt;h3&gt;
  
  
  The port dance, briefly
&lt;/h3&gt;

&lt;p&gt;The default remote JMX stack rides on RMI. The connection URL tells you most of the story:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service:jmx:rmi:///jndi/rmi://host:1099/jmxrmi
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I'll spare you the full anatomy behind that URL — there's a JNDI lookup in it, and that second,&lt;br&gt;
dynamically assigned port from the ritual above; few people ever learn the details, which is&lt;br&gt;
rather the point. Dynamic ports were a reasonable design for 1999's flat networks. Between&lt;br&gt;
today's firewalls, NAT, and containers, they're friction — not because RMI is bad, but because&lt;br&gt;
the network it was designed for no longer exists.&lt;/p&gt;

&lt;p&gt;The JMXMP URL:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;service:jmx:jmxmp://host:9875
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One socket. TCP in, TCP out. That's the whole networking story.&lt;/p&gt;

&lt;h3&gt;
  
  
  How I ended up in this codebase
&lt;/h3&gt;

&lt;p&gt;I maintain &lt;a href="https://druvu.com" rel="noopener noreferrer"&gt;JConsoleBooster&lt;/a&gt;, a modernized JConsole. It shipped fine for&lt;br&gt;
years on the 2008-era JMXMP jar — the one historically distributed as &lt;code&gt;jmxremote_optional&lt;/code&gt; /&lt;br&gt;
&lt;code&gt;jmx-optional&lt;/code&gt;, out of Sun's OpenDMK project, republished over the years by several parties&lt;br&gt;
because people kept needing single-socket JMX.&lt;/p&gt;

&lt;p&gt;Then I moved the app to a &lt;code&gt;jlink&lt;/code&gt;-built runtime. An automatic module from 2008 does not&lt;br&gt;
cooperate with the module system: no &lt;code&gt;module-info&lt;/code&gt;, split-package hazards, services declared&lt;br&gt;
in ways &lt;code&gt;jlink&lt;/code&gt; can't see. The choices were: abandon the transport, or take responsibility&lt;br&gt;
for the code.&lt;/p&gt;

&lt;p&gt;So I forked it and took the code apart — 87 files of 2007 Sun code. I won't pretend I read&lt;br&gt;
every line myself; it's 2026, and AI did much of the first-pass reading. The rule that&lt;br&gt;
mattered: nothing got deleted, changed, or believed without a proof — a test, a&lt;br&gt;
zero-references check, an API diff.&lt;/p&gt;
&lt;h3&gt;
  
  
  What's inside a library frozen in 2008
&lt;/h3&gt;

&lt;p&gt;Some of what I found is what you'd expect from code written for a platform that no longer&lt;br&gt;
exists. Some of it surprised me:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Dead RMI plumbing — in the socket-protocol library.&lt;/strong&gt; Proxy references, unmarshalling
helpers, an RMI exporter. The generic connector framework was written to serve both
transports, and the RMI half stayed behind like scaffolding nobody removed. I deleted it
after proving zero references.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plaintext defaults.&lt;/strong&gt; Encryption and authentication existed — as options, off unless asked.
Normal for 2007; indefensible for something you point at a JVM in production today.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;An authorization check that fails open on current JDKs.&lt;/strong&gt; It was written for the
SecurityManager era: it asks the runtime for the caller's identity through an API that, on a
modern JDK, always answers "nobody" — and it treats "nobody" as "security isn't enabled here,
let it through." On the JVMs of its time, correct. On today's, an allow-all with extra steps.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;And the part that made the effort worth it: the protocol itself is fine.&lt;/strong&gt; Message-based,
single connection, SASL negotiation for auth, TLS for transport — the design decisions Sun
made hold up. The packaging aged. The idea didn't.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last point matters. This isn't a rewrite-because-old-code-is-bad story. The original&lt;br&gt;
authors solved the right problem well; the code just outlived the platform assumptions around&lt;br&gt;
it.&lt;/p&gt;
&lt;h3&gt;
  
  
  The second life
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://github.com/DenissLarka/druvu-lib-jmxmp" rel="noopener noreferrer"&gt;druvu-lib-jmxmp&lt;/a&gt; 2.0.0, on Maven Central,&lt;br&gt;
Java 21 baseline — the same protocol, newborn. Three changes carry the release; everything&lt;br&gt;
else is detail.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. It's a real Java module — and still a drop-in.&lt;/strong&gt; The monolith is now three JPMS modules&lt;br&gt;
(&lt;code&gt;com.druvu.jmxmp.common&lt;/code&gt; / &lt;code&gt;server&lt;/code&gt; / &lt;code&gt;client&lt;/code&gt;), and &lt;code&gt;jlink&lt;/code&gt; treats them as first-class&lt;br&gt;
citizens. The public API — &lt;code&gt;javax.management.remote.jmxmp&lt;/code&gt; and friends — is kept frozen,&lt;br&gt;
verified by a snapshot test that diffs the exported surface against the original. If your code&lt;br&gt;
compiled against &lt;code&gt;jmxremote_optional&lt;/code&gt;, it compiles against this.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. The server refuses to run open.&lt;/strong&gt; Authentication is mandatory — the server env can't be&lt;br&gt;
built without it — and the connection is encrypted either way: bring your own TLS context, or&lt;br&gt;
the server generates an ephemeral self-signed certificate at startup. This costs adoption&lt;br&gt;
convenience, and I decided that's the&lt;br&gt;
right trade: nobody exposes a management port to the world &lt;em&gt;on purpose&lt;/em&gt;, but unauthenticated&lt;br&gt;
JMX endpoints keep showing up in the wild by accident, and they're a documented entry point for&lt;br&gt;
cryptominers. This library's answer is that the accident can't happen.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Map&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="o"&gt;?&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;env&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;JmxmpServerSecurity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tls&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sslContext&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;                  &lt;span class="c1"&gt;// optional — omit it, get an ephemeral self-signed cert&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;authenticator&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myAuthenticator&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// SASL/PLAIN — required&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;                         &lt;span class="c1"&gt;// refuses to build without an authenticator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mandatory authentication buys something besides keeping strangers out. Plenty of teams use&lt;br&gt;
JMX operations as the low-ceremony alternative to building an admin UI: change a setting at&lt;br&gt;
runtime, flip a feature toggle, trigger a business operation. It works — until something&lt;br&gt;
changes in production and nobody can say who changed it, or when. With every connection&lt;br&gt;
authenticated, the operation knows its caller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Override&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;switchFeature&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Subject&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;current&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getPrincipals&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;iterator&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;next&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getName&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="n"&gt;log&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;info&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{} switched feature '{}' {}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;operator&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt; &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="s"&gt;"on"&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"off"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;features&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;put&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;on&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="c1"&gt;// log: "john switched feature 'beta-checkout' on"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The connector runs each request inside the authenticated subject's context, so&lt;br&gt;
&lt;code&gt;Subject.current()&lt;/code&gt; in your MBean is the operator who issued the call — an audit trail with&lt;br&gt;
no extra plumbing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. It fails closed.&lt;/strong&gt; The SecurityManager-era identity lookup is gone; the server reads the&lt;br&gt;
authenticated subject from &lt;code&gt;Subject.current()&lt;/code&gt;, and a request with no subject is refused, not&lt;br&gt;
waved through. Deserialization — the classic JMX attack surface — goes through a&lt;br&gt;
deny-by-default filter (&lt;code&gt;JmxmpSerialFilter&lt;/code&gt;): allow-listed classes deserialize, everything else&lt;br&gt;
doesn't, before any application code sees the bytes. Authorization beyond authentication is&lt;br&gt;
there if you need it: a small RBAC SPI with a programmatic policy builder, default-deny.&lt;/p&gt;

&lt;p&gt;The precise diffs — what was removed, what each fix looks like — are in the repo history and&lt;br&gt;
release notes. I'd rather link them than paraphrase them.&lt;/p&gt;

&lt;h3&gt;
  
  
  Did it survive contact with reality?
&lt;/h3&gt;

&lt;p&gt;The test matrix runs the connector in every module arrangement — both sides modular, both on&lt;br&gt;
the classpath, mixed. That matrix earned its keep: it caught the freshly-modularized library&lt;br&gt;
silently behaving as a classpath citizen in one spot, where a &lt;code&gt;Class.forName&lt;/code&gt; reached across a&lt;br&gt;
module seam it had no right to cross. "It compiles with a module-info" and "it's modular" are&lt;br&gt;
different claims; only a test can tell them apart.&lt;/p&gt;

&lt;p&gt;And it runs in production of a sort: JConsoleBooster ships on it — every remote connection the&lt;br&gt;
app makes goes through this library.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where it stands on security claims
&lt;/h3&gt;

&lt;p&gt;Deliberately narrow. I'm not going to call the library "secure" — that's an adjective, not a&lt;br&gt;
fact. The facts: the fail-open path is fixed, transport encryption and authentication are&lt;br&gt;
non-optional, deserialization is allow-listed, and the delta for each is inspectable commit by&lt;br&gt;
commit.&lt;/p&gt;

&lt;p&gt;It's maintained by one person, best-effort, latest-version-only — the support model is&lt;br&gt;
spelled out in SECURITY.md. GitHub private vulnerability reporting is switched on. If you&lt;br&gt;
can break it, I want to know.&lt;/p&gt;

&lt;h3&gt;
  
  
  When you should not use this
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;An SSH tunnel to the default RMI connector already works for you.&lt;/strong&gt; Then it works. This
is for when the tunnel is the friction.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You're below Java 21.&lt;/strong&gt; The library assumes a modern JDK; the old republications exist
and stay where they are.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;You need anonymous local monitoring.&lt;/strong&gt; JConsole attaching to a local PID doesn't involve
remote JMX at all — nothing to change.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your daily client is JDK Mission Control.&lt;/strong&gt; JMC speaks the default RMI connector; teaching
it JMXMP is a plugin dance this library doesn't attempt to solve. Stock JConsole could
historically be coaxed into it with the connector on its classpath;
&lt;a href="https://druvu.com" rel="noopener noreferrer"&gt;JConsoleBooster&lt;/a&gt; speaks it out of the box.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Your security model lives in the old access-file / SecurityManager mechanics.&lt;/strong&gt; That
machinery is gone here, not emulated; migrating means adopting the SASL + RBAC model.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Coordinates
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.druvu&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;druvu-lib-jmxmp&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;2.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/DenissLarka/druvu-lib-jmxmp" rel="noopener noreferrer"&gt;github.com/DenissLarka/druvu-lib-jmxmp&lt;/a&gt;&lt;br&gt;
— issues and the pinned "what next" thread are the feedback channel. If you're running JMXMP&lt;br&gt;
somewhere unusual, I'd genuinely like to hear what it's doing there.&lt;/p&gt;

</description>
      <category>java</category>
      <category>jvm</category>
      <category>opensource</category>
    </item>
    <item>
      <title>ServiceLoader is great — until you want a constructor argument</title>
      <dc:creator>Deniss Larka</dc:creator>
      <pubDate>Thu, 11 Jun 2026 11:59:59 +0000</pubDate>
      <link>https://dev.to/deniss_larka/serviceloader-is-great-until-you-want-a-constructor-argument-2767</link>
      <guid>https://dev.to/deniss_larka/serviceloader-is-great-until-you-want-a-constructor-argument-2767</guid>
      <description>&lt;p&gt;I was building a small library that reads accounting files — GnuCash first, other formats maybe later. It's the textbook case for pluggable implementations: an &lt;code&gt;AccBook&lt;/code&gt; interface in the API module, one implementation per format in its own module, and the application picks up whichever one is on the path.&lt;/p&gt;

&lt;p&gt;Why not &lt;code&gt;AccBook book = new GnucashAccBook(path)&lt;/code&gt; and move on? For one format in one app — do exactly that. But the moment your code says &lt;code&gt;new GnucashAccBook&lt;/code&gt;, it depends on that implementation: the GnuCash module has to be on the compile path, its classes exported, its name written in every place that opens a book. Add a second format and each of those places grows an &lt;code&gt;if&lt;/code&gt;. The interface stops protecting you once you name the class behind it.&lt;/p&gt;

&lt;p&gt;Java already ships the tool for exactly this. No framework, no container, works on the module path: &lt;code&gt;ServiceLoader&lt;/code&gt;. So I reached for it. Then I hit a wall I'd hit before and forgotten about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The naive version
&lt;/h2&gt;

&lt;p&gt;The interface is plain:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Discovery is genuinely easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServiceLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But my &lt;code&gt;GnucashAccBook&lt;/code&gt; can't do anything without the file path. And &lt;code&gt;ServiceLoader&lt;/code&gt; only knows how to call a no-arg constructor (or a static &lt;code&gt;provider()&lt;/code&gt; method). There is nowhere to hand it the &lt;code&gt;Path&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;So you reach for one of two workarounds, and both are worse than they look.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workaround 1 — construct empty, configure after:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServiceLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="nc"&gt;Configurable&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;   &lt;span class="c1"&gt;// ...and hope nobody touched `book` before this line&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now the object exists in an invalid state between construction and &lt;code&gt;init&lt;/code&gt;. You've swapped a constructor argument for a runtime contract the compiler can't see. (And &lt;code&gt;findFirst()&lt;/code&gt; quietly picks one if two implementations are on the path — hold that thought.)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Workaround 2 — put a factory behind ServiceLoader:&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AccBookFactory&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ServiceLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccBookFactory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;findFirst&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;orElseThrow&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is actually the right shape. But now every project grows an &lt;code&gt;XFactory&lt;/code&gt; interface, the &lt;code&gt;findFirst().orElseThrow()&lt;/code&gt; dance, and — if you care that two implementations on the path is a mistake — your own "throw if more than one" check. I wrote that glue three times before I admitted it was a pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Extracting the pattern
&lt;/h2&gt;

&lt;p&gt;So I pulled it into a tiny library, &lt;code&gt;druvu-lib-loader&lt;/code&gt;. It is the factory-behind-&lt;code&gt;ServiceLoader&lt;/code&gt; pattern, made generic once, with the implementation's arguments passed through a type-keyed map.&lt;/p&gt;

&lt;p&gt;The implementation side becomes a factory that &lt;em&gt;receives&lt;/em&gt; its arguments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;GnucashBookFactory&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;ComponentFactory&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="nf"&gt;createComponent&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Dependencies&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;GnucashAccBook&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getDependency&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
    &lt;span class="nd"&gt;@Override&lt;/span&gt;
    &lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;Class&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;type&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// Seasoned Spring developers will recognize the silhouette of an old friend.&lt;/span&gt;
        &lt;span class="c1"&gt;// This one retires right after construction, though — no container moves in.&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You register it exactly like any other service — &lt;code&gt;META-INF/services&lt;/code&gt;, or in &lt;code&gt;module-info.java&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;provides&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;druvu&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;lib&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ComponentFactory&lt;/span&gt;
    &lt;span class="n"&gt;with&lt;/span&gt; &lt;span class="n"&gt;com&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;myapp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;gnucash&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;GnucashBookFactory&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And in the API module you give callers one obvious entry point — a &lt;code&gt;static load&lt;/code&gt; right on the interface:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="nf"&gt;id&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Account&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;accounts&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="kd"&gt;static&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;ComponentLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Dependencies&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So everything the caller ever sees is this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;AccBook&lt;/span&gt; &lt;span class="n"&gt;book&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;AccBook&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;load&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constructor argument is passed. A single implementation is guaranteed — &lt;code&gt;ComponentLoader.load&lt;/code&gt; throws if two factories match the type, instead of silently choosing one. No container, no annotations, no classpath scanning.&lt;/p&gt;

&lt;p&gt;And when multiple implementations &lt;em&gt;are&lt;/em&gt; the point — a plugin system, an exporter per format — the same pattern flips around:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Exporter&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;exporters&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;MultiComponentLoader&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;loadAll&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Exporter&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;deps&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Every registered factory gets the same &lt;code&gt;Dependencies&lt;/code&gt;, you get all the instances. Same discovery, same argument-passing, opposite cardinality rule.&lt;/p&gt;

&lt;h2&gt;
  
  
  It's not magic
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;AccBook.load(path)&lt;/code&gt; isn't reflection sorcery and it isn't a new DI framework. Underneath it is still &lt;code&gt;ServiceLoader&lt;/code&gt;, still the JPMS &lt;code&gt;provides&lt;/code&gt;/&lt;code&gt;uses&lt;/code&gt; you would have written anyway. The &lt;code&gt;static load&lt;/code&gt; is a plain method you write once. The only thing the library adds is the part the JDK left out: a typed way to pass arguments to the implementation, plus a fail-fast "exactly one" rule.&lt;/p&gt;

&lt;p&gt;One honest caveat. The compiler checks what you put &lt;em&gt;into&lt;/em&gt; &lt;code&gt;Dependencies&lt;/code&gt; — &lt;code&gt;Dependencies.of(Path.class, path)&lt;/code&gt; won't accept a &lt;code&gt;String&lt;/code&gt; where a &lt;code&gt;Path&lt;/code&gt; belongs. But it can't check that you supplied everything the factory will ask for. Forget the &lt;code&gt;Path&lt;/code&gt; and the code still compiles — it fails only when &lt;code&gt;load()&lt;/code&gt; runs and &lt;code&gt;getDependency(Path.class)&lt;/code&gt; comes up empty. This is a real difference from Spring: Spring checks all its wiring at startup, so a missing bean stops the app from booting. Here there is no startup check — the check happens when you call &lt;code&gt;load()&lt;/code&gt;. Call it at startup and you catch the mistake at startup. Call it later, and you catch it later. The good news: the &lt;code&gt;load(...)&lt;/code&gt; call and the factory that reads it sit right next to each other, so when it fails, there is one obvious place to look. But the compiler won't catch it for you.&lt;/p&gt;

&lt;h2&gt;
  
  
  When not to use it
&lt;/h2&gt;

&lt;p&gt;If your implementations have no-arg constructors, you don't need any of this — plain &lt;code&gt;ServiceLoader&lt;/code&gt; is the right answer, and the library falls back to it anyway. If you already run inside Spring or Guice, use the container you have; this isn't trying to replace it. It earns its place in one specific spot: libraries and modular components that need pluggable, &lt;em&gt;argument-taking&lt;/em&gt; implementations without dragging in a container.&lt;/p&gt;

&lt;h2&gt;
  
  
  That's the whole idea
&lt;/h2&gt;

&lt;p&gt;It's on Maven Central as &lt;code&gt;com.druvu:druvu-lib-loader&lt;/code&gt; — source and docs on &lt;a href="https://github.com/DenissLarka/druvu-lib-loader" rel="noopener noreferrer"&gt;GitHub&lt;/a&gt;, Apache-2.0, Java 21+. If you've ever hand-written the factory-behind-&lt;code&gt;ServiceLoader&lt;/code&gt; glue yourself, I'd genuinely like to know whether this matches how you did it — or where you'd have done it differently. &lt;a href="https://github.com/DenissLarka/druvu-lib-loader/issues" rel="noopener noreferrer"&gt;Issues&lt;/a&gt; and disagreement welcome.&lt;/p&gt;

</description>
      <category>java</category>
      <category>architecture</category>
      <category>tutorial</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
