<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://bridgetownrb.com/" version="2.1.2">Bridgetown</generator><link href="https://headius.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://headius.com/" rel="alternate" type="text/html" /><updated>2026-04-11T02:52:55+00:00</updated><id>https://headius.com/feed.xml</id><title type="html">Headius Enterprises</title><subtitle>Enterprise JRuby support, consulting, and priority bug-fixing from the core developers who built it. 20+ years of JVM and Ruby expertise.</subtitle><entry><title type="html">Non-null variable declaration in Java using instanceof patterns</title><link href="https://headius.com/blog/inline-null-check-with-instanceof/" rel="alternate" type="text/html" title="Non-null variable declaration in Java using instanceof patterns" /><published>2025-12-04T00:00:00+00:00</published><updated>2025-12-04T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-12-04-inline-null-check-with-instanceof.md</id><content type="html" xml:base="https://headius.com/blog/inline-null-check-with-instanceof/">&lt;p&gt;Ever since &lt;a href=&quot;https://blog.jruby.org/2025/04/jruby-10-part-1-whats-new&quot;&gt;JRuby 10 upgraded to Java 21&lt;/a&gt;, I’ve been re-learning Java with all the excellent language enhancements of the past decade. One of my favorites has to be the &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; &lt;a href=&quot;https://openjdk.org/jeps/394&quot;&gt;pattern matching features&lt;/a&gt; added in Java 16. Today I also realized I can use an &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; pattern to null-check and assign a variable at the same time.&lt;/p&gt;

&lt;h2 id=&quot;using-instanceof-to-null-check&quot;&gt;Using instanceof to null-check&lt;/h2&gt;

&lt;p&gt;When checking if a value in Java is &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; some type, we can get a false result in two cases:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The value is an instance of a type not equal to or descended from the specified type.&lt;/li&gt;
  &lt;li&gt;The value is &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This second property turns instanceof into a weird sort of null-check when applied to a variable or method that matches the &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; type, since we know the only false result must be when it’s &lt;code class=&quot;highlighter-rouge&quot;&gt;null&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kc&quot;&gt;null&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// prints &quot;false&quot;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Of course this is longer than just saying &lt;code class=&quot;highlighter-rouge&quot;&gt;foo == null&lt;/code&gt; but &lt;em&gt;so what&lt;/em&gt;? &lt;strong&gt;It works!&lt;/strong&gt;&lt;/p&gt;

&lt;h2 id=&quot;using-instanceof-patterns-for-inline-null-checking&quot;&gt;Using instanceof patterns for inline null checking&lt;/h2&gt;

&lt;p&gt;The above example doesn’t really have any practical use, which is probably why you never see folks talking about it. But when combined with an &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; pattern, we can do something more fun.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// getString() is a method that might return null&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;firstCondition&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// something&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;getString&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;length: &quot;&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;length&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;else&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;string is null&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve just done an inline null check and variable declaration in the middle of an &lt;code class=&quot;highlighter-rouge&quot;&gt;if ... else&lt;/code&gt; chain! The &lt;code class=&quot;highlighter-rouge&quot;&gt;string&lt;/code&gt; value &lt;strong&gt;must&lt;/strong&gt; be non-null in the &lt;code class=&quot;highlighter-rouge&quot;&gt;length&lt;/code&gt; branch, so we can safely call methods on it. And we know it &lt;strong&gt;must&lt;/strong&gt; be null if we end up in the final &lt;code class=&quot;highlighter-rouge&quot;&gt;else&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;but-why&quot;&gt;But why?&lt;/h2&gt;

&lt;p&gt;Because it’s there?&lt;/p&gt;

&lt;p&gt;Without &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; patterns, you’d need to move everything after the &lt;code class=&quot;highlighter-rouge&quot;&gt;firstCondition&lt;/code&gt; branch into the following &lt;code class=&quot;highlighter-rouge&quot;&gt;else&lt;/code&gt;, declare a temporary variable for the value of &lt;code class=&quot;highlighter-rouge&quot;&gt;getString()&lt;/code&gt;, and then do a nested null-checking branch. This pattern (pun intended) allows us to skip all that.&lt;/p&gt;

&lt;h3 id=&quot;is-it-better&quot;&gt;Is it better?&lt;/h3&gt;

&lt;p&gt;I can’t say. It’s frequently shorter, and requires less indented code.&lt;/p&gt;

&lt;h3 id=&quot;does-it-convey-the-intent-to-null-check&quot;&gt;Does it convey the intent to null-check?&lt;/h3&gt;

&lt;p&gt;Not if you don’t know about this “hidden” behavior of &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt;. That’s your fault, though!&lt;/p&gt;

&lt;h3 id=&quot;wont-it-be-slower-than-a-null-equality-check&quot;&gt;Won’t it be slower than a null equality check?&lt;/h3&gt;

&lt;p&gt;Actually, I had this question too, so I fired up the HotSpot disassembler.&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Blah&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;System&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getProperty&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;foo&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;for&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;100000000&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;++)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;bar&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;foo&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;instanceof&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;no&quot;&gt;IO&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;println&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;string&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-XX&lt;/span&gt;:+UnlockDiagnosticVMOptions &lt;span class=&quot;nt&quot;&gt;-XX&lt;/span&gt;:+PrintAssembly Blah.java
OpenJDK 64-Bit Server VM warning: PrintAssembly is enabled&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; turning on DebugNonSafepoints to gain additional output
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It turns out, HotSpot’s just as smart as I am!&lt;/p&gt;

&lt;p&gt;Here’s what the field access and &lt;code class=&quot;highlighter-rouge&quot;&gt;instanceof&lt;/code&gt; turn into for this case:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;  0x0000000117153544:   ldr		w11, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;x1, &lt;span class=&quot;c&quot;&gt;#0xc]&lt;/span&gt;
  0x0000000117153548:   lsl		x10, x11, &lt;span class=&quot;c&quot;&gt;#3&lt;/span&gt;
                          &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;getfield foo &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;reexecute&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;rethrow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0 &lt;span class=&quot;nv&quot;&gt;return_oop&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;0&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
                          &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; - Blah::bar@1 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;line 9&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
  0x000000011715354c:   cbnz	x10, &lt;span class=&quot;c&quot;&gt;#0x117153568&lt;/span&gt;
  0x0000000117153550:   ldp		x29, x30, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;sp, &lt;span class=&quot;c&quot;&gt;#0x20]&lt;/span&gt;
  0x0000000117153554:   add		sp, sp, &lt;span class=&quot;c&quot;&gt;#0x30&lt;/span&gt;
  0x0000000117153558:   ldr		x8, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;x28, &lt;span class=&quot;c&quot;&gt;#0x28]&lt;/span&gt;
                          &lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;   &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;poll_return&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  0x000000011715355c:   cmp		sp, x8
  0x0000000117153560:   b.hi	&lt;span class=&quot;c&quot;&gt;#0x117153580&lt;/span&gt;
  0x0000000117153564:   ret
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Instead of doing a more complex inheritance check, the &lt;code class=&quot;highlighter-rouge&quot;&gt;foo&lt;/code&gt; field (loaded by &lt;code class=&quot;highlighter-rouge&quot;&gt;lsl&lt;/code&gt; into the &lt;code class=&quot;highlighter-rouge&quot;&gt;x10&lt;/code&gt; register from the &lt;code class=&quot;highlighter-rouge&quot;&gt;Blah&lt;/code&gt; class in &lt;code class=&quot;highlighter-rouge&quot;&gt;x11&lt;/code&gt;) is compared with zero (if that’s not the case, &lt;code class=&quot;highlighter-rouge&quot;&gt;cbnz&lt;/code&gt; branches to the &lt;code class=&quot;highlighter-rouge&quot;&gt;println&lt;/code&gt; branch at &lt;code class=&quot;highlighter-rouge&quot;&gt;0x117153568&lt;/code&gt;), and then the rest of the instructions just tidy up and return from the &lt;code class=&quot;highlighter-rouge&quot;&gt;bar()&lt;/code&gt; method.&lt;/p&gt;

&lt;p&gt;Basically, because the only possible branch in the code is a null check, the JVM’s JIT will compile it as such.&lt;/p&gt;

&lt;p&gt;So with the performance question out of the way, only one remains:&lt;/p&gt;

&lt;p&gt;What do you think?&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-rjava&quot;&gt;Join the discussion on r/java!&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">Warbled Sidekiq: Zero-install Executable for JVM</title><link href="https://headius.com/blog/warbled-sidekiq/" rel="alternate" type="text/html" title="Warbled Sidekiq: Zero-install Executable for JVM" /><published>2025-10-23T00:00:00+00:00</published><updated>2025-10-23T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-10-23-warbled-sidekiq.md</id><content type="html" xml:base="https://headius.com/blog/warbled-sidekiq/">&lt;p&gt;In my previous post, I showed &lt;a href=&quot;https://blog.headius.com/2025/10/packaging-ruby-apps-with-warbler-jar-files.html&quot;&gt;how to use Warbler&lt;/a&gt; to package a simple image-processing tool as an executable jar. This post will demonstrate how to “warble” a larger project: the &lt;a href=&quot;https://sidekiq.org/&quot;&gt;Sidekiq background job server&lt;/a&gt;!&lt;/p&gt;

&lt;h2 id=&quot;warbling-sidekiq&quot;&gt;Warbling Sidekiq&lt;/h2&gt;

&lt;p&gt;Sidekiq is one of the most successful packaged software projects in the Ruby world. It provides high-scale background job processing for Ruby applications atop Redis, and it has been commercially successful via enterprise features and support arrangements. It also happens to work great with JRuby and takes advantage of our excellent parallel threading capabilities.&lt;/p&gt;

&lt;p&gt;Seems like a perfect use case for Warbler!&lt;/p&gt;

&lt;h3 id=&quot;the-easy-way&quot;&gt;The Easy Way&lt;/h3&gt;

&lt;p&gt;The easiest way to set up a new warbler configuration for an existing gem (that you may or may not control) is to create your own wrapper project.&lt;/p&gt;

&lt;p&gt;I’ve started that for Sidekiq here: &lt;a href=&quot;https://github.com/headius/sidekiq-warbler&quot;&gt;sidekiq-warbler&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;You’ll notice the project is quite slim, containing only a few files for Warbler to package a Sidekiq executable JAR:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Gemfile and gemspec to specify dependencies. Note that this is not actually pushed as a gem, but Warbler currently auto-detects dependencies using these files.&lt;/li&gt;
  &lt;li&gt;config/warble.rb to specify the &lt;code class=&quot;highlighter-rouge&quot;&gt;sidekiq&lt;/code&gt; executable as the “main” and “sidekiq” as the filename for the JAR. I’ve removed unused configuration options from the generated warble.rb.&lt;/li&gt;
  &lt;li&gt;Rakefile to load in rake tasks for warbler.&lt;/li&gt;
  &lt;li&gt;README.md with basic usage information.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given this, the process is simple:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;git clone https://github.com/headius/sidekiq-warbler.git&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;cd sidekiq-warbler&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;bundle&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;bundle exec rake jar&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s see it in action, using the “plain old Ruby” example from Sidekiq itself:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sidekiq-warbler &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bundle
Fetching gem metadata from https://rubygems.org/............
Resolving dependencies...
Bundle &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;!&lt;/span&gt; 2 Gemfile dependencies, 15 gems now installed.
Use &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;bundle info &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;gemname]&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt; to see where a bundled gem is installed.
sidekiq-warbler &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rake jar
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; sidekiq.jar
Creating sidekiq.jar
sidekiq-warbler &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; sidekiq.jar &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; ../sidekiq/examples/por.rb


               m,
               &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$b&lt;/span&gt;
          .ss,  &lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;:         .,d&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;
          &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;P,d&lt;span class=&quot;nv&quot;&gt;$P&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;    .,md$P&quot;&apos;&lt;/span&gt;
           ,&lt;span class=&quot;nv&quot;&gt;$$$$$b&lt;/span&gt;/md&lt;span class=&quot;nv&quot;&gt;$$$P&lt;/span&gt;^&lt;span class=&quot;s1&quot;&gt;&apos;
         .d$$$$$$/$$$P&apos;&lt;/span&gt;
         &lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;^&lt;span class=&quot;s1&quot;&gt;&apos; `&quot;/$$$&apos;&lt;/span&gt;       ____  _     _      _    _
         &lt;span class=&quot;nv&quot;&gt;$:&lt;/span&gt;    &lt;span class=&quot;s1&quot;&gt;&apos;,$$:       / ___|(_) __| | ___| | _(_) __ _
         `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
                $$:         ___) | | (_| |  __/   &amp;lt;| | (_| |
                $$         |____/|_|\__,_|\___|_|\_\_|\__, |
              .d$$                                       |_|


INFO  2025-10-23T15:37:27.684Z pid=85096 tid=1ryk: Running in jruby 10.0.2.0 (3.4.2) 2025-08-07 cba6031bd0 OpenJDK 64-Bit Server VM 21.0.8+9-LTS on 21.0.8+9-LTS +indy +jit [arm64-darwin]
INFO  2025-10-23T15:37:27.684Z pid=85096 tid=1ryk: See LICENSE and the LGPL-3.0 for licensing details.
INFO  2025-10-23T15:37:27.684Z pid=85096 tid=1ryk: Upgrade to Sidekiq Pro for more features and support: https://sidekiq.org
INFO  2025-10-23T15:37:27.686Z pid=85096 tid=1ryk: Sidekiq 8.0.8 connecting to Redis with options {size: 10, pool_name: &quot;internal&quot;, url: nil}
INFO  2025-10-23T15:37:27.707Z pid=85096 tid=1ryk: Sidekiq 8.0.8 connecting to Redis with options {size: 5, pool_name: &quot;default&quot;, url: nil}
INFO  2025-10-23T15:37:27.710Z pid=85096 tid=1ryk: Starting processing, hit Ctrl-C to stop
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s that easy!&lt;/p&gt;

&lt;h3 id=&quot;adding-official-warbler-support-to-sidekiq&quot;&gt;Adding Official Warbler Support to Sidekiq&lt;/h3&gt;

&lt;p&gt;In cases where you own or control a given gem, you can also add support for Warbler directly to the project. I’ve done that on a branch of Sidekiq here: https://github.com/headius/sidekiq/tree/warbled&lt;/p&gt;

&lt;p&gt;You’ll notice a few differences in the “official support” &lt;code class=&quot;highlighter-rouge&quot;&gt;config/warble.rb&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Hack to disable the &quot;war&quot; trait in Warbler&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Warbler::Traits::War&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;detect?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Warbler::Traits::Rack&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;def&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;self&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;detect?&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;false&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Warbler web application assembly configuration file&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Warbler&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;config&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;executable&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bin/sidekiq&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;The “hack” here disables Warbler’s automatic detection of web applications, so that we can just produce a plain executable JAR file instead of a web application WAR file. I’ve filed an issue with Warbler to make this easier in the future: https://github.com/jruby/warbler/issues/587&lt;/li&gt;
  &lt;li&gt;The JAR file name is detected from the gem name (“sidekiq”) and we’re using a “main” script from the same gem, so the config is a bit simpler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I’ve also tweaked Sidekiq’s &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; to pin Rails at 7.1 (we’re in the process of shipping 7.2 and 8+ support for JRuby) and disable some native CRuby extensions we don’t support (&lt;code class=&quot;highlighter-rouge&quot;&gt;debug&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;vernier&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;ruby-prof&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The result is basically the same:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sidekiq &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;rake jar
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; sidekiq.jar
Creating sidekiq.jar
sidekiq &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; sidekiq.jar
INFO  2025-10-23T16:01:50.836Z &lt;span class=&quot;nv&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86190 &lt;span class=&quot;nv&quot;&gt;tid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1xca: &lt;span class=&quot;o&quot;&gt;==================================================================&lt;/span&gt;
INFO  2025-10-23T16:01:50.837Z &lt;span class=&quot;nv&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86190 &lt;span class=&quot;nv&quot;&gt;tid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1xca:   Please point Sidekiq to a Rails application or a Ruby file
INFO  2025-10-23T16:01:50.837Z &lt;span class=&quot;nv&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86190 &lt;span class=&quot;nv&quot;&gt;tid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1xca:   to load your job classes with &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;DIR|FILE].
INFO  2025-10-23T16:01:50.837Z &lt;span class=&quot;nv&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86190 &lt;span class=&quot;nv&quot;&gt;tid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1xca: &lt;span class=&quot;o&quot;&gt;==================================================================&lt;/span&gt;
INFO  2025-10-23T16:01:50.837Z &lt;span class=&quot;nv&quot;&gt;pid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;86190 &lt;span class=&quot;nv&quot;&gt;tid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1xca: sidekiq &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;options]
    &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--concurrency&lt;/span&gt; INT            processor threads to use
    &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--environment&lt;/span&gt; ENV            Application environment
    &lt;span class=&quot;nt&quot;&gt;-g&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--tag&lt;/span&gt; TAG                    Process tag &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;procline
    &lt;span class=&quot;nt&quot;&gt;-q&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--queue&lt;/span&gt; QUEUE[,WEIGHT]       Queues to process with optional weights
    &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--require&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;PATH|DIR]         Location of Rails application with &lt;span class=&quot;nb&quot;&gt;jobs &lt;/span&gt;or file to require
    &lt;span class=&quot;nt&quot;&gt;-t&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--timeout&lt;/span&gt; NUM                Shutdown &lt;span class=&quot;nb&quot;&gt;timeout&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--verbose&lt;/span&gt;                    Print more verbose output
    &lt;span class=&quot;nt&quot;&gt;-C&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--config&lt;/span&gt; PATH                path to YAML config file
    &lt;span class=&quot;nt&quot;&gt;-V&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;                    Print version and &lt;span class=&quot;nb&quot;&gt;exit&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;-h&lt;/span&gt;, &lt;span class=&quot;nt&quot;&gt;--help&lt;/span&gt;                       Show &lt;span class=&quot;nb&quot;&gt;help&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ipv4-vs-ipv6&quot;&gt;IPv4 vs IPv6&lt;/h3&gt;

&lt;p&gt;If you’re on a platform that supports IPv6 you may notice that JRuby (the JDK, really) will try to use IPv6 addresses and connections by default. On my system, the Redis server only bound itself to IPv4, preventing Sidekiq from being able to make that connection.&lt;/p&gt;

