<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Bruno Borges</title>
    <description>The latest articles on DEV Community by Bruno Borges (@brunoborges).</description>
    <link>https://dev.to/brunoborges</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F48223%2F61cf5f1e-516d-44f6-8895-8f5743706993.jpeg</url>
      <title>DEV Community: Bruno Borges</title>
      <link>https://dev.to/brunoborges</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/brunoborges"/>
    <language>en</language>
    <item>
      <title>How I Automated Weekly Twitter/X Posts With GitHub Actions</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Tue, 14 Apr 2026 16:06:59 +0000</pubDate>
      <link>https://dev.to/brunoborges/how-i-automated-weekly-twitterx-posts-with-github-actions-53l8</link>
      <guid>https://dev.to/brunoborges/how-i-automated-weekly-twitterx-posts-with-github-actions-53l8</guid>
      <description>&lt;p&gt;Every Monday at 10 AM Eastern, &lt;a href="https://x.com/javaevolved" rel="noopener noreferrer"&gt;@javaevolved&lt;/a&gt; now tweets a modern Java pattern — automatically. No manual steps, no third-party services, no cron servers. Just a GitHub Actions workflow, a couple of JBang scripts, and the Twitter API.&lt;/p&gt;

&lt;p&gt;Here's how it works, and how you can do the same for your own project.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://javaevolved.github.io" rel="noopener noreferrer"&gt;Java Evolved&lt;/a&gt; is a static site with 113 code patterns showing the old way vs. the modern way to write Java. Each pattern has a title, summary, old/modern approach labels, JDK version, and a link to its detail page.&lt;/p&gt;

&lt;p&gt;I wanted to promote each pattern on Twitter — one per week, in random order, cycling forever. The requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Fully automated&lt;/strong&gt; — no manual tweeting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-drafted tweets&lt;/strong&gt; — reviewable and editable before they go live&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Resumable&lt;/strong&gt; — survives failures, picks up where it left off&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Auditable&lt;/strong&gt; — git history shows what was posted and when&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zero infrastructure&lt;/strong&gt; — no servers, no databases, no paid services&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Architecture
&lt;/h2&gt;

