<?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 Art of Computer Programming Was Clickbait</title>
      <dc:creator>Deniss Larka</dc:creator>
      <pubDate>Sat, 03 Oct 2026 19:53:31 +0000</pubDate>
      <link>https://dev.to/deniss_larka/the-art-of-computer-programming-was-clickbait-2d2j</link>
      <guid>https://dev.to/deniss_larka/the-art-of-computer-programming-was-clickbait-2d2j</guid>
      <description>&lt;h2&gt;
  
  
  1. Nine o'clock
&lt;/h2&gt;

&lt;p&gt;Nine in the morning. A lecture hall, sixty students, one professor, a task on the board.&lt;/p&gt;

&lt;p&gt;Not quicksort. Not a linked list. Nothing that balances itself.&lt;/p&gt;

&lt;p&gt;Build a shop. Registration, catalog, basket, payment, and a back end that stays up when the shop is mentioned on television. Those who prefer may build a game instead, in three dimensions, with other people in it. You have until twelve.&lt;/p&gt;

&lt;p&gt;At twelve there are fifty-one shops and nine games. Most of them work.&lt;/p&gt;

&lt;p&gt;The second part begins at one, one student at a time, and it consists of the question that was always on the exam, in smaller handwriting.&lt;/p&gt;

&lt;p&gt;Why.&lt;/p&gt;

&lt;h2&gt;
  
  
  2. The bait
&lt;/h2&gt;

&lt;p&gt;In 1968 a thirty-year-old mathematician published the first volume of a book with a clickbait title. There was nothing to click. The mouse was shown to the public that December, and nobody had yet double-clicked anything.&lt;/p&gt;

&lt;p&gt;A publisher had asked him for a book about compilers. He wrote the other thing instead and called it &lt;em&gt;The Art of Computer Programming&lt;/em&gt;, in a field where art was the last word anyone was waiting for.&lt;/p&gt;

&lt;h2&gt;
  
  
  3. The catch
&lt;/h2&gt;

&lt;p&gt;It worked. People who would not be seen with the word art on any other book have kept this one on the shelf for fifty-eight years. The spines are mostly unbroken, which is the usual condition of scripture.&lt;/p&gt;

&lt;h2&gt;
  
  
  4. The line
&lt;/h2&gt;

&lt;p&gt;Six years later they gave him the Turing Award, and he spent the lecture on the word.&lt;/p&gt;

&lt;p&gt;He had looked it up. &lt;em&gt;Ars, artis&lt;/em&gt;: skill. Not the museum. The hand.&lt;/p&gt;

&lt;p&gt;Then he drew a line.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Science is knowledge which we understand so well that we can teach it to a computer; and if we don't fully understand something, it is an art to deal with it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Hold the line still and watch what crosses it. Sorting crossed it in the fifties. The compiler crossed it the week a machine wrote one. The line has not moved. Everything the machine has taken was on the far side of it already.&lt;/p&gt;

&lt;p&gt;He also quoted the people who had wanted the word gone before he ever used it. In 1959 the editorial board of the profession's new journal wrote that a transition "from an art to a disciplined science must be effected." It was policy, from the first board, in the first years. He noted that the discipline had since become a science "merely by deciding to call it computer science."&lt;/p&gt;

&lt;p&gt;We became engineers by the same method.&lt;/p&gt;

&lt;h2&gt;
  
  
  5. Why I am entitled to say this
&lt;/h2&gt;

&lt;p&gt;I am, by any fair measure, one of the better programmers I know, and I can defend perhaps a tenth of what I have shipped. The rest compiles.&lt;/p&gt;

&lt;h2&gt;
  
  
  6. The forgetting
&lt;/h2&gt;

&lt;p&gt;Between the book and the exam we learned the names of things. The build tool, and the tool that configures the build tool. The logger with fourteen settings, of which the thirteenth matters, at night. We learned to inject dependencies and to invert control, and we called the sum of it architecture, and now and then it was.&lt;/p&gt;

&lt;p&gt;In those years the question in smaller handwriting went unasked, sprint after sprint, for the plain reason that nobody was going to fail us for it. Reviews asked whether it worked. When somebody did ask why it was shaped like that, the answer was the name of a framework.&lt;/p&gt;