&lt;p&gt;The magic flag to force the JDK to use IPv4 is &lt;code class=&quot;highlighter-rouge&quot;&gt;-Djava.net.preferIPv4Stack=true&lt;/code&gt;. You can pass that directly to the &lt;code class=&quot;highlighter-rouge&quot;&gt;java&lt;/code&gt; command, or use environment variable &lt;code class=&quot;highlighter-rouge&quot;&gt;JDK_JAVA_OPTIONS&lt;/code&gt; as I do here:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;sidekiq &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JDK_JAVA_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-Djava.net.preferIPv4Stack=true&quot;&lt;/span&gt;
sidekiq &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; sidekiq.jar &lt;span class=&quot;nt&quot;&gt;-r&lt;/span&gt; ./examples/por.rb
NOTE: Picked up JDK_JAVA_OPTIONS: &lt;span class=&quot;nt&quot;&gt;-Djava&lt;/span&gt;.net.preferIPv4Stack&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;true


               &lt;/span&gt;m,
               &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$b&lt;/span&gt;
          .ss,  &lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;:         .,d&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;
          &lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;P,d&lt;span class=&quot;nv&quot;&gt;$P&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;    .,md$P&quot;&apos;&lt;/span&gt;
           ,&lt;span class=&quot;nv&quot;&gt;$$$$$b&lt;/span&gt;/md&lt;span class=&quot;nv&quot;&gt;$$$P&lt;/span&gt;^&lt;span class=&quot;s1&quot;&gt;&apos;
         .d$$$$$$/$$$P&apos;&lt;/span&gt;
         &lt;span class=&quot;nv&quot;&gt;$$&lt;/span&gt;^&lt;span class=&quot;s1&quot;&gt;&apos; `&quot;/$$$&apos;&lt;/span&gt;       ____  _     _      _    _
         &lt;span class=&quot;nv&quot;&gt;$:&lt;/span&gt;    &lt;span class=&quot;s1&quot;&gt;&apos;,$$:       / ___|(_) __| | ___| | _(_) __ _
         `b     :$$        \___ \| |/ _` |/ _ \ |/ / |/ _` |
                $$:         ___) | | (_| |  __/   &amp;lt;| | (_| |
                $$         |____/|_|\__,_|\___|_|\_\_|\__, |
              .d$$                                       |_|

...
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;why-warble&quot;&gt;Why Warble?&lt;/h2&gt;

&lt;p&gt;We’ve seen that it’s pretty easy to turn a larger app like Sidekiq into an executable JAR file, but what’s the real advantage here?&lt;/p&gt;

&lt;p&gt;Some of that comes from running on JRuby, which you can get with or without Warbler (Sidekiq works well and is regularly tested on JRuby):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Better parallel scaling across cores, for both Sidekiq itself and any garbage produced along the way (JVM’s GCs are highly concurrent and super scalable).&lt;/li&gt;
  &lt;li&gt;Better tooling based on JVM profiling and monitoring features.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the other features are specific to a “warbled” executable JAR file:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;“Zero-install” other than needing a JDK on the host system.&lt;/li&gt;
  &lt;li&gt;No native libraries, no build tools, no additional dependencies.&lt;/li&gt;
  &lt;li&gt;Enterprise-friendly: they don’t have to know or care about Ruby to deploy your product.&lt;/li&gt;
  &lt;li&gt;IP-safe: Warbler can precompile Ruby code into JRuby’s bytecode format, making it very difficult to steal.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Basically, if you want to ship Ruby tools and applications for modern organizations, JRuby and Warbler are a great way to get there!&lt;/p&gt;

&lt;h2 id=&quot;your-turn&quot;&gt;Your Turn&lt;/h2&gt;

&lt;p&gt;Obviously there’s enormous potential for Ruby tools and applications to be packaged and shipped using Warbler. If you’re interested in making this happen for you project, here’s the steps to take:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure it runs on JRuby! Pure-Ruby libraries should “just work”, but more complicated apps may require alternative gems.&lt;/li&gt;
  &lt;li&gt;Decide what your command-line “main” should look like when run as an executable jar. This may simply be your existing bin script.&lt;/li&gt;
  &lt;li&gt;Follow docs on the &lt;a href=&quot;https://github.com/jruby/warbler&quot;&gt;Warbler project&lt;/a&gt; for configuring and warbling your app.&lt;/li&gt;
  &lt;li&gt;Profit! Enterprises love simple executable tools!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;JRuby is clearly the future of Ruby in the enterprise, and Warbler is a huge part of that. I am always here to help you build and package Ruby applications with JRuby and Warbler. Follow the links below and let’s set up a chat or call to help you bring your apps to a wider world!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-rruby&quot;&gt;Join the discussion on r/ruby!&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">Packaging Ruby Apps with Warbler: Executable JAR Files</title><link href="https://headius.com/blog/packaging-ruby-apps-with-warbler-jar-files/" rel="alternate" type="text/html" title="Packaging Ruby Apps with Warbler: Executable JAR Files" /><published>2025-10-21T00:00:00+00:00</published><updated>2025-10-21T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-10-21-packaging-ruby-apps-with-warbler-jar-files.md</id><content type="html" xml:base="https://headius.com/blog/packaging-ruby-apps-with-warbler-jar-files/">&lt;p&gt;&lt;a href=&quot;https://github.com/jruby/warbler&quot;&gt;Warbler&lt;/a&gt; is the JRuby ecosystem’s tool for packaging up Ruby apps with all dependencies in a single deployable file. We’ve just released an update, so let’s explore how to use Warbler to create all-in-one packaged Ruby apps!&lt;/p&gt;

&lt;h2 id=&quot;application-packaging-for-the-java-world&quot;&gt;Application Packaging for the Java World&lt;/h2&gt;

&lt;p&gt;The Java world has been creating distributable, “run anywhere” packages since Java was first released in 1996. Java source code is compiled to bytecode, stored in .class files and then archived together with metadata in JAR files (Java ARchive) that can be run as command-line executable files or as deployable web applications. A JAR file is just a zip file, laid out in a specific way to contain the code and resources your application or library needs, and essentially all libraries for the JVM get distributed as JAR files.&lt;/p&gt;

&lt;p&gt;The JRuby distribution includes &lt;code class=&quot;highlighter-rouge&quot;&gt;lib/jruby.jar&lt;/code&gt;, for example, which is where all of the internal JRuby Java and Ruby code is located:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
jruby 10.0.3.0-SNAPSHOT &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3.4.5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2025-10-10 0ea455e57a OpenJDK 64-Bit Server VM 21.0.8+9-LTS on 21.0.8+9-LTS +indy +jit &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;arm64-darwin]
jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-l&lt;/span&gt; lib/jruby.jar
&lt;span class=&quot;nt&quot;&gt;-rw-r--r--&lt;/span&gt;  1 headius  staff  16928929 Oct 14 12:59 lib/jruby.jar
jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;java &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; lib/jruby.jar &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt;
jruby 10.0.3.0-SNAPSHOT &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3.4.5&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2025-10-10 0ea455e57a OpenJDK 64-Bit Server VM 21.0.8+9-LTS on 21.0.8+9-LTS +indy +jit &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;arm64-darwin]
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;When distributed as a deployable web application, the filename typically ends with “.war” (Web ARchive) and contains a combination of code, configuration, and other JAR files (dependencies of the app). Enterprise applications are packaged in “.ear” files (Enterprise ARchive) which will in turn contain jars and wars and additional configuration for enterprise Java servers. At the end of the day, though, they’re all just zip files laid out in a particular way.&lt;/p&gt;

&lt;h3 id=&quot;write-once-run-anywhere&quot;&gt;Write Once, Run Anywhere&lt;/h3&gt;

&lt;p&gt;This is all possible because of the Write Once, Run Anywhere (WORA) promise of JVM bytecode: if your application runs on the JVM, you can write, compile, and package it once and deploy it anywhere without recompiling.&lt;/p&gt;

&lt;p&gt;Compare this to the way external dependencies are managed in Ruby:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Gem files provide installable packaging for Ruby libraries, but they must be unpacked into a specific path on the filesystem to be usable.&lt;/li&gt;
  &lt;li&gt;Libraries with native code require separate binary gems for each platform or must be built at install time and installed on the filesystem.&lt;/li&gt;
  &lt;li&gt;Ruby itself must be built for each platform you plan to run it on, or downloaded as a platform-specific binary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Wouldn’t it be nice if we could package Ruby applications for distribution without these hassles, in a way that enterprises and security-conscious companies can handle? That’s where Warbler comes in!&lt;/p&gt;

&lt;h3 id=&quot;warbler-packaging-tool-for-jruby-applications&quot;&gt;Warbler: packaging tool for JRuby applications&lt;/h3&gt;

&lt;p&gt;Warbler is the JRuby tool of choice for packaging Ruby applications for distribution. Given a Ruby utility or application, Warbler can:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Package your Ruby utility &lt;strong&gt;along with gem and jar dependencies&lt;/strong&gt; (including JRuby itself) as a single executable JAR file.&lt;/li&gt;
  &lt;li&gt;Package your Rails, Hanami, Sinatra or other web application as a &lt;strong&gt;deployable WAR file&lt;/strong&gt; (again with all dependencies included).&lt;/li&gt;
  &lt;li&gt;Add a &lt;strong&gt;mini-server&lt;/strong&gt; to your WAR file so it can be &lt;strong&gt;run directly at the command line&lt;/strong&gt;.&lt;/li&gt;
  &lt;li&gt;Precompile all Ruby code to JRuby’s bytecode format, to &lt;strong&gt;obfuscate and protect your intellectual property&lt;/strong&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s try a simple example to get started.&lt;/p&gt;

&lt;h2 id=&quot;warbler-in-practice&quot;&gt;Warbler in Practice&lt;/h2&gt;

&lt;p&gt;We’ll be packaging a demo utility called &lt;a href=&quot;https://github.com/headius/image_voodoo_demo&quot;&gt;image_voodoo_demo&lt;/a&gt; for these examples, based on the &lt;a href=&quot;https://github.com/jruby/image_voodoo&quot;&gt;image_voodoo&lt;/a&gt; image-manipulation gem.&lt;/p&gt;

&lt;p&gt;ImageVoodoo is a Ruby wrapper around the JDK’s built-in image-processing APIs (inspired by the ImageMagick gem), allowing JRuby users to scale, crop, thumbnail, greyscale, and apply other transformations to images without installing external libraries. This demo takes the example script from ImageVoodoo and turns it into a command-line utility called &lt;code class=&quot;highlighter-rouge&quot;&gt;voodoo&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Here’s basic use of the &lt;code class=&quot;highlighter-rouge&quot;&gt;voodoo&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;image_voodoo_demo
Successfully installed image_voodoo_demo-0.1.0
Parsing documentation &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;image_voodoo_demo-0.1.0
Done installing documentation &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;image_voodoo_demo after 0 seconds
1 gem installed
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;voodoo PXL_20250802_162259596.jpg
wrote demo files to /Users/headius/work/jruby/PXL_20250802_162259596/
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt; PXL_20250802_162259596
border.jpg
cropped_thumb.jpg
cropped.jpg
reduced.jpg
resized.jpg
resized.png
thumb.jpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you’ve already got Ruby or JRuby installed, this workflow is fine. But what if you want to distribute this utility to someone with no knowledge of the Ruby ecosystem? Let’s package image_voodoo_demo as an executable JAR file!&lt;/p&gt;

&lt;h3 id=&quot;executable-jar-files&quot;&gt;Executable JAR Files&lt;/h3&gt;

&lt;p&gt;Java’s JAR files can be marked as “executable” by including metadata that configures a “main” entry point. In the case of JRuby, the “main” points to &lt;code class=&quot;highlighter-rouge&quot;&gt;org.jruby.main.Main&lt;/code&gt;, which bootstraps JRuby and runs your code. Warbler can generate a JAR file for your Ruby tool that includes this configuration and launches your Ruby tool directly.&lt;/p&gt;

&lt;p&gt;The simplest way to do this is to run Warbler from within the source repo for your utility.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;ls&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-1&lt;/span&gt;
exe
Gemfile
Gemfile.lock
image_voodoo_demo.gemspec
lib
Rakefile
README.md
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;image_voodoo_demo has a pretty standard gem layout, with a &lt;code class=&quot;highlighter-rouge&quot;&gt;gemspec&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt; to specify dependencies and gem configuration.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;exe/voodoo
&lt;span class=&quot;c&quot;&gt;#!/usr/bin/env ruby&lt;/span&gt;

require &lt;span class=&quot;s1&quot;&gt;&apos;image_voodoo/demo&apos;&lt;/span&gt;

filename &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; ARGV[0]

ImageVoodoo::Demo.output_demo_files&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;filename&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;

image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;tail &lt;/span&gt;image_voodoo_demo.gemspec
  spec.bindir &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;exe&quot;&lt;/span&gt;
  spec.executables &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; spec.files.grep&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;%r&lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\A&lt;/span&gt;exe/&lt;span class=&quot;o&quot;&gt;})&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt; |f| File.basename&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;f&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
  spec.require_paths &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;lib&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;]&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# Uncomment to register a new dependency of your gem&lt;/span&gt;
  spec.add_dependency &lt;span class=&quot;s2&quot;&gt;&quot;image_voodoo&quot;&lt;/span&gt;, &lt;span class=&quot;s2&quot;&gt;&quot;0.9.3&quot;&lt;/span&gt;

  &lt;span class=&quot;c&quot;&gt;# For more information and examples about making a new gem, check out our&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# guide at: https://bundler.io/guides/creating_gem.html&lt;/span&gt;
end
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;voodoo&lt;/code&gt; command just loads the demo code and executes it against &lt;code class=&quot;highlighter-rouge&quot;&gt;ARGV[0]&lt;/code&gt;, and the &lt;code class=&quot;highlighter-rouge&quot;&gt;gemspec&lt;/code&gt; includes &lt;code class=&quot;highlighter-rouge&quot;&gt;exe/voodoo&lt;/code&gt; as its sole executable.&lt;/p&gt;

&lt;p&gt;With this layout and configuration, using Warbler is simple!&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;warbler
Fetching warbler-2.1.0.gem
Successfully installed warbler-2.1.0
Parsing documentation &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;warbler-2.1.0
Installing ri documentation &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;warbler-2.1.0
Done installing documentation &lt;span class=&quot;k&quot;&gt;for &lt;/span&gt;warbler after 0 seconds
1 gem installed
image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;warble
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; image_voodoo_demo.jar
Creating image_voodoo_demo.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We’ve just created an executable JAR containing everything needed for the &lt;code class=&quot;highlighter-rouge&quot;&gt;voodoo&lt;/code&gt; command:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The code and executable for image_voodoo_demo&lt;/li&gt;
  &lt;li&gt;The image_voodoo gem&lt;/li&gt;
  &lt;li&gt;JRuby’s own JAR file and dependencies&lt;/li&gt;
  &lt;li&gt;The Ruby standard library&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We run the jar with the &lt;code class=&quot;highlighter-rouge&quot;&gt;java -jar&lt;/code&gt; command line:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo $ java -jar image_voodoo_demo.jar ~/Downloads/PXL_20250802_162259596.jpg
wrote demo files to /Users/headius/work/image_voodoo_demo/PXL_20250802_162259596/
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It works!&lt;/p&gt;

&lt;h3 id=&quot;going-deeper&quot;&gt;Going Deeper&lt;/h3&gt;

&lt;p&gt;Here’s an abbreviated listing of the contents of the &lt;code class=&quot;highlighter-rouge&quot;&gt;image_voodoo_demo.jar&lt;/code&gt; file:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jar tf image_voodoo_demo.jar
JarMain.class
META-INF/
META-INF/MANIFEST.MF
META-INF/init.rb
META-INF/lib/
META-INF/lib/jruby-core-10.0.2.0-complete.jar
META-INF/lib/jruby-stdlib-10.0.2.0.jar
META-INF/main.rb
gems/
gems/bundler-2.6.9/
gems/bundler-2.6.9/exe/
gems/bundler-2.6.9/exe/bundle
gems/bundler-2.6.9/exe/bundler
gems/image_voodoo-0.9.3/
gems/image_voodoo-0.9.3/Gemfile
gems/image_voodoo-0.9.3/History.txt
gems/image_voodoo-0.9.3/Jars.lock
...
image_voodoo_demo/
image_voodoo_demo/Gemfile
image_voodoo_demo/Gemfile.lock
image_voodoo_demo/README.md
image_voodoo_demo/Rakefile
image_voodoo_demo/exe/
image_voodoo_demo/exe/voodoo
image_voodoo_demo/lib/
image_voodoo_demo/lib/image_voodoo/
image_voodoo_demo/lib/image_voodoo/demo/
image_voodoo_demo/lib/image_voodoo/demo.rb
image_voodoo_demo/lib/image_voodoo/demo/version.rb
specifications/
specifications/bundler-2.6.9.gemspec
specifications/image_voodoo-0.9.3.gemspec
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;JarMain.class&lt;/code&gt; is the “main” file for the JVM. It sets up the JRuby runtime and in-archive gem paths and then launches our executable &lt;code class=&quot;highlighter-rouge&quot;&gt;voodoo&lt;/code&gt; command.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;META-INF&lt;/code&gt; contains the JAR metadata along with any dependency libraries. In this case it just needs the &lt;code class=&quot;highlighter-rouge&quot;&gt;jruby-core&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;jruby-stdlib&lt;/code&gt; jars to have a complete JRuby runtime available.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gems&lt;/code&gt; contains all dependency gems for this tool.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;image_voodoo_demo&lt;/code&gt; naturally contains our demo tool’s source files.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s customize the building of this jar a little bit.&lt;/p&gt;

&lt;h3 id=&quot;customizing-warbler&quot;&gt;Customizing Warbler&lt;/h3&gt;

&lt;p&gt;Warbler supports a number of configuration settings and can dump a dummy config file to &lt;code class=&quot;highlighter-rouge&quot;&gt;config/warble.rb&lt;/code&gt;:&lt;/p&gt;

&lt;p&gt;&lt;em&gt;(Note: the config directory must exist to generate the config file; this will be fixed in warbler 2.1.1)&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;warble config
&lt;span class=&quot;nb&quot;&gt;cp&lt;/span&gt; /Users/headius/work/jruby/lib/ruby/gems/shared/gems/warbler-2.1.0/warble.rb config/warble.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s say my &lt;code class=&quot;highlighter-rouge&quot;&gt;image_voodoo_demo&lt;/code&gt; utility is super-proprietary IP but I need to deliver it to a customer for use on-premises. The configuration setting we’re looking for is &lt;code class=&quot;highlighter-rouge&quot;&gt;features&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;head&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-12&lt;/span&gt; config/warble.rb
&lt;span class=&quot;c&quot;&gt;# Disable Rake-environment-task framework detection by uncommenting/setting to false&lt;/span&gt;
&lt;span class=&quot;c&quot;&gt;# Warbler.framework_detection = false&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Warbler web application assembly configuration file&lt;/span&gt;
Warbler::Config.new &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; |config|
  &lt;span class=&quot;c&quot;&gt;# Features: additional options controlling how the jar is built.&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# Currently the following features are supported:&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# - *gemjar*: package the gem repository in a jar file in WEB-INF/lib&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# - *executable*: embed a web server and make the war executable&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# - *runnable*: allows to run bin scripts e.g. `java -jar my.war -S rake -T`&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# - *compiled*: compile .rb files to .class files&lt;/span&gt;
  &lt;span class=&quot;c&quot;&gt;# config.features = %w(gemjar)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We modify this line to &lt;code class=&quot;highlighter-rouge&quot;&gt;config.features = %w[compiled]&lt;/code&gt; and re-run the &lt;code class=&quot;highlighter-rouge&quot;&gt;warble&lt;/code&gt; command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;warble
java &lt;span class=&quot;nt&quot;&gt;-classpath&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/Users/headius/work/jruby/lib/ruby/gems/shared/gems/jruby-jars-10.0.2.0/lib/jruby-core-10.0.2.0-complete.jar&quot;&lt;/span&gt;:&lt;span class=&quot;s2&quot;&gt;&quot;/Users/headius/work/jruby/lib/ruby/gems/shared/gems/jruby-jars-10.0.2.0/lib/jruby-stdlib-10.0.2.0.jar&quot;&lt;/span&gt;  &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
        org.jruby.Main &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; jrubyc  &lt;span class=&quot;s2&quot;&gt;&quot;lib/image_voodoo/demo.rb&quot;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;lib/image_voodoo/demo/version.rb&quot;&lt;/span&gt;
Ignoring prism-1.5.2 because its extensions are not built. Try: gem pristine prism &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt; 1.5.2
Ignoring resolv-0.6.2 because its extensions are not built. Try: gem pristine resolv &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt; 0.6.2
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; image_voodoo_demo.jar
Creating image_voodoo_demo.jar
&lt;span class=&quot;nb&quot;&gt;rm&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-f&lt;/span&gt; lib/image_voodoo/demo.class lib/image_voodoo/demo/version.class
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Our build has changed a bit, using JRuby’s &lt;code class=&quot;highlighter-rouge&quot;&gt;jrubyc&lt;/code&gt; command-line compiler to precompile the &lt;code class=&quot;highlighter-rouge&quot;&gt;image_voodoo_demo&lt;/code&gt; sources into JVM class files containing JRuby bytecode.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jar tf image_voodoo_demo.jar | &lt;span class=&quot;nb&quot;&gt;grep &lt;/span&gt;image_voodoo_demo/lib
image_voodoo_demo/lib/
image_voodoo_demo/lib/image_voodoo/
image_voodoo_demo/lib/image_voodoo/demo/
image_voodoo_demo/lib/image_voodoo/demo.class
image_voodoo_demo/lib/image_voodoo/demo.rb
image_voodoo_demo/lib/image_voodoo/demo/version.class
image_voodoo_demo/lib/image_voodoo/demo/version.rb
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The .rb files here are just stubs to load the .class files:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;unzip image_voodoo_demo.jar image_voodoo_demo/lib/image_voodoo/demo.rb
Archive:  image_voodoo_demo.jar
replace image_voodoo_demo/lib/image_voodoo/demo.rb? &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;y]es, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;n]o, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;A]ll, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;N]one, &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;r]ename: y
  inflating: image_voodoo_demo/lib/image_voodoo/demo.rb