&lt;p&gt;The system has three components:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;content/*.yaml → [Queue Generator] → social/queue.txt
                                    → social/tweets.yaml
                                    → social/state.yaml

social/* → [Post Script] → Twitter API v2 → updated state

GitHub Actions cron → runs Post Script every Monday
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Everything lives in the repository. State is tracked via committed files, not external databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component 1: The Queue &amp;amp; Tweet Generator
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;html-generators/generatesocialqueue.java&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;A JBang script that scans all content YAML files, shuffles them randomly, and produces three files:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;social/queue.txt&lt;/code&gt;&lt;/strong&gt; — the posting order, one &lt;code&gt;category/slug&lt;/code&gt; per line&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;social/tweets.yaml&lt;/code&gt;&lt;/strong&gt; — pre-drafted tweet text for each pattern&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;social/state.yaml&lt;/code&gt;&lt;/strong&gt; — a pointer tracking where we are in the queue&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The tweet template looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;☕ {title}

{summary}

{oldApproach} → {modernApproach} (JDK {jdkVersion}+)

🔗 https://javaevolved.github.io/{category}/{slug}.html

#Java #JavaEvolved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator also validates that every tweet fits within Twitter's 280-character limit. If a summary is too long, it's automatically truncated with an ellipsis. Of the 113 patterns, 12 needed truncation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Handling New Patterns
&lt;/h3&gt;

&lt;p&gt;When you re-run the generator after adding new content files, it detects new patterns and appends them to the end of the existing queue — preserving the current order and any manual tweet edits. Deleted or renamed patterns are automatically pruned.&lt;/p&gt;

&lt;p&gt;Use &lt;code&gt;--reshuffle&lt;/code&gt; to force a full reshuffle when the cycle is exhausted.&lt;/p&gt;

&lt;h2&gt;
  
  
  Component 2: The Post Script
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;html-generators/socialpost.java&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Another JBang script that:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Reads the current index from &lt;code&gt;social/state.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Looks up the next pattern key in &lt;code&gt;social/queue.txt&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Retrieves the pre-drafted tweet text from &lt;code&gt;social/tweets.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Posts to the Twitter API v2 using OAuth 1.0a&lt;/li&gt;
&lt;li&gt;Updates the state file &lt;strong&gt;only after confirmed API success&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  OAuth 1.0a in Java
&lt;/h3&gt;

&lt;p&gt;I initially planned to use a shell script with &lt;code&gt;curl&lt;/code&gt; and &lt;code&gt;openssl&lt;/code&gt; for OAuth signing. That turned out to be a bad idea — percent-encoding, signature base strings, and nonce generation are error-prone in Bash.&lt;/p&gt;

&lt;p&gt;Instead, the post script uses Java's built-in &lt;code&gt;java.net.http.HttpClient&lt;/code&gt; and &lt;code&gt;javax.crypto.Mac&lt;/code&gt; for HMAC-SHA1 signing. Here's the core of the OAuth signature:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Build signature base string&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;paramString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;oauthParams&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;entrySet&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stream&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;map&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getKey&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"="&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;e&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getValue&lt;/span&gt;&lt;span class="o"&gt;()))&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;collect&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Collectors&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;joining&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;baseString&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;method&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;url&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;paramString&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signingKey&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;consumerSecret&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"&amp;amp;"&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;percentEncode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tokenSecret&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// HMAC-SHA1&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;mac&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;javax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getInstance&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"HmacSHA1"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;init&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="n"&gt;javax&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;crypto&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;spec&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SecretKeySpec&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;signingKey&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;),&lt;/span&gt; &lt;span class="s"&gt;"HmacSHA1"&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;signature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Base64&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getEncoder&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;encodeToString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;mac&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;doFinal&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;baseString&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getBytes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;UTF_8&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The script also supports &lt;code&gt;--dry-run&lt;/code&gt; to preview the next tweet without posting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;jbang html-generators/socialpost.java &lt;span class="nt"&gt;--dry-run&lt;/span&gt;

Queue has 113 entries, current index: 1
Pattern: language/guarded-patterns
Tweet &lt;span class="o"&gt;(&lt;/span&gt;200 chars&lt;span class="o"&gt;)&lt;/span&gt;:
&lt;span class="nt"&gt;---&lt;/span&gt;
☕ Guarded patterns with when

Add conditions to pattern cases using when guards.

Nested &lt;span class="k"&gt;if&lt;/span&gt; → when Clause &lt;span class="o"&gt;(&lt;/span&gt;JDK 21+&lt;span class="o"&gt;)&lt;/span&gt;

🔗 https://javaevolved.github.io/language/guarded-patterns.html

&lt;span class="c"&gt;#Java #JavaEvolved&lt;/span&gt;
&lt;span class="nt"&gt;---&lt;/span&gt;
DRY RUN — not posting.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Component 3: The GitHub Actions Workflow
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;File:&lt;/strong&gt; &lt;code&gt;.github/workflows/social-post.yml&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Weekly Social Post&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;schedule&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;cron&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;14&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;1'&lt;/span&gt;  &lt;span class="c1"&gt;# Every Monday at 14:00 UTC (10 AM ET)&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;       &lt;span class="c1"&gt;# Manual trigger&lt;/span&gt;

&lt;span class="na"&gt;concurrency&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;group&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;social-post&lt;/span&gt;
  &lt;span class="na"&gt;cancel-in-progress&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;post&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v6&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v5&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jbangdev/setup-jbang@main&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Post to Twitter&lt;/span&gt;
        &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;TWITTER_CONSUMER_KEY&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.TWITTER_APP_CONSUMER_KEY }}&lt;/span&gt;
          &lt;span class="c1"&gt;# ... other secrets&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;jbang html-generators/socialpost.java&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Commit updated state&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;git add social/state.yaml&lt;/span&gt;
          &lt;span class="s"&gt;git commit -m "chore: update social post state [skip ci]"&lt;/span&gt;
          &lt;span class="s"&gt;git pull --rebase&lt;/span&gt;
          &lt;span class="s"&gt;git push&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A few details worth noting:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;concurrency&lt;/code&gt; group&lt;/strong&gt; prevents double-posts if a manual dispatch overlaps with the cron&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;[skip ci]&lt;/code&gt;&lt;/strong&gt; in the commit message prevents the state update from triggering other workflows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Social files live in &lt;code&gt;social/&lt;/code&gt;&lt;/strong&gt;, not &lt;code&gt;content/&lt;/code&gt; — the deploy workflow watches &lt;code&gt;content/**&lt;/code&gt;, so keeping state separate avoids unnecessary site rebuilds&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;git pull --rebase&lt;/code&gt;&lt;/strong&gt; before push handles the rare case where another commit lands between checkout and push&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  The Economics
&lt;/h2&gt;

&lt;p&gt;Twitter's API pricing means each tweet costs about $0.01. With 113 patterns posted weekly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;~$0.52/year&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~2.2 years&lt;/strong&gt; of unique content per cycle before reshuffling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That's essentially free for a perpetual social media presence.&lt;/p&gt;

&lt;h2&gt;
  
  
  Built in a Single Copilot CLI Session
&lt;/h2&gt;

&lt;p&gt;This entire feature — the queue generator, the post script, the GitHub Actions workflow, the tweet drafts, the documentation updates — was built in a single interactive session with &lt;a href="https://githubnext.com/projects/copilot-cli" rel="noopener noreferrer"&gt;GitHub Copilot CLI&lt;/a&gt;. From planning to the first live tweet, everything happened in the terminal.&lt;/p&gt;

&lt;p&gt;The session included planning the architecture, getting a rubber-duck critique (which caught several issues — like using shell for OAuth signing and putting state files where they'd trigger deploys), implementing all three components, testing locally with &lt;code&gt;--dry-run&lt;/code&gt;, committing, pushing, and triggering the first real tweet.&lt;/p&gt;

&lt;p&gt;You can read the full session transcript here: &lt;a href="https://gist.github.com/brunoborges/40ef1b5e9b05de279dab64e443b96a11" rel="noopener noreferrer"&gt;gist.github.com/brunoborges/40ef1b5e9b05de279dab64e443b96a11&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd Do Differently
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Add Bluesky support&lt;/strong&gt; — the AT Protocol API is simpler than Twitter's OAuth 1.0a. The architecture already supports it; just add a second API call in the post script.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Content hash tracking&lt;/strong&gt; — if a pattern's title or summary changes, the pre-drafted tweet goes stale. A hash per entry would flag which drafts need regeneration.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try It Yourself
&lt;/h2&gt;

&lt;p&gt;The entire implementation is open source at &lt;a href="https://github.com/javaevolved/javaevolved.github.io" rel="noopener noreferrer"&gt;github.com/javaevolved/javaevolved.github.io&lt;/a&gt;. You'll need:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;A Twitter Developer account with OAuth 1.0a credentials (Read + Write)&lt;/li&gt;
&lt;li&gt;Java 25+ and &lt;a href="https://www.jbang.dev/" rel="noopener noreferrer"&gt;JBang&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Content in YAML with &lt;code&gt;title&lt;/code&gt;, &lt;code&gt;summary&lt;/code&gt;, &lt;code&gt;oldApproach&lt;/code&gt;, &lt;code&gt;modernApproach&lt;/code&gt;, &lt;code&gt;jdkVersion&lt;/code&gt;, &lt;code&gt;category&lt;/code&gt;, and &lt;code&gt;slug&lt;/code&gt; fields&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Generate the queue, review the drafts, push, and let GitHub Actions handle the rest.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Follow &lt;a href="https://x.com/javaevolved" rel="noopener noreferrer"&gt;@javaevolved&lt;/a&gt; for a new modern Java pattern every Monday.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>github</category>
      <category>githubactions</category>
    </item>
    <item>
      <title>Translating a Website into 8 Languages with AI Agents in One Night</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Thu, 26 Feb 2026 05:58:07 +0000</pubDate>
      <link>https://dev.to/brunoborges/translating-a-website-into-8-languages-with-ai-agents-in-one-night-50k7</link>
      <guid>https://dev.to/brunoborges/translating-a-website-into-8-languages-with-ai-agents-in-one-night-50k7</guid>
      <description>&lt;h2&gt;
  
  
  How I used Claude Sonnet 4.6 and fleets of GitHub Copilot Coding Agents to internationalize java.evolved — from spec to deployment
&lt;/h2&gt;




&lt;p&gt;&lt;a href="https://javaevolved.github.io" rel="noopener noreferrer"&gt;java.evolved&lt;/a&gt; is a static site I built to showcase modern Java patterns side-by-side with their legacy equivalents. 112 patterns across 11 categories — language, collections, streams, concurrency, and more — each with code comparisons, explanations, and curated documentation links. All generated from YAML content files by a JBang-powered Java build script.&lt;/p&gt;

&lt;p&gt;By the end of February 25, the entire site was English-only. By the morning of February 26, it was available in &lt;strong&gt;9 languages&lt;/strong&gt; — English, German, Spanish, Portuguese (Brazil), Simplified Chinese, Arabic, French, Japanese, and Korean — with full RTL support for Arabic. The total human effort was a few hours of prompting, reviewing PRs, and filing one bug.&lt;/p&gt;

&lt;p&gt;This is the story of that experiment.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Architecture Decision: Let the AI Draft the Spec
&lt;/h2&gt;

&lt;p&gt;The first step wasn't writing code. It was writing a specification.&lt;/p&gt;

&lt;p&gt;I opened &lt;a href="https://github.com/javaevolved/javaevolved.github.io/issues/74" rel="noopener noreferrer"&gt;issue #74&lt;/a&gt; — "Plan architectural change for i18n" — and assigned it to a Copilot Coding Agent. The prompt was simple: propose an architectural plan for internationalizing the website, considering the existing static-site structure.&lt;/p&gt;

&lt;p&gt;The agent (&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/75" rel="noopener noreferrer"&gt;PR #75&lt;/a&gt;) came back with a comprehensive i18n specification that addressed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Two-layer translation model:&lt;/strong&gt; UI strings (labels, nav, footer) separated from content translations (pattern titles, explanations, summaries)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Partial translation files:&lt;/strong&gt; Translation files contain &lt;em&gt;only&lt;/em&gt; translatable fields. Structural data (code snippets, navigation links, metadata) always comes from the English source of truth&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Graceful fallback:&lt;/strong&gt; Missing translations fall back to English with a build-time warning — no page is ever blank&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Locale registry:&lt;/strong&gt; A simple &lt;code&gt;locales.properties&lt;/code&gt; file drives the entire build pipeline and language selector&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;AI-friendly design:&lt;/strong&gt; The architecture was explicitly designed so that an AI receives the full English content and returns a partial translation file — no field-filtering logic needed in the prompt&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This last point proved prescient. The clean separation between "what changes per locale" and "what stays constant" meant each translation task was self-contained and parallelizable.&lt;/p&gt;

&lt;p&gt;After iterating through review comments over 5 days (the original PR had 12 comments of back-and-forth), the spec was merged on February 25.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 1: Building the Infrastructure
&lt;/h2&gt;

&lt;p&gt;With the spec in hand, I coordinated an agent locally with Coilot CLI to implement the core i18n infrastructure in &lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/83" rel="noopener noreferrer"&gt;PR #83&lt;/a&gt; — the generator changes that made everything locale-aware:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;code&gt;generate.java&lt;/code&gt; build script learned to iterate all locales from &lt;code&gt;locales.properties&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Template tokens like &lt;code&gt;{{sections.codeComparison}}&lt;/code&gt; replaced hard-coded English strings&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;hreflang&lt;/code&gt; meta tags, a locale picker dropdown, and client-side locale detection were added&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;resolveSnippet&lt;/code&gt; function loads translated content overlaid onto the English base, falling back gracefully when a translation doesn't exist&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was the only PR that required significant intervention. Everything after it was delegation.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 2: The First Translations (Spanish + Portuguese)
&lt;/h2&gt;

&lt;p&gt;The first real translation — Spanish — came in &lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/84" rel="noopener noreferrer"&gt;PR #84&lt;/a&gt;. This PR also migrated the English content files from JSON to YAML for improved readability — a format change that the AI handled naturally since the generator already supported multiple formats.&lt;/p&gt;

&lt;p&gt;Brazilian Portuguese followed immediately in &lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/85" rel="noopener noreferrer"&gt;PR #85&lt;/a&gt;, completing all 112 pattern files (going from 3/112 translated to 112/112 — 100% coverage). These first two translations validated the architecture and surfaced a CI issue: the deploy workflow's path triggers didn't match YAML files or the &lt;code&gt;translations/&lt;/code&gt; directory. A quick fix in &lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/86" rel="noopener noreferrer"&gt;PR #86&lt;/a&gt; resolved that.&lt;/p&gt;




&lt;h2&gt;
  
  
  Phase 3: The Fleet — 6 Languages in Parallel
&lt;/h2&gt;

&lt;p&gt;This is where things got interesting. With the architecture proven and two complete translations validated, I launched a &lt;strong&gt;fleet of Copilot Coding Agents&lt;/strong&gt; — multiple agents working in parallel, each assigned a single language. I used the Copilot CLI &lt;code&gt;/delegate&lt;/code&gt; command to dispatch them asynchonously, all with the same prompt:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;PR&lt;/th&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Agent&lt;/th&gt;
&lt;th&gt;Time to PR&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/87" rel="noopener noreferrer"&gt;#87&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Japanese (日本語)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~36 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/88" rel="noopener noreferrer"&gt;#88&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;German (Deutsch)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~35 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/89" rel="noopener noreferrer"&gt;#89&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;French (Français)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~32 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/90" rel="noopener noreferrer"&gt;#90&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Chinese 中文 (简体)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~58 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/91" rel="noopener noreferrer"&gt;#91&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Arabic (العربية)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~53 min&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/94" rel="noopener noreferrer"&gt;#94&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Korean (한국어)&lt;/td&gt;
&lt;td&gt;Copilot&lt;/td&gt;
&lt;td&gt;~24 min&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Each agent independently:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the i18n spec and understood the file structure&lt;/li&gt;
&lt;li&gt;Created a UI strings YAML file (&lt;code&gt;translations/strings/{locale}.yaml&lt;/code&gt;) with 60+ translated keys&lt;/li&gt;
&lt;li&gt;Generated 112 content translation YAML files — one per pattern, across all 11 categories&lt;/li&gt;
&lt;li&gt;Registered the locale in &lt;code&gt;locales.properties&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Opened a PR with a detailed description of what was translated&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All six PRs were opened within a span of about 12 minutes (2:33 AM to 2:45 AM) and were all merged within the next hour. &lt;strong&gt;No translation conflicts&lt;/strong&gt;, no merge issues, no structural errors. The partial-file architecture did exactly what it was designed to do — each agent's output was isolated to its own locale directory.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Numbers
&lt;/h3&gt;

&lt;p&gt;By the time the fleet finished:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;8 locales&lt;/strong&gt; fully supported (besides English)&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;112 patterns × 8 translated locales = 896 content translation files&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 UI string files&lt;/strong&gt; with 60+ keys each&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;~1,008 generated HTML pages&lt;/strong&gt; (112 pages × 8 locales)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;8 localized search indexes&lt;/strong&gt; (&lt;code&gt;snippets.json&lt;/code&gt; per locale)&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Arabic Surprise: RTL Support
&lt;/h2&gt;

&lt;p&gt;The most impressive moment was the Arabic translation (&lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/91" rel="noopener noreferrer"&gt;PR #91&lt;/a&gt;). The agent not only translated, it also noted that Arabic is a right-to-left language and &lt;strong&gt;proactively added RTL infrastructure&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Added &lt;code&gt;dir="{{htmlDir}}"&lt;/code&gt; to the &lt;code&gt;&amp;lt;html&amp;gt;&lt;/code&gt; tag in both templates&lt;/li&gt;
&lt;li&gt;Modified the generator to resolve &lt;code&gt;htmlDir&lt;/code&gt; to &lt;code&gt;"rtl"&lt;/code&gt; for Arabic and &lt;code&gt;"ltr"&lt;/code&gt; for all other locales&lt;/li&gt;
&lt;li&gt;Updated the Python benchmark generator to pass the new token&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a genuinely thoughtful architectural change that I hadn't explicitly requested. The agent understood that Arabic support implies RTL layout and acted on it.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3itxwxcyyoksvae22m3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fw3itxwxcyyoksvae22m3.png" alt="Arabic localization on the Java Evolved website" width="800" height="604"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  The One Bug
&lt;/h3&gt;

&lt;p&gt;Of course, it wasn't perfect. When &lt;code&gt;dir="rtl"&lt;/code&gt; is applied globally, the browser flips &lt;em&gt;everything&lt;/em&gt; — including Java source code blocks and the navigation bar, which IMO should always be LTR regardless of locale. I spotted this when reviewing the deployed site and &lt;a href="https://github.com/javaevolved/javaevolved.github.io/issues/96" rel="noopener noreferrer"&gt;filed issue #96&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;A Copilot Coding Agent &lt;a href="https://github.com/javaevolved/javaevolved.github.io/pull/97" rel="noopener noreferrer"&gt;fixed it in PR #97&lt;/a&gt; within minutes, adding targeted CSS overrides:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight css"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;nav&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.nav-inner&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ltr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;pre&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nt"&gt;code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.code-text&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.card-code&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nt"&gt;dir&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;"rtl"&lt;/span&gt;&lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="nc"&gt;.compare-code&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nl"&gt;direction&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;ltr&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;text-align&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;left&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nl"&gt;unicode-bidi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;embed&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Arabic prose continues to render RTL. Java code stays LTR. One bug, one issue, one PR, fixed in minutes. That's the entire human intervention required for 8-language support.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why It Worked: Architecture as Force Multiplier
&lt;/h2&gt;

&lt;p&gt;This experiment succeeded because of deliberate architectural decisions, not just because of a magic AI prompt. A few design choices made parallelized AI translation almost trivially easy:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Source of Truth Separation
&lt;/h3&gt;

&lt;p&gt;Content files contain both translatable text and structural data (code, navigation, metadata). But translation files contain &lt;em&gt;only&lt;/em&gt; the translatable fields. The generator overlays translations onto the English base at build time. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI agents can't accidentally modify code snippets or break navigation&lt;/li&gt;
&lt;li&gt;Translation files are small and self-contained&lt;/li&gt;
&lt;li&gt;Structural changes to the English source propagate immediately without needing to update translations&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Partial File Fallback
&lt;/h3&gt;

&lt;p&gt;If a pattern has no translation file for a locale, the site shows the English content with an "untranslated" banner. This means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Partial translations are always valid&lt;/li&gt;
&lt;li&gt;New patterns added in English are immediately visible in all locales&lt;/li&gt;
&lt;li&gt;There's no "all or nothing" pressure on translation completeness&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  3. Convention Over Configuration
&lt;/h3&gt;

&lt;p&gt;Each locale follows the same directory structure. Adding a new language is a checklist:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Add a line to &lt;code&gt;locales.properties&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create &lt;code&gt;translations/strings/{locale}.yaml&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Create files under &lt;code&gt;translations/content/{locale}/&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;No code changes needed in the generator. No template modifications. The AI spec anticipated this workflow, and the agents followed it exactly.&lt;/p&gt;




&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;h3&gt;
  
  
  What Went Well
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Spec-first approach:&lt;/strong&gt; Having the AI draft the specification before any implementation meant the architecture was explicitly designed for AI-driven translation from day one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fleet parallelism:&lt;/strong&gt; Six language translations running simultaneously with zero conflicts demonstrates that well-isolated task boundaries enable effortless parallelization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Proactive problem-solving:&lt;/strong&gt; The Arabic agent identifying and implementing RTL support without being explicitly asked shows that LLMs can reason about the downstream implications of their translations&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Technical term preservation:&lt;/strong&gt; Across all 9 languages, Java API names, annotations, JDK versions, and code examples were correctly left in English — the agents understood what should and shouldn't be translated in a technical context&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  What Required Human Intervention
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;One bug:&lt;/strong&gt; The RTL global flip affecting code blocks. This is a classic CSS gotcha that even experienced developers might miss on first pass. Filed as an issue, fixed by an agent in minutes&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CI pipeline fixes:&lt;/strong&gt; The deploy workflow needed path trigger updates after the JSON→YAML migration. A routine ops fix&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review and merge:&lt;/strong&gt; Each PR needed a human to review and merge. The content quality was consistently good, but spot-checking translations in languages you speak is still important&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Timeline
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Time (UTC)&lt;/th&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Feb 20, 20:33&lt;/td&gt;
&lt;td&gt;Issue #74 opened — "Plan architectural change for i18n"&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 20, 20:33&lt;/td&gt;
&lt;td&gt;PR #75 opened — i18n specification (by Copilot Agent)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 25, 20:56&lt;/td&gt;
&lt;td&gt;PR #75 merged (after 5 days of procrastination, and other projects at hand... finally!)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 25, 23:49&lt;/td&gt;
&lt;td&gt;PR #83 merged — core i18n infrastructure&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 01:28&lt;/td&gt;
&lt;td&gt;PR #84 merged — Spanish translation + JSON→YAML migration&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 02:27&lt;/td&gt;
&lt;td&gt;PR #85 merged — Complete Brazilian Portuguese (112/112)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 02:29&lt;/td&gt;
&lt;td&gt;PR #86 merged — CI path trigger fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 02:33–02:45&lt;/td&gt;
&lt;td&gt;PRs #87–#91 opened — fleet of 6 language agents launched&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 03:09–03:39&lt;/td&gt;
&lt;td&gt;All fleet PRs merged (Japanese, German, French, Chinese, Arabic)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 03:51&lt;/td&gt;
&lt;td&gt;PR #94 opened — Korean (final language)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 03:59&lt;/td&gt;
&lt;td&gt;Issue #96 filed — Arabic RTL bug&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 04:12&lt;/td&gt;
&lt;td&gt;PR #97 merged — RTL fix&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Feb 26, 04:15&lt;/td&gt;
&lt;td&gt;PR #94 merged — Korean complete&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;From spec merge to 8-language site: ~7 hours.&lt;/strong&gt; From fleet launch to all 6 languages merged: &lt;strong&gt;~1 hour.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;The experiment proved a simple thesis: &lt;strong&gt;if you design your architecture for AI-driven workflows, AI agents can do remarkable things.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;I didn't build a custom translation pipeline or prompt-engineering framework. I wrote a specification. An AI agent drafted it, I refined it through code review, and then a fleet of agents executed against it. The architecture — partial files, fallback behavior, source-of-truth separation — did all the heavy lifting.&lt;/p&gt;

&lt;p&gt;The result is a Java patterns reference site available in 8 languages, with 1,008 generated pages, serving developers who speak English, German, Spanish, Portuguese, Chinese, Arabic, French, and Korean. It handles right-to-left layouts. It has localized search. Every page has proper &lt;code&gt;hreflang&lt;/code&gt; tags for SEO.&lt;/p&gt;

&lt;p&gt;And the only bug was a CSS rule that took 13 minutes from report to fix.&lt;/p&gt;

&lt;p&gt;The future isn't AI replacing developers. It's developers designing systems that let AI do what it's good at — repetitive, structured, parallelizable work — while humans focus on architecture, review, and the occasional RTL bug.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;java.evolved is open source at &lt;a href="https://github.com/javaevolved/javaevolved.github.io" rel="noopener noreferrer"&gt;github.com/javaevolved/javaevolved.github.io&lt;/a&gt;. The i18n specification lives at &lt;a href="https://github.com/javaevolved/javaevolved.github.io/blob/main/specs/i18n/i18n-spec.md" rel="noopener noreferrer"&gt;specs/i18n/i18n-spec.md&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>github</category>
      <category>java</category>
      <category>i18n</category>
    </item>
    <item>
      <title>Enabling AI Agents to Use a Real Debugger Instead of Logging</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Mon, 16 Feb 2026 21:00:39 +0000</pubDate>
      <link>https://dev.to/brunoborges/enabling-ai-agents-to-use-a-real-debugger-instead-of-logging-bep</link>
      <guid>https://dev.to/brunoborges/enabling-ai-agents-to-use-a-real-debugger-instead-of-logging-bep</guid>
      <description>&lt;p&gt;Every Java developer has been there. Something breaks, and the first instinct is to litter the code with &lt;code&gt;System.out.println("&amp;gt;&amp;gt;&amp;gt; HERE 1")&lt;/code&gt;. Then &lt;code&gt;HERE 2&lt;/code&gt;. Then &lt;code&gt;HERE 3 — value is: " + x&lt;/code&gt;. Rebuild. Rerun. Stare at the console. Repeat.&lt;/p&gt;

&lt;p&gt;We've been doing this for decades. And now, so have our AI agents.&lt;/p&gt;

&lt;p&gt;When you ask an AI coding assistant to debug a Java application, it almost always reaches for the same playbook: add logging statements, recompile, rerun, read the output, and reason about what happened. It's the &lt;code&gt;println&lt;/code&gt; debugging loop, automated — but it's still &lt;code&gt;println&lt;/code&gt; debugging.&lt;/p&gt;

&lt;p&gt;What if the agent could just... use a real debugger?&lt;/p&gt;

&lt;h2&gt;
  
  
  The JDK ships a perfectly good debugger. Nobody uses it.
&lt;/h2&gt;

&lt;p&gt;Every JDK installation since the beginning of time includes &lt;code&gt;jdb&lt;/code&gt; — the Java Debugger. It's a command-line tool that lets you set breakpoints, step through code, inspect variables, catch exceptions, and examine threads. It speaks the same JDWP protocol that IntelliJ and Eclipse use under the hood.&lt;/p&gt;

&lt;p&gt;And it's &lt;strong&gt;purely text-based&lt;/strong&gt;, which makes it a perfect tool for AI agents that operate through terminal commands.&lt;/p&gt;

&lt;p&gt;The problem is that no agent knows how to use it. Until now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent Skills: Teaching new tricks through Markdown
&lt;/h2&gt;

&lt;p&gt;Anthropic's &lt;a href="https://agentskills.io/specification" rel="noopener noreferrer"&gt;Agent Skills&lt;/a&gt; framework lets you package instructions, scripts, and reference material into a structured directory that AI agents can load dynamically. The format is simple: a &lt;code&gt;SKILL.md&lt;/code&gt; file with YAML frontmatter and Markdown instructions, plus optional helper scripts and reference docs.&lt;/p&gt;

&lt;p&gt;Think of a skill as a runbook that the agent reads just-in-time when it recognizes a relevant task. The key insight is &lt;strong&gt;progressive disclosure&lt;/strong&gt; — the agent only loads the skill's description at startup (~100 tokens), and pulls in the full instructions only when it decides the skill is needed.&lt;/p&gt;

&lt;p&gt;I decided to build one that teaches agents how to operate JDB.&lt;/p&gt;

&lt;h2&gt;
  
  
  Building the skill: a conversation with Copilot
&lt;/h2&gt;

&lt;p&gt;The entire skill was built in a &lt;a href="https://gist.github.com/brunoborges/3b2f883c62409b6ceeacd0fb5a8dc811" rel="noopener noreferrer"&gt;single conversation session&lt;/a&gt; with GitHub Copilot CLI. The process was surprisingly natural — I described what I wanted, and we iterated through research, design, implementation, and testing together.&lt;/p&gt;

&lt;p&gt;The conversation started with a simple prompt:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;"Java (the JDK) has a Debugger CLI. Let's build a skill so that AI agents can debug applications in real time."&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Copilot researched the Agent Skills specification, studied the Anthropic public skills repository for patterns, read Oracle's JDB documentation, and then produced the complete skill — all within the same session.&lt;/p&gt;

&lt;h3&gt;
  
  
  What the skill contains
&lt;/h3&gt;

&lt;p&gt;The resulting &lt;a href="https://github.com/brunoborges/jdb-debugger-skill" rel="noopener noreferrer"&gt;&lt;code&gt;jdb-debugger-skill&lt;/code&gt;&lt;/a&gt; has a clean structure:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;jdb-debugger-skill/
├── SKILL.md                        # Core instructions for the agent
├── scripts/
│   ├── jdb-launch.sh               # Launch a JVM under JDB
│   ├── jdb-attach.sh               # Attach to a running JVM
│   ├── jdb-diagnostics.sh          # Automated thread dumps
│   └── jdb-breakpoints.sh          # Bulk-load breakpoints from a file
└── references/
    ├── jdb-commands.md              # Complete command reference
    └── jdwp-options.md              # JDWP agent configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;SKILL.md&lt;/code&gt; opens with a &lt;strong&gt;decision tree&lt;/strong&gt; — a pattern borrowed from Anthropic's own webapp-testing skill — that guides the agent to the right approach:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User wants to debug Java app →
  ├─ App is already running with JDWP agent?
  │   ├─ Yes → Attach: scripts/jdb-attach.sh --port &amp;lt;port&amp;gt;
  │   └─ No  → Can you restart with JDWP?
  │       ├─ Yes → Launch with: scripts/jdb-launch.sh &amp;lt;mainclass&amp;gt;
  │       └─ No  → Suggest adding JDWP agent to JVM flags
  │
  ├─ What does the user need?
  │   ├─ Set breakpoints &amp;amp; step through code → Interactive JDB session
  │   ├─ Collect thread dumps / diagnostics → scripts/jdb-diagnostics.sh
  │   └─ Catch a specific exception → Use `catch` command in JDB
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then it provides concrete debugging workflow patterns — how to investigate a &lt;code&gt;NullPointerException&lt;/code&gt;, how to watch a method's behavior, how to diagnose a deadlock — written as step-by-step JDB command sequences the agent can follow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real test: debugging a buggy Swing app, live
&lt;/h2&gt;

&lt;p&gt;To prove this wasn't just theoretical, we built a sample Swing application with four intentional bugs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;NullPointerException&lt;/strong&gt; — &lt;code&gt;processMessage()&lt;/code&gt; returns &lt;code&gt;null&lt;/code&gt; for empty input&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Off-by-one error&lt;/strong&gt; — the warning counter always shows one less than actual&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NullPointerException after clear&lt;/strong&gt; — &lt;code&gt;warningHistory&lt;/code&gt; is set to &lt;code&gt;null&lt;/code&gt; instead of &lt;code&gt;.clear()&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;StringIndexOutOfBoundsException&lt;/strong&gt; — &lt;code&gt;text.substring(0, 3)&lt;/code&gt; on input shorter than 3 characters&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then we debugged it. In the same conversation session. With the agent driving JDB.&lt;/p&gt;

&lt;h3&gt;
  
  
  The debugging session
&lt;/h3&gt;

&lt;p&gt;The agent launched the app under JDB, set exception catches and method breakpoints, and ran the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;gt; catch java.lang.NullPointerException
&amp;gt; catch java.lang.StringIndexOutOfBoundsException
&amp;gt; stop in com.example.WarningApp.showWarning
&amp;gt; run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When I clicked "Show Warning" in the Swing UI, JDB immediately hit the breakpoint. The agent stepped through the code, inspecting variables at each step:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Breakpoint hit: "thread=AWT-EventQueue-0", com.example.WarningApp.showWarning(), line=80
80            String text = inputField.getText();

AWT-EventQueue-0[1] next
Step completed: line=83
83            String processed = processMessage(text);

AWT-EventQueue-0[1] print text
 text = "bruno"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It stepped into &lt;code&gt;processMessage&lt;/code&gt;, verified the return value, then stepped back out:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWT-EventQueue-0[1] step
Step completed: com.example.WarningApp.processMessage(), line=105
105            String trimmed = message.trim();

AWT-EventQueue-0[1] step up
Step completed: com.example.WarningApp.showWarning(), line=83

AWT-EventQueue-0[1] print processed
 processed = "⚠ BRUNO ⚠"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then came the moment where it caught the off-by-one bug red-handed. The agent stepped to the counter update and inspected the state:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;AWT-EventQueue-0[1] print warningCount
 warningCount = 0

AWT-EventQueue-0[1] next
Step completed: line=93
93            counterLabel.setText("Warnings shown: " + (warningCount - 1));

AWT-EventQueue-0[1] print warningCount
 warningCount = 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There it is. &lt;code&gt;warningCount&lt;/code&gt; is &lt;code&gt;1&lt;/code&gt;, but line 93 displays &lt;code&gt;warningCount - 1&lt;/code&gt;, which is &lt;code&gt;0&lt;/code&gt;. The agent identified the bug by observing the live state of the program at the exact line where the defect occurs — no logging, no guessing, no recompilation.&lt;/p&gt;

&lt;h3&gt;
  
  
  A small but important lesson: compile with &lt;code&gt;-g&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;One interesting moment in the session: the first time we tried &lt;code&gt;locals&lt;/code&gt;, JDB responded:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Local variable information not available. Compile with -g to generate variable information
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The agent immediately recognized the issue, quit JDB, recompiled with &lt;code&gt;javac -g&lt;/code&gt; (which includes debug symbols), and relaunched. This is exactly the kind of practical knowledge that a skill should encode — and that we later made sure to document in the SKILL.md.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Beyond &lt;code&gt;println&lt;/code&gt; debugging
&lt;/h3&gt;

&lt;p&gt;The standard AI debugging loop today looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read the code&lt;/li&gt;
&lt;li&gt;Add &lt;code&gt;System.out.println&lt;/code&gt; or logging statements&lt;/li&gt;
&lt;li&gt;Recompile&lt;/li&gt;
&lt;li&gt;Run the program&lt;/li&gt;
&lt;li&gt;Read the output&lt;/li&gt;
&lt;li&gt;Reason about what happened&lt;/li&gt;
&lt;li&gt;Modify the code&lt;/li&gt;
&lt;li&gt;Repeat&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;With JDB, the agent can:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set breakpoints at suspicious locations&lt;/li&gt;
&lt;li&gt;Run the program&lt;/li&gt;
&lt;li&gt;Inspect the &lt;strong&gt;actual runtime state&lt;/strong&gt; — variable values, call stacks, thread states&lt;/li&gt;
&lt;li&gt;Step through execution line by line&lt;/li&gt;
&lt;li&gt;Catch exceptions at the exact throw site&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is a fundamentally different approach. The agent observes the program's behavior &lt;strong&gt;as it runs&lt;/strong&gt;, rather than inferring it from log output after the fact.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interactive debugging as a first-class agent capability
&lt;/h3&gt;

&lt;p&gt;What makes this work so well is the combination of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;JDB being text-based&lt;/strong&gt; — it reads commands from stdin and writes output to stdout, which is exactly how AI agents interact with tools&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Agent Skills being just Markdown&lt;/strong&gt; — no SDK, no API integration, no plugin framework. You write instructions in a &lt;code&gt;.md&lt;/code&gt; file and the agent follows them&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Helper scripts as black boxes&lt;/strong&gt; — the agent runs &lt;code&gt;scripts/jdb-attach.sh --port 5005&lt;/code&gt; without needing to understand the script internals&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The skill follows the same "black-box scripts" pattern used by Anthropic's own &lt;a href="https://github.com/anthropics/skills/tree/main/skills/webapp-testing" rel="noopener noreferrer"&gt;webapp-testing skill&lt;/a&gt;, which uses Playwright scripts the agent invokes without reading their source.&lt;/p&gt;

&lt;h3&gt;
  
  
  The shift from static analysis to dynamic observation
&lt;/h3&gt;

&lt;p&gt;Most AI coding tools today work with &lt;strong&gt;static&lt;/strong&gt; information — source code, type signatures, documentation. JDB gives agents access to &lt;strong&gt;dynamic&lt;/strong&gt; information — what actually happens at runtime. This is especially valuable for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Concurrency bugs&lt;/strong&gt; — thread dumps and deadlock detection through JDB's &lt;code&gt;threads&lt;/code&gt; and &lt;code&gt;where all&lt;/code&gt; commands&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;State-dependent bugs&lt;/strong&gt; — inspecting object fields and local variables at specific points in execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Exception investigation&lt;/strong&gt; — catching exceptions at the throw site rather than reading stack traces after the fact&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration issues&lt;/strong&gt; — attaching to running services to observe behavior with real data&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Try it yourself
&lt;/h2&gt;

&lt;p&gt;The skill is open source: &lt;strong&gt;&lt;a href="https://github.com/brunoborges/jdb-debugger-skill" rel="noopener noreferrer"&gt;github.com/brunoborges/jdb-debugger-skill&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The repository includes a sample Swing app with the four intentional bugs described above, so you can reproduce the exact debugging session. The full conversation transcript is available as &lt;a href="https://gist.github.com/brunoborges/3b2f883c62409b6ceeacd0fb5a8dc811" rel="noopener noreferrer"&gt;a GitHub Gist&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To get started:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;/skill add jdb-debugger
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Then just ask: &lt;em&gt;"Debug my Java application — there's a NullPointerException I can't figure out."&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next
&lt;/h2&gt;

&lt;p&gt;This is a starting point. The skill currently covers the core JDB workflow, but there are natural extensions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Conditional breakpoints&lt;/strong&gt; and watchpoints for more surgical debugging&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Integration with build tools&lt;/strong&gt; — auto-detecting Maven/Gradle projects and compiling with &lt;code&gt;-g&lt;/code&gt; before launching JDB&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Remote debugging recipes&lt;/strong&gt; — patterns for Kubernetes pods, Docker containers, and cloud-hosted JVMs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Composability with other skills&lt;/strong&gt; — combining JDB debugging with code analysis or test-generation skills&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The bigger takeaway is this: every command-line tool that developers use daily is a potential agent skill. Debuggers, profilers, database CLIs, network tools — they're all text-based interfaces waiting to be taught to AI agents.&lt;/p&gt;

&lt;p&gt;The JDK gave us the debugger thirty years ago. We just needed to write the instructions.&lt;/p&gt;

</description>
      <category>agents</category>
      <category>ai</category>
      <category>java</category>
      <category>tooling</category>
    </item>
    <item>
      <title>How to Configure JDK 25 for GitHub Copilot Coding Agent</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Thu, 05 Feb 2026 23:37:16 +0000</pubDate>
      <link>https://dev.to/brunoborges/how-to-configure-jdk-25-for-github-copilot-coding-agent-2ecn</link>
      <guid>https://dev.to/brunoborges/how-to-configure-jdk-25-for-github-copilot-coding-agent-2ecn</guid>
      <description>&lt;p&gt;GitHub Copilot coding agent runs in an ephemeral GitHub Actions environment where it can build your code, run tests, and execute tools. By default, it uses the pre-installed Java version on the runner—but what if your project needs a specific version like &lt;strong&gt;JDK 25&lt;/strong&gt;?&lt;/p&gt;

&lt;p&gt;In this post, I'll show you how to configure Copilot coding agent's environment to use any Java version, including the latest JDK 25, ensuring that Copilot can successfully build and test your Java projects.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;When Copilot coding agent works on your repository, it attempts to discover and install dependencies through trial and error. For Java projects, this means:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Copilot might try to use an older JDK version pre-installed on the runner&lt;/li&gt;
&lt;li&gt;Build failures occur if your project requires newer Java features (records, pattern matching, virtual threads, etc.)&lt;/li&gt;
&lt;li&gt;Time is wasted as Copilot tries different approaches to fix JDK-related issues&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The solution? &lt;strong&gt;Preconfigure Copilot's environment&lt;/strong&gt; with the exact JDK version your project needs.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Solution: copilot-setup-steps.yml
&lt;/h2&gt;

&lt;p&gt;GitHub provides a special workflow file called &lt;code&gt;copilot-setup-steps.yml&lt;/code&gt; that runs &lt;em&gt;before&lt;/em&gt; Copilot starts working. Think of it as your project's "pre-flight checklist" for Copilot.&lt;/p&gt;

&lt;p&gt;Create this file at &lt;code&gt;.github/workflows/copilot-setup-steps.yml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Copilot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Setup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Steps"&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="c1"&gt;# This job name MUST be exactly "copilot-setup-steps"&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up JDK &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify Java version&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;java -version&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn dependency:go-offline -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let's break down the key parts:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Job Name is Critical
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# MUST be this exact name&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Copilot only recognizes the job if it's named &lt;code&gt;copilot-setup-steps&lt;/code&gt;. Any other name will be ignored.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Setting Up JDK 25 with setup-java
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up JDK &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
  &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
  &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
    &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
    &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;actions/setup-java&lt;/code&gt; action supports multiple JDK distributions:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Distribution&lt;/th&gt;
&lt;th&gt;Vendor&lt;/th&gt;
&lt;th&gt;Notes&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;temurin&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Eclipse Adoptium&lt;/td&gt;
&lt;td&gt;Recommended, community standard&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zulu&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Azul&lt;/td&gt;
&lt;td&gt;Good compatibility&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;corretto&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Amazon&lt;/td&gt;
&lt;td&gt;AWS-optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;microsoft&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Microsoft&lt;/td&gt;
&lt;td&gt;Azure-optimized&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;oracle&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Oracle&lt;/td&gt;
&lt;td&gt;Official Oracle JDK&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;liberica&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;BellSoft&lt;/td&gt;
&lt;td&gt;Full and lite versions available&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  3. Caching Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This caches your Maven dependencies between Copilot sessions, significantly speeding up subsequent runs. For Gradle projects, use &lt;code&gt;cache: 'gradle'&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Pre-downloading Dependencies
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download dependencies&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn dependency:go-offline -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures all dependencies are downloaded before Copilot starts. This is especially important if:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You have private dependencies that require authentication&lt;/li&gt;
&lt;li&gt;Your project has many dependencies&lt;/li&gt;
&lt;li&gt;You want faster Copilot response times&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Complete Example for a Maven Project
&lt;/h2&gt;

&lt;p&gt;Here's a production-ready configuration for a Java 25 Maven project:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Copilot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Setup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Steps"&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up JDK &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Verify Java version&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;java -version&lt;/span&gt;
          &lt;span class="s"&gt;echo "JAVA_HOME=$JAVA_HOME"&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and cache dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="pi"&gt;|&lt;/span&gt;
          &lt;span class="s"&gt;mvn dependency:go-offline -B&lt;/span&gt;
          &lt;span class="s"&gt;mvn compile -DskipTests -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Gradle Configuration
&lt;/h2&gt;

&lt;p&gt;For Gradle projects, adjust the workflow accordingly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Copilot&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Setup&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;Steps"&lt;/span&gt;

&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;workflow_dispatch&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;push&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;
  &lt;span class="na"&gt;pull_request&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;paths&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="s"&gt;.github/workflows/copilot-setup-steps.yml&lt;/span&gt;

&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up JDK &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gradle'&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Setup Gradle&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;gradle/actions/setup-gradle@v4&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Build and cache dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;./gradlew dependencies --write-locks&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Handling Private Dependencies
&lt;/h2&gt;

&lt;p&gt;If your project uses private Maven repositories, you'll need to configure authentication. Create secrets in the &lt;code&gt;copilot&lt;/code&gt; environment:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to your repository &lt;strong&gt;Settings&lt;/strong&gt; → &lt;strong&gt;Environments&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Click the &lt;code&gt;copilot&lt;/code&gt; environment (create it if it doesn't exist)&lt;/li&gt;
&lt;li&gt;Add environment secrets for your credentials&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Then reference them in your workflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-latest&lt;/span&gt;

    &lt;span class="na"&gt;permissions&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;read&lt;/span&gt;

    &lt;span class="na"&gt;steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Checkout code&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/checkout@v5&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Set up JDK &lt;/span&gt;&lt;span class="m"&gt;25&lt;/span&gt;
        &lt;span class="na"&gt;uses&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;actions/setup-java@v4&lt;/span&gt;
        &lt;span class="na"&gt;with&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
          &lt;span class="na"&gt;java-version&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;25'&lt;/span&gt;
          &lt;span class="na"&gt;distribution&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;temurin'&lt;/span&gt;
          &lt;span class="na"&gt;cache&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s1"&gt;'&lt;/span&gt;&lt;span class="s"&gt;maven'&lt;/span&gt;
          &lt;span class="na"&gt;server-id&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;private-repo&lt;/span&gt;
          &lt;span class="na"&gt;server-username&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MAVEN_USERNAME }}&lt;/span&gt;
          &lt;span class="na"&gt;server-password&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;${{ secrets.MAVEN_PASSWORD }}&lt;/span&gt;

      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Download dependencies&lt;/span&gt;
        &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn dependency:go-offline -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Testing Your Configuration
&lt;/h2&gt;

&lt;p&gt;The &lt;code&gt;copilot-setup-steps.yml&lt;/code&gt; workflow automatically runs when you:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Push changes to the workflow file&lt;/li&gt;
&lt;li&gt;Create a PR that modifies it&lt;/li&gt;
&lt;li&gt;Manually trigger it from the &lt;strong&gt;Actions&lt;/strong&gt; tab&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This lets you validate your setup before Copilot uses it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Tips for Java Projects
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Compile the Code
&lt;/h3&gt;

&lt;p&gt;Pre-compiling your code helps Copilot understand your codebase faster:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Compile project&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn compile test-compile -DskipTests -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Generate Sources
&lt;/h3&gt;

&lt;p&gt;If you use code generation (Lombok processors, annotation processors, etc.):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Generate sources&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn generate-sources generate-test-sources -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Install the Project Locally
&lt;/h3&gt;

&lt;p&gt;For multi-module Maven projects:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Install modules&lt;/span&gt;
  &lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;mvn install -DskipTests -B&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Using Larger Runners
&lt;/h2&gt;

&lt;p&gt;For large Java projects, consider using &lt;a href="https://docs.github.com/en/actions/using-github-hosted-runners/using-larger-runners" rel="noopener noreferrer"&gt;larger GitHub-hosted runners&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;copilot-setup-steps&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;runs-on&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;ubuntu-4-core&lt;/span&gt;  &lt;span class="c1"&gt;# More CPU and RAM&lt;/span&gt;
    &lt;span class="c1"&gt;# ... rest of configuration&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Larger runners provide more resources for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Faster dependency downloads&lt;/li&gt;
&lt;li&gt;Quicker compilation&lt;/li&gt;
&lt;li&gt;Running memory-intensive tests&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;By creating a &lt;code&gt;copilot-setup-steps.yml&lt;/code&gt; workflow, you ensure that GitHub Copilot coding agent has access to the exact Java version your project needs. This eliminates build failures, speeds up Copilot's work, and provides a consistent development environment.&lt;/p&gt;

&lt;p&gt;Key takeaways:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Create &lt;code&gt;.github/workflows/copilot-setup-steps.yml&lt;/code&gt;&lt;/strong&gt; with the exact job name &lt;code&gt;copilot-setup-steps&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use &lt;code&gt;actions/setup-java@v4&lt;/code&gt;&lt;/strong&gt; to install JDK 25 or any version you need&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable caching&lt;/strong&gt; for Maven or Gradle dependencies&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Pre-download dependencies&lt;/strong&gt; to speed up Copilot's work&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Test your workflow&lt;/strong&gt; by pushing changes or manually triggering it&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Now Copilot can work with your Java 25 project just as effectively as it would on your local machine!&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;References:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.github.com/en/copilot/how-tos/use-copilot-agents/coding-agent/customize-the-agent-environment" rel="noopener noreferrer"&gt;Customizing the development environment for GitHub Copilot coding agent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/setup-java" rel="noopener noreferrer"&gt;actions/setup-java&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/actions/setup-java#supported-distributions" rel="noopener noreferrer"&gt;Supported Java distributions&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Published: February 5, 2026&lt;/em&gt;&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>java</category>
      <category>openjdk</category>
    </item>
    <item>
      <title>Copilot SDK for Java 1.0.7: Session Lifecycle Hooks and Enhanced Observability</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Thu, 05 Feb 2026 22:49:26 +0000</pubDate>
      <link>https://dev.to/brunoborges/copilot-sdk-for-java-107-session-lifecycle-hooks-and-enhanced-observability-4kmi</link>
      <guid>https://dev.to/brunoborges/copilot-sdk-for-java-107-session-lifecycle-hooks-and-enhanced-observability-4kmi</guid>
      <description>&lt;p&gt;I'm excited to announce the release of &lt;strong&gt;Copilot SDK for Java v1.0.7&lt;/strong&gt;, bringing powerful new capabilities for session lifecycle management, improved observability, and comprehensive documentation. This release represents a significant step forward in feature parity with the official .NET SDK, with 54 commits adding over 5,200 lines of new functionality and tests.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's New in 1.0.7
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Session Lifecycle Hooks
&lt;/h3&gt;

&lt;p&gt;The most significant addition in this release is the extended hooks system. While v1.0.6 introduced pre-tool and post-tool hooks, v1.0.7 adds three new lifecycle hooks that give you fine-grained control over session behavior:&lt;/p&gt;

&lt;h4&gt;
  
  
  onSessionStart
&lt;/h4&gt;

&lt;p&gt;Called when a session starts (whether new or resumed), this hook lets you perform initialization, logging, or validation before the session begins processing:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;SessionConfig&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setOnSessionStart&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Session started: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSessionId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
        &lt;span class="c1"&gt;// Initialize session-specific resources&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completedFuture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SessionStartHookOutput&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  onSessionEnd
&lt;/h4&gt;

&lt;p&gt;Called when a session ends, enabling cleanup operations, metrics collection, or audit logging:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setOnSessionEnd&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;Duration&lt;/span&gt; &lt;span class="n"&gt;sessionDuration&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;calculateDuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="n"&gt;metricsCollector&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;recordSessionDuration&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sessionDuration&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completedFuture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;SessionEndHookOutput&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  onUserPromptSubmitted
&lt;/h4&gt;

&lt;p&gt;Called when the user submits a prompt, allowing you to enrich, validate, or transform prompts before they're processed:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setOnUserPromptSubmitted&lt;/span&gt;&lt;span class="o"&gt;((&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;invocation&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;enrichedPrompt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;addProjectContext&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;input&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPrompt&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;CompletableFuture&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;completedFuture&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
        &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;UserPromptSubmittedHookOutput&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setUpdatedPrompt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;enrichedPrompt&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
    &lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="o"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These hooks open up powerful use cases including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Security gates&lt;/strong&gt;: Validate tool calls against allowlists before execution&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audit logging&lt;/strong&gt;: Record all tool invocations for compliance&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Result enrichment&lt;/strong&gt;: Add metadata or transform tool outputs&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Session analytics&lt;/strong&gt;: Track session patterns and user behavior&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Client-Level Lifecycle Events
&lt;/h3&gt;

&lt;p&gt;Beyond session-scoped hooks, v1.0.7 introduces client-level lifecycle event subscriptions. This is particularly useful for applications managing multiple concurrent sessions:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Subscribe to all lifecycle events&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onLifecycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Session "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSessionId&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="s"&gt;" status: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getType&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Subscribe to specific event types&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onLifecycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SessionLifecycleEventTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CREATED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;initializeSessionResources&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSessionId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;onLifecycle&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SessionLifecycleEventTypes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;DELETED&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;cleanupSessionResources&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getSessionId&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Available event types include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;CREATED&lt;/code&gt; — A new session was created&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;DELETED&lt;/code&gt; — A session was deleted&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;UPDATED&lt;/code&gt; — Session state was updated&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;FOREGROUND&lt;/code&gt; — Session moved to foreground (TUI mode)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BACKGROUND&lt;/code&gt; — Session moved to background (TUI mode)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Foreground Session Control
&lt;/h3&gt;

&lt;p&gt;For applications running in TUI+Server mode (&lt;code&gt;copilot --ui-server&lt;/code&gt;), v1.0.7 adds APIs to control which session is displayed in the terminal UI:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Get the currently displayed session&lt;/span&gt;
&lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;currentSession&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getForegroundSessionId&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Switch the TUI to display a different session&lt;/span&gt;
&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setForegroundSessionId&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;anotherSessionId&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This enables building sophisticated multi-session orchestrators that can programmatically switch the TUI focus based on user activity or priority.&lt;/p&gt;

&lt;h3&gt;
  
  
  New Event Types
&lt;/h3&gt;

&lt;p&gt;Two new event types enhance observability:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;SessionShutdownEvent&lt;/strong&gt; — Emitted when a session is shutting down, includes the reason and exit code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SessionShutdownEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Session shutting down: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getReason&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;SkillInvokedEvent&lt;/strong&gt; — Emitted when a skill is invoked, includes the skill name and invocation context:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;on&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;SkillInvokedEvent&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Skill invoked: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; 
        &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getData&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;getSkillName&lt;/span&gt;&lt;span class="o"&gt;());&lt;/span&gt;
&lt;span class="o"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Extended Event Data
&lt;/h3&gt;

&lt;p&gt;Several existing events now include additional fields:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;New Fields&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AssistantMessageEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;id&lt;/code&gt;, &lt;code&gt;isLastReply&lt;/code&gt;, &lt;code&gt;thinkingContent&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;AssistantUsageEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;outputReasoningTokens&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SessionCompactionCompleteEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;success&lt;/code&gt;, &lt;code&gt;messagesRemoved&lt;/code&gt;, &lt;code&gt;tokensRemoved&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SessionErrorEvent&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Extended error context&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  JaCoCo Test Coverage
&lt;/h3&gt;

&lt;p&gt;This release integrates JaCoCo 0.8.14 for test coverage reporting. Coverage reports are now:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Generated automatically during builds at &lt;code&gt;target/site/jacoco-coverage/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Summarized in GitHub Actions workflow summaries&lt;/li&gt;
&lt;li&gt;Uploaded as CI artifacts for detailed analysis&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Documentation Improvements
&lt;/h2&gt;

&lt;p&gt;We've significantly expanded the documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://copilot-community-sdk.github.io/copilot-sdk-java/hooks.html" rel="noopener noreferrer"&gt;Session Hooks Guide&lt;/a&gt;&lt;/strong&gt; — Comprehensive guide covering all 5 hook types with practical examples&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://copilot-community-sdk.github.io/copilot-sdk-java/documentation.html" rel="noopener noreferrer"&gt;Events Reference&lt;/a&gt;&lt;/strong&gt; — Complete documentation of all 33 event types&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://copilot-community-sdk.github.io/copilot-sdk-java/advanced.html" rel="noopener noreferrer"&gt;Advanced Usage&lt;/a&gt;&lt;/strong&gt; — Enhanced with lifecycle events and foreground session control&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Breaking Changes
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Copilot CLI&lt;/strong&gt;: Minimum required version updated from 0.0.400 to &lt;strong&gt;0.0.404&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;Add the dependency to your &lt;code&gt;pom.xml&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.github.copilot-community-sdk&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;copilot-sdk-java&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.7&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Or try it instantly with JBang:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;jbang https://github.com/copilot-community-sdk/copilot-sdk-java/blob/main/jbang-example.java
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;We continue to track the official .NET SDK and port new features as they become available. Upcoming priorities include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Additional MCP server integrations&lt;/li&gt;
&lt;li&gt;Enhanced error recovery patterns&lt;/li&gt;
&lt;li&gt;Performance optimizations for high-throughput scenarios&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Contributing
&lt;/h2&gt;

&lt;p&gt;The Copilot SDK for Java is a community-driven project. We welcome contributions! Check out our &lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java" rel="noopener noreferrer"&gt;GitHub repository&lt;/a&gt; and the &lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java/blob/main/CONTRIBUTING.md" rel="noopener noreferrer"&gt;contributing guidelines&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java/releases/tag/v1.0.7" rel="noopener noreferrer"&gt;Release Notes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java/blob/main/CHANGELOG.md" rel="noopener noreferrer"&gt;Full Changelog&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://copilot-community-sdk.github.io/copilot-sdk-java/" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://central.sonatype.com/artifact/com.github.copilot-community-sdk/copilot-sdk-java" rel="noopener noreferrer"&gt;Maven Central&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;em&gt;Published: February 5, 2026&lt;/em&gt;&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>java</category>
      <category>monitoring</category>
      <category>news</category>
    </item>
    <item>
      <title>Running GitHub Copilot CLI Safely with Docker Sandbox</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Mon, 02 Feb 2026 16:38:43 +0000</pubDate>
      <link>https://dev.to/brunoborges/running-github-copilot-cli-safely-with-docker-sandbox-2f4i</link>
      <guid>https://dev.to/brunoborges/running-github-copilot-cli-safely-with-docker-sandbox-2f4i</guid>
      <description>&lt;p&gt;Docker just released a game-changing feature for developers who love running AI coding agents in "YOLO mode": &lt;strong&gt;Docker Sandboxes&lt;/strong&gt;. If you've ever hesitated to let an AI agent run wild with full permissions on your machine, this is the solution you've been waiting for.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is Docker Sandbox?
&lt;/h2&gt;

&lt;p&gt;Docker Sandboxes provide disposable, isolated microVM environments purpose-built for coding agents. Each agent runs in a completely isolated version of your development environment. When it installs packages, modifies configurations, or deletes files, &lt;strong&gt;your host machine remains untouched&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;This isolation enables what Docker calls "Level 4 Coding Agent Autonomy": letting agents like Claude Code, Codex CLI, &lt;strong&gt;GitHub Copilot CLI&lt;/strong&gt;, Gemini CLI, and Kiro run unattended without constant permission prompts, while keeping your system safe.&lt;/p&gt;

&lt;h3&gt;
  
  
  Key Benefits
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;MicroVM-based isolation&lt;/strong&gt; — Each agent runs inside a dedicated microVM with hypervisor-level security&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Real development environment&lt;/strong&gt; — Agents can install system packages, run services, and modify files freely&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Safe Docker access&lt;/strong&gt; — Agents can build and run Docker containers inside the sandbox, with no access to your host Docker daemon&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Fast reset&lt;/strong&gt; — If an agent goes off the rails, delete the sandbox and spin up a fresh one in seconds&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Running Copilot CLI in a Docker Sandbox
&lt;/h2&gt;

&lt;p&gt;Docker Sandbox works great with GitHub Copilot CLI. Here's how to get started:&lt;/p&gt;

&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Docker Desktop 4.59 or later&lt;/li&gt;
&lt;li&gt;macOS or Windows&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Create a GitHub Copilot CLI Sandbox
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker sandbox create copilot ./your-project-folder &lt;span class="nt"&gt;--&lt;/span&gt; &lt;span class="nt"&gt;--yolo&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This creates an isolated sandbox with Copilot CLI ready to go in full autonomous, YOLO mode.&lt;/p&gt;

&lt;h3&gt;
  
  
  Authenticate with GitHub
&lt;/h3&gt;

&lt;p&gt;Since Docker Sandbox doesn't yet pull authentication tokens from your local &lt;code&gt;~/.copilot/&lt;/code&gt; folder during the sandbox creation process for Copilot, you'll need to authenticate manually once inside the sandbox.&lt;/p&gt;

&lt;p&gt;Run the &lt;code&gt;/login&lt;/code&gt; command inside the Copilot CLI. You'll see something like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;⠦ Waiting for authorization...

Enter one-time code: ABCD-1234 at https://github.com/login/device

Press any key to copy to clipboard and open browser...
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Follow the device flow to authenticate, and you're ready to go!&lt;/p&gt;

&lt;h3&gt;
  
  
  Useful Commands
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# List all your sandboxes&lt;/span&gt;
docker sandbox &lt;span class="nb"&gt;ls&lt;/span&gt;

&lt;span class="c"&gt;# Access a running sandbox interactively&lt;/span&gt;
docker sandbox &lt;span class="nb"&gt;exec&lt;/span&gt; &lt;span class="nt"&gt;-it&lt;/span&gt; &amp;lt;sandbox-name&amp;gt; bash

&lt;span class="c"&gt;# Remove a sandbox when done&lt;/span&gt;
docker sandbox &lt;span class="nb"&gt;rm&lt;/span&gt; &amp;lt;sandbox-name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Why This Matters
&lt;/h2&gt;

&lt;p&gt;Running AI coding agents with full autonomy has always been a trade-off between productivity and risk. Docker Sandboxes eliminate that trade-off by providing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;True isolation&lt;/strong&gt; — Your host machine is completely protected&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No permission fatigue&lt;/strong&gt; — Let the agent work without interruption&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Easy recovery&lt;/strong&gt; — Something went wrong? Nuke the sandbox and start fresh&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Docker is continuing to expand Sandboxes based on developer feedback:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Linux support&lt;/li&gt;
&lt;li&gt;MCP Gateway support
&lt;/li&gt;
&lt;li&gt;Ability to expose ports to the host and access host-exposed services&lt;/li&gt;
&lt;li&gt;Support for additional coding agents&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Get Started
&lt;/h2&gt;

&lt;p&gt;Ready to let your AI coding agents run free (safely)? Check out the official documentation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.docker.com/products/docker-sandboxes/" rel="noopener noreferrer"&gt;Docker Sandboxes Product Page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/ai/sandboxes/get-started/" rel="noopener noreferrer"&gt;Getting Started Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/ai/sandboxes/agents/" rel="noopener noreferrer"&gt;Supported Agents&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;em&gt;Happy coding — and let your agents loose!&lt;/em&gt; 🚀&lt;/p&gt;

</description>
      <category>docker</category>
      <category>githubcopilot</category>
      <category>github</category>
    </item>
    <item>
      <title>Java devs: build something awesome with Copilot CLI and win big prizes</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Mon, 02 Feb 2026 15:27:12 +0000</pubDate>
      <link>https://dev.to/brunoborges/java-devs-build-something-awesome-with-copilot-cli-and-win-big-prizes-gl</link>
      <guid>https://dev.to/brunoborges/java-devs-build-something-awesome-with-copilot-cli-and-win-big-prizes-gl</guid>
      <description>&lt;p&gt;Here’s today's invitation: join the GitHub Copilot CLI Challenge and build something with Copilot right in your terminal. Visit the Challenge page for rules, FAQ, and submission template.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I’m excited about Copilot CLI (especially for Java)
&lt;/h2&gt;

&lt;p&gt;If you write Java for a living, you already know the truth: the terminal is where we build and test; it is where feedback loops are short; and where most productivity gains come from “small wins” repeated hundreds of times.&lt;/p&gt;

&lt;p&gt;Most Java developers use Maven or Gradle, and IDEs (especially IntelliJ) have fantastic support for both. But in practice, we still drop to the terminal quite regularly:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;run a very specific Maven goal or Gradle task&lt;/li&gt;
&lt;li&gt;we want to reproduce CI as much as possible&lt;/li&gt;
&lt;li&gt;add flags to isolate one failing test&lt;/li&gt;
&lt;li&gt;check output in the same environment your teammates (and CI) will see&lt;/li&gt;
&lt;li&gt;run a single test the same way CI does, but you don’t remember the exact incantation.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If we’re already inside the terminal running commands, we might as well ask Copilot CLI to help us do the right thing faster. GitHub Copilot CLI brings an agentic workflow to the place where those loops happen: the command line. And the best part: you can keep it grounded in your repo and your actual build output.&lt;/p&gt;

&lt;p&gt;You can also join Johannes' live stream this week on the topic!&lt;/p&gt;

&lt;p&gt;

&lt;iframe class="tweet-embed" id="tweet-2017976333385556316-376" src="https://platform.twitter.com/embed/Tweet.html?id=2017976333385556316"&gt;
&lt;/iframe&gt;

  // Detect dark theme
  var iframe = document.getElementById('tweet-2017976333385556316-376');
  if (document.body.className.includes('dark-theme')) {
    iframe.src = "https://platform.twitter.com/embed/Tweet.html?id=2017976333385556316&amp;amp;theme=dark"
  }





&lt;/p&gt;

&lt;h2&gt;
  
  
  The challenge (quick overview)
&lt;/h2&gt;

&lt;p&gt;The challenge is quite open-ended: build an application using GitHub Copilot CLI. But there is a judging criteria: (1) use of GitHub Copilot CLI; (2) share usability and user experience; and (3) must be original and creative!&lt;/p&gt;

&lt;p&gt;The challenge is already up and running since January 22nd, but there is still time. The submissions are due by February 15th at 11:59 PM PST and the winners will be announced on February 26th.&lt;/p&gt;

&lt;p&gt;This challenge comes with really cool prizes: the top 3 winners will get $1,000 USD plus a GitHub Universe 2026 ticket and a winner badge. Then, the next 25 runner-ups will get a 1-year GitHub Copilot Pro+ subscription with a runner-up badge. All valid submissions get a GitHub completion badge.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ship a Java tool, for end-users or for developers
&lt;/h2&gt;

&lt;p&gt;It can be a Spring Starter, a Quarkus Extension, a JavaFX application, a web application, Maven or Gradle plugins, a Java Swing application, plugins for IntelliJ or Eclipse, or even Apache JMeter! This is the kind of challenge where a small, well-executed tool can be more impressive than a giant “AI demo.” If you’re on the fence, here’s my recommendation:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pick a problem you hit every week&lt;/li&gt;
&lt;li&gt;build a thin vertical slice in a day&lt;/li&gt;
&lt;li&gt;make it pleasant to use&lt;/li&gt;
&lt;li&gt;write a clean “how to run it” section&lt;/li&gt;
&lt;li&gt;tell the story of how Copilot CLI helped you iterate&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Need more to think on what to build?
&lt;/h3&gt;

&lt;p&gt;These are Java-friendly ideas that fit the judging criteria and are designed to be realistic, shippable, and easy for judges to evaluate:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Test failure triage assistant for Maven/Gradle: parse surefire output, summarize likely causes, suggest next commands to run&lt;/li&gt;
&lt;li&gt;Log explainer: ingest a stack trace + environment info and generate a focused explanation + remediation checklist&lt;/li&gt;
&lt;li&gt;Repo onboarding CLI: generate a “first 30 minutes” guide for your project (build, tests, conventions, release process)&lt;/li&gt;
&lt;li&gt;Changelog helper: read git history and propose a changelog entry + release notes draft&lt;/li&gt;
&lt;li&gt;OpenAPI → Spring Boot starter: take an OpenAPI spec and scaffold a production-ready service layout&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Prompts you can try today (copy/paste inspiration)
&lt;/h3&gt;

&lt;p&gt;In your terminal (inside a repo), try asking Copilot CLI things like:&lt;/p&gt;

&lt;p&gt;“Summarize why my Maven tests are failing from this output, then suggest the next 3 commands I should run.”&lt;br&gt;
“Generate a JUnit 5 test for this class focusing on boundary cases.”&lt;br&gt;
“Explain this stack trace like I’m onboarding to the project; point me to the likely source file and fix.”&lt;br&gt;
“Propose a refactor that reduces duplication but keeps the public API stable.”&lt;br&gt;
“Write a README section that explains how to run this tool, with examples.”&lt;/p&gt;

&lt;p&gt;The key is to keep the agent grounded in real inputs: actual logs, actual code, actual constraints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Programmatic control via the Copilot SDK for Java
&lt;/h2&gt;

&lt;p&gt;If you want to build a Java app that programmatically drives Copilot CLI, start here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java" rel="noopener noreferrer"&gt;https://github.com/copilot-community-sdk/copilot-sdk-java&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  More Resources
&lt;/h2&gt;

&lt;p&gt;Use these to bootstrap your project fast:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Kotlin MCP development collection: &lt;a href="https://github.com/github/awesome-copilot/blob/main/collections/kotlin-mcp-development.md" rel="noopener noreferrer"&gt;https://github.com/github/awesome-copilot/blob/main/collections/kotlin-mcp-development.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Java MCP development collection: &lt;a href="https://github.com/github/awesome-copilot/blob/main/collections/java-mcp-development.md" rel="noopener noreferrer"&gt;https://github.com/github/awesome-copilot/blob/main/collections/java-mcp-development.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Java development collection (Spring Boot, Quarkus, JUnit, Javadoc, upgrade guides): &lt;a href="https://github.com/github/awesome-copilot/blob/main/collections/java-development.md" rel="noopener noreferrer"&gt;https://github.com/github/awesome-copilot/blob/main/collections/java-development.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;OpenAPI → Spring Boot application collection: &lt;a href="https://github.com/github/awesome-copilot/blob/main/collections/openapi-to-application-java-spring-boot.md" rel="noopener noreferrer"&gt;https://github.com/github/awesome-copilot/blob/main/collections/openapi-to-application-java-spring-boot.md&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Copilot SDK for Java: &lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java" rel="noopener noreferrer"&gt;https://github.com/copilot-community-sdk/copilot-sdk-java&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Ready? Here’s your next step
&lt;/h2&gt;

&lt;p&gt;If you build Java tools, this challenge is a great excuse to ship something useful and learn an agentic workflow you can reuse. Join the challenge and start your submission here:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/challenges/github-2026-01-21"&gt;https://dev.to/challenges/github-2026-01-21&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you do build something, tag me on DEV / socials — I’d love to see what you ship.&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>java</category>
      <category>githubchallenge</category>
      <category>devchallenge</category>
    </item>
    <item>
      <title>Introducing the Copilot Community SDKs</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Thu, 22 Jan 2026 21:42:54 +0000</pubDate>
      <link>https://dev.to/brunoborges/introducing-the-copilot-community-sdks-an</link>
      <guid>https://dev.to/brunoborges/introducing-the-copilot-community-sdks-an</guid>
      <description>&lt;p&gt;I am excited to announce the &lt;a href="https://github.com/copilot-community-sdk" rel="noopener noreferrer"&gt;Copilot Community SDK&lt;/a&gt; — a community-driven collection of SDKs for building integrations with the GitHub Copilot CLI and related Copilot tooling. This project aims to make it easier for developers to extend Copilot workflows across languages and tools not yet covered by first-party support in the official &lt;a href="https://github.com/github/copilot-sdk" rel="noopener noreferrer"&gt;copilot-sdk&lt;/a&gt; repository.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is the Copilot Community SDK?
&lt;/h2&gt;

&lt;p&gt;The Copilot Community SDK is an open-source effort providing language bindings and SDKs that let developers programmatically interact with the Copilot CLI. Each SDK wraps the Copilot CLI server and exposes idiomatic APIs for your favourite languages, making it easier to build tools, plugins, and workflows that leverage Copilot’s capabilities in custom environments.&lt;/p&gt;

&lt;p&gt;This is especially useful for tooling and extensions that need Copilot automation without embedding a lot of CLI plumbing yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotlight: &lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-rust/" rel="noopener noreferrer"&gt;Rust Copilot SDK&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;Based on Rust 1.85, the Rust SDK allows developers to build apps that will interact with the GitHub Copilot CLI agent runtime through JSON-RPC over stdio or TCP.&lt;/p&gt;

&lt;h2&gt;
  
  
  Spotlight: &lt;a href="https://github.com/copilot-community-sdk/copilot-sdk-java/" rel="noopener noreferrer"&gt;Java Copilot SDK&lt;/a&gt;
&lt;/h2&gt;

&lt;p&gt;This project already contains the Java Copilot Community SDK. Designed for JVM-based tooling and services, the Java Community SDK wraps the Copilot CLI, letting Java developers call Copilot programmatically from their apps or plugins.&lt;/p&gt;

&lt;p&gt;With the Java Copilot Community SDK you can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Invoke Copilot completions from Java code&lt;/li&gt;
&lt;li&gt;Build integrations in JVM-focused tools (e.g., IDE plugins, Maven/Gradle plugins, Jenkins plugins, and more)&lt;/li&gt;
&lt;li&gt;Combine Copilot suggestions with custom workflows&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This gives you the building blocks to embed Copilot-powered features in places a CLI alone can’t reach. The SDK is available on Maven Central and can be easily embedded into Java-based tools and applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example: Apache JMeter Copilot Chat Plugin
&lt;/h3&gt;

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

&lt;p&gt;To show the Copilot Community SDK in action, let’s look at a real-world example: the &lt;a href="https://github.com/brunoborges/jmeter-copilot-plugin/tree/main" rel="noopener noreferrer"&gt;Apache JMeter Copilot Chat plugin&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Apache JMeter with a Copilot Chat experience
&lt;/h4&gt;

&lt;p&gt;This project brings a GitHub Copilot Chat experience directly into Apache JMeter, the popular performance and load-testing tool. Using the Copilot Community SDK as a bridge, it allows users to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Ask Copilot questions about JMeter capabilities&lt;/li&gt;
&lt;li&gt;Ask Copilot to trigger a load test and show results in the browser&lt;/li&gt;
&lt;li&gt;Generate or modify JMeter test components with natural language&lt;/li&gt;
&lt;li&gt;Get help with scripting and performance test logic without leaving the tool&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  🚀 Start Building and Join the GitHub Copilot CLI Challenge
&lt;/h2&gt;

&lt;p&gt;If this got you thinking &lt;em&gt;“I could build something cool with this”&lt;/em&gt;, now is the perfect time to do it.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;GitHub Copilot CLI Challenge&lt;/strong&gt; is an open invitation to experiment, build, and ship creative integrations using Copilot. Whether you’re hacking on a weekend project, extending an existing tool, or prototyping something wild, your work can earn real rewards, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🎟️ GitHub Universe tickets
&lt;/li&gt;
&lt;li&gt;🤖 GitHub Copilot Pro subscriptions
&lt;/li&gt;
&lt;li&gt;🎁 And more prizes for standout projects
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Copilot Community SDK&lt;/strong&gt; is a great foundation for challenge entries, especially if you want to go beyond simple CLI usage and embed Copilot into real tools, plugins, or workflows. Java, Rust, and other community SDKs let you meet Copilot where developers already work.&lt;/p&gt;

&lt;h3&gt;
  
  
  How to get started
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Join the challenge and check the rules and prizes
&lt;/li&gt;
&lt;li&gt;Pick a language SDK (or help create a new one)
&lt;/li&gt;
&lt;li&gt;Build an integration, plugin, or workflow powered by Copilot
&lt;/li&gt;
&lt;li&gt;Share your project with the community
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;👉 &lt;strong&gt;Join the GitHub Copilot CLI Challenge here:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
&lt;a href="https://dev.to/devteam/join-the-github-copilot-cli-challenge-win-github-universe-tickets-copilot-pro-subscriptions-and-50af"&gt;https://dev.to/devteam/join-the-github-copilot-cli-challenge-win-github-universe-tickets-copilot-pro-subscriptions-and-50af&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you build something using the Copilot Community SDK, let me know. I’ll happily try it out, share feedback, and help promote it. Let’s see how far we can push Copilot together.&lt;/p&gt;

</description>
      <category>githubcopilot</category>
      <category>rust</category>
      <category>java</category>
    </item>
    <item>
      <title>Have You Ever Migrated To or From Rust? Lessons from Java Community Voices</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Wed, 15 Oct 2025 04:00:00 +0000</pubDate>
      <link>https://dev.to/brunoborges/have-you-ever-migrated-to-or-from-rust-lessons-from-java-community-voices-589k</link>
      <guid>https://dev.to/brunoborges/have-you-ever-migrated-to-or-from-rust-lessons-from-java-community-voices-589k</guid>
      <description>&lt;h2&gt;
  
  
  Setting the Stage
&lt;/h2&gt;

&lt;p&gt;On &lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/have_you_migrated_to_or_from_rust/" rel="noopener noreferrer"&gt;r/java&lt;/a&gt;, developer &lt;a href="https://www.reddit.com/user/fenugurod/" rel="noopener noreferrer"&gt;u/fenugurod&lt;/a&gt; started a thoughtful discussion:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I don't want to start any flame war but I'm seeking different perspectives than mine. I'm doing a career change out of Go right now into Java, because the company that I work on decided to use more Java, and it has been nice. On personal projects I was kind of doing the same, but to Rust.  &lt;/p&gt;

&lt;p&gt;I like Go's tradeoffs, but over time I'm getting disappointed with the type system that does not let me express logic with more precision. I think Rust would be a perfect language, for me, if it had a GC. The immutability by default, option and result types, enums, are really good. On the other hand, Java has an amazing GC, is getting more and more features, and now I'm wondering if Java could fill this sweet spot of development that I'm looking for. A massive point in favour of Java is the market, there are almost no positions to Rust.  &lt;/p&gt;

&lt;p&gt;This is the reason of this thread, to understand if others have migrated to or from Rust to get their perspectives. I don't care that much about performance, Rust is faster but Java is plenty. I also don't care about shiny new type system features. The core for me is immutability (optional), ADT, Option/Result types. I care about safety, simplicity, and being able to evolve the software over time.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This sparked a remarkably nuanced debate among developers who have lived in both ecosystems. The comments reveal less of a &lt;em&gt;language war&lt;/em&gt; and more of a pragmatic conversation about &lt;strong&gt;safety, maintainability, and team dynamics&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s unpack what the thread teaches us.&lt;/p&gt;




&lt;h2&gt;
  
  
  1. “Don’t ‘migrate’ — learn broadly.”
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/comment/lv7b92q/" rel="noopener noreferrer"&gt;u/bowbahdoe&lt;/a&gt; put it best:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;You as an individual should not "migrate" to anything. Learn all the things you are able to. Take the jobs that serve your interests.  &lt;/p&gt;

&lt;p&gt;Because its always fun to point out and not everyone knows, you can get ADTs similar to Rust in Java via sealed interfaces.&lt;/p&gt;


&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="n"&gt;sealed&lt;/span&gt; &lt;span class="kd"&gt;interface&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Dog&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Cat&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;double&lt;/span&gt; &lt;span class="n"&gt;jumpDistance&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
    &lt;span class="n"&gt;record&lt;/span&gt; &lt;span class="nf"&gt;Mouse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;boolean&lt;/span&gt; &lt;span class="n"&gt;quiet&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;Animal&lt;/span&gt; &lt;span class="o"&gt;{}&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Analysis:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This perspective reflects a mature understanding of how engineers grow. Technology decisions often feel existential early in a career (“Should I be a Rust developer or a Java developer?”), but over time, the best engineers realize that &lt;em&gt;breadth builds judgment&lt;/em&gt;.  &lt;/p&gt;

&lt;p&gt;As the commenter notes, Java continues to evolve in expressiveness—features like &lt;em&gt;sealed classes&lt;/em&gt;, &lt;em&gt;pattern matching&lt;/em&gt;, and &lt;em&gt;records&lt;/em&gt; are closing the gap with Rust’s algebraic data types.&lt;/p&gt;




&lt;h2&gt;
  
  
  2. The Hybrid Reality: Use Both
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/comment/lv6sz6e/" rel="noopener noreferrer"&gt;u/repeating_bears&lt;/a&gt; offered a pragmatic example:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I'm currently using both. Mostly Java but my application has a native component that's written in Rust. I tried Graal initially, but it seems like it doesn't or didn't support AWT/Swing on Windows and I needed that.  &lt;/p&gt;

&lt;p&gt;There are some really good things about Rust. Other than what you mentioned, serde is great, and the type inference is much better than Java (e.g. keeping the inference context across things like chained method invocations). Macros are great a lot of the time but occasionally horrible.  &lt;/p&gt;

&lt;p&gt;But I personally am not and don't aspire to be a low-level systems programmer. I'm happy with the performance of the JVM. It's always been good enough for every real-world performance requirement I've had. I feel like a lot of the annoying aspects of Rust exist because the language designers care about things that I don't generally care about. That's not a criticism, it just doesn't really fit my programming niche.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Commentary:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This captures the “&lt;strong&gt;and, not or&lt;/strong&gt;” philosophy that defines modern software stacks. Rust and Java can coexist beautifully: Rust shines where performance or memory control matter (native modules, libraries, system glue), while Java dominates in business logic, scalability, and developer productivity.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Language boundaries are not hard walls anymore. JNI, GraalVM, and FFI make hybrid architectures practical. The best developers choose integration, not migration.&lt;/p&gt;




&lt;h2&gt;
  
  
  3. The Cost of Rewrites
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/comment/lv7af7h/" rel="noopener noreferrer"&gt;u/lambda_lord_legacy&lt;/a&gt; reminded everyone:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;People here are going to like java. People in the rust sub will like rust. Everyone has their preferences.  &lt;/p&gt;

&lt;p&gt;At the end of the day these are just tools. Unless you're doing something very low level, you can create all the same things in java vs rust. So it's up to you what you want to do.  &lt;/p&gt;

&lt;p&gt;Migrations have cost. You need to be REALLY sure you will benefit from it to make it worth while.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Analysis:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Rewrites are emotionally satisfying but strategically risky. Organizations often underestimate &lt;strong&gt;migration debt&lt;/strong&gt; — the integration work, testing effort, and retraining required when adopting a new language.  &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key takeaway:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Unless there’s a structural or existential reason (like platform support or runtime constraints), rewriting in another language rarely delivers proportional value. Optimize where it matters most, and measure impact before committing.&lt;/p&gt;




&lt;h2&gt;
  
  
  4. Java Is “Fast Enough”
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/comment/lv7g2of/" rel="noopener noreferrer"&gt;u/SuspiciousDepth5924&lt;/a&gt; added:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Honestly, even with very low level stuff you could probably get by with having most of your code in java* assuming you'd be willing to deal with FFI for the hot loops. I mean people get by with writing python for ML workloads, and it actually works since most of the heavy lifting is being done by C and friends.  &lt;/p&gt;

&lt;p&gt;And I mean Java &lt;em&gt;is&lt;/em&gt; fast, just not quite "John Carmack optimized C" fast.  &lt;/p&gt;

&lt;p&gt;(*) Notable exception is places where you can't run a jvm in the first place like small microcontrollers etc.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Commentary:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is a reality many forget: for 95% of applications, &lt;strong&gt;Java’s performance is not the bottleneck&lt;/strong&gt;. The JVM’s JIT compiler, adaptive optimization, and garbage collection make it “fast enough” for the vast majority of workloads.  &lt;/p&gt;

&lt;p&gt;Rust shines in specialized domains—embedded, real-time, or performance-critical systems—but most businesses care more about &lt;strong&gt;delivery velocity&lt;/strong&gt; and &lt;strong&gt;maintainability&lt;/strong&gt; than microsecond gains.&lt;/p&gt;




&lt;h2&gt;
  
  
  5. Beyond Java and Rust: The JVM Ecosystem
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://www.reddit.com/r/java/comments/1o6lihq/comment/lv77kgl/" rel="noopener noreferrer"&gt;u/Typen&lt;/a&gt; shared a balanced view:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;I have a strong preference for Java based on my personal experience level with it, but Rust is a fine language. I know the internet likes to argue over every little thing, but I'm in the camp that likes them both.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;&lt;strong&gt;Insight:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
The modern JVM gives you more than one flavor of expressiveness. &lt;strong&gt;Kotlin&lt;/strong&gt;, &lt;strong&gt;Scala&lt;/strong&gt;, and &lt;strong&gt;Clojure&lt;/strong&gt; all offer richer type systems or functional paradigms while keeping Java interoperability.  &lt;/p&gt;

&lt;p&gt;If what draws you to Rust is its safety and ADT-like expressiveness, you might already find 80% of that inside the JVM without leaving your ecosystem.&lt;/p&gt;




&lt;h2&gt;
  
  
  Key Takeaways for Professionals
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Consideration&lt;/th&gt;
&lt;th&gt;Question to Ask&lt;/th&gt;
&lt;th&gt;Strategic Insight&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Motivation clarity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Why do I want this migration?&lt;/td&gt;
&lt;td&gt;If it’s about language aesthetics, the payoff may not justify the cost.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Ecosystem &amp;amp; team readiness&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can my team learn Rust? Is hiring feasible?&lt;/td&gt;
&lt;td&gt;Rust’s talent pool is still small compared to Java’s.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Hybrid vs full rewrite&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Can we adopt Rust only where it adds value?&lt;/td&gt;
&lt;td&gt;Many organizations succeed with hybrid architectures.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Refactoring cost &amp;amp; risk&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;How much regression risk can we afford?&lt;/td&gt;
&lt;td&gt;Rewrites introduce new bugs as fast as they remove old ones.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Maintainability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Who will maintain this five years from now?&lt;/td&gt;
&lt;td&gt;Familiarity and tooling often outweigh elegance.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Alternative paths&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Would Kotlin or Scala satisfy our needs?&lt;/td&gt;
&lt;td&gt;Sometimes the evolution path is within the same ecosystem.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Tooling maturity&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Are build, debug, and deploy pipelines ready?&lt;/td&gt;
&lt;td&gt;Tooling gaps can outweigh language benefits.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This Reddit thread is a snapshot of something bigger: &lt;strong&gt;how developers mature in their thinking about tools&lt;/strong&gt;.  &lt;/p&gt;

&lt;p&gt;Rust and Java aren’t competitors as much as they are complementary paradigms — one optimized for control and safety, the other for scalability and ecosystem depth.  &lt;/p&gt;

&lt;p&gt;The smartest developers don’t pledge allegiance. They build judgment.&lt;/p&gt;

&lt;p&gt;When you’re faced with the “Rust vs Java” question, reframe it as:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;“What problem am I solving, and which language makes my team fastest, safest, and happiest doing it?”&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the migration that really matters.&lt;/p&gt;

</description>
      <category>java</category>
      <category>rust</category>
      <category>programming</category>
    </item>
    <item>
      <title>A Better Way to Tune the JVM in Dockerfiles and Kubernetes Manifests</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Fri, 12 Sep 2025 03:36:26 +0000</pubDate>
      <link>https://dev.to/azure/a-better-way-to-tune-the-jvm-in-dockerfiles-and-kubernetes-manifests-329c</link>
      <guid>https://dev.to/azure/a-better-way-to-tune-the-jvm-in-dockerfiles-and-kubernetes-manifests-329c</guid>
      <description>&lt;p&gt;When tuning the JVM inside containers, I often see Dockerfiles and Kubernetes manifests with long, hard-to-read java commands packed with -Xmx, -XX:+UseG1GC, and other flags. Every time you want to tweak memory or GC settings, you have to edit those commands and rebuild or redeploy.&lt;/p&gt;




&lt;h3&gt;
  
  
  Free webinar Java on Kubernetes Performance Engineering
&lt;/h3&gt;

&lt;p&gt;Running Java on Kubernetes? It's harder - and more critical - than you think. Join us September 25th, 2025, for a live webinar with Akamas &amp;amp; Microsoft on tuning Java apps at scale. JVM tips, GC tuning, JDK 25, Project Leyden &amp;amp; more.&lt;/p&gt;

&lt;p&gt;Register at &lt;a href="https://akamas.io/events/java-on-kubernetes-lessons-in-performance-engineering-with-akamas-and-microsoft/" rel="noopener noreferrer"&gt;https://akamas.io/events/java-on-kubernetes-lessons-in-performance-engineering-with-akamas-and-microsoft/&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  What about JAVA_OPTS?
&lt;/h1&gt;

&lt;p&gt;This flag is not read by the JVM itself. It only works if the launch script explicitly uses it and expands its content into the call to the java launcher. It behaves exactly the same as Apache Tomcat's CATALINA_OPTS, which is read by catalina.sh.&lt;/p&gt;

&lt;p&gt;There’s a much cleaner way to do this... and it’s built into the JDK. It’s the most modern, well-scoped solution, playing well with deployment orchestration solutions like Kubernetes.&lt;/p&gt;

&lt;h1&gt;
  
  
  🚀 Enter JDK_JAVA_OPTIONS
&lt;/h1&gt;

&lt;p&gt;JDK_JAVA_OPTIONS is an environment variable that the JVM reads automatically. Whatever you put there gets appended to the command line of every JDK tool (java, javac, jshell, etc.) This means you can move all your tuning flags out of your launcher command, keeping your Dockerfiles and manifests clean and maintainable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Cleaned-up Dockerfile
&lt;/h2&gt;

&lt;p&gt;Before (messy):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["java", "-Xmx512m", "-XX:+UseG1GC", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After (clean):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;ENV&lt;/span&gt;&lt;span class="s"&gt; JDK_JAVA_OPTIONS="-Xmx512m -XX:+UseG1GC"&lt;/span&gt;
&lt;span class="k"&gt;CMD&lt;/span&gt;&lt;span class="s"&gt; ["java", "-jar", "app.jar"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now, if you need to change heap size or GC settings, you just update the environment variable. No need to touch the command.&lt;/p&gt;

&lt;h2&gt;
  
  
  Kubernetes Example
&lt;/h2&gt;

&lt;p&gt;If your container is likely to end up on Kubernetes, don't even tune the JVM in the Dockerfile. Consider leaving this as an external configuration, coming from the Kubernetes manifest. &lt;/p&gt;

&lt;p&gt;This is because the container image doesn't know what memory limits will be applied during runtime. And even if you set a MaxRAMPercentage for the heap, the amount you set may not be ideal if the container ends up with too much memory, potentially wasting memory from the delta not used, for example.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;containers&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp&lt;/span&gt;
    &lt;span class="na"&gt;image&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;myapp:latest&lt;/span&gt;
    &lt;span class="na"&gt;env&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
      &lt;span class="pi"&gt;-&lt;/span&gt; &lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;JDK_JAVA_OPTIONS&lt;/span&gt;
        &lt;span class="na"&gt;value&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="s"&gt;-Xmx512m&lt;/span&gt;&lt;span class="nv"&gt; &lt;/span&gt;&lt;span class="s"&gt;-XX:+UseG1GC"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your deployment YAML stays clean and your operations team can tune JVM behavior at deploy time without changing the image.&lt;/p&gt;

&lt;h1&gt;
  
  
  Why JDK_JAVA_OPTIONS Is Better
&lt;/h1&gt;

&lt;p&gt;✅ Cleaner Manifests – fewer arguments to maintain&lt;br&gt;
🔄 Configurable at Runtime – no rebuild required&lt;br&gt;
🛠 Debug-Friendly – JVM prints the options it picked up&lt;br&gt;
🐳 Perfect for Containers – works across Docker, Kubernetes, ECS, etc.&lt;br&gt;
🎯 Applies to All JDK Tools – not just java, but also javac, jshell, etc.&lt;/p&gt;

&lt;p&gt;If you’re running Java apps in Docker or Kubernetes, stop hardcoding JVM flags into your command line. Use JDK_JAVA_OPTIONS instead. It makes your images cleaner, your manifests easier to read, and your JVM tuning more flexible.&lt;/p&gt;
&lt;h1&gt;
  
  
  Precedence, Order of Priority, and Scope
&lt;/h1&gt;

&lt;p&gt;The HotSpot JVM supports, in practice, three environment variables. And for many historical reasons we ended up in this situation. &lt;/p&gt;

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

&lt;p&gt;But in what order are they processed, and where? The rule of thumb is below. Except that in JDK 8, the variable JDK_JAVA_OPTIONS is not supported.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;
_JAVA_OPTIONS &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; JDK_JAVA_OPTIONS &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; JAVA_TOOL_OPTIONS

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Besides the order they are processed, there is also where they are processed and how this impacts what parameters are effective. &lt;/p&gt;

&lt;p&gt;According to the bug &lt;a href="https://bugs.openjdk.org/browse/JDK-8170832" rel="noopener noreferrer"&gt;JDK-8170832&lt;/a&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;code&gt;_JAVA_OPTIONS&lt;/code&gt; and &lt;code&gt;JAVA_TOOL_OPTIONS&lt;/code&gt; environment variable are interpreted by the VM, not the launcher [java], hence no @-files or launcher-only options. The former is undocumented and unsupported but widely used; the latter is documented and supported.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You can see for yourself the behaviour of these environment variables with this little project I built (with GitHub Copilot): &lt;a href="https://github.com/brunoborges/jdk-env-vars" rel="noopener noreferrer"&gt;github.com/brunoborges/jdk-env-vars&lt;/a&gt;&lt;/p&gt;

&lt;h1&gt;
  
  
  Thoughts?
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;What JVM tuning tricks are you using in containers today?&lt;/li&gt;
&lt;li&gt;Have you tried JDK_JAVA_OPTIONS yet? &lt;/li&gt;
&lt;li&gt;Did you know about it? &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Comment below.&lt;/p&gt;

&lt;h1&gt;
  
  
  Resources
&lt;/h1&gt;

&lt;p&gt;Resources&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/troubleshoot/environment-variables-and-system-properties.html#GUID-A91E7E21-2E91-48C4-89A4-836A7C0EE93B" rel="noopener noreferrer"&gt;OpenJDK 21: JAVA_TOOL_OPTIONS documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.oracle.com/en/java/javase/21/docs/specs/man/java.html#using-the-jdk_java_options-launcher-environment-variable" rel="noopener noreferrer"&gt;OpenJDK 21: JDK_JAVA_OPTIONS documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://bugs.openjdk.org/browse/JDK-8170832" rel="noopener noreferrer"&gt;JDK-8170832&lt;/a&gt;: Add a new launcher environment variables&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>kubernetes</category>
      <category>java</category>
      <category>docker</category>
      <category>performance</category>
    </item>
    <item>
      <title>Announcing Azure Command Launcher for Java</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Tue, 27 May 2025 23:36:41 +0000</pubDate>
      <link>https://dev.to/azure/announcing-azure-command-launcher-for-java-jo3</link>
      <guid>https://dev.to/azure/announcing-azure-command-launcher-for-java-jo3</guid>
      <description>&lt;h2&gt;
  
  
  Optimizing JVM Configuration for Azure Deployments
&lt;/h2&gt;

&lt;p&gt;Tuning the Java Virtual Machine (JVM) for cloud deployments is notoriously challenging. Over 30% of developers tend to deploy Java workloads with no JVM configuration at all, therefore relying on the default settings of the HotSpot JVM. &lt;/p&gt;

&lt;p&gt;The default settings in OpenJDK are intentionally conservative, designed to work across a wide range of environments and scenarios. However, these defaults often lead to suboptimal resource utilization in cloud-based deployments, where memory and CPU tend to be dedicated for application workloads (use of containers and VMs) but still require intelligent management to maximize efficiency and cost-effectiveness.&lt;/p&gt;

&lt;p&gt;To address this, we are excited to introduce jaz, a new JVM launcher optimized specifically for Azure. jaz provides better default ergonomics for Java applications running in containers and virtual machines, ensuring a more efficient use of resources right from the start, and leverages advanced JVM features automatically, such as &lt;a href="https://dev.java/learn/jvm/cds-appcds/#appcds" rel="noopener noreferrer"&gt;AppCDS&lt;/a&gt; and in the future, &lt;a href="https://openjdk.org/projects/leyden/" rel="noopener noreferrer"&gt;Project Leyden&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why jaz?
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Conservative Defaults Lead to Underutilization of Resources
&lt;/h3&gt;

&lt;p&gt;When deploying Java applications to the cloud, developers often need to fine-tune JVM parameters such as heap size, garbage collection strategies, and other tuning configurations to achieve better resource utilization and potentially higher performance. The default OpenJDK settings, while safe, do not take full advantage of available resources in cloud environments, leading to unnecessary waste and increased operational costs.&lt;/p&gt;

&lt;p&gt;While advancements in dynamic heap sizing are underway by Oracle, Google, and Microsoft, they are still in development and will be available primarily in future major releases of OpenJDK. In the meantime, developers running applications on current and older JDK versions (such as OpenJDK 8, 11, 17, and 21) still need to optimize their configurations manually or rely on external tools like Paketo Buildpacks, which automate tuning but may not be suitable for all use cases.&lt;/p&gt;

&lt;p&gt;With jaz, we are providing a smarter starting point for Java applications on Azure, with default configurations designed for cloud environments. The jaz launcher helps by:&lt;/p&gt;

&lt;p&gt;Optimizing resource utilization: By setting JVM parameters tailored for cloud deployments, jaz reduces wasted memory and CPU cycles.&lt;br&gt;
Improve first-deploy performance: New applications often require trial and error to find the right JVM settings. jaz increases the likelihood of better performance on first deployment.&lt;br&gt;
Enhance cost efficiency: By making better use of available resources, applications using jaz can reduce unnecessary cloud costs.&lt;/p&gt;

&lt;p&gt;This tool is ideal for developers who:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Want better JVM defaults without diving deep into tuning guides&lt;/li&gt;
&lt;li&gt;Develop and deploy cloud native microservices with Spring Boot, Quarkus, or Micronaut&lt;/li&gt;
&lt;li&gt;Prefer container-based workflows such as Kubernetes and OpenShift&lt;/li&gt;
&lt;li&gt;Deploy Java workloads on Azure Container Apps, Azure Kubernetes Service, Azure Red Hat OpenShift, or Azure VMs&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  How jaz works?
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;jaz&lt;/code&gt; sits between your container startup command and the JVM. It will:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Detect the cloud environment (e.g., container limits, available memory)&lt;/li&gt;
&lt;li&gt;Analyzes the workload type and selects best-fit JVM options&lt;/li&gt;
&lt;li&gt;Launches the Java process with optimized flags, such as:

&lt;ul&gt;
&lt;li&gt;Heap sizing&lt;/li&gt;
&lt;li&gt;GC selection and tuning&lt;/li&gt;
&lt;li&gt;Logging and diagnostics settings as needed&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  Example Usage
&lt;/h2&gt;

&lt;p&gt;Instead of this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ JAVA_OPTS="-XX:... several JVM tuning flags"
$ java $JAVA_OPTS -jar myapp.jar"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use:&lt;/p&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;

$ jaz -jar myapp.jar

You will automatically benefit from:

 * Battle-tested defaults for cloud native and container workloads
 * Reduced memory waste
 * Better startup and warmup performance
 * No manual tuning required

## How to Access jaz (Private Preview)

jaz is currently available through a Private Preview. During this phase, we are working closely with selected customers to refine the experience and gather feedback.

To request access:

👉 [Submit your interest here](https://aka.ms/jaz-privatepreview)

Participants in the Private Preview will receive access to jaz via easily installed standalone Linux packages for container images of the Microsoft Build of OpenJDK and Eclipse Temurin (for Java 8). Customers will have direct communication with our engineering and product teams to further enhance the tool to fit their needs. For a sneak peek, you can read the [documentation](http://learn.microsoft.com/java/jaz/overview).

## Our Roadmap

Our long-term vision for `jaz` includes adaptive JVM configuration based on telemetry and usage patterns, helping developers achieve optimal performance across all Azure services.

⚙️ JVM Configuration Profiles
📦 AppCDS Support
📦 Leyden Support
🔄 Continuous Tuning
📊 Share telemetry through Prometheus

We’re excited to work with the Java community to shape this tool. Your feedback will be critical in helping us deliver a smarter, cloud-native Java runtime experience on Azure.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

</description>
      <category>azure</category>
      <category>java</category>
      <category>openjdk</category>
      <category>cloud</category>
    </item>
    <item>
      <title>Java 15 in 2020: Reasons to *not* use Java?</title>
      <dc:creator>Bruno Borges</dc:creator>
      <pubDate>Wed, 27 May 2020 01:52:28 +0000</pubDate>
      <link>https://dev.to/brunoborges/java-15-in-2020-reasons-to-not-use-java-3ekg</link>
      <guid>https://dev.to/brunoborges/java-15-in-2020-reasons-to-not-use-java-3ekg</guid>
      <description>&lt;p&gt;In 2020, Java 15 will be released with major features (some coming out of preview mode), both at language, API, and runtime levels.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://openjdk.java.net/projects/jdk/15/" rel="noopener noreferrer"&gt;https://openjdk.java.net/projects/jdk/15/&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Considering the changes that have already made through since Java 8 and the upcoming changes in Java 15, what are in your opinion the use cases that no longer make sense to use Java, and why?&lt;/p&gt;

</description>
      <category>healthydebate</category>
      <category>java</category>
      <category>javascript</category>
      <category>go</category>
    </item>
  </channel>
</rss>