&lt;p&gt;We built the interview out of the first part. Forty-five minutes, a whiteboard, a sorting problem. We tested, with great seriousness, the one part of the job that could be taught to a computer, because it was the one part with an answer key.&lt;/p&gt;

&lt;h2&gt;
  
  
  7. The artifact
&lt;/h2&gt;

&lt;p&gt;A machine can now write a C compiler in a matter of days, and has, and a web browser, and has.&lt;/p&gt;

&lt;p&gt;So the question gets asked, usually near the coffee machine and usually by someone with a mortgage. What is left for us.&lt;/p&gt;

&lt;p&gt;Knuth saw this coming in 1974. In the same lecture he described a field called automatic programming, whose proponents, he wrote, would love to give a lecture entitled &lt;em&gt;Computer Programming as an Artifact&lt;/em&gt;, programming having become by then a relic. He did not think they would completely succeed. He wished them well regardless, on the grounds that turning an art into a science is how the art advances.&lt;/p&gt;

&lt;p&gt;That answer is fifty-two years old.&lt;/p&gt;

&lt;h2&gt;
  
  
  8. The question
&lt;/h2&gt;

&lt;p&gt;Is what the machine makes art, or trash it learned a trillion times over and reproduces without end?&lt;/p&gt;

&lt;p&gt;Nobody will say. Ask the machine and it gives you three paragraphs for each side and a summary. Ask the man who wrote the book; he gave up email in 1990 and is still writing the book.&lt;/p&gt;

&lt;p&gt;Whether the machine's compiler is art will never be settled. Whether yours was will be settled at one o'clock, with your name at the top of the sheet.&lt;/p&gt;

&lt;p&gt;The machine will build anything. It will sign nothing. A signature is a promise, and a promise needs someone with something to lose.&lt;/p&gt;

&lt;p&gt;Take the line once more. What we do not fully understand is an art to deal with. Nobody fully understands the machine, including the people who made it. Dealing with it is therefore art, by the line drawn in 1974, and the dealing comes to one act: deciding what you will put your name under.&lt;/p&gt;

&lt;h2&gt;
  
  
  9. A short dictionary for the second part
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Framework, n.&lt;/strong&gt; An architecture whose oral exam was taken by a stranger.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Boilerplate, n.&lt;/strong&gt; Science we kept doing by hand, out of respect.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Senior, adj.&lt;/strong&gt; Able to be asked why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Technical debt, n.&lt;/strong&gt; Interest on an unanswered why.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Legacy, n.&lt;/strong&gt; Decisions that outlived their defense.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Code review, n.&lt;/strong&gt; The oral exam, shortened to four letters.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Art, n.&lt;/strong&gt; The remainder.&lt;/p&gt;

&lt;h2&gt;
  
  
  10. One o'clock
&lt;/h2&gt;

&lt;p&gt;One o'clock. The first student sits down. The shop has worked since eleven. The laptop stays closed.&lt;/p&gt;

&lt;p&gt;Why is the basket in the database and the catalog in memory.&lt;/p&gt;

&lt;p&gt;What happens at three minutes past twelve when the payment provider does not fail but answers in forty seconds.&lt;/p&gt;

&lt;p&gt;What breaks first when the television mentions you, and did you choose it.&lt;/p&gt;

&lt;p&gt;Which of your fifty-nine classmates could read the password table.&lt;/p&gt;

&lt;p&gt;Show me one place where you refused what you were offered, and say why.&lt;/p&gt;

&lt;p&gt;What did you leave out.&lt;/p&gt;

&lt;p&gt;The student built the shop in two hours and had one hour to find out what had been built.&lt;/p&gt;

&lt;p&gt;The second part begins at one. It always did. Most of us went to lunch.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Reference: Donald Knuth, "Computer Programming as an Art", Turing Award lecture, Communications of the ACM, December 1974. All quotations above are from it.&lt;/em&gt;&lt;/p&gt;