image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;cat &lt;/span&gt;image_voodoo_demo/lib/image_voodoo/demo.rb
load __FILE__.sub&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/.rb&lt;span class=&quot;nv&quot;&gt;$/&lt;/span&gt;, &lt;span class=&quot;s1&quot;&gt;&apos;.class&apos;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;%
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can use the &lt;code class=&quot;highlighter-rouge&quot;&gt;javap&lt;/code&gt; disassembler to see the contents of one of these precompiled class files. Good luck turning this back into source code!&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;image_voodoo_demo &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;javap &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-classpath&lt;/span&gt; image_voodoo_demo.jar image_voodoo_demo/lib/image_voodoo/demo
Warning: File image_voodoo_demo.jar&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;/image_voodoo_demo/lib/image_voodoo/demo.class&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; does not contain class image_voodoo_demo/lib/image_voodoo/demo
Compiled from &lt;span class=&quot;s2&quot;&gt;&quot;lib/image_voodoo/demo.rb&quot;&lt;/span&gt;
public class lib.image_voodoo.demo &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
  public static &lt;span class=&quot;o&quot;&gt;{}&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    Code:
       0: new           &lt;span class=&quot;c&quot;&gt;#11                 // class java/lang/StringBuilder&lt;/span&gt;
       3: dup
       4: invokespecial &lt;span class=&quot;c&quot;&gt;#14                 // Method java/lang/StringBuilder.&quot;&amp;lt;init&amp;gt;&quot;:()V&lt;/span&gt;
       7: ldc           &lt;span class=&quot;c&quot;&gt;#16                 // String \u0000\u0000\u0000\u0002\u0000\u0000\n®\b\tH\u0002\&quot;\u0001\u0000S\u0001z\fimage_voodooÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0002\u0000t\u0000\u0000H\u0003\&quot;\u0001\u0000S\u0001z\u0019image_voodoo/demo/versionÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0003\u0000t\u0000\u0001H\u00053t\u0000\u0002\u0001_\u0000u-t\u0000\u0002\u0001\u0007requireÿÿÿÿÿ\u0007Xt\u0006\u0000_\u0000I\u0001t\u0006\u0000\u0000\u0018lib/image_voodoo/demo.rb\u0006H\u00062t\u0000\u0001_\u0000\u0002H\u0018I\u0002t\u0006\u0000\u0000\u0018lib/image_voodoo/demo.rb\u0019-t\u0000\u0001\u0001ÿÿÿÿÿ\u0007\tXt\u0006\u0000_\u0000I\u0001t\u0006\u0000\u0000\u0018lib/image_voodoo/demo.rb\u00076S\u0003H\u0017I\u0002t\u0006\u0000\u0000\u0018lib/image_voodoo/demo.rb\u0018-:\u0001\u0002ÿÿÿÿÿ\u0011output_demo_filesÿÿÿÿÿ\b\t\ft\u0000\u0000ffU\u0001\u0000fÿÿÿÿÿt\u0000\u0000\nl\u0000\u0000t\u0000\u0000\u0000H\b?t\u0000\u0001s\u0001f$\u0000\u0002t\u0000\u0001ÿÿÿÿþl\u0000\u0000wS\u0004\u0000t\u0000\u0002-t\u0000\u0002\u0003\bfilenameÿÿÿÿÿ\u000bImageVoodooÿÿÿÿÿ\nwith_imageÿÿÿÿÿ0;L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\t\ft\u0005\u0000\u0001ff\nl\u0000\u0000t\u0005\u0000\u0001\u0000H\t?t\u0005\u0001\u0001s\u0001f#\u0000\u0002t\u0005\u0001\u0001\u0002l\u0003\u0001z\u0002.*ÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\t\u0000t\u0005\u0002\u0001Xl\u0002\u0000t\u0005\u0002\u0001H\n?t\u0005\u0003\u0001s\u0004f\&quot;\u0000\u0005t\u0005\u0003\u0001\u0001l\u0002\u0000\u0000t\u0005\u0004\u0001\u0006L\u0007CL1_LBL\u0000t\u0005\u0004\u0001Xt\u0005\u0005\u0001N\u0001L\u0007CL1_LBL\u0001:L\u0007CL1_LBL\u0000?t\u0005\u0006\u0001s\u0004f\&quot;\u0000\u0006t\u0005\u0006\u0001\u0001l\u0002\u0000\u0000t\u0005\u0007\u0001Xt\u0005\u0005\u0001t\u0005\u0007\u0001:L\u0007CL1_LBL\u0001H\u000b$\u0000\u0007l\u0000\u0000ÿÿÿÿþfdwS\u0005\u0000t\u0005\b\u0001H\f\u0017\u0000\bl\u0000\u0000ÿÿÿÿûfdfÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0000Èfÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0001\u0090fÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0002XwS\u0006\u0000t\u0005\t\u0001H\r$\u0000\tl\u0000\u0000ÿÿÿÿþf2wS\u0007\u0000t\u0005\n\u0001H\u000e\u0017\u0000\nl\u0000\u0000ÿÿÿÿýfdfÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0096wS\b\u0000t\u0005\u000b\u0001H\u0012Xt\u0005\r\u0001{\u0002:\u000bf\u0014:\fz\u0006FF0000ÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0012\&quot;\u0000\rl\u0000\u0000\u0001t\u0005\r\u0001\u0002t\u0005\f\u0001Pt\u0005\u000f\u0001\u0002l\u0002\u0000z\u000b/border.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0012ÿÿÿÿþ\u000fft\u0018lib/image_voodoo/demo.rb\u0012\&quot;\u0000\u000et\u0005\f\u0001\u0001t\u0005\u000f\u0001\u0000t\u0005\u000e\u0001H\u0013!\u0000\u000fl\u0000\u0000\u0001F?è\u0000\u0000\u0000\u0000\u0000\u0000\u0000t\u0005\u0010\u0001Pt\u0005\u0012\u0001\u0002l\u0002\u0000z\f/reduced.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0013ÿÿÿÿþ\u0010ft\u0018lib/image_voodoo/demo.rb\u0013\&quot;\u0000\u000et\u0005\u0010\u0001\u0001t\u0005\u0012\u0001\u0000t\u0005\u0011\u0001H\u0014?t\u0005\u0015\u0001s\u0004f%\u0000\u0010t\u0005\u0015\u0001\u0000\u0000t\u0005\u0016\u0001Pt\u0005\u0014\u0001\u0005z\u0014wrote demo files to ÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0014t\u0005\u0016\u0001z\u0001/ÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0014l\u0002\u0000z\u0001/ÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0014ÿÿÿÿþ\u001eft\u0018lib/image_voodoo/demo.rb\u0014\&quot;\u0001\u0011S\u0001t\u0005\u0014\u0001\u0000t\u0005\u0013\u0001-t\u0005\u0013\u0001&amp;lt;:L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\u0012t\u0005\u0017\u0001ct\u0005\u0018\u0001\u0002\u0001t\u0005\u0017\u00010t\u0005\u0018\u0001:L\u0007CL1_LBL\u0002\u0012\u0003imgÿÿÿÿÿ\u0004Fileÿÿÿÿÿ\bbasenameÿÿÿÿÿ\bfilenameÿÿÿÿÿ\u0003Dirÿÿÿÿÿ\u0006exist?ÿÿÿÿÿ\u0005mkdirÿÿÿÿÿ\u0011cropped_thumbnailÿÿÿÿÿ\twith_cropÿÿÿÿÿ\tthumbnailÿÿÿÿÿ\u0006resizeÿÿÿÿÿ\u0005widthÿÿÿÿÿ\u0005colorÿÿÿÿÿ\nadd_borderÿÿÿÿÿ\u0004saveÿÿÿÿÿ\u0007qualityÿÿÿÿÿ\u0003pwdÿÿÿÿÿ\u0004putsÿÿÿÿÿ\r;L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\ft\u0005\u0000\u0002ff\nl\u0000\u0000t\u0005\u0000\u0002\u0000H\u000bPt\u0005\u0002\u0002\u0002l\u0001\u0001z\u0012/cropped_thumb.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u000bÿÿÿÿþ\u0016ft\u0018lib/image_voodoo/demo.rb\u000b\&quot;\u0000\u0002l\u0000\u0000\u0001t\u0005\u0002\u0002\u0000t\u0005\u0001\u0002-t\u0005\u0001\u0002&amp;lt;:L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\u0012t\u0005\u0003\u0002ct\u0005\u0004\u0002\u0002\u0001t\u0005\u0003\u00020t\u0005\u0004\u0002:L\u0007CL2_LBL\u0000\u0003\u0004img2ÿÿÿÿÿ\bbasenameÿÿÿÿÿ\u0004saveÿÿÿÿÿ\r;L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\ft\u0005\u0000\u0003ff\nl\u0000\u0000t\u0005\u0000\u0003\u0000H\fPt\u0005\u0002\u0003\u0002l\u0001\u0001z\f/cropped.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\fÿÿÿÿþ\u0010ft\u0018lib/image_voodoo/demo.rb\f\&quot;\u0000\u0002l\u0000\u0000\u0001t\u0005\u0002\u0003\u0000t\u0005\u0001\u0003-t\u0005\u0001\u0003&amp;lt;:L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\u0012t\u0005\u0003\u0003ct\u0005\u0004\u0003\u0002\u0001t\u0005\u0003\u00030t\u0005\u0004\u0003:L\u0007CL3_LBL\u0000\u0003\u0004img2ÿÿÿÿÿ\bbasenameÿÿÿÿÿ\u0004saveÿÿÿÿÿ\r;L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\ft\u0005\u0000\u0004ff\nl\u0000\u0000t\u0005\u0000\u0004\u0000H\rPt\u0005\u0002\u0004\u0002l\u0001\u0001z\n/thumb.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\rÿÿÿÿþ\u000eft\u0018lib/image_voodoo/demo.rb\r\&quot;\u0000\u0002l\u0000\u0000\u0001t\u0005\u0002\u0004\u0000t\u0005\u0001\u0004-t\u0005\u0001\u0004&amp;lt;:L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\u0012t\u0005\u0003\u0004ct\u0005\u0004\u0004\u0002\u0001t\u0005\u0003\u00040t\u0005\u0004\u0004:L\u0007CL4_LBL\u0000\u0003\u0004img2ÿÿÿÿÿ\bbasenameÿÿÿÿÿ\u0004saveÿÿÿÿÿ\u0010;L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\ft\u0005\u0000\u0005ff\nl\u0000\u0000t\u0005\u0000\u0005\u0000H\u000fPt\u0005\u0002\u0005\u0002l\u0001\u0001z\f/resized.jpgÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u000fÿÿÿÿþ\u0010ft\u0018lib/image_voodoo/demo.rb\u000f\&quot;\u0000\u0002l\u0000\u0000\u0001t\u0005\u0002\u0005\u0000t\u0005\u0001\u0005H\u0010Pt\u0005\u0004\u0005\u0002l\u0001\u0001z\f/resized.pngÿÿÿÿþ\u0010\u0018lib/image_voodoo/demo.rb\u0010ÿÿÿÿþ\u0010ft\u0018lib/image_voodoo/demo.rb\u0010\&quot;\u0000\u0002l\u0000\u0000\u0001t\u0005\u0004\u0005\u0000t\u0005\u0003\u0005-t\u0005\u0003\u0005&amp;lt;:L\u0015_GLOBAL_ENSURE_BLOCK_\u0000\u0012t\u0005\u0005\u0005ct\u0005\u0006\u0005\u0002\u0001t\u0005\u0005\u00050t\u0005\u0006\u0005:L\u0007CL5_LBL\u0000\u0003\u0004img2ÿÿÿÿÿ\bbasenameÿÿÿÿÿ\u0004saveÿÿÿÿÿ\t\u0007\u0000\u0003\u0000\u0000\u0000\u0000ÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000 \u0005ffffffffffff\u0000\bÿ\u0000\u0000\u0000\u0097\u0005\u0005\u0002\u0000\u000bImageVoodooÿÿÿÿÿ\u0000\u0000\u0000\u0000ÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000?øffffffffffff\u0000ÿ\u0000\u0000\u0000¥ÿ\u0000\u0000\u0000û\u0004\u0006\u0001\u0000\u0004Demoÿÿÿÿÿ\u0001\u0000\u0000\u0000ÿ\u0000\u0000\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000?øffffffffffff\u0000ÿ\u0000\u0000\u0001\u0001ÿ\u0000\u0000\u0001Q\u0003\u0007\u0003\u0000\u0011output_demo_filesÿÿÿÿÿ\u0002\u0000\u0001\bfilename\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000?ýffffffffffff\u0000ÿ\u0000\u0000\u0001nÿ\u0000\u0000\u0001¬\u0000\b\u0019\u0003fÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿ\u0012with_image &amp;amp;|img|1ÿÿÿÿÿ\u0003\u0001\u0002\u0003img\bbasename\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000?ýffffftffffff\u0000ÿ\u0000\u0000\u0001Üÿ\u0000\u0000\u0005\u0082\u0000\u000b\u0005\u0001fÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿ\u001acropped_thumbnail &amp;amp;|img2|2ÿÿÿÿÿ\u0004\u0001\u0001\u0004img2\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000 \u0000ffffftffffff\u0000ÿ\u0000\u0000\u0006cÿ\u0000\u0000\u0007=\u0000\f\u0005\u0001fÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿ\u0012with_crop &amp;amp;|img2|3ÿÿÿÿÿ\u0004\u0001\u0001\u0004img2\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000 \u0000ffffftffffff\u0000ÿ\u0000\u0000\u0007`ÿ\u0000\u0000\b4\u0000\r\u0005\u0001fÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿ\u0012thumbnail &amp;amp;|img2|4ÿÿÿÿÿ\u0004\u0001\u0001\u0004img2\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000 \u0000ffffftffffff\u0000ÿ\u0000\u0000\bWÿ\u0000\u0000\t)\u0000\u000e\u0007\u0001fÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿ\u000fresize &amp;amp;|img2|5ÿÿÿÿÿ\u0004\u0001\u0001\u0004img2\u0000ÿ\u0000\u0001\u0000\u0000\u0000\u0000\u0000ÿÿ\u0000\u0000 \u0000ffffftffffff\u0000ÿ\u0000\u0000\tLÿ\u0000\u0000\n\u008b&lt;/span&gt;
       9: invokevirtual &lt;span class=&quot;c&quot;&gt;#20                 // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;&lt;/span&gt;
      12: invokevirtual &lt;span class=&quot;c&quot;&gt;#24                 // Method java/lang/Object.toString:()Ljava/lang/String;&lt;/span&gt;
      15: putstatic     &lt;span class=&quot;c&quot;&gt;#26                 // Field script_ir:Ljava/lang/String;&lt;/span&gt;
      18: &lt;span class=&quot;k&quot;&gt;return

  &lt;/span&gt;public static void main&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;java.lang.String[]&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    Code:
       0: invokestatic  &lt;span class=&quot;c&quot;&gt;#34                 // Method org/jruby/Ruby.newInstance:()Lorg/jruby/Ruby;&lt;/span&gt;
       3: astore_1
       4: aload_1
       5: aload_1
       6: getstatic     &lt;span class=&quot;c&quot;&gt;#26                 // Field script_ir:Ljava/lang/String;&lt;/span&gt;
       9: ldc           &lt;span class=&quot;c&quot;&gt;#36                 // String ISO-8859-1&lt;/span&gt;
      11: invokevirtual &lt;span class=&quot;c&quot;&gt;#42                 // Method java/lang/String.getBytes:(Ljava/lang/String;)[B&lt;/span&gt;
      14: ldc           &lt;span class=&quot;c&quot;&gt;#43                 // String lib/image_voodoo/demo.rb&lt;/span&gt;
      16: invokestatic  &lt;span class=&quot;c&quot;&gt;#49                 // Method org/jruby/ir/runtime/IRRuntimeHelpers.decodeScopeFromBytes:(Lorg/jruby/Ruby;[BLjava/lang/String;)Lorg/jruby/ir/IRScope;&lt;/span&gt;
      19: invokevirtual &lt;span class=&quot;c&quot;&gt;#53                 // Method org/jruby/Ruby.runInterpreter:(Lorg/jruby/ParseResult;)Lorg/jruby/runtime/builtin/IRubyObject;&lt;/span&gt;
      22: &lt;span class=&quot;k&quot;&gt;return

  &lt;/span&gt;public static org.jruby.ir.IRScope loadIR&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;org.jruby.Ruby, java.lang.String&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    Code:
       0: aload_0
       1: getstatic     &lt;span class=&quot;c&quot;&gt;#26                 // Field script_ir:Ljava/lang/String;&lt;/span&gt;
       4: ldc           &lt;span class=&quot;c&quot;&gt;#36                 // String ISO-8859-1&lt;/span&gt;
       6: invokevirtual &lt;span class=&quot;c&quot;&gt;#42                 // Method java/lang/String.getBytes:(Ljava/lang/String;)[B&lt;/span&gt;
       9: aload_1
      10: invokestatic  &lt;span class=&quot;c&quot;&gt;#49                 // Method org/jruby/ir/runtime/IRRuntimeHelpers.decodeScopeFromBytes:(Lorg/jruby/Ruby;[BLjava/lang/String;)Lorg/jruby/ir/IRScope;&lt;/span&gt;
      13: areturn
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;future-topics&quot;&gt;Future Topics&lt;/h2&gt;

&lt;p&gt;I’m trying to do more frequent, compact blog posts, so I’m not going to get into the more elaborate generation of deployable web archives today. Here’s a few topics I’ll try to cover in future posts:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Generating deployable WAR files from any rack-compatible web application.&lt;/li&gt;
  &lt;li&gt;Including jar dependencies from the JVM ecosystem as part of your tool.&lt;/li&gt;
  &lt;li&gt;Getting around the JVM requirement: using JVM packaging tools to combine your jar with a complete runnable JVM environment.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A good friend of the JRuby project – Mohit Sindhwani – also &lt;a href=&quot;https://notepad.onghu.com/2021/jruby-win-day2-creating-jar-files/&quot;&gt;blogged about Warbler back in 2021&lt;/a&gt;. His company and many others use Warbler for commercial packaging of Ruby applications every day, ranging from simple command-line utilities all the way up to enterprise scale applications.&lt;/p&gt;

&lt;p&gt;The Warbler project just had its first major release in almost a decade, so we’re currently updating it for modern JRuby, Ruby, and JVM features and requirements. There’s a lot of room for cleanup and new features!&lt;/p&gt;

&lt;p&gt;If you’re interested in packaging your Ruby tools for easy, secure distribution to friends, family, or customers… please give Warbler a try and let us know how we can help you!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://github.com/jruby/warbler&quot;&gt;https://github.com/jruby/warbler&lt;/a&gt;&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-rruby&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1ocje8h/packaging_ruby_apps_with_warbler_executable_jar/&quot;&gt;Join the discussion on r/ruby!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">Updating Deprecations with Version Information</title><link href="https://headius.com/blog/updating-deprecations-with-version/" rel="alternate" type="text/html" title="Updating Deprecations with Version Information" /><published>2025-10-10T00:00:00+00:00</published><updated>2025-10-10T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-10-10-updating-deprecations-with-version.md</id><content type="html" xml:base="https://headius.com/blog/updating-deprecations-with-version/">&lt;p&gt;Java 9 added the ability to mark a &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotation with a “since” version, so we figured it was worth updating JRuby.&lt;/p&gt;

&lt;h2 id=&quot;deprecation-in-java&quot;&gt;Deprecation in Java&lt;/h2&gt;

&lt;p&gt;Deprecation is the process of marking a feature as “no longer supported” or “on its way out”, usually in a programmatic way so users can see warnings at build time. Nearly all languages and ecosystems have some way to mark features, APIs, or libraries as deprecated.&lt;/p&gt;

&lt;p&gt;In Java 1.4 and earlier, this was done via the &lt;code class=&quot;highlighter-rouge&quot;&gt;@deprecated&lt;/code&gt; directive (note lower-case “d”) in JavaDoc, which was great for documentation users but required extra processing by source-level tools and was not visible to any tools at runtime.&lt;/p&gt;

&lt;p&gt;Java 1.5 introduced a new annotation &lt;code class=&quot;highlighter-rouge&quot;&gt;java.lang.Deprecated&lt;/code&gt; that is retained at runtime and requires only annotation-awareness to process. It has become a generally preferred way to mark classes, methods, and fields as deprecated. Unfortunately, it provided no additional information, like a date or version after which the feature should not be used, or whether it would eventually be removed.&lt;/p&gt;

&lt;p&gt;Java 9 fixed that, by adding two attributes to the &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotation: &lt;code class=&quot;highlighter-rouge&quot;&gt;since&lt;/code&gt;, indicating when the deprecation first went into effect and &lt;code class=&quot;highlighter-rouge&quot;&gt;forRemoval&lt;/code&gt;, indicating that the feature has been scheduled for removal. The &lt;code class=&quot;highlighter-rouge&quot;&gt;since&lt;/code&gt; attribute is only informational, but a setting of &lt;code class=&quot;highlighter-rouge&quot;&gt;forRemoval=true&lt;/code&gt; tells the compiler to complain about uses even more loudly (and perhaps fail the build).&lt;/p&gt;

&lt;h2 id=&quot;deprecation-in-jruby&quot;&gt;Deprecation in JRuby&lt;/h2&gt;

&lt;p&gt;The modern era of JRuby extends back almost to the release of Java 1.5, so we’ve steadily added &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotations throughout our codebase as we move away from old features and peculiarly-named methods. These deprecations are generally not visible to the average JRuby user, but if you use JRuby as an API or embed it into a larger Java application, you’ll get compile-time warnings for deprecated features.&lt;/p&gt;

&lt;p&gt;With the release of JRuby 10, we now depend on a minimum of Java 21… which means we can also use the &lt;code class=&quot;highlighter-rouge&quot;&gt;since&lt;/code&gt; tag. But what of all the existing deprecations?&lt;/p&gt;

&lt;h3 id=&quot;updating-deprecations-with-since&quot;&gt;Updating deprecations with “since”&lt;/h3&gt;

&lt;p&gt;This week, we decided it would be worth rewriting all “bare” &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotations to include &lt;code class=&quot;highlighter-rouge&quot;&gt;since&lt;/code&gt; information. The process required a few steps:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Get the source locations of all &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotations that did not contain any attributes.&lt;/li&gt;
  &lt;li&gt;For each location, determine the first JRuby release in which the deprecation was shipped.&lt;/li&gt;
  &lt;li&gt;Update the deprecations to point at that version.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;With a little Ruby and Git, here’s the script I came up with:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# scan all .java files in the dir specified by ARGV[0]&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Dir&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ARGV&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;+&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;/**/*.java&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;].&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;c1&quot;&gt;# read all lines from the file&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;file_lines&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;readlines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;file_lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each_with_index&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;line&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gsub!&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sr&quot;&gt;/@Deprecated\S*$/&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt;
      &lt;span class=&quot;c1&quot;&gt;# use git blame to find the commit that introduced the annotation&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;sha&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`git blame -L &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;i&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;+&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt; &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot; &quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# use git describe to get the first tag that contains that commit&lt;/span&gt;
      &lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;sb&quot;&gt;`git describe --abbrev=0 --tags --contains &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;sha&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sb&quot;&gt;`&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;split&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;~&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)[&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt;

      &lt;span class=&quot;c1&quot;&gt;# add that tag to a &quot;since&quot; attribute for the annotation&lt;/span&gt;
      &lt;span class=&quot;s2&quot;&gt;&quot;@Deprecated(since = &lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;tag&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;se&quot;&gt;\&quot;&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;)&quot;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
  &lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

  &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;file_lines&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;join&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s1&quot;&gt;&apos;&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;processed &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;file&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;ups-and-downs&quot;&gt;Ups and downs&lt;/h3&gt;

&lt;p&gt;The PR I created is here: &lt;a href=&quot;https://github.com/jruby/jruby/pull/9027&quot;&gt;https://github.com/jruby/jruby/pull/9027&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the up side, this clearly indicates when each deprecation was added, so we can start to remove the oldest once (for some definition of “oldest”). It also gives better output to users so they know how long the feature they’re using has been deprecated.&lt;/p&gt;

&lt;p&gt;On the down side, I had to manually fix several tags that were bogus or inaccurate (which maybe is a plus, really), and a piece of code was ever moved then the history is likely broken and the “since” version might be inaccurate. A improved version might also try to track file moves, but that was more than I needed here.&lt;/p&gt;

&lt;p&gt;There’s also a small concern that we’ve basically just updated all of those &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; lines, so any future blaming will have to dig deeper to find out when and why they were deprecated. But I kept this change as a single commit, so at worst you’ll have to &lt;code class=&quot;highlighter-rouge&quot;&gt;git blame&lt;/code&gt; the parent commit in such cases.&lt;/p&gt;

&lt;h3 id=&quot;what-do-you-think&quot;&gt;What do you think?&lt;/h3&gt;

&lt;p&gt;Before I merged this, I asked if anyone could think of a good reason not to do it… and I got no responses. What do you think of this rewriting of &lt;code class=&quot;highlighter-rouge&quot;&gt;@Deprecated&lt;/code&gt; annotations to include the version?&lt;/p&gt;

&lt;p&gt;If you decide to proceed with a similar transformation, I hope this script is helpful! Remember you can always just run it with JRuby, and the easiest way to run JRuby for a simple script would be to use &lt;a href=&quot;https://www.jbang.dev/&quot;&gt;jbang&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jbang run@jruby deprecated_since.rb core/src/main/java
processed core/src/main/java/org/jruby/AbstractRubyMethod.java
processed core/src/main/java/org/jruby/Appendable.java
processed core/src/main/java/org/jruby/BasicObjectStub.java
processed core/src/main/java/org/jruby/DelegatedModule.java
processed core/src/main/java/org/jruby/EvalType.java
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Enjoy!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-rruby&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1o3eiid/updating_jrubys_deprecations_with_since_version/&quot;&gt;Join the discussion on r/ruby!&lt;/a&gt;&lt;/h2&gt;

&lt;h2 id=&quot;join-the-discussion-on-rjava&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/java/comments/1o3eku0/updating_historical_deprecations_with_since/&quot;&gt;Join the discussion on r/java!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">JRuby and Leyden: Even Better Startup</title><link href="https://headius.com/blog/jruby-jdk25-startup-time-follow-up/" rel="alternate" type="text/html" title="JRuby and Leyden: Even Better Startup" /><published>2025-09-26T00:00:00+00:00</published><updated>2025-09-26T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-09-26-jruby-jdk25-startup-time-follow-up.md</id><content type="html" xml:base="https://headius.com/blog/jruby-jdk25-startup-time-follow-up/">&lt;p&gt;At the end of my post on JRuby and JDK 25 startup time features, I teased a bit of the unreleased improvements from &lt;a href=&quot;https://openjdk.org/projects/leyden/&quot;&gt;Project Leyden&lt;/a&gt;. It turns out the latest commits improve startup time &lt;strong&gt;even more&lt;/strong&gt;, so it seems worth posting a quick follow-up!&lt;/p&gt;

&lt;h2 id=&quot;project-leyden-is-lit&quot;&gt;Project Leyden is LIT&lt;/h2&gt;

&lt;p&gt;Of the many OpenJDK projects I follow, Leyden has been near the top as far as activity and interest. In the past month, there’s been 527 commits to all branches… over 15 commits per day. And this doesn’t include commits being done by contributors on their own repositories. It’s exciting to watch!&lt;/p&gt;

&lt;p&gt;After my recent post, &lt;a href=&quot;https://bsky.app/profile/shipilev.bsky.social&quot;&gt;Aleksey Shipilëv&lt;/a&gt; reached out to me on Bluesky:&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://bsky.app/profile/shipilev.bsky.social/post/3lzm3ss3fjs22&quot;&gt;&lt;img src=&quot;/images/aleksey_leyden_followup.png&quot; alt=&quot;Aleksey Shipilëv Bluesky post about recent Leyden improvements&quot; /&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you know Aleksey, you know to listen when he makes a suggestion. Naturally, I got to work building my own “premain” Leyden JDK build to try things out.&lt;/p&gt;

&lt;h3 id=&quot;baseline-startup&quot;&gt;Baseline Startup&lt;/h3&gt;

&lt;p&gt;To refresh your memory, our previous best time — with AOTCache and JRuby’s “dev” mode (which disables many optimizations) — was a lovely little &lt;strong&gt;423ms&lt;/strong&gt;, down from an unaided 943ms.&lt;/p&gt;

&lt;p&gt;We’ll try our baseline startup time training again, but this time I’ll leave off the &lt;code class=&quot;highlighter-rouge&quot;&gt;--dev&lt;/code&gt; flag and let JRuby and the JVM fully optimize and record everything.&lt;/p&gt;

&lt;p&gt;(Recall that the &lt;code class=&quot;highlighter-rouge&quot;&gt;--nocache&lt;/code&gt; flag turns off JRuby’s automatic use of AppCDS, which is incompatible with AOTCache.)&lt;/p&gt;

&lt;p&gt;Same training run:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCacheOutput&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot baseline_training.rb
...
Reading AOTConfiguration jruby.aot.config and writing AOTCache jruby.aot
AOTCache creation is &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;: jruby.aot 110788608 bytes
Removed temporary AOT configuration file jruby.aot.config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note this new AOTCache is 110MB, a full 50MB larger than the 60MB from before. There must be something good in there… let’s try it.&lt;/p&gt;

&lt;p&gt;(To make things a bit easier to read, I’ll be using &lt;code class=&quot;highlighter-rouge&quot;&gt;TIMEFMT=&quot;run time: %mE&quot;&lt;/code&gt; to format zsh’s &lt;code class=&quot;highlighter-rouge&quot;&gt;time&lt;/code&gt; command.)&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ time jruby --nocache -J-XX:AOTCache=jruby.aot -e 1
run time: 353ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;With the latest Leyden “premain” patches, JRuby’s base startup — sacrificing no features and no optimizations — is down to &lt;strong&gt;353ms&lt;/strong&gt;, over 16% faster than our previous best!&lt;/p&gt;

&lt;h3 id=&quot;gem-list&quot;&gt;gem list&lt;/h3&gt;

&lt;p&gt;One thing I didn’t show in my previous post was the performance of &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; without explicitly training the AOTCache for &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt;. Recall that without any help, &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; for 88 gems took &lt;strong&gt;1546ms&lt;/strong&gt;, and our best time was &lt;strong&gt;815ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Here’s a &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; time with the latest Leyden “premain” AOTCache patches, trained only for baseline JRuby startup:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
run &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;: 919ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Not bad! A single, simple training run has reduced &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; time by 40%, and we’ve achieved a sub-second run time without specifically training the AOTCache for this command. This means you can use a simple training run for your JRuby development environment and still see a massive improvement in startup time for arbitrary Ruby commands.&lt;/p&gt;

&lt;p&gt;Of course, we want to see what happens if we specifically train the updated AOTCache for &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt;. Targeted training like is useful when you have one type of command you must run repeatedly, or in a serverless Amazon Lambda-style environment that needs to quickly launch a small tool.&lt;/p&gt;

&lt;p&gt;The training run proceeds as before:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCacheOutput&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot gem_list_training.rb
...
Reading AOTConfiguration jruby.aot.config and writing AOTCache jruby.aot
AOTCache creation is &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;: jruby.aot 175816704 bytes
Removed temporary AOT configuration file jruby.aot.config
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And here’s the &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; run time with our custom &lt;code class=&quot;highlighter-rouge&quot;&gt;AOTCache&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
run &lt;span class=&quot;nb&quot;&gt;time&lt;/span&gt;: 714ms
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another new record: we’re down to &lt;strong&gt;714ms&lt;/strong&gt;, beating the old record of 825ms by 13%, and we &lt;strong&gt;haven’t disabled any optimizations&lt;/strong&gt;.&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;h3 id=&quot;watch-this-space&quot;&gt;Watch This Space&lt;/h3&gt;

&lt;p&gt;Even with these incredible results, I want to reiterate that it’s early days for Project Leyden.&lt;/p&gt;

&lt;p&gt;We still haven’t achieved a “cold” &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; run time comparable to the 280ms from running it in-process in a loop, so clearly there’s optimizations happening that aren’t getting saved and restored.&lt;/p&gt;

&lt;p&gt;And some of these AOTCache sizes might be prohibitively large; an uncached filesystem will take time to load up 100-200MB of AOTCache data, and that will be even worse if it’s loaded across the network.&lt;/p&gt;

&lt;p&gt;But wow it’s exciting stuff. I have been telling JRuby users for years that “it’s not our fault” and “the JVM folks are working on it”. Finally, the dream of fast JRuby startup is coming true.&lt;/p&gt;

&lt;h3 id=&quot;other-options&quot;&gt;Other Options&lt;/h3&gt;

&lt;p&gt;My previous post neglected to mention other options for improving JRuby startup, like the &lt;a href=&quot;https://blog.headius.com/2024/09/jruby-on-crac-part-1-lets-get-cracking.html&quot;&gt;Project CRaC “checkpoint and restore” feature&lt;/a&gt; I blogged about previously and alternative JVMs like &lt;a href=&quot;https://developer.ibm.com/languages/semeru-runtimes/&quot;&gt;IBM Semeru&lt;/a&gt; (aka “OpenJ9”) and its &lt;a href=&quot;https://www.ibm.com/docs/en/semeru-runtime-ce-z/17.0.0?topic=sharing-introduction&quot;&gt;Shared Class Cache&lt;/a&gt; and &lt;a href=&quot;https://www.ibm.com/docs/en/semeru-runtime-ce-z/17.0.0?topic=documentation-jitserver-technology&quot;&gt;JITServer&lt;/a&gt; features. We’ll explore those and other options in an upcoming post!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1nr630x/jruby_and_leyden_even_better_startup/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">JRuby and JDK 25: Startup Time with AOTCache</title><link href="https://headius.com/blog/jruby-jdk25-startup-time-with-aotcache/" rel="alternate" type="text/html" title="JRuby and JDK 25: Startup Time with AOTCache" /><published>2025-09-24T00:00:00+00:00</published><updated>2025-09-24T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-09-24-jruby-jdk25-startup-time-with-aotcache.md</id><content type="html" xml:base="https://headius.com/blog/jruby-jdk25-startup-time-with-aotcache/">&lt;p&gt;&lt;a href=&quot;https://openjdk.org/projects/jdk/25/&quot;&gt;JDK 25&lt;/a&gt; is the newest LTS release since JDK 21, and it ships with a gaggle of amazing VM-level features. This post will cover one of the most important improvements for command-line ecosystems like JRuby’s: the AOTCache (ahead-of-time cache) and its ability to pre-optimize code for future runs.&lt;/p&gt;

&lt;p&gt;We’ll explore how AOTCache can speed up your JRuby workflow, starting with a discussion of JRuby startup time challenges and finishing with “coming soon” features that didn’t quite make it into the JDK 25 release.&lt;/p&gt;

&lt;h2 id=&quot;the-challenge-of-fast-startup-on-the-jvm&quot;&gt;The Challenge of Fast Startup on the JVM&lt;/h2&gt;

&lt;p&gt;It’s worth taking a quick look at why startup time has been such a difficult challenge for JRuby, and how we’ve worked to improve it over the years.&lt;/p&gt;

&lt;h3 id=&quot;why-does-jruby-start-up-slowly&quot;&gt;Why does JRuby start up slowly?&lt;/h3&gt;

&lt;p&gt;Whenever folks ask me “what’s the main issue I’ll have migrating to JRuby” the answer is almost always “startup time”. If the startup time problem had been solved for us 20 years ago, JRuby would already be the de-facto production Ruby for most developers. So why haven’t we just fixed it?&lt;/p&gt;

&lt;p&gt;The unfortunate answer is that it’s really not our fault.&lt;/p&gt;

&lt;h3 id=&quot;starting-from-scratch&quot;&gt;Starting from Scratch&lt;/h3&gt;

&lt;p&gt;&lt;img src=&quot;/images/jruby_architecture.001.png&quot; alt=&quot;JRuby architecture diagram&quot; /&gt;&lt;/p&gt;

&lt;p&gt;There’s many things about Ruby, JRuby, and the JVM that contribute to slow startup:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Ruby applications typically start from uncompiled source every time you run a command, and most of these commands load dozens or hundreds of source files. We have to re-parse and re-compile all that code every time.&lt;/li&gt;
  &lt;li&gt;JRuby’s internal representation of Ruby code starts out interpreted, to reduce the overhead of optimizing code that’s only run once. We delay full optimization until we know methods will be used repeatedly.&lt;/li&gt;
  &lt;li&gt;JRuby itself is compiled Java bytecode, which must be loaded into the JVM from the filesystem and interpreted by the JVM. Like JRuby’s mixed-mode runtime, the JVM defers optimization of that code until it can analyze and profile code. This means the JRuby parser, compiler, interpreter, core classes, and even our JIT compiler all start out “cold”.&lt;/li&gt;
  &lt;li&gt;The JDK itself is largely written in Java, so even the platform JRuby is built upon takes some time to optimize.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even though we’ve made great improvements to JRuby’s startup, like [reducing memory allocation] and [deferring initialization of rarely-used state], we’re fighting an uphill battle. Most of the startup delay is due to the JVM itself.&lt;/p&gt;

&lt;p&gt;Here’s JRuby 10 running a simple command line with no startup-time help on &lt;strong&gt;JDK 21&lt;/strong&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  1.63s user 0.08s system 181% cpu 0.943 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s not the end of the world, but &lt;strong&gt;943ms&lt;/strong&gt; to basically run “hello, world” isn’t great.&lt;/p&gt;

&lt;h3 id=&quot;comparing-with-cruby&quot;&gt;Comparing with CRuby&lt;/h3&gt;

&lt;p&gt;Where JRuby has many layers of optimization, CRuby starts out with much of its code already optimized:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The parser, compiler, interpreter, and most of the core classes are written in C and &lt;strong&gt;start&lt;/strong&gt; as native code.&lt;/li&gt;
  &lt;li&gt;The CRuby boot process has been refined for 30 years to start up as fast as possible.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;As a result, CRuby takes about &lt;strong&gt;45ms&lt;/strong&gt; for its baseline startup (around 20x faster than JRuby’s time above).&lt;/p&gt;

&lt;p&gt;This design does put some hard limits on CRuby’s runtime optimizations, but it also sets a high bar for JRuby. Challenge accepted! Let’s see what we can do to narrow that gap.&lt;/p&gt;

&lt;h3 id=&quot;what-happens-once-the-jvm-optimizes-jruby&quot;&gt;What happens once the JVM optimizes JRuby?&lt;/h3&gt;

&lt;p&gt;Fortunately for JRuby users, the JVM doesn’t take &lt;em&gt;too&lt;/em&gt; long to kick in, and once it does we can start to see the true performance of JRuby.&lt;/p&gt;

&lt;p&gt;Let’s try a more real-world example, running a fairly heavy command: &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with about 88 gems.&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem_list_timing.rb&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;20&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jruby&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[-S gem list]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
  &lt;span class=&quot;vg&quot;&gt;$stderr&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;puts&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;iteration &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;: &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Time&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;now&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;t&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This simple script runs in JRuby and basically just re-launches additional JRuby instances in the same process. Here’s the output of timings from &lt;code class=&quot;highlighter-rouge&quot;&gt;stderr&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby gem_list_timing.rb &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
iteration 0: 1.0590579999999998
iteration 1: 0.435281
iteration 2: 0.39674800000000005
iteration 3: 0.40792199999999995
iteration 4: 0.358851
iteration 5: 0.327706
iteration 6: 0.356299
iteration 7: 0.383793
iteration 8: 0.306416
iteration 9: 0.29007
iteration 10: 0.301724
iteration 11: 0.391308
iteration 12: 0.302654
iteration 13: 0.2939
iteration 14: 0.300466
iteration 15: 0.301517
iteration 16: 0.285803
iteration 17: 0.316092
iteration 18: 0.28180099999999997
iteration 19: 0.280565
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Even though the first iteration here is actually the &lt;strong&gt;second&lt;/strong&gt; time JRuby has booted in this process (the JRuby running the script precedes it), we’re still taking over a second just to list installed gems. But the JVM optimizations kick in quickly, with the second iteration taking half as much time and the final iteration (still getting faster) taking a bit over one-quarter as much time.&lt;/p&gt;

&lt;h2 id=&quot;improving-startup-one-step-at-a-time&quot;&gt;Improving startup one step at a time&lt;/h2&gt;

&lt;p&gt;Clearly, JRuby can run simple commands fast once the JVM gets going. Our challenge has been finding ways to shorten that curve without sacrificing JRuby functionality. Let’s explore a few of these tricks.&lt;/p&gt;

&lt;h3 id=&quot;technique-1-do-less&quot;&gt;Technique #1: Do Less&lt;/h3&gt;

&lt;p&gt;One of the simplest ways to eliminate startup-time overhead is to simply do less of it.&lt;/p&gt;

&lt;p&gt;You can see in the diagram above that there are many phases to running JRuby code on the JVM:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;We parse and compile Ruby to our intermediate representation (IR)&lt;/li&gt;
  &lt;li&gt;We interpret that Ruby IR for a while&lt;/li&gt;
  &lt;li&gt;Eventually, we compile our IR into JVM bytecode&lt;/li&gt;
  &lt;li&gt;The JVM interprets that bytecode for a while&lt;/li&gt;
  &lt;li&gt;An early, simple optimizer in the JVM turns that bytecode into semi-optimized native code&lt;/li&gt;
  &lt;li&gt;That native code continues to be profiled and analyzed&lt;/li&gt;
  &lt;li&gt;A later, profile-driven optimizer recompiles the bytecode into optimized native code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Of course by the time we reach that last stage, short commands may already have exited, and we’ve wasted a lot of effort optimizing code that hardly gets run.&lt;/p&gt;

&lt;p&gt;Realizing that many commands in a typical Ruby workflow don’t require the highest-performing execution, we introduced a development mode flag: &lt;code class=&quot;highlighter-rouge&quot;&gt;--dev&lt;/code&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  1.63s user 0.08s system 181% cpu 0.941 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  0.82s user 0.07s system 123% cpu 0.723 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example runs a trivial Ruby script that just evaluates the number &lt;code class=&quot;highlighter-rouge&quot;&gt;1&lt;/code&gt; and exits. JRuby without any flags takes 941ms. When we use the &lt;code class=&quot;highlighter-rouge&quot;&gt;--dev&lt;/code&gt; flag, however, that time reduces to 723ms. What’s happening here?&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;--dev&lt;/code&gt; flag basically turns off several stages in the optimization pipeline, so we start running more quickly and avoid doing optimizations that won’t be useful:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;JRuby’s bytecode compiler is disabled, so we just stay in the JRuby IR interpreter.&lt;/li&gt;
  &lt;li&gt;The JVM’s second-stage profiled optimizer is disabled, so it just generates simple native code for JRuby’s internal logic and the rest of the JDK.&lt;/li&gt;
  &lt;li&gt;There’s a &lt;a href=&quot;https://github.com/jruby/jruby/blob/master/bin/.dev_mode.java_opts&quot;&gt;few other tweaks&lt;/a&gt; that also have a small impact.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This has been, up until recently, the best way to improve JRuby startup. Let’s review a newer technique I blogged about a few months ago, the &lt;a href=&quot;https://openjdk.org/jeps/310&quot;&gt;Application Class Data Sharing&lt;/a&gt;.&lt;/p&gt;

&lt;h2 id=&quot;technique-2-preprocess-jruby-and-jdk-code-with-appcds&quot;&gt;Technique #2: Preprocess JRuby and JDK code with AppCDS&lt;/h2&gt;

&lt;p&gt;Application Class Data Sharing (AppCDS) is an OpenJDK feature that allows you to save off some of the early boot-time work of loading and processing JVM class files, eliminating that processing for future runs. In my post &lt;a href=&quot;https://blog.headius.com/2025/02/boosting-jruby-startup-with-appcds-and-aotcache.html&quot;&gt;Boosting JRuby Startup with AppCDS and AOT caching&lt;/a&gt;, I walk through the details of “AppCDS” and show how you can use it with JRuby.&lt;/p&gt;

&lt;p&gt;Since then, we’ve released JRuby 10 and updates for JRuby 9.4 that will &lt;strong&gt;automatically&lt;/strong&gt; use AppCDS to improve startup! This feature can be disabled with the &lt;code class=&quot;highlighter-rouge&quot;&gt;--nocache&lt;/code&gt; flag. Let’s review  startup performance with and without AppCDS.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# JRuby with no startup help
$ time jruby --nocache -e 1
jruby --nocache -e 1  1.61s user 0.08s system 182% cpu 0.925 total

# JRuby with AppCDS (on by default)
$ time jruby -e 1
jruby -e 1  1.28s user 0.06s system 191% cpu 0.702 total

# JRuby with &quot;dev&quot; mode only
$ time jruby --nocache --dev -e 1
jruby --nocache --dev -e 1  0.83s user 0.07s system 125% cpu 0.717 total

# JRuby with &quot;dev&quot; mode and AppCDS
$ time jruby --dev -e 1
jruby --dev -e 1  0.63s user 0.04s system 118% cpu 0.567 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now we’re talking! With AppCDS, base JRuby startup in normal mode (with &lt;strong&gt;full optimization&lt;/strong&gt; still enabled) drops down to 702ms, less than “dev” mode alone. And JRuby with both AppCDS &lt;strong&gt;and&lt;/strong&gt; “dev” mode gets down to 567ms!&lt;/p&gt;

&lt;p&gt;At these speeds, startup time becomes almost a non-issue, but what about a heavier command like &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt;?&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# gem list with no startup help
$ time jruby --nocache -S gem list &amp;gt; /dev/null
jruby --nocache -S gem list &amp;gt; /dev/null  3.44s user 0.14s system 215% cpu 1.663 total

# gem list with AppCDS
$ time jruby -S gem list &amp;gt; /dev/null
jruby -S gem list &amp;gt; /dev/null  3.07s user 0.14s system 229% cpu 1.397 total

# gem list with &quot;dev&quot; mode only
$ time jruby --nocache --dev -S gem list &amp;gt; /dev/null
jruby --nocache --dev -S gem list &amp;gt; /dev/null  1.52s user 0.11s system 141% cpu 1.148 total

# gem list with &quot;dev&quot; mode and AppCDS
$ time jruby --dev -S gem list &amp;gt; /dev/null
jruby --dev -S gem list &amp;gt; /dev/null  1.31s user 0.09s system 145% cpu 0.967 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Ok, for a more “real world” command line, we’re still slower than we’d like taking nearly a second just to list gems (similar to “iteration 0” in our looping test). We need to do better!&lt;/p&gt;

&lt;h3 id=&quot;technique-3-upgrade-the-jdk&quot;&gt;Technique #3: Upgrade the JDK&lt;/h3&gt;

&lt;p&gt;There’s a saying in the Java world: “If you want better performance… just wait a bit.” This reflects two truths about the JVM:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;It might take a while to optimize, for many reasons spelled out above.&lt;/li&gt;
  &lt;li&gt;Newer releases of the JDK almost always run faster than older versions.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s try the above AppCDS examples with &lt;strong&gt;JDK 25&lt;/strong&gt; instead of JDK 21:&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;# gem list with no startup help
$ time jruby --nocache -S gem list &amp;gt; /dev/null
jruby --nocache -S gem list &amp;gt; /dev/null  3.30s user 0.14s system 222% cpu 1.546 total

# gem list with AppCDS
$ time jruby -S gem list &amp;gt; /dev/null
jruby -S gem list &amp;gt; /dev/null  3.02s user 0.14s system 234% cpu 1.350 total

# gem list with &quot;dev&quot; mode only
$ time jruby --nocache --dev -S gem list &amp;gt; /dev/null
jruby --nocache --dev -S gem list &amp;gt; /dev/null  1.51s user 0.16s system 149% cpu 1.116 total

# gem list with &quot;dev&quot; mode and AppCDS
$ time jruby --dev -S gem list &amp;gt; /dev/null
jruby --dev -S gem list &amp;gt; /dev/null  1.40s user 0.13s system 158% cpu 0.966 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s some modest improvements here just from upgrading:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with no help reduces from 1663ms to 1546ms (7.1% improvement).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with AppCDS reduces from 1397ms to 1350ms (3.4% improvement).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with “dev” mode reduces from 1148ms to 1116ms (2.8% improvement).&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with both “dev” mode and AppCDS reduces from 0.967ms to 0.966ms (less than 1% improvement).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We’re seeing gains, but they’re not super impressive. But JDK 25 has an ace up its sleeve: AOTCache.&lt;/p&gt;

&lt;h3 id=&quot;technique-4-save-optimization-data-across-runs&quot;&gt;Technique #4: Save optimization data across runs&lt;/h3&gt;

&lt;p&gt;The features in JDK 25 build on the capabilities of AppCDS and add several enhancements compared to JDK 21:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;AOTCache replaces AppCDS as the primary mechanism for improving startup (&lt;a href=&quot;https://openjdk.org/jeps/483&quot;&gt;JEP 843&lt;/a&gt;: “Ahead-of-Time Class Loading &amp;amp; Linking” shipped in JDK 24)&lt;/li&gt;
  &lt;li&gt;Creating an AOTCache is made easier, with only one “training” step required (&lt;a href=&quot;https://openjdk.org/jeps/514&quot;&gt;JEP 514&lt;/a&gt;: “Ahead-of-Time Command-Line Ergonomics”)&lt;/li&gt;
  &lt;li&gt;The AOTCache can now save optimization profiles from a training run to aid optimization of future runs (&lt;a href=&quot;https://openjdk.org/jeps/515&quot;&gt;JEP 515&lt;/a&gt;: “Ahead-of-Time Method Profiling”)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s try switching from AppCDS to the AOTCache.&lt;/p&gt;

&lt;p&gt;First, we need to do a training run to allow the JVM to record optimization profiles. We’ll run JRuby in a loop within a single process and save the resulting AOTCache.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;baseline_training.rb&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jruby&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Ruby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new_instance&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;eval_scriptlet&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;1&quot;&lt;/span&gt;
  &lt;span class=&quot;nb&quot;&gt;print&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;.&quot;&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;ruby&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;tear_down&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;puts&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This time, rather than running JRuby’s &lt;code class=&quot;highlighter-rouge&quot;&gt;main&lt;/code&gt; entry point, we create new JRuby runtime instances in a loop, evaluate some code, and tear them down. Doing this 100 times gives the JVM a chance to “learn” how JRuby boots up and executes.&lt;/p&gt;

&lt;p&gt;To train with AOTCache, we disable AppCDS (&lt;code class=&quot;highlighter-rouge&quot;&gt;--nocache&lt;/code&gt;) and use the JVM flag &lt;code class=&quot;highlighter-rouge&quot;&gt;-XX:AOTCacheOutput=jruby.aot&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCacheOutput&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot baseline_training.rb
....................................................................................................
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;5.678s][warning][aot] Skipping org/jruby/util/JDBCDriverUnloader: Duplicated unregistered class
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;5.678s][warning][aot] Skipping org/jruby/util/JDBCDriverUnloader: Duplicated unregistered class
...
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;5.681s][warning][aot] Skipping org/joda/time/base/BaseDateTime: Old class has been linked
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;5.681s][warning][aot] Skipping jdk/proxy2/&lt;span class=&quot;nv&quot;&gt;$Proxy28&lt;/span&gt;: Unsupported location
Temporary AOTConfiguration recorded: jruby.aot.config
Launching child process /Library/Java/JavaVirtualMachines/zulu-25.jdk/Contents/Home/bin/java to assemble AOT cache jruby.aot using configuration jruby.aot.config
Picked up JAVA_TOOL_OPTIONS: &lt;span class=&quot;nt&quot;&gt;-Djava&lt;/span&gt;.class.path&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;: &lt;span class=&quot;nt&quot;&gt;--add-opens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;java.base/java.io&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.jruby.dist &lt;span class=&quot;nt&quot;&gt;--add-opens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;java.base/java.nio.channels&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.jruby.dist &lt;span class=&quot;nt&quot;&gt;--add-opens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;java.base/sun.nio.ch&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.jruby.dist &lt;span class=&quot;nt&quot;&gt;--add-opens&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;java.management/sun.management&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.jruby.dist &lt;span class=&quot;nt&quot;&gt;-Xss2048k&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Djffi&lt;/span&gt;.boot.library.path&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/Users/headius/work/jruby/lib/jni &lt;span class=&quot;nt&quot;&gt;-Djava&lt;/span&gt;.security.egd&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;file:/dev/urandom &lt;span class=&quot;nt&quot;&gt;--enable-native-access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;org.jruby.dist &lt;span class=&quot;nt&quot;&gt;--sun-misc-unsafe-memory-access&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;allow &lt;span class=&quot;nt&quot;&gt;--module-path&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/Users/headius/work/jruby/lib/jruby.jar &lt;span class=&quot;nt&quot;&gt;-Djruby&lt;/span&gt;.home&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/Users/headius/work/jruby &lt;span class=&quot;nt&quot;&gt;-Djruby&lt;/span&gt;.lib&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/Users/headius/work/jruby/lib &lt;span class=&quot;nt&quot;&gt;-Djruby&lt;/span&gt;.script&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-Djruby&lt;/span&gt;.shell&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;/bin/sh &lt;span class=&quot;nt&quot;&gt;-XX&lt;/span&gt;:AOTCacheOutput&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-XX&lt;/span&gt;:AOTConfiguration&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot.config &lt;span class=&quot;nt&quot;&gt;-XX&lt;/span&gt;:AOTMode&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;create
Reading AOTConfiguration jruby.aot.config and writing AOTCache jruby.aot
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;0.285s][warning][aot] Skipping org/joda/time/tz/FixedDateTimeZone: Unlinked class not supported by AOTClassLinking
...
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;0.286s][warning][aot] Skipping org/joda/time/DateTimeZone: Unlinked class not supported by AOTClassLinking
AOTCache creation is &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;: jruby.aot 60653568 bytes
Removed temporary AOT configuration file jruby.aot.config
&lt;span class=&quot;err&quot;&gt;$&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Now, we can run our previous tests, passing the JVM flag &lt;code class=&quot;highlighter-rouge&quot;&gt;-XX:AOTCache=jruby.aot&lt;/code&gt; to use the generated AOTCache file:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# JRuby with AOTCache&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  1.10s user 0.08s system 203% cpu 0.581 total