</description>
      <category>ai</category>
      <category>programming</category>
      <category>architecture</category>
      <category>software</category>
    </item>
    <item>
      <title>Write your pages in PHP. Run them on the JVM.</title>
      <dc:creator>Deniss Larka</dc:creator>
      <pubDate>Sat, 15 Aug 2026 10:39:16 +0000</pubDate>
      <link>https://dev.to/deniss_larka/write-your-pages-in-php-run-them-on-the-jvm-j6</link>
      <guid>https://dev.to/deniss_larka/write-your-pages-in-php-run-them-on-the-jvm-j6</guid>
      <description>&lt;p&gt;druvu-lib-web 2.0.0 is out: a small Java web framework on Jetty 12 whose template language is PHP. Not "PHP-like" — the 2.0 engine runs real PHP 8 layouts: variables, every control structure, string interpolation, heredocs, includes, arrow functions, and 115 standard functions, with semantics settled by running whole templates against PHP 8 and matching its output byte for byte. There is no PHP runtime anywhere — the engine is Java, your handlers are Java, and &lt;code&gt;request.setAttribute("customer", customer)&lt;/code&gt; becomes &lt;code&gt;$customer&lt;/code&gt; in the page.&lt;/p&gt;

&lt;p&gt;One thing is deliberately different from PHP: output is HTML-escaped by default, with &lt;code&gt;raw()&lt;/code&gt; as the opt-out — forgetting is safe instead of being a hole. (That's also the main breaking change from 1.x.) Repo, docs and a runnable example: &lt;a href="https://github.com/DenissLarka/druvu-lib-web" rel="noopener noreferrer"&gt;https://github.com/DenissLarka/druvu-lib-web&lt;/a&gt;. If you try it and a template renders differently than real PHP would — that's a bug by definition, and &lt;a href="https://github.com/DenissLarka/druvu-lib-web/issues/23" rel="noopener noreferrer"&gt;I want to hear about it&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>java</category>
      <category>php</category>
      <category>opensource</category>
      <category>webdev</category>
    </item>
    <item>
      <title>What JConsole would look like if anyone had kept going</title>
      <dc:creator>Deniss Larka</dc:creator>
      <pubDate>Mon, 03 Aug 2026 15:00:00 +0000</pubDate>
      <link>https://dev.to/deniss_larka/what-jconsole-would-look-like-if-anyone-had-kept-going-41g8</link>
      <guid>https://dev.to/deniss_larka/what-jconsole-would-look-like-if-anyone-had-kept-going-41g8</guid>
      <description>&lt;p&gt;There's a kind of Java engineer for whom JMX is not a monitoring buzzword but a daily&lt;br&gt;
instrument: you connect to a live JVM, read a queue depth, flip a feature toggle, invoke the&lt;br&gt;
operation somebody exposed years ago for exactly this situation. I'm one of them — my day job&lt;br&gt;
keeps me close to production JVMs where guessing is expensive. And the universal client for&lt;br&gt;
this work is JConsole: it ships with every JDK, speaks to any JVM, and needs no server, no&lt;br&gt;
agent, no vendor.&lt;/p&gt;

&lt;p&gt;It also fights &lt;em&gt;you&lt;/em&gt;, every single day, in the same three places.&lt;/p&gt;

&lt;p&gt;Every operation argument is a raw text field. The MBean wants an ISO date, or one of exactly&lt;br&gt;
three legal values, or a file path — JConsole hands you an empty string box and wishes you&lt;br&gt;
luck. There are no saved connections: every morning, the same &lt;code&gt;host:port&lt;/code&gt; typed from memory,&lt;br&gt;
for every JVM in your care. And remote connections ride RMI, whose two-port firewall dance is&lt;br&gt;
&lt;a href="https://dev.to/deniss_larka/the-jdks-forgotten-jmx-protocol-4pci"&gt;a story of its own&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;None of it is hard to fix. It simply never was.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz7ohhsjluiku715vlq9d.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fz7ohhsjluiku715vlq9d.png" alt="JConsoleBooster" width="800" height="392"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The rule I'm breaking
&lt;/h3&gt;

&lt;p&gt;The 2026-approved answer is to stop using a desktop tool: ship the metrics to an APM, build a&lt;br&gt;
Grafana dashboard, deploy an agent. All three are good answers. One still opening a window in 2026&lt;br&gt;
is somebody who cannot let go of his childhood toys, and I have decided to accept the charge.&lt;/p&gt;

&lt;p&gt;The toys have one advantage. None of the approved answers covers the moment that actually&lt;br&gt;
defines JVM ops: you, one specific production JVM, right now — read this value, invoke this&lt;br&gt;
operation, watch what happens. For that, I want a direct&lt;br&gt;
connection, no platform in between, nothing to deploy before I can look.&lt;/p&gt;

&lt;p&gt;JConsole has survived twenty years — and it's open source. Meanwhile the JDK quietly solved the historical pain of &lt;em&gt;shipping&lt;/em&gt; desktop Java: &lt;code&gt;jlink&lt;/code&gt; and&lt;br&gt;
&lt;code&gt;jpackage&lt;/code&gt; make a self-contained, installable app practical — own runtime bundled, no JDK&lt;br&gt;
required on the target machine.&lt;/p&gt;

&lt;p&gt;The pieces were on the table. I forked JConsole and started fixing my own daily frictions.&lt;br&gt;
The result is &lt;a href="https://druvu.com/downloads/jconsole-booster.html" rel="noopener noreferrer"&gt;JConsoleBooster&lt;/a&gt; — and three&lt;br&gt;
changes carry it.&lt;/p&gt;

&lt;p&gt;It did occur to me, somewhere around the third weekend, that Oracle ships a thing called VisualVM, and that a reasonable person would have opened it before hand-editing a twenty-year-old&lt;br&gt;
Swing dialog. I gave the thought the consideration it deserved and went back to the dialog.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Operations become forms, not string fields
&lt;/h3&gt;

&lt;p&gt;MBean operation parameters can declare lightweight markup — a combo of legal values, a date, a&lt;br&gt;
file — and the Operations tab renders real widgets for them: a dropdown instead of a string&lt;br&gt;
box, a date picker instead of an ISO-format guessing game, a file chooser instead of a&lt;br&gt;
hand-typed path. Results that are JSON come back pretty-printed instead of as a one-line brick.&lt;br&gt;
An operation that returns a file (a report, an Excel export) downloads like a file, not like a&lt;br&gt;
&lt;code&gt;byte[]&lt;/code&gt; toString.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9zy4hu843bpomubmd42.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fp9zy4hu843bpomubmd42.png" alt="Operations markup" width="800" height="453"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftagt468h9zr9y2hwgmff.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Ftagt468h9zr9y2hwgmff.png" alt="An operation that returned a PDF, offered as Download or Open instead of a byte array" width="800" height="447"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Nothing about your MBeans has to change wholesale — the markup is opt-in, plain JConsole&lt;br&gt;
renders the same operations as plain strings.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Your whole estate, one menu
&lt;/h3&gt;

&lt;p&gt;Connections live in a bookmarks file: plain text, grouped, with &lt;code&gt;*bold*&lt;/code&gt; and &lt;code&gt;[red ALERT]&lt;/code&gt;&lt;br&gt;
markup for the entries that deserve visual weight — production vs staging, the box that pages&lt;br&gt;
you at night. The file is yours: version it, share it with the team, edit it in any editor.&lt;br&gt;
JConsoleBooster renders it as a menu, and any JVM in your care is one click away instead of a&lt;br&gt;
&lt;code&gt;host:port&lt;/code&gt; recital.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwx7yxd83p051d3d2ztfw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fwx7yxd83p051d3d2ztfw.png" alt="The bookmarks menu" width="701" height="331"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Installs — and updates — like a real app
&lt;/h3&gt;

&lt;p&gt;Signed and notarized &lt;code&gt;.dmg&lt;/code&gt; for macOS (Apple Silicon), signed &lt;code&gt;.msix&lt;/code&gt; for Windows, &lt;code&gt;.AppImage&lt;/code&gt;&lt;br&gt;
for Linux — each with the Java runtime bundled via &lt;code&gt;jlink&lt;/code&gt;, so the target machine needs no JDK,&lt;br&gt;
no &lt;code&gt;JAVA_HOME&lt;/code&gt;, no ceremony.&lt;/p&gt;

&lt;p&gt;And once it's installed, it keeps itself current. A running copy notices the next release,&lt;br&gt;
downloads it and applies it — no download page, no reinstall; on Windows that took about two&lt;br&gt;
and a half seconds for a 37 MB package. The three operating systems hold strong and entirely&lt;br&gt;
incompatible views on how an application may replace itself while running, and I have given up&lt;br&gt;
trying to change their minds: Windows swaps the package and restarts, macOS mounts the new build&lt;br&gt;
and asks you to quit and drag it across, Linux replaces the file and asks for a restart.&lt;/p&gt;

&lt;p&gt;The Linux AppImage is unsigned — Linux has no OS signing gate — use SHA-256, as Mr. Stallman would.&lt;/p&gt;

&lt;h3&gt;
  
  
  The transport, in one paragraph
&lt;/h3&gt;

&lt;p&gt;For remote JVMs, JConsoleBooster's default transport is JMXMP — the JDK's forgotten&lt;br&gt;
single-socket JMX protocol: type a bare &lt;code&gt;host:port&lt;/code&gt; and you connect over one fixed port through&lt;br&gt;
the firewall, no RMI two-port dance. I revived and hardened that library as a separate project;&lt;br&gt;
the full story is in&lt;br&gt;
&lt;a href="https://dev.to/deniss_larka/the-jdks-forgotten-jmx-protocol-4pci"&gt;The JDK's forgotten JMX protocol&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  When the box has no screen
&lt;/h3&gt;

&lt;p&gt;The JVMs that most need poking often sit on machines with no display at all. So the same tool&lt;br&gt;
runs headless: a REPL that speaks JMXMP, or &lt;code&gt;-e&lt;/code&gt; for a scripted one-liner, with the same&lt;br&gt;
transport, certificate-trust and credential rules the window enforces — the GUI is a front end,&lt;br&gt;
not the product. There's an optional single-jar build for when copying one file to a bastion&lt;br&gt;
host is the entire deployment story.&lt;/p&gt;

&lt;h3&gt;
  
  
  Tested daily, where it matters
&lt;/h3&gt;

&lt;p&gt;I run JConsoleBooster every working day against real production JVMs. Every feature above&lt;br&gt;
exists because the default behaviour cost me time in a real workflow — and earned its place by&lt;br&gt;
surviving daily use since. Whatever fights me next becomes the next release; that loop is the&lt;br&gt;
development model.&lt;/p&gt;

&lt;p&gt;Right now, that's the updater. It asks nobody's permission for anything — an excellent quality in&lt;br&gt;
a background service and a poor one in a colleague, particularly one who closes your window&lt;br&gt;
while you are still using it. So that's what's next.&lt;/p&gt;

&lt;p&gt;And one idea I keep turning over without deciding: a tab that renders HTML an MBean hands back.&lt;br&gt;
Your service exposes a single method that returns a form; the app draws it, posts your answer&lt;br&gt;
back, draws whatever comes next. An operations console for your service, for the days you need&lt;br&gt;
to control the thing and don't want to build a front end for it. Tell me if you'd use that — or&lt;br&gt;
why it's a terrible idea.&lt;/p&gt;

&lt;p&gt;Downloads: &lt;a href="https://druvu.com/downloads/jconsole-booster.html" rel="noopener noreferrer"&gt;druvu.com/downloads/jconsole-booster&lt;/a&gt;&lt;br&gt;
— pick your OS. Repo:&lt;br&gt;
&lt;a href="https://github.com/DenissLarka/jconsole-booster" rel="noopener noreferrer"&gt;github.com/DenissLarka/jconsole-booster&lt;/a&gt; — issues welcome. If JMX is part of your day, I'd genuinely like to hear what still fights you.&lt;/p&gt;

</description>
      <category>java</category>
      <category>jvm</category>
      <category>opensource</category>
      <category>ui</category>
    </item>
    <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;${latest.version}&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>