&lt;span class=&quot;c&quot;&gt;# JRuby with &quot;dev&quot; mode and AOTCache&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  0.65s user 0.07s system 139% cpu 0.518 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Wow! Baseline startup with AOTCache is almost as fast as AppCDS with “dev” mode (&lt;strong&gt;581ms&lt;/strong&gt; vs 567ms), and AOTCache plus “dev” mode gives us our best time yet: &lt;strong&gt;518ms&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;We can see that &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; has similar improvements:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# gem list with AOTCache&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null  2.69s user 0.13s system 244% cpu 1.152 total

&lt;span class=&quot;c&quot;&gt;# gem list with &quot;dev&quot; mode and AOTCache&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null  1.39s user 0.11s system 173% cpu 0.861 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Another record… &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; with 88 gems is now comfortably less than a second, at &lt;strong&gt;861ms&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Can we do better by training our AOTCache using &lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt;?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;gem_list_training.rb&lt;/strong&gt;*&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;mi&quot;&gt;100&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;times&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jruby&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;sx&quot;&gt;%w[-S gem list]&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;ss&quot;&gt;:string&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Yes we can!&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ time jruby --nocache --dev -J-XX:AOTCache=jruby.aot -S gem list &amp;gt; /dev/null
jruby --nocache --dev -J-XX:AOTCache=jruby.aot -S gem list &amp;gt; /dev/null  1.39s user 0.17s system 189% cpu 0.825 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;New record: &lt;strong&gt;825ms&lt;/strong&gt;!&lt;/p&gt;

&lt;h3 id=&quot;technique-5-save-optimized-code&quot;&gt;Technique #5: Save optimized code!&lt;/h3&gt;

&lt;p&gt;But wait, there’s more!&lt;/p&gt;

&lt;p&gt;The features of AOTCache that shipped with JDK 25 are obviously outstanding, and have delivered the best startup times yet for JRuby users. But the OpenJDK folks haven’t been standing still, and there’s even more coming!&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://openjdk.org/projects/leyden/&quot;&gt;Project Leyden&lt;/a&gt; is the OpenJDK project where this work has been going on, and in late August they released a second Early Access build containing previews of that work. Several features that didn’t make the cut are available for testing and experimentation:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;AOT compilation of Java methods&lt;/strong&gt;: Cache compiled native code.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AOT generation of Dynamic Proxies and reflection data&lt;/strong&gt;: Cache optimized forms of commonly used JVM metaprogramming features.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;AOT optimization for class lookup&lt;/strong&gt;: Cache class lookup data to avoid doing the same lookups again.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? We can try out these features just by downloading &lt;a href=&quot;https://mail.openjdk.org/pipermail/leyden-dev/2025-August/002586.html&quot;&gt;Leyden EA2&lt;/a&gt; and training the AOTCache as before.&lt;/p&gt;

&lt;p&gt;I present to you the fastest JRuby startup times ever seen on a standard JDK:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# baseline startup with AOTCache (Leyden EA2)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  0.55s user 0.06s system 131% cpu 0.462 total

&lt;span class=&quot;c&quot;&gt;# baseline startup with &quot;dev&quot; mode and AOTCache (Leden EA2)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1  0.46s user 0.06s system 122% cpu 0.423 total

&lt;span class=&quot;c&quot;&gt;# gem list with AOTCache (Leyden EA2)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null  1.82s user 0.11s system 186% cpu 1.035 total

&lt;span class=&quot;c&quot;&gt;# gem list with &quot;dev&quot; mode and AOTCache (Leyden EA2)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;jruby.aot &lt;span class=&quot;nt&quot;&gt;-S&lt;/span&gt; gem list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null  1.07s user 0.09s system 142% cpu 0.815 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;JRuby baseline startup &lt;strong&gt;less than half a second&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Gem list down to only &lt;strong&gt;815ms&lt;/strong&gt;!&lt;/p&gt;

&lt;p&gt;Amazing!&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;h3 id=&quot;jruby-enhancements&quot;&gt;JRuby Enhancements&lt;/h3&gt;

&lt;p&gt;Using all our tricks and the latest enhancements to the JVM, we’ve managed to improve JRuby startup time by a huge amount:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Baseline startup went from 943ms to &lt;strong&gt;423ms&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem list&lt;/code&gt; went from 1663ms to &lt;strong&gt;815ms&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That’s over 50% faster startup across the board, with more improvements on the way!&lt;/p&gt;

&lt;p&gt;Over the next few weeks, the JRuby team will be transitioning our automatic startup optimizations from AppCDS to AOTCache, to give users the best possible startup time for daily use. But you’ve already seen how you can experiment with these features today. Try it out and let us know how it goes!&lt;/p&gt;

&lt;h3 id=&quot;project-leyden-collaboration&quot;&gt;Project Leyden Collaboration&lt;/h3&gt;

&lt;p&gt;We’re also going to be collaborating with the Project Leyden team to share our findings and help them to improve the AOTCache. We’re obviously &lt;strong&gt;very excited&lt;/strong&gt; about this work, since it may finally solve the #1 complaint about using JRuby.&lt;/p&gt;

&lt;p&gt;What will the Ruby world be like when JRuby users can have true parallelism, world-class garbage collection, and fast straight-line performance &lt;strong&gt;without compromising startup time&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;We’re about to find out.&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1npiavg/jruby_and_jdk_25_startup_time_with_aotcache/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">3D Charts and More with JRuby and JFreeChart</title><link href="https://headius.com/blog/3d-charts-and-more-with-jruby-and-jfreechart/" rel="alternate" type="text/html" title="3D Charts and More with JRuby and JFreeChart" /><published>2025-05-01T00:00:00+00:00</published><updated>2025-05-01T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-05-01-3d-charts-and-more-with-jruby-and-jfreechart.md</id><content type="html" xml:base="https://headius.com/blog/3d-charts-and-more-with-jruby-and-jfreechart/">&lt;p&gt;After playing with JFreeChart and JRuby and writing up a lovely blog post called “&lt;a href=&quot;https://blog.headius.com/2025/04/beautiful-charts-with-jruby-and-jfreechart.html&quot;&gt;Beautiful Charts with JRuby and JFreeChart&lt;/a&gt;” it seems that &lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1kbqh5l/creating_beautiful_charts_with_jruby_and/&quot;&gt;some folks&lt;/a&gt; did not agree with my assessment of the charts as “beautiful” and others did not realize the power of what they’d just seen.&lt;/p&gt;

&lt;p&gt;So, let’s see if this one grabs your attention. As before, this example is available in the &lt;a href=&quot;https://github.com/headius/jruby-charts&quot;&gt;headius/jruby-charts&lt;/a&gt; repository.&lt;/p&gt;

&lt;h2 id=&quot;orson-charts&quot;&gt;Orson Charts&lt;/h2&gt;

&lt;p&gt;Along with a comprehensive and configurable set of 2D chart formats, the JFreeChart project also ships &lt;a href=&quot;https://github.com/jfree/orson-charts&quot;&gt;Orson Charts&lt;/a&gt;, an equally extensive set of 3D chart renderers. The library has been designed to work alongside JFreeChart and has all the same support for file output and GUI integration.&lt;/p&gt;

&lt;p&gt;So you don’t think my bar chart was beautiful? Ok, maybe you’re right. Let’s kick it up a notch!&lt;/p&gt;

&lt;h3 id=&quot;everything-is-better-in-3d&quot;&gt;Everything is Better in 3D&lt;/h3&gt;

&lt;p&gt;It took me about 30 minutes to port one of the &lt;a href=&quot;https://github.com/jfree/jfree-demos/tree/master/src/main/java/com/orsoncharts/demo&quot;&gt;Orson Charts demos&lt;/a&gt;, a 3D rendering of &lt;a href=&quot;https://github.com/jfree/jfree-demos/blob/master/src/main/java/com/orsoncharts/demo/CategoryMarkerDemo2.java&quot;&gt;quarterly revenues for four application monitoring companies&lt;/a&gt;. I’ve made the following tweaks along the way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Data is read in from a json file using the Ruby &lt;code class=&quot;highlighter-rouge&quot;&gt;json&lt;/code&gt; library.&lt;/li&gt;
  &lt;li&gt;The GUI and interactive elements have been removed.&lt;/li&gt;
  &lt;li&gt;The chart is rendered to various file formats rather than to the screen.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full source is here: &lt;a href=&quot;https://github.com/headius/jruby-charts/blob/master/examples/category_chart.rb&quot;&gt;3D Bar Chart with JRuby and Orson Charts&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Let’s jump in!&lt;/p&gt;

&lt;h3 id=&quot;adding-the-dependencies&quot;&gt;Adding the Dependencies&lt;/h3&gt;

&lt;p&gt;Orson Charts has a Maven artifact just like JFreeChart, so we add it to our &lt;code class=&quot;highlighter-rouge&quot;&gt;Jarfile&lt;/code&gt; and re-lock the dependencies.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:jfreechart:1.5.5&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.chart3d:2.1.0&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Locking is done with &lt;code class=&quot;highlighter-rouge&quot;&gt;lock_jars&lt;/code&gt; as before.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby-charts &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;lock_jars

&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; jar root dependencies &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;

      org.jfree:jfreechart:1.5.5:compile
      org.jfree:org.jfree.chart3d:2.1.0:compile

Jars.lock updated
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;importing-classes-for-cleaner-code&quot;&gt;Importing Classes for Cleaner Code&lt;/h3&gt;

&lt;p&gt;In my previous post, I mostly just referenced classes using their Java package + class format, but for this example we’ll import the classes ahead of time. This is an optional step; you can always just reference the classes directly.&lt;/p&gt;

&lt;p&gt;If you want to be explicit about imports, you can use &lt;code class=&quot;highlighter-rouge&quot;&gt;java_import&lt;/code&gt; which assigns a constant of the same name in the current namespace.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Color&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BufferedImage&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imageio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ImageIO&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Chart3DFactory&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DefaultKeyedValues&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StandardCategoryDataset3D&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;interaction&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StandardKeyedValues3DItemSelection&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;label&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StandardCategoryItemLabelGenerator&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;legend&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;LegendAnchor&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart3d&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;marker&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;CategoryMarker&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You can also just assign the constants yourself, if you prefer.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;no&quot;&gt;Color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Color&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;BufferedImage&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;java&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;awt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BufferedImage&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;generating-the-dataset-from-json&quot;&gt;Generating the Dataset from JSON&lt;/h3&gt;

&lt;p&gt;My previous post used canned data hand-entered into the dataset object, but of course you will typically read data from a database or json-formatted data source. I have created a &lt;a href=&quot;https://github.com/headius/jruby-charts/blob/master/data/app_monitoring_revenue.json&quot;&gt;json data file&lt;/a&gt; based on the revenue demo’s data:&lt;/p&gt;

&lt;div class=&quot;language-json highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
  &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;New Relic&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Q2/19&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;141.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Q3/19&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;146.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Q4/19&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;153.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
    &lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;&quot;Q1/20&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;w&quot;&gt; &lt;/span&gt;&lt;span class=&quot;mf&quot;&gt;160.0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;&lt;span class=&quot;w&quot;&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We can read it in and populate a &lt;code class=&quot;highlighter-rouge&quot;&gt;StandardCategoryDataset3D&lt;/code&gt; using idiomatic Ruby code.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;json&apos;&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardCategoryDataset3D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JSON&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;load&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;read&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;data/app_monitoring_revenue.json&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;DefaultKeyedValues&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;subset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;each&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;put&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_series_as_row&lt;/span&gt; &lt;span class=&quot;nb&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;values&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Let’s construct the chart now.&lt;/p&gt;

&lt;h3 id=&quot;creating-and-configuring-a-3d-bar-chart&quot;&gt;Creating and Configuring a 3D Bar Chart&lt;/h3&gt;

&lt;p&gt;The Orson Charts API feels familiar if you have used JFreeChart. This example does more customization and prettier rendering than my previous simple charts. Let’s walk through it.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Chart3DFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_bar_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;Quarterly Revenues&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;Application &amp;amp; Performance Monitoring Companies&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;dataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Quarter&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
  &lt;span class=&quot;s2&quot;&gt;&quot;$million Revenues&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart_box_color&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;255&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;127&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;legend_anchor&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;LegendAnchor&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BOTTOM_RIGHT&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We use &lt;code class=&quot;highlighter-rouge&quot;&gt;Chart3DFactory&lt;/code&gt; to &lt;code class=&quot;highlighter-rouge&quot;&gt;create_bar_chart&lt;/code&gt; with a title, subtitle, dataset, row, column, and value axis labels (row axis left empty).&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;gridline_paint_for_values&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Color&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;BLACK&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;renderer&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;renderer&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_label_generator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;
  &lt;span class=&quot;no&quot;&gt;StandardCategoryItemLabelGenerator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;StandardCategoryItemLabelGenerator&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;VALUE_TEMPLATE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_selection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;StandardKeyedValues3DItemSelection&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;item_label_generator&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;item_selection&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_selection&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;renderer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;item_label_generator&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;item_label_generator&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we set the gridline color to black and set up a basic renderer for the bar labels to hide the numeric values (we’re visualizing the data, not trying to render every value in text).&lt;/p&gt;

&lt;p&gt;That’s about it to get the basic 3D bar chart ready! The original example also includes features like highlighting a specific column, interactively browsing the data, and other features that are more suited for a rich GUI application.&lt;/p&gt;

&lt;h3 id=&quot;generating-output&quot;&gt;Generating Output&lt;/h3&gt;

&lt;p&gt;Let’s generate a PNG image from our chart. Unlike the previous examples, the &lt;code class=&quot;highlighter-rouge&quot;&gt;BarChart3D&lt;/code&gt; class does not have a convenience method for creating and drawing into a &lt;code class=&quot;highlighter-rouge&quot;&gt;BufferedImage&lt;/code&gt;, so we have a few extra lines of code here.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;600&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;category_chart_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BufferedImage&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;BufferedImage&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;TYPE_INT_RGB&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;category_chart_graphics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_chart_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_graphics&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_chart_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We create an RGB &lt;code class=&quot;highlighter-rouge&quot;&gt;BufferedImage&lt;/code&gt; of the specified dimentions, get a &lt;code class=&quot;highlighter-rouge&quot;&gt;Graphics2D&lt;/code&gt; object from it for writing, and tell the chart to draw itself into the specified area.&lt;/p&gt;

&lt;p&gt;Now we can write it to a file.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;category_chart_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;category_chart.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;ImageIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;category_chart_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PNG&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;category_chart_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_outputstream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And we’ve got our &lt;a href=&quot;/images/category_chart.png&quot;&gt;chart&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/category_chart.png&quot; alt=&quot;3D bar chart generated with JRuby and Orson Charts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Finally, we should at least pretend we’re writing production code and clean up the graphics context and the open file.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;category_chart_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;dispose&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;category_chart_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;close&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;bonus-round&quot;&gt;Bonus Round!&lt;/h2&gt;

&lt;p&gt;A number of commenters also seemed to miss the real point of my article: this is just one of thousands of libraries available to JRuby users. Maybe you don’t want this chart as a PNG and want to render it in a browser as SVG? No problem!&lt;/p&gt;

&lt;h3 id=&quot;jfreecharts-svg-support&quot;&gt;JFreeCharts SVG Support&lt;/h3&gt;

&lt;p&gt;Using exactly the same API, you can also output SVG. First, we’ll add the additional dependency to our &lt;code class=&quot;highlighter-rouge&quot;&gt;Jarfile&lt;/code&gt;.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:jfreechart:1.5.5&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.chart3d:2.1.0&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.svg:5.0.6&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;After locking, we can require the libraries and render the exact same chart object to an SVG file.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;require_jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree.svg&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;5.0.6&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;SVGGraphics2D&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;svg_graphics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;SVGGraphics2D&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;svg_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;defs_key_prefix&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;jruby_charts&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;element_hinting&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;svg_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;width&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;height&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;svg&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svg_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_svg_element&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;id&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;category_chart.svg&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;svg&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And &lt;a href=&quot;/images/category_chart.svg&quot;&gt;there it is&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/category_chart.svg&quot; alt=&quot;3D bar chart as SVG generated with JRuby and Orson Charts&quot; /&gt;&lt;/p&gt;

&lt;p&gt;But wait, there’s more!&lt;/p&gt;

&lt;h3 id=&quot;jfreecharts-pdf-support&quot;&gt;JFreeCharts PDF Support&lt;/h3&gt;

&lt;p&gt;PDF generation has typically been a struggle for CRuby users, with only a few working libraries, some abandoned and most incomplete. JRuby users, on the other hand, have a large number of full-featured PDF libraries available to them. In this case, JFreeChart provides its own PDF support that integrates directly into the same API.&lt;/p&gt;

&lt;p&gt;We just add and lock the dependency in our &lt;code class=&quot;highlighter-rouge&quot;&gt;Jarfile&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:jfreechart:1.5.5&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.chart3d:2.1.0&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.svg:5.0.6&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:org.jfree.pdf:2.0&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And render the chart to &lt;a href=&quot;/images/category_chart.pdf&quot;&gt;PDF&lt;/a&gt;:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;require_jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree.pdf&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;2.0.1&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;PDFDocument&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pdf_doc&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;PDFDocument&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pdf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;title&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Application &amp;amp; Performance Monitoring Companies Revenue&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pdf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;author&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Charles Oliver Nutter&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;page&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;612&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;468&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pdf_graphics&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;page&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;graphics2D&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;draw&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pdf_graphics&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Rectangle&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;612&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;468&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;))&lt;/span&gt;
&lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;category_chart.pdf&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pdf_doc&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pdf_bytes&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;the-bottom-line&quot;&gt;The Bottom Line&lt;/h2&gt;

&lt;p&gt;The point of these posts is not to wow you with my &lt;em&gt;amazing&lt;/em&gt; chart-designing skills. You can get your own hands dirty with these APIs and customize them to your heart’s content. I’m a &lt;em&gt;programmer&lt;/em&gt;, not a graphic designer!&lt;/p&gt;

&lt;p&gt;What I really want you get out of this is that it’s &lt;strong&gt;easy&lt;/strong&gt; and &lt;strong&gt;fun&lt;/strong&gt; to use these libraries in JRuby… without ever leaving Ruby. We’re talking about thousands of battle-tested, production ready tools that you can integrate into your applications &lt;strong&gt;today&lt;/strong&gt;. No other Ruby implementation can give you that as quickly and easily.&lt;/p&gt;

&lt;p&gt;JRuby is being actively developed by me and the rest of the JRuby team. We’re working full-time to deliver the best possible JVM Ruby runtime that we can. I just want to continue building tools that Rubyists like you will find useful!&lt;/p&gt;

&lt;p&gt;So, what would you like to see next?&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1kcbthz/3d_charts_svg_and_pdf_with_jruby_and_jfreechart/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">Creating Beautiful Charts with JRuby and JFreeChart</title><link href="https://headius.com/blog/beautiful-charts-with-jruby-and-jfreechart/" rel="alternate" type="text/html" title="Creating Beautiful Charts with JRuby and JFreeChart" /><published>2025-04-30T00:00:00+00:00</published><updated>2025-04-30T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-04-30-beautiful-charts-with-jruby-and-jfreechart.md</id><content type="html" xml:base="https://headius.com/blog/beautiful-charts-with-jruby-and-jfreechart/">&lt;p&gt;I recently returned from &lt;a href=&quot;https://rubykaigi.org/2025/&quot;&gt;RubyKaigi&lt;/a&gt; where I had the opportunity to sit down with members of the Japanese Ruby community and show them a little bit of JRuby. One of the items that came up a few times was the difficulty of utilizing external libraries from Ruby: if it’s a C library, typically you have to either write a C extension or do the extra work of writing up an FFI binding.&lt;/p&gt;

&lt;p&gt;If the library is not implemented in C or Ruby, things get even weirder.&lt;/p&gt;

&lt;p&gt;One example is the &lt;a href=&quot;https://github.com/red-data-tools/charty&quot;&gt;Charty&lt;/a&gt; library, one of the more popular options for generating beautiful chart graphics on Ruby. But Charty actually wraps a &lt;em&gt;Python&lt;/em&gt; library called &lt;a href=&quot;https://matplotlib.org/&quot;&gt;matplotlib&lt;/a&gt;, and the bindings for CRuby literally load Python into the current process and call it via Ruby C API calls which then make Python C API calls. The horror!&lt;/p&gt;

&lt;p&gt;Also recently, a &lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1kalb7s/rails_action_mailer_rendering_charts_or_graphs_in/&quot;&gt;Reddit Ruby post&lt;/a&gt; demonstrated the use of the QuickChart library, which is implemented in JavaScript… yuck! In this case, the call-out is via process launching to the QuickChart command line, but either way it’s not at really integrated into the Ruby app using it.&lt;/p&gt;

&lt;p&gt;Is there a better way? You bet there is: use a JVM-based library from JRuby! This post will show you how to get started with JRuby’s Java integration and a few quick examples of using the JFreeChart library from Ruby code. I promise you won’t have to write a single line of Java, C, Python, or JavaScript.&lt;/p&gt;

&lt;h2 id=&quot;jfreechart&quot;&gt;JFreeChart&lt;/h2&gt;

&lt;p&gt;The &lt;a href=&quot;https://www.jfree.org/jfreechart/&quot;&gt;JFreeChart library&lt;/a&gt; provides an extensive set of chart formats, with a huge array of rendering options and easy integration with either JVM-based GUI toolkits or simple image-file output. Unlike many of the other charting libraries, it also supports live updating of a given chart GUI, making it an excellent choice for desktop monitoring tools.&lt;/p&gt;

&lt;p&gt;Building a GUI with JRuby is fun and easy, but for this post we’ll focus on just the chart and image generation parts of the API and how you can use them easily from Ruby.&lt;/p&gt;

&lt;h2 id=&quot;jrubys-java-integration&quot;&gt;JRuby’s Java Integration&lt;/h2&gt;

&lt;p&gt;The magic starts with JRuby’s &lt;a href=&quot;https://github.com/jruby/jruby/wiki/CallingJavaFromJRuby&quot;&gt;Java integration layer&lt;/a&gt;. The simplest way to use a Java library is to basically pretend it’s a Ruby library, with a few tweaks along the way:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Download the library’s jar file and dependencies, or use jar-dependencies (part of JRuby’s standard library) to fetch them for you.&lt;/li&gt;
  &lt;li&gt;Require the jar file and dependencies manually (a simple &lt;code class=&quot;highlighter-rouge&quot;&gt;require &quot;myfile.jar&quot;&lt;/code&gt; works in JRuby!) or by using &lt;code class=&quot;highlighter-rouge&quot;&gt;require_jar&lt;/code&gt; from &lt;code class=&quot;highlighter-rouge&quot;&gt;jar-dependencies&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Call the Java classes you’re interested from Ruby as if they were plain old Ruby classes. You can even import them, Java-style, but it’s often not necessary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Let’s start with a simple example.&lt;/p&gt;

&lt;h3 id=&quot;calling-into-the-javalangruntime-class&quot;&gt;Calling into the java.lang.Runtime class&lt;/h3&gt;

&lt;p&gt;Assuming you’ve installed &lt;a href=&quot;https://www.jruby.org/2025/04/14/jruby-10-0-0-0.html&quot;&gt;JRuby 10&lt;/a&gt;, you can start playing with JVM libraries directly from IRB. Let’s fire it up and make sure we’re running on JRuby.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;irb
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:001&amp;gt; RUBY_ENGINE
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;jruby&quot;&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:002&amp;gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The JDK comes with a large number of built-in features, of course, and I like to use the &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/lang/Runtime.html&quot;&gt;java.lang.Runtime&lt;/a&gt; class for quick demonstrations. It provides methods to query a bit of information about the running JVM and the host operating system. Let’s “import” it into our session and call a few methods.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:002&amp;gt; rt &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; java.lang.Runtime.runtime
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; &lt;span class=&quot;c&quot;&gt;#&amp;lt;Java::JavaLang::Runtime:0x39a87e72&amp;gt;&lt;/span&gt;
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:003&amp;gt; rt.available_processors
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 8
irb&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;main&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;:004&amp;gt; rt.free_memory
&lt;span class=&quot;o&quot;&gt;=&amp;gt;&lt;/span&gt; 222145360
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There’s a few bits of magic here that make it really feel like Ruby:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;JRuby defines a few top-level methods for common Java package roots like &lt;code class=&quot;highlighter-rouge&quot;&gt;java&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;javax&lt;/code&gt;, &lt;code class=&quot;highlighter-rouge&quot;&gt;org&lt;/code&gt;, and &lt;code class=&quot;highlighter-rouge&quot;&gt;com&lt;/code&gt;. We use this to reference the Java package &lt;code class=&quot;highlighter-rouge&quot;&gt;java.lang&lt;/code&gt; here and access the &lt;code class=&quot;highlighter-rouge&quot;&gt;Runtime&lt;/code&gt; class.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;Runtime&lt;/code&gt; defines a static method (similar to a Ruby class or singleton method) called &lt;code class=&quot;highlighter-rouge&quot;&gt;getRuntime&lt;/code&gt; which gets the singleton instance of &lt;code class=&quot;highlighter-rouge&quot;&gt;Runtime&lt;/code&gt; for the current JVM process. JRuby turns Java “getter” and “setter” methods into Ruby “attribute accessors”, so you can just call &lt;code class=&quot;highlighter-rouge&quot;&gt;Runtime.runtime&lt;/code&gt; here.&lt;/li&gt;
  &lt;li&gt;The Ruby calls to &lt;code class=&quot;highlighter-rouge&quot;&gt;available_processors&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;free_memory&lt;/code&gt; translate to the Java methods &lt;code class=&quot;highlighter-rouge&quot;&gt;getAvailableProcessors&lt;/code&gt; and &lt;code class=&quot;highlighter-rouge&quot;&gt;getFreeMemory&lt;/code&gt; in the same way. The original methods are still callable, but we add aliases for the Ruby-friendly names.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We can accomplish a tremendous amount with the libraries shipped in the JDK, ranging from simple database access to professional desktop GUI development.&lt;/p&gt;

&lt;p&gt;If the libraries we want to use are not shipped with the JDK, however, we need to fetch them. It’s always possible to download the library’s “jar” file directly, but then you may also have to hunt down its dependency jars. A better way is to use JRuby’s &lt;a href=&quot;https://github.com/jruby/jar-dependencies&quot;&gt;jar-dependencies&lt;/a&gt; library.&lt;/p&gt;

&lt;h3 id=&quot;jar-dependencies&quot;&gt;jar-dependencies&lt;/h3&gt;

&lt;p&gt;Like Ruby gems get pushed to &lt;a href=&quot;https://rubygems.org/&quot;&gt;rubygems.org&lt;/a&gt;, Java libraries get published as jar files to &lt;a href=&quot;https://maven.apache.org/&quot;&gt;Maven&lt;/a&gt; repositories, a global, federated repository of every version of every Java library known to man. In order to make it as easy as possible to use these libraries from JRuby, we’ve built extensive Maven tooling for both Ruby and Java applications.&lt;/p&gt;

&lt;p&gt;The simplest way to load a JVM library from Maven into your JRuby app is to use &lt;code class=&quot;highlighter-rouge&quot;&gt;jar-dependencies&lt;/code&gt;, a built-in tool for fetching and managing Java dependencies in either standalone applications or in gems.&lt;/p&gt;

&lt;p&gt;For our examples, we want to fetch JFreeChart and its dependency libraries. If we search for it at &lt;a href=&quot;https://search.maven.org&quot;&gt;search.maven.org&lt;/a&gt;, we can acquire its “Maven coordinates”.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/maven_search_jfreechart.png&quot; alt=&quot;Searching Maven Central for JFreeChart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;The most recently-published artifact is jfreechart 1.5.5. From here we can see the coordinates we’re looking for:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The group ID, used for namespacing libraries, is &lt;code class=&quot;highlighter-rouge&quot;&gt;org.jfree&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;The artifact ID, which identifies the library, is &lt;code class=&quot;highlighter-rouge&quot;&gt;jfreechart&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;And the version we want is &lt;code class=&quot;highlighter-rouge&quot;&gt;1.5.5&lt;/code&gt; (Maven strongly encourages always using specific versions).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Given that, we can set up our JFreeChart project.&lt;/p&gt;

&lt;h2 id=&quot;jruby-and-jfreechart-happy-together&quot;&gt;JRuby and JFreeChart: Happy Together!&lt;/h2&gt;

&lt;p&gt;Without knowing anything about JFreeChart, I was able to load it up and get some simple examples working from IRB just last night. I was so impressed, I decided to write this blog post!&lt;/p&gt;

&lt;p&gt;I’ve pushed this example as &lt;a href=&quot;https://github.com/headius/jruby-charts&quot;&gt;headius/jruby-charts&lt;/a&gt; on GitHub so you can follow along.&lt;/p&gt;

&lt;h3 id=&quot;using-jar-dependencies-to-fetch-jfreechart&quot;&gt;Using jar-dependencies to fetch JFreeChart&lt;/h3&gt;

&lt;p&gt;We’ll start by creating a &lt;code class=&quot;highlighter-rouge&quot;&gt;jar-dependencies&lt;/code&gt; “Jarfile”, which is roughly equivalent to Bundler’s “Gemfile”:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree:jfreechart:1.5.5&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Similar to a Gemfile’s gem name and version, we specify the Maven coordinates of the library we want (separated by colons). Once we have this file, we can fetch and “lock” this dependency with the &lt;code class=&quot;highlighter-rouge&quot;&gt;lock_jars&lt;/code&gt; command.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby-charts &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;lock_jars

&lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt; jar root dependencies &lt;span class=&quot;nt&quot;&gt;--&lt;/span&gt;

      org.jfree:jfreechart:1.5.5:compile

Jars.lock created
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This command fetches JFreeChart and any other dependencies to your local Maven repository, which by default is in &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.m2/repository&lt;/code&gt;. In general, we recommend using jar-dependencies this way, so that your jars don’t conflict with other gems’ jars, and all applications on a given system use the same set of downloaded files. It’s also possible to fetch the jars and ship them inside your application or gem, but we’ll leave that example for another day.&lt;/p&gt;

&lt;p&gt;Let’s get to the fun stuff: using JFreeChart from Ruby!&lt;/p&gt;

&lt;h3 id=&quot;generating-a-simple-bar-chart&quot;&gt;Generating a simple bar chart&lt;/h3&gt;

&lt;p&gt;Our first example will create a simple &lt;a href=&quot;https://github.com/headius/jruby-charts/blob/master/examples/barchart.rb&quot;&gt;bar chart&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;First, we need to load in the jars we just downloaded and locked.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Use jar-dependencies, included with JRuby, to load JFreeChart&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jar-dependencies&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;require_jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jfreechart&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.5.5&apos;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;At this point &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/index.html&quot;&gt;all of the classes of JFreeChart&lt;/a&gt; are available to Ruby. We start by creating a &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/org/jfree/data/category/DefaultCategoryDataset.html&quot;&gt;DefaultCategoryDataset&lt;/a&gt; which will hold our bar chart data.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Create an empty CategoryDataSet&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;category&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DefaultCategoryDataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;JFreeChart provides a wide array of dataset types that can source data from a database, a JVM-based collection object (which includes Ruby collections), or other forms of structured data. The “default” version works nicely for a simple example. Let’s fill it with some data.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Add values to the dataset&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;44&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Ben and Jerry&apos;s&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Flavors by creamery&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;31&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Baskin Robbins&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Flavors by creamery&quot;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;11&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Cold Stone&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Flavors by creamery&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This bar chart will display a count of ice cream flavors from three well-known purveyors of the creamery arts. The &lt;code class=&quot;highlighter-rouge&quot;&gt;add_value&lt;/code&gt; method here is &lt;code class=&quot;highlighter-rouge&quot;&gt;addValue&lt;/code&gt; in Java, and takes a number, a column key, and a row key.&lt;/p&gt;

&lt;p&gt;Given our dataset, we can now request that JFreeChart create a basic bar chart for us using the &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/org/jfree/chart/ChartFactory.html&quot;&gt;ChartFactory&lt;/a&gt; class.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Create a bar chart with default settings&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ChartFactory&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;ChartFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_bar_chart&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;How Many Ice Cream Flavors?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt;
                                          &lt;span class=&quot;s2&quot;&gt;&quot;Creamery&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Flavors&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar_data&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We import the &lt;code class=&quot;highlighter-rouge&quot;&gt;ChartFactory&lt;/code&gt; class for convenience (which is basically equivalent to doing &lt;code class=&quot;highlighter-rouge&quot;&gt;ChartFactory = org.jfree.chart.ChartFactory&lt;/code&gt;) and then call &lt;code class=&quot;highlighter-rouge&quot;&gt;create_bar_chart&lt;/code&gt; to generate a bar chart with default settings. The arguments we pass are the name of the chart, the label for the X axis, and the label for the Y axis, and our dataset.&lt;/p&gt;

&lt;p&gt;Now that we have a chart, we can use Java’s graphics APIs (provided with the JDK by default) to output a PNG file.&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Create a buffered image in memory at 500x500&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_buffered_image&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Write the image as a PNG to a file&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;bar_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;barchart.png&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imageio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ImageIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;bar_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;PNG&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;bar_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_outputstream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;JFreeChart’s bar chart type knows how to create a Java2D &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/java/awt/image/BufferedImage.html&quot;&gt;BufferedImage&lt;/a&gt;, which we can then pass to &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/imageio/ImageIO.html&quot;&gt;ImageIO&lt;/a&gt; to write it out. In this case, we even use a Ruby &lt;code class=&quot;highlighter-rouge&quot;&gt;File&lt;/code&gt; as the target, using a bit of JRuby magic that turns it into a Java &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.base/java/io/OutputStream.html&quot;&gt;OutputStream&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;With our jar dependencies locked and our script written, we can simply run it with JRuby from a command line.&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;jruby-charts &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby examples/barchart.rb
2025-04-30 14:58:21.744 java[84148:2288876] +[IMKClient subclass]: chose IMKClient_Modern
2025-04-30 14:58:21.744 java[84148:2288876] +[IMKInputSession subclass]: chose IMKInputSession_Modern
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Depending on your platform, you may see a little bit of “helpful” JVM output indicating that the graphics subsystem has been loaded.&lt;/p&gt;

&lt;p&gt;Let’s see what we just made!&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/barchart.png&quot; alt=&quot;A bar chart generated by JRuby and JFreeChart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Just a few lines of code and we’re done! Such fun!&lt;/p&gt;

&lt;h3 id=&quot;everyone-loves-pie&quot;&gt;Everyone loves pie!&lt;/h3&gt;

&lt;p&gt;The Java integration walkthrough and bar chart example above should whet your appetite for doing cool things with JRuby, but I include a &lt;a href=&quot;https://github.com/headius/jruby-charts/blob/master/examples/piechart.rb&quot;&gt;pie chart example&lt;/a&gt; here to show a few differences:&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;# Use jar-dependencies, included with JRuby, to load JFreeChart&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;require&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jar-dependencies&apos;&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;require_jar&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;org.jfree&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;jfreechart&apos;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1.5.5&apos;&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create an empty PieDataset&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;general&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;DefaultPieDataset&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add values to the dataset&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fun&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.45&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;1&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Useful&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.25&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Cool&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.15&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;3&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Enterprisey&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.10&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;insert_value&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;4&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Exciting&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.5&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create a pie chart with default settings&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_chart&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;org&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;jfree&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;chart&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ChartFactory&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_pie_chart&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Why JRuby?&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pie_data&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Anti-alias the chart to look a bit cleaner&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;anti_alias&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Access the actual PiePlot to tweak additional settings&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_plot&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pie_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;plot&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_plot&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_explode_percent&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Fun&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mf&quot;&gt;0.20&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Create a buffered image in memory at 500x500&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;pie_image&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pie_chart&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;create_buffered_image&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;mi&quot;&gt;500&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Write the image as a GIF to a file&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;pie_file&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;File&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;piechart.gif&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;w&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;imageio&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;ImageIO&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;write&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;pie_image&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;gif&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;pie_file&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;to_outputstream&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This example takes advantage of a few customizations provided by JFreeChart:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;The edges of the pie are set to anti-alias for a cleaner look (&lt;code class=&quot;highlighter-rouge&quot;&gt;pie_chart.anti_alias = true&lt;/code&gt;, which calls &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/org/jfree/chart/JFreeChart.html#setAntiAlias(boolean)&quot;&gt;setAntiAlias&lt;/a&gt;).&lt;/li&gt;
  &lt;li&gt;We access the actual &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/org/jfree/chart/plot/PiePlot.html&quot;&gt;PiePlot&lt;/a&gt; object to &lt;a href=&quot;https://www.jfree.org/jfreechart/javadoc/org/jfree/chart/plot/PiePlot.html#setExplodePercent(K,double)&quot;&gt;“explode”&lt;/a&gt; one of the elements out of the pie.&lt;/li&gt;
  &lt;li&gt;Instead of a PNG, we output a GIF, just because. The standard &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.desktop/javax/imageio/package-summary.html&quot;&gt;Java ImageIO support&lt;/a&gt; can handle BMP, GIF, JPEG, PNG, TIFF, and WBMP, and there’s third-party support for everything else.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And here’s the resulting pie chart:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/piechart.gif&quot; alt=&quot;A pie chart generated by JRuby and JFreeChart&quot; /&gt;&lt;/p&gt;

&lt;p&gt;It’s as easy as pie!&lt;/p&gt;

&lt;h2 id=&quot;your-turn&quot;&gt;Your Turn&lt;/h2&gt;

&lt;p&gt;In this post, you learned the following:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Basics of Java integration in JRuby&lt;/li&gt;
  &lt;li&gt;How to fetch and load Java libraries from Maven&lt;/li&gt;
  &lt;li&gt;A few simple ways to create charts with JFreeChart&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We didn’t have to write a single line of Java code, and we didn’t have to call out to any nasty C, Python, or JavaScript libraries. The code you see and the libraries we loaded all run in the same JVM process alongside your Ruby code, and can be easily deployed to any system with a JDK. It’s really that simple!&lt;/p&gt;

&lt;p&gt;JFreeChart is just one charting library out of many in the Java ecosystem, and there’s thousands of other useful libraries you can start using with JRuby today. Need to generate PDFs or Office documents? Try &lt;a href=&quot;https://github.com/LibrePDF/OpenPDF&quot;&gt;OpenPDF&lt;/a&gt; or &lt;a href=&quot;https://poi.apache.org/&quot;&gt;Apache Poi&lt;/a&gt;. Need to integrate with unusual databases? &lt;a href=&quot;https://docs.oracle.com/en/java/javase/21/docs/api/java.sql/java/sql/package-summary.html&quot;&gt;JDBC&lt;/a&gt; has you covered with a standard API. Want to deploy a single binary for your entire application? JRuby’s &lt;a href=&quot;https://github.com/jruby/warbler&quot;&gt;Warbler&lt;/a&gt; project allows you to bundle everything up as a single jar file.&lt;/p&gt;

&lt;p&gt;Ruby faces many challenges these days, and we’re solving them one at a time with JRuby and the JVM. I hope you will experiment with JRuby yourself and create something beautiful!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1kbqh5l/creating_beautiful_charts_with_jruby_and/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">JRuby 10, Part 1: What’s New</title><link href="https://headius.com/blog/jruby-10-part-one-whats-new/" rel="alternate" type="text/html" title="JRuby 10, Part 1: What&apos;s New" /><published>2025-04-09T00:00:00+00:00</published><updated>2025-04-09T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-04-02-jruby-10-part-one-whats-new.md</id><content type="html" xml:base="https://headius.com/blog/jruby-10-part-one-whats-new/">&lt;p&gt;I am very excited to introduce you to JRuby 10, our biggest leap forward since “JRuby 9000” was released almost a decade ago.&lt;/p&gt;

&lt;p&gt;With up-to-date Ruby compatibility, support for modern JVM features, and a big cleanup of internal code and external APIs, we believe this is our most important release ever. This article will provide a first look at these improvements and help you get started on your JRuby 10 journey.&lt;/p&gt;

&lt;h2 id=&quot;moving-forward&quot;&gt;Moving Forward&lt;/h2&gt;

&lt;p&gt;With such a long time since our last major release, we decided JRuby 10 had to make some big moves. As a result, this is the most up-to-date and powerful JRuby release we’ve ever put together. Here’s a few of the major upgrades you’ll see when you move to JRuby 10.&lt;/p&gt;

&lt;h3 id=&quot;compatibility-jump-to-ruby-34&quot;&gt;Compatibility jump to Ruby 3.4&lt;/h3&gt;

&lt;p&gt;It’s been over three years since we released &lt;a href=&quot;https://www.jruby.org/2022/11/23/jruby-9-4-0-0.html&quot;&gt;JRuby 9.4&lt;/a&gt; with Ruby 3.1 compatibility, so naturally a version update was in order. Last year we decided it made the most sense for us to target Ruby 3.4, since it would be released late in 2024 around the time we hoped to wrap up JRuby 10.&lt;/p&gt;

&lt;p&gt;That meant implementing &lt;a href=&quot;https://github.com/jruby/jruby/issues/7517&quot;&gt;Ruby 3.2&lt;/a&gt;, &lt;a href=&quot;https://github.com/jruby/jruby/issues/8029&quot;&gt;3.3&lt;/a&gt;, &lt;em&gt;and&lt;/em&gt; &lt;a href=&quot;https://github.com/jruby/jruby/issues/8395&quot;&gt;3.4&lt;/a&gt; features, and getting literally thousands of new tests and specs passing.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;CRuby core class test results&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;JRuby 9.4 running Ruby 3.1 tests:

4888 tests, 1859373 assertions, 0 failures, 0 errors, 22 skips

JRuby 10 running Ruby 3.4 tests:

5461 tests, 1903359 assertions, 0 failures, 0 errors, 43 skips
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Over the past month, we merged our JRuby 10 development branch back to main, and just this week we finally went &lt;a href=&quot;https://github.com/jruby/jruby/actions&quot;&gt;“green” in CI&lt;/a&gt; after hundreds of hours of work. We’re confident this is the most compatible a JRuby “dot zero” release has ever been, and being current on Ruby compatibility means we’ve got more time to work on aggressive optimization plans.&lt;/p&gt;

&lt;h3 id=&quot;java-up-to-21&quot;&gt;Java up to 21&lt;/h3&gt;

&lt;p&gt;Like many projects in the Java world, we chose to maintain support for Java 8 for over a decade after it was released, due to challenging issues involving Java 9+ and the new restrictions of the Java Platform Module System. Even though we made sure JRuby supported the latest Java releases, we could only depend upon Java 8-level features. Meanwhile the JDK added powerful enhancements like native function calls (&lt;a href=&quot;https://openjdk.org/projects/panama/&quot;&gt;Project Panama&lt;/a&gt;) and lightweight threads (&lt;a href=&quot;https://openjdk.org/projects/loom/&quot;&gt;Project Loom&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For JRuby 10, we’re finally updating our minimum Java version… to the most recent “long term support” version 21. Many of the JRuby features described in this post are possible because of that move.&lt;/p&gt;

&lt;p&gt;We’ve already started to integrate the features that &lt;a href=&quot;https://openjdk.org/projects/jdk/21/&quot;&gt;Java 21&lt;/a&gt; provides, and we’re looking forward to bringing ten years of JVM enhancements to Ruby users.&lt;/p&gt;

&lt;h3 id=&quot;full-optimization-by-default&quot;&gt;Full optimization by default&lt;/h3&gt;

&lt;p&gt;Starting with Java 7, JRuby has supported optimizing Ruby code using a feature called “&lt;a href=&quot;https://wiki.openjdk.org/display/HotSpot/Method+handles+and+invokedynamic&quot;&gt;invokedynamic&lt;/a&gt;”, which allows us to teach the JVM how Ruby code works. “Indy” is absolutely critical for Ruby performance on JRuby, but it has also taken time to evolve at the JVM level. Because of the extra startup and warmup time required to use indy on older JVMs, JRuby ran by default in a “middle tier” of optimization, using indy only for simple Ruby operations and using slower inline caching techniques for heavier ones. Users had to enable indy with the JRuby flag &lt;code class=&quot;highlighter-rouge&quot;&gt;-Xcompile.invokedyamic&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;But no longer!&lt;/p&gt;

&lt;p&gt;JRuby 10 runs with &lt;a href=&quot;https://github.com/jruby/jruby/pull/8450&quot;&gt;full invokedynamic optimization&lt;/a&gt; by default. That means you’ll get the best available performance for your JRuby scripts and applications without passing any additional flags.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;JRuby default red/black performance, 9.4 vs 10&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby9.4 &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; bench/bench_red_black.rb 5
jruby 9.4.12.0 &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3.1.4&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2025-02-11 f4ab75096a OpenJDK 64-Bit Server VM 21.0.5+11-LTS on 21.0.5+11-LTS +jit &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;arm64-darwin]
1.0899240000000001
0.632956
0.604672
0.612468
0.5976049999999999
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; bench/bench_red_black.rb 5
jruby 10.0.0.0-SNAPSHOT &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;3.4.2&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; 2025-04-09 e2909f9baf OpenJDK 64-Bit Server VM 21.0.5+11-LTS on 21.0.5+11-LTS +indy +jit &lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;arm64-darwin]
1.651375
0.46118200000000004
0.363622
0.23990799999999998
0.177958
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;For development and testing environments, where aggressive optimizations are usually not helpful and just slow down command-line use, we provide other options…&lt;/p&gt;

&lt;h3 id=&quot;startup-time-improvements&quot;&gt;Startup time improvements&lt;/h3&gt;

&lt;p&gt;The number one complaint from JRuby users has always been startup time (and to a lesser extent, warmup time of large apps). With the leap to Java 21, we’ve started to leverage a few new JVM features to get JRuby apps starting up more quickly:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;OpenJDK’s “&lt;a href=&quot;https://openjdk.org/jeps/310&quot;&gt;Application Class Data Store&lt;/a&gt;” (AppCDS) allows pre-caching code and metadata during startup to reduce the cost of future commands. JRuby’s &lt;a href=&quot;https://github.com/jruby/jruby/blob/master/bin/jruby.sh&quot;&gt;main executable&lt;/a&gt; will automatically use the right AppCDS flags on Java 21+ to optimize and cache as much as possible. This has halved the startup time of a typical JRuby command, and there’s a lot more we can do to utilize this feature.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openjdk.org/projects/crac/&quot;&gt;Project CRaC&lt;/a&gt; (Coordinated Restore at Checkpoint) is an experimental JVM feature that allows users to “checkpoint” a running process and launch multiple future processes by restoring that checkpoint. There’s limitations, such as only being able to restore a single process at a time, but when CRaC works for you it can reduce startup time of even large apps to just a few milliseconds. The JRuby launcher supports CRaC with a few flags described in my first “&lt;a href=&quot;https://blog.headius.com/2024/09/jruby-on-crac-part-1-lets-get-cracking.html&quot;&gt;JRuby on CRaC&lt;/a&gt;” blog post.&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://openjdk.org/projects/leyden/&quot;&gt;Project Leyden&lt;/a&gt; is the next-generation “AppCDS”, also storing data like JIT-compiled native code and optimization profiles from previous runs. The goal of Leyden is to eventually save off everything needed to start right up with optimized code, skipping the slow early stages of execution. The JRuby Team will incorporate Leyden flags into our launcher as they become available (with preview support already in Java 24)… and JRuby users will just have to upgrade their JDK to take advantage.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These features combined with our reduced-overhead &lt;code class=&quot;highlighter-rouge&quot;&gt;--dev&lt;/code&gt; flag mean JRuby starts up faster than ever before… fast enough to take most of the pain out of command-line development.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;JRuby “hello world” startup time, 9.4 vs 10&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby9.4 &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;puts &quot;Hello, world!&quot;&apos;&lt;/span&gt;
Hello, world!
        0.99 real         1.13 user         0.10 sys
&lt;span class=&quot;o&quot;&gt;[]&lt;/span&gt; jruby &lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;puts &quot;Hello, world!&quot;&apos;&lt;/span&gt;
Hello, world!
        0.81 real         0.91 user         0.08 sys
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;getting-started&quot;&gt;Getting Started&lt;/h2&gt;

&lt;p&gt;Trying JRuby has never been easier! Here’s a quick walkthrough on getting JRuby into your development toolbox.&lt;/p&gt;

&lt;h3 id=&quot;install-a-jdk&quot;&gt;Install a JDK&lt;/h3&gt;

&lt;p&gt;The only requirement for JRuby 10 is a Java 21 or higher JDK installation. I personally like the &lt;a href=&quot;https://www.azul.com/downloads/?package=jdk#zulu&quot;&gt;OpenJDK “Zulu” builds from Azul&lt;/a&gt;, but there’s excellent and well-supported free binaries from the &lt;a href=&quot;https://adoptium.net/temurin/releases/&quot;&gt;Eclipse project&lt;/a&gt;, &lt;a href=&quot;https://docs.aws.amazon.com/corretto/latest/corretto-21-ug/downloads-list.html&quot;&gt;Amazon&lt;/a&gt;, &lt;a href=&quot;https://www.microsoft.com/openjdk&quot;&gt;Microsoft&lt;/a&gt;, and &lt;a href=&quot;https://www.oracle.com/java/technologies/downloads/&quot;&gt;Oracle&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Install the JDK in whatever way is most appropriate for your platform… usually that will mean a system-level package on Linux/BSD or an installer on MacOS and Windows. Then just point your &lt;code class=&quot;highlighter-rouge&quot;&gt;JAVA_HOME&lt;/code&gt; environment variable at the JDK and JRuby’s launcher will figure out the rest.&lt;/p&gt;

&lt;h3 id=&quot;install-jruby&quot;&gt;Install JRuby&lt;/h3&gt;

&lt;p&gt;Most Rubyists will be familiar with Ruby managers and switchers like &lt;a href=&quot;https://github.com/rbenv/rbenv&quot;&gt;rbenv&lt;/a&gt;, &lt;a href=&quot;https://rvm.io/&quot;&gt;rvm&lt;/a&gt;, or &lt;a href=&quot;https://github.com/postmodern/chruby&quot;&gt;chruby&lt;/a&gt;. JRuby 10 preview snapshots can be usually be installed as “jruby-head” or “jruby-dev”, and after our release as “jruby-10” or just “jruby”.&lt;/p&gt;

&lt;p&gt;JRuby is also a JVM-based project (“write once, run anywhere”, remember?), so &lt;a href=&quot;https://www.jruby.org/&quot;&gt;installing it yourself&lt;/a&gt; is as easy as unpacking a tarball or zip and putting the &lt;code class=&quot;highlighter-rouge&quot;&gt;bin&lt;/code&gt; dir in your &lt;code class=&quot;highlighter-rouge&quot;&gt;PATH&lt;/code&gt;. There’s no build step and no build tools needed to start using JRuby today.&lt;/p&gt;

&lt;p&gt;Users of &lt;code class=&quot;highlighter-rouge&quot;&gt;chruby&lt;/code&gt; can unpack this tarball into &lt;code class=&quot;highlighter-rouge&quot;&gt;~/.rubies&lt;/code&gt; since it does not support “head” installs.&lt;/p&gt;

&lt;h3 id=&quot;try-it-out&quot;&gt;Try it out&lt;/h3&gt;

&lt;p&gt;JRuby supports the standard &lt;code class=&quot;highlighter-rouge&quot;&gt;irb&lt;/code&gt; REPL, as well as other modern alternatives like &lt;code class=&quot;highlighter-rouge&quot;&gt;pry&lt;/code&gt;. Once you have &lt;code class=&quot;highlighter-rouge&quot;&gt;jruby -v&lt;/code&gt; working, you can &lt;code class=&quot;highlighter-rouge&quot;&gt;gem install&lt;/code&gt; your favorite tools and test them out on JRuby. It’s really that easy.&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ irb
irb(main):001&amp;gt; JRUBY_VERSION
=&amp;gt; &quot;10.0.0.0-SNAPSHOT&quot;
irb(main):002&amp;gt; RUBY_VERSION
=&amp;gt; &quot;3.4.2&quot;
irb(main):003&amp;gt; java.lang.System.get_property(&quot;java.version&quot;)
=&amp;gt; &quot;21.0.5&quot;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;riding-the-rails&quot;&gt;Riding the Rails&lt;/h2&gt;

&lt;p&gt;Ruby without Rails would be like… well I guess it would still be Ruby, but there’s no doubt Rails is still the most popular use case for Ruby. JRuby has supported Rails since the 1.x era, and we’ve worked with the Rails and JRuby community to keep up with new releases and features. JRuby represents the best way to achieve single-process multicore scaling of Rails applications, and thousands of Rails users around the world rely on it to better utilize precious computing resources.&lt;/p&gt;

&lt;p&gt;We’ll publish a post soon that describes how to get started with JRuby on Rails, all the way from generating an app to deploying on Puma or inside a Java application server.&lt;/p&gt;

&lt;h3 id=&quot;rails-compatibility&quot;&gt;Rails compatibility&lt;/h3&gt;

&lt;p&gt;Keeping up with Rails is no small feat, and with only a handful of contributors we have tended to lag a bit behind. JRuby currently has database support for ActiveRecord up to 7.1, and work is ongoing to support Rails 8. If you have an interest in helping the JRuby team update our ActiveRecord adapter (&lt;a href=&quot;https://github.com/jruby/activerecord-jdbc-adapter&quot;&gt;ActiveRecord-JDBC&lt;/a&gt;, which uses the pure-JVM Java DataBase Connectivity API), please let us know! We’re hoping to have full support for Rails 8 by this summer.&lt;/p&gt;

&lt;h3 id=&quot;generating-an-app&quot;&gt;Generating an app&lt;/h3&gt;

&lt;p&gt;The typical Rails commands should all work on JRuby exactly the same as on standard CRuby, and with our startup time improvements and the –dev flag, we’re able to run those commands faster than any past JRuby version.&lt;/p&gt;

&lt;h3 id=&quot;config-changes&quot;&gt;Config changes&lt;/h3&gt;

&lt;p&gt;Only minor configuration changes are required to run a Rails app on JRuby:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Switch to the appropriate ActiveRecord-JDBC adapter for your database, in &lt;code class=&quot;highlighter-rouge&quot;&gt;database.yml&lt;/code&gt; and in your &lt;code class=&quot;highlighter-rouge&quot;&gt;Gemfile&lt;/code&gt;.&lt;/li&gt;
  &lt;li&gt;Configure the Puma server for a number of threads based on the CPU cores available, using “2n+1” as a good rule of thumb.&lt;/li&gt;
  &lt;li&gt;Make sure there’s enough database connections in the pool for the number of Puma threads you configure.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Once you’ve generated a new app or made config changes to your existing app, just &lt;code class=&quot;highlighter-rouge&quot;&gt;bundle&lt;/code&gt; and fire it up! JRuby is a true Ruby implementation, and we work very hard to ensure Ruby libraries and applications like Rails work the same as on CRuby.&lt;/p&gt;

&lt;h2 id=&quot;integrating-with-jvm-languages&quot;&gt;Integrating with JVM Languages&lt;/h2&gt;

&lt;p&gt;Half the fun of JRuby comes from taking advantage of the Java platform. Here’s just a few examples of things you can’t do as easily or quickly on any other Ruby implementation.&lt;/p&gt;

&lt;h3 id=&quot;ruby-on-the-jvm&quot;&gt;Ruby on the JVM&lt;/h3&gt;

&lt;p&gt;We’ve always had a goal of keeping JRuby a standard “JVM language”, runnable on any Java build compatible with our minimum requirement. This means you can deploy JRuby on Linux, MacOS, and Windows, of course, but also unusual and exotic platforms like the BSDs, Solaris, and more. We also can run on any hardware supported by the JVM, which basically means any system with enough memory and at least 32 bit registers is fair game.&lt;/p&gt;

&lt;p&gt;This also means JRuby can be deployed anywhere Java applications can be deployed, alongside enterprise Java apps using Spring or Jakarta EE. Ruby and Rails developers can expand their target market to any shop that hosts JVM-based apps… which dwarfs the number of shops that would be comfortable installing the libraries and development tools necessary to run CRuby. JRuby brings the Ruby world into the enterprise, and brings enterprise opportunities to every Rubyist.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;JRuby versus Ruby running threaded “tarai” benchmark&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-text highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;$ jruby bench_ractors_tarai.rb
                      user     system      total        real
serial           21.580000   0.070000  21.650000 ( 21.395228)
threaded         32.690000   0.100000  32.790000 (  4.260730)

$ ruby3.4 --yjit bench_ractors_tarai.rb
                      user     system      total        real
serial           25.037682   0.060370  25.098052 ( 25.257134)
threaded         24.998077   0.055508  25.053585 ( 25.058869)
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;calling-into-java&quot;&gt;Calling into Java&lt;/h3&gt;

&lt;p&gt;Part of running on the JVM is being able to integrate other languages and their libraries into your JRuby apps. With JRuby, you can call Java, &lt;a href=&quot;https://www.scala-lang.org/&quot;&gt;Scala&lt;/a&gt;, &lt;a href=&quot;https://clojure.org/&quot;&gt;Clojure&lt;/a&gt;, &lt;a href=&quot;https://kotlinlang.org/&quot;&gt;Kotlin&lt;/a&gt;, and any other JVM language from Ruby with ease. Imagine bring the entire world of JVM languages and libraries to your Ruby app… what could you do when that kind of power?&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Using Java’s Swing GUI API from Ruby&lt;/em&gt;&lt;/p&gt;

&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;n&quot;&gt;java_import&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;swing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JFrame&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;JFrame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Hello Swing&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;swing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JButton&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;new&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;Klick Me!&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add_action_listener&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;do&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;|&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;javax&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;swing&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JOptionPane&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;showMessageDialog&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kp&quot;&gt;nil&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;&amp;lt;&amp;lt;~&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EOS&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;
    &amp;lt;html&amp;gt;Hello from &amp;lt;b&amp;gt;&amp;lt;u&amp;gt;JRuby&amp;lt;/u&amp;gt;&amp;lt;/b&amp;gt;.&amp;lt;br&amp;gt;
    Button &apos;&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;#{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;evt&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;getActionCommand&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;sh&quot;&gt;&apos; clicked.
&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;  EOS&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;end&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Add the button to the frame&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;get_content_pane&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;button&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;# Show frame&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;set_default_close_operation&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;JFrame&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;::&lt;/span&gt;&lt;span class=&quot;no&quot;&gt;EXIT_ON_CLOSE&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;pack&lt;/span&gt;
&lt;span class=&quot;n&quot;&gt;frame&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;visible&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;kp&quot;&gt;true&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;using-jvm-libraries&quot;&gt;Using JVM libraries&lt;/h3&gt;

&lt;p&gt;Of course in order to use more than just the standard JDK libraries, you need to be able to download and load them into your app. JRuby provides tools like &lt;a href=&quot;https://github.com/jruby/jar-dependencies&quot;&gt;jar-dependencies&lt;/a&gt; to let your JRuby gems use libraries published to Maven Central, &lt;a href=&quot;https://github.com/jruby/warbler&quot;&gt;Warbler&lt;/a&gt; to bundle your app and libraries into a single executable jar or war file (“Web ARchive”), and an enhanced &lt;code class=&quot;highlighter-rouge&quot;&gt;require&lt;/code&gt; that loads Java JAR files right into your app. It’s the easiest and most fun way to explore all that the JVM has to offer.&lt;/p&gt;

&lt;p&gt;Want to build a cross-platform desktop UI? The JVM has a half-dozen different frameworks for building GUI applications, and with JRuby-supported libraries like &lt;a href=&quot;https://github.com/AndyObtiva/glimmer&quot;&gt;Glimmer&lt;/a&gt; and &lt;a href=&quot;https://github.com/jruby/jrubyfx&quot;&gt;JRubyFX&lt;/a&gt;, you don’t have to give up Ruby to get there.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Hello World in Glimmer&lt;/em&gt;&lt;/p&gt;
&lt;div class=&quot;language-ruby highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kp&quot;&gt;include&lt;/span&gt; &lt;span class=&quot;no&quot;&gt;Glimmer&lt;/span&gt;

&lt;span class=&quot;n&quot;&gt;shell&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
  &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Glimmer&quot;&lt;/span&gt;

  &lt;span class=&quot;n&quot;&gt;label&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;text&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;Hello, World!&quot;&lt;/span&gt;
  &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;open&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;&lt;img src=&quot;/images/glimmer-hello-world.png&quot; alt=&quot;Hello World in Glimmer&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Interested in building Android apps for mobile or embedded applications? JRuby provides the &lt;a href=&quot;http://ruboto.org/&quot;&gt;Ruboto&lt;/a&gt; framework for building Android apps in Ruby without having to write a single line of Java or Kotlin.&lt;/p&gt;

&lt;p&gt;This also puts you on the leading edge of emerging technologies. As the JVM platform evolves and new features like Leyden, CRaC, &lt;a href=&quot;https://openjdk.org/jeps/448&quot;&gt;SIMD vector operations&lt;/a&gt;, &lt;a href=&quot;https://openjdk.org/projects/babylon/&quot;&gt;GPU integration&lt;/a&gt; and native function support in Panama move from experimental to standard, your apps can start using them with a simple JDK upgrade.&lt;/p&gt;

&lt;p&gt;You’ve even got a leg up on adding AI capabilities to your app; Ruby APIs for ChatGPT, Claude, Copilot and others are just starting to appear, but there’s dozens of existing JVM libraries for these LLMs and other bleeding edge AI technologies. You can start using AI in your Ruby apps today with JRuby!&lt;/p&gt;

&lt;h2 id=&quot;whats-next&quot;&gt;What’s Next?&lt;/h2&gt;

&lt;p&gt;Over the coming weeks, I’ll publish more detailed posts on each of these areas to help you get started as a new JRuby user or to upgrade your existing JRuby applications. We are very proud of the work we’ve done to bring JRuby 10 to life, and I guarantee every Ruby shop can be faster and more scalable by taking advantage of JRuby and the JVM.&lt;/p&gt;

&lt;p&gt;Ruby’s future depends on projects like JRuby and creative developers like you. Let’s show them what we can do!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/ruby/comments/1jv91vy/jruby_10_part_1_whats_new/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry><entry><title type="html">Boosting JRuby Startup with AppCDS and AOT caching</title><link href="https://headius.com/blog/boosting-jruby-startup-with-appcds-and-aotcache/" rel="alternate" type="text/html" title="Boosting JRuby Startup with AppCDS and AOT caching" /><published>2025-02-13T00:00:00+00:00</published><updated>2025-02-13T00:00:00+00:00</updated><id>repo://posts.collection/_posts/2025-03-13-boosting-jruby-startup-with-appcds-and-aotcache.md</id><content type="html" xml:base="https://headius.com/blog/boosting-jruby-startup-with-appcds-and-aotcache/">&lt;p&gt;Hello again friends! Today I’ll show how you can improve JRuby startup right today, as well as a sneak preview of some exciting new developments in this area!&lt;/p&gt;

&lt;h2 id=&quot;application-class-data-store-appcds&quot;&gt;Application Class Data Store (AppCDS)&lt;/h2&gt;

&lt;p&gt;I’m still ramping up my blogging efforts, but I wanted to share the results of recent experiments using JRuby with Application Class Data Store and the new AOTCache feature being previewed in JDK 24&lt;/p&gt;

&lt;p&gt;Around the times of Java 1.7 or 1.8, teams at Sun (and then Oracle) started shipping a JDK feature called &lt;a href=&quot;https://docs.oracle.com/javase/8/docs/technotes/guides/vm/class-data-sharing.html&quot;&gt;Class Data Sharing&lt;/a&gt; (CDS). This was a new, specialized cache file that could be used to “pre-load” much of the runtime metadata from starting a JVM-based application. Initially, this only included the JDK’s own class metadata, but by JDK 10 a new version of the feature was made available: &lt;a href=&quot;https://openjdk.org/jeps/310&quot;&gt;Application CDS&lt;/a&gt;, basically a Class Data Store for your own applications.&lt;/p&gt;

&lt;p&gt;We have experimented with this feature in JRuby over the years, but the user experience has been cumbersome and the file can get stale if you reinstall JRuby or switch to a different JDK. That appears to be improving with recent JDK releases.&lt;/p&gt;

&lt;h3 id=&quot;automatic-cds-with-jruby-10&quot;&gt;Automatic CDS with JRuby 10&lt;/h3&gt;

&lt;p&gt;In JDK 21, we can now request that the AppCDS archive be generated automatically, and then use it each time we start up. Here’s JRuby without any help from AppCDS:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.35s user 0.16s system 165% cpu 1.518 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.73s user 0.21s system 183% cpu 1.603 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.37s user 0.14s system 180% cpu 1.385 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.14s user 0.10s system 118% cpu 1.045 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.19s user 0.10s system 120% cpu 1.067 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.17s user 0.11s system 123% cpu 1.034 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;And JRuby with experimental “automatic” CDS support:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.81s user 0.11s system 184% cpu 1.044 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.95s user 0.12s system 192% cpu 1.078 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  1.80s user 0.11s system 188% cpu 1.011 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.93s user 0.09s system 121% cpu 0.837 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.95s user 0.09s system 119% cpu 0.878 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.94s user 0.09s system 121% cpu 0.844 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s a huge improvement you’ll get for free on JRuby 10! But today I also want to look at the new preview of AOT caching (Ahead Of Time data and code caching) in JDK24.&lt;/p&gt;

&lt;h2 id=&quot;aot-caching-from-project-leyden&quot;&gt;AOT Caching from Project Leyden&lt;/h2&gt;

&lt;p&gt;OpenJDK’s &lt;a href=&quot;https://openjdk.org/projects/leyden/&quot;&gt;Project Leyden&lt;/a&gt; has been working for the past several years on bringing AOT (Ahead-of-Time compilation) to the JVM without the limitations of a closed-world system like GraalVM’s Native Image. The general feature is being called the “AOT cache”, but there’s a lot to unpack here.&lt;/p&gt;

&lt;p&gt;AOT caching is similar to AppCDS in that it caches class files and their metadata, but it will eventually include optimization artifacts such as:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Code profiles from previous runs, so we can jump into optimization earlier;&lt;/li&gt;
  &lt;li&gt;Native code produced by the JIT for hot classes and methods during a training run;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These efforts are starting to bear fruit, and JDK24 includes the first version of AOT caching: &lt;a href=&quot;https://openjdk.org/jeps/483&quot;&gt;Ahead-of-Time Class Loading and Linking&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;How does this early version of AOT caching stack up against AppCDS?&lt;/p&gt;

&lt;h3 id=&quot;jruby-and-jdk24-aotcache&quot;&gt;JRuby and JDK24 AOTCache&lt;/h3&gt;

&lt;p&gt;So given our numbers above for JRuby startup, let’s try JRuby with JDK24’s AOT caching.&lt;/p&gt;

&lt;p&gt;First we need to run JDK24 with AOT caching flags that record training data. In this case, I make JRuby start up additional instances of itself repeatedly in a loop. (The &lt;code class=&quot;highlighter-rouge&quot;&gt;--nocache&lt;/code&gt; flag here disables automatic AppCDS)&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTMode&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;record &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTConfiguration&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aotconf &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;1000.times { r = org.jruby.Ruby.newInstance; r.evalScriptlet(%[require &quot;rubygems&quot;]); r.tearDown }&apos;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTMode&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;create &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTConfiguration&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aotconf &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; &lt;span class=&quot;s1&quot;&gt;&apos;100.times { org.jruby.Ruby.newInstance.tearDown }&apos;&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;2.506s][warning][cds] java.lang.ClassNotFoundException: DashE
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;2.506s][warning][cds] Preload Warning: Cannot find DashE
&lt;span class=&quot;o&quot;&gt;[&lt;/span&gt;2.809s][warning][cds] Skipping jdk/internal/event/Event: JFR event class
AOTCache creation is &lt;span class=&quot;nb&quot;&gt;complete&lt;/span&gt;: lib/jruby.aot
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;A few warnings later and we have an AOT cache of JRuby startup code. Here’s the same baseline &lt;code class=&quot;highlighter-rouge&quot;&gt;-e 1&lt;/code&gt; calls from above, but this time using the AOT cache:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.73s user 0.17s system 277% cpu 1.047 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.87s user 0.16s system 286% cpu 1.058 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  2.82s user 0.14s system 279% cpu 1.057 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.84s user 0.08s system 126% cpu 0.732 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.85s user 0.07s system 127% cpu 0.722 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--nocache&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-J-XX&lt;/span&gt;:AOTCache&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;lib/jruby.aot &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.85s user 0.07s system 127% cpu 0.722 total
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;We have a new winner for JRuby startup time! (ignoring more restricted options like &lt;a href=&quot;https://blog.headius.com/2024/09/jruby-on-crac-part-1-lets-get-cracking.html&quot;&gt;JRuby on CRaC&lt;/a&gt;, which will see a &lt;strong&gt;Part 2&lt;/strong&gt; post real soon!)&lt;/p&gt;

&lt;h2 id=&quot;how-can-i-use-this-today&quot;&gt;How Can I Use This Today?&lt;/h2&gt;

&lt;p&gt;JRuby 9.4 users on Java 11 or higher can use AppCDS today, and with JDK24 you can also experiment with AOT caching.&lt;/p&gt;

&lt;h3 id=&quot;jruby-94-and-appcds&quot;&gt;JRuby 9.4 and AppCDS&lt;/h3&gt;

&lt;p&gt;All you need to try AppCDS on JRuby is to generate the AppCDS archive (&lt;code class=&quot;highlighter-rouge&quot;&gt;lib/jruby.jsa&lt;/code&gt;):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Make sure you’re running JDK11 or higher.&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;gem install jruby-startup&lt;/code&gt;&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;highlighter-rouge&quot;&gt;generate-appcds&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;gem &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;jruby-startup
Successfully installed jruby-startup-0.0.6
1 gem installed
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;generate-appcds
...
&lt;span class=&quot;k&quot;&gt;***&lt;/span&gt; Success!

JRuby versions 9.2.1 or higher should detect /Users/headius/work/jruby/lib/jruby.jsa and use it automatically.
For versions of JRuby 9.2 or earlier, &lt;span class=&quot;nb&quot;&gt;set &lt;/span&gt;the following environment variables:

&lt;span class=&quot;nv&quot;&gt;VERIFY_JRUBY&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;1
&lt;span class=&quot;nv&quot;&gt;JAVA_OPTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-XX:G1HeapRegionSize=2m -XX:SharedArchiveFile=/Users/headius/work/jruby/lib/jruby.jsa&quot;&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.92s user 0.09s system 118% cpu 0.807 total
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;time &lt;/span&gt;jruby &lt;span class=&quot;nt&quot;&gt;--dev&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-e&lt;/span&gt; 1
  0.93s user 0.09s system 121% cpu 0.838 total
...
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;highlighter-rouge&quot;&gt;jruby.jsa&lt;/code&gt; file is automatically passed to the JVM when you start JRuby, boosting startup as seen at the top of this post. Newer JDKs will see a bigger improvement!&lt;/p&gt;

&lt;h3 id=&quot;jruby-94-and-jdk24-aot-caching&quot;&gt;JRuby 9.4 and JDK24 AOT Caching&lt;/h3&gt;

&lt;p&gt;You’ll need to install a build of JDK24, which just dropped a &lt;a href=&quot;https://jdk.java.net/24/&quot;&gt;release candidate&lt;/a&gt; last week (Feb 6).&lt;/p&gt;

&lt;p&gt;The same flags shown above will work with JRuby 9.4, but there’s no &lt;code class=&quot;highlighter-rouge&quot;&gt;--nocache&lt;/code&gt; flag to disable automatic CDS. Just make sure you delete the &lt;code class=&quot;highlighter-rouge&quot;&gt;lib/jruby.jsa&lt;/code&gt; file before playing with AOT caching on JRuby 9.4.&lt;/p&gt;

&lt;p&gt;Have fun, and let us know how it goes!&lt;/p&gt;

&lt;h2 id=&quot;join-the-discussion-on-reddit&quot;&gt;&lt;a href=&quot;https://www.reddit.com/r/javavirtualmachine/comments/1ioncqe/boosting_jruby_startup_with_appcds_and_aot_caching/&quot;&gt;Join the discussion on Reddit!&lt;/a&gt;&lt;/h2&gt;</content><author><name>Charles Oliver Nutter</name></author></entry></feed>