<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[one step at a time]]></title><description><![CDATA[one step at a time]]></description><link>https://zziga.com</link><generator>RSS for Node</generator><lastBuildDate>Mon, 13 Apr 2026 23:08:52 GMT</lastBuildDate><atom:link href="https://zziga.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Custom JRE for spring-boot app (Java 20)]]></title><description><![CDATA[I start with a very simple spring-boot project generated by Spring Initializr.
Inpom.xml I've got spring-boot-starter-parent as parent and java.version
<project ...>
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boo...]]></description><link>https://zziga.com/custom-jre-for-spring-boot-app-java-20</link><guid isPermaLink="true">https://zziga.com/custom-jre-for-spring-boot-app-java-20</guid><category><![CDATA[JRE vs JDK]]></category><category><![CDATA[java20]]></category><category><![CDATA[Dockerfile]]></category><category><![CDATA[Springboot]]></category><category><![CDATA[custom-jre]]></category><dc:creator><![CDATA[Ziga G]]></dc:creator><pubDate>Fri, 24 Mar 2023 08:02:52 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/stock/unsplash/nDd3dIkkOLo/upload/d0966c439e03318bfd4c7dad4e938103.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>I start with a very simple spring-boot project generated by <a target="_blank" href="https://start.spring.io/#!type=maven-project&amp;language=java&amp;platformVersion=3.0.5&amp;packaging=jar&amp;jvmVersion=20&amp;groupId=foo.bar&amp;artifactId=demo&amp;name=demo&amp;description=Demo%20project%20for%20Spring%20Boot&amp;packageName=foo.bar.demo&amp;dependencies=web">Spring Initializr</a>.</p>
<p>In<code>pom.xml</code> I've got <code>spring-boot-starter-parent</code> as parent and <code>java.version</code></p>
<pre><code class="lang-xml"><span class="hljs-tag">&lt;<span class="hljs-name">project</span> <span class="hljs-attr">...</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">modelVersion</span>&gt;</span>4.0.0<span class="hljs-tag">&lt;/<span class="hljs-name">modelVersion</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">parent</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>org.springframework.boot<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>spring-boot-starter-parent<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>3.0.5<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">relativePath</span>/&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">parent</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">groupId</span>&gt;</span>foo.bar<span class="hljs-tag">&lt;/<span class="hljs-name">groupId</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">artifactId</span>&gt;</span>demo-custom-jre<span class="hljs-tag">&lt;/<span class="hljs-name">artifactId</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">version</span>&gt;</span>0.0.1-SNAPSHOT<span class="hljs-tag">&lt;/<span class="hljs-name">version</span>&gt;</span>
  <span class="hljs-tag">&lt;<span class="hljs-name">properties</span>&gt;</span>
    <span class="hljs-tag">&lt;<span class="hljs-name">java.version</span>&gt;</span>20<span class="hljs-tag">&lt;/<span class="hljs-name">java.version</span>&gt;</span>
  <span class="hljs-tag">&lt;/<span class="hljs-name">properties</span>&gt;</span>
...
</code></pre>
<p>Does it start? Yes!</p>
<pre><code class="lang-bash">mvn spring-boot:run
...
[INFO] --- spring-boot-maven-plugin:3.0.5:run (default-cli) @ demo-custom-jre ---
[INFO] Attaching agents: []

  .   ____          _            __ _ _
 /\\ / ___<span class="hljs-string">'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '</span>_ | <span class="hljs-string">'_| | '</span>_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  <span class="hljs-string">'  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.0.5)

INFO [ main] c.z.d.d.DemoCustomJreApplication         : Starting DemoCustomJreApplication using Java 20 with PID 58559 (/Users/ziga/git/demo-custom-jre/target/classes started by ziga in /Users/ziga/git/demo-custom-jre)
...
INFO [ main] c.z.d.d.DemoCustomJreApplication         : Started DemoCustomJreApplication in 0.664 seconds (process running for 0.829)
[CTRL-C]</span>
</code></pre>
<p>In Spring Boot guide <a target="_blank" href="https://spring.io/guides/topicals/spring-boot-docker/">https://spring.io/guides/topicals/spring-boot-docker/</a> thet start with a basic <code>Dockerfile</code></p>
<pre><code class="lang-yaml"><span class="hljs-string">FROM</span> <span class="hljs-string">eclipse-temurin:17-jdk-alpine</span>
<span class="hljs-string">VOLUME</span> <span class="hljs-string">/tmp</span>
<span class="hljs-string">ARG</span> <span class="hljs-string">JAR_FILE</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">${JAR_FILE}</span> <span class="hljs-string">app.jar</span>
<span class="hljs-string">ENTRYPOINT</span> [<span class="hljs-string">"java"</span>,<span class="hljs-string">"-jar"</span>,<span class="hljs-string">"/app.jar"</span>]
</code></pre>
<p>I <em>could</em> use it to build and start the app</p>
<pre><code class="lang-bash">./mvnw package
docker build --build-arg JAR_FILE=target/*.jar -t foo .
docker run -p 8080:8080 foo
</code></pre>
<p>but since I want to be on Java 20 I need a Docker Image with JDK/JRE version 20. Or I build a Custom JRE for my app.</p>
<p>I want to keep it simple, so everything shall be done in a Dockerfile. Multi-stage Docker build helps me to segregate steps a bit:</p>
<ul>
<li><p>compile and build the app</p>
</li>
<li><p>identify the required Java modules</p>
</li>
<li><p>build Custom JRE</p>
</li>
<li><p>pack everything together</p>
</li>
</ul>
<pre><code class="lang-yaml"><span class="hljs-comment"># Build the app.jar</span>
<span class="hljs-string">FROM</span> <span class="hljs-string">azul/zulu-openjdk:20</span> <span class="hljs-string">as</span> <span class="hljs-string">app</span>
<span class="hljs-string">ENV</span> <span class="hljs-string">HOME=/usr/app</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">mkdir</span> <span class="hljs-string">-p</span> <span class="hljs-string">$HOME</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">$HOME</span>
<span class="hljs-string">ADD</span> <span class="hljs-string">.</span> <span class="hljs-string">$HOME</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">./mvnw</span> <span class="hljs-string">package</span> <span class="hljs-string">-DskipTests</span>

<span class="hljs-comment"># Use 'jdeps' to generate list of required modules</span>
<span class="hljs-string">FROM</span> <span class="hljs-string">azul/zulu-openjdk:20</span> <span class="hljs-string">as</span> <span class="hljs-string">deps</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/usr/app</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">--from=app</span> <span class="hljs-string">/usr/app/target/*.jar</span> <span class="hljs-string">app.jar</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">set</span> <span class="hljs-string">-eux;</span> <span class="hljs-string">\</span>
  <span class="hljs-string">"$JAVA_HOME"</span><span class="hljs-string">/bin/jar</span> <span class="hljs-string">-xf</span> <span class="hljs-string">app.jar</span> <span class="hljs-string">&amp;&amp;</span> <span class="hljs-string">\</span>
  <span class="hljs-string">"$JAVA_HOME"</span><span class="hljs-string">/bin/jdeps</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--ignore-missing-deps</span> <span class="hljs-string">\</span>
  <span class="hljs-string">-q</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--multi-release</span> <span class="hljs-number">20</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--recursive</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--print-module-deps</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--class-path="./BOOT-INF/lib/*"</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--module-path="./BOOT-INF/lib/*"</span> <span class="hljs-string">\</span>
  <span class="hljs-string">./app.jar</span> <span class="hljs-string">&gt;</span> <span class="hljs-string">jre-deps.info</span>

<span class="hljs-comment"># Build Custom JRE</span>
<span class="hljs-string">FROM</span> <span class="hljs-string">azul/zulu-openjdk:20</span> <span class="hljs-string">as</span> <span class="hljs-string">builder</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/usr/app</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">--from=deps</span> <span class="hljs-string">/usr/app/jre-deps.info</span> <span class="hljs-string">jre-deps.info</span>
<span class="hljs-string">RUN</span> <span class="hljs-string">set</span> <span class="hljs-string">-eux;</span> <span class="hljs-string">\</span>
  <span class="hljs-string">"$JAVA_HOME"</span><span class="hljs-string">/bin/jlink</span> <span class="hljs-string">--verbose</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--compress</span> <span class="hljs-number">2</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--strip-java-debug-attributes</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--no-header-files</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--no-man-pages</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--output</span> <span class="hljs-string">jre</span> <span class="hljs-string">\</span>
  <span class="hljs-string">--add-modules</span> <span class="hljs-string">"$(cat jre-deps.info)"</span>

<span class="hljs-comment"># Build final image with app.jar and JRE from earlier stages</span>
<span class="hljs-string">FROM</span> <span class="hljs-string">ubuntu:20.04</span>
<span class="hljs-string">ENV</span> <span class="hljs-string">JAVA_HOME</span> <span class="hljs-string">/opt/java/openjdk</span>
<span class="hljs-string">ENV</span> <span class="hljs-string">PATH</span> <span class="hljs-string">$JAVA_HOME/bin:$PATH</span>
<span class="hljs-string">WORKDIR</span> <span class="hljs-string">/usr/app</span>
<span class="hljs-string">EXPOSE</span> <span class="hljs-number">8080</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">--from=builder</span> <span class="hljs-string">/usr/app/jre</span> <span class="hljs-string">$JAVA_HOME</span>
<span class="hljs-string">COPY</span> <span class="hljs-string">--from=deps</span> <span class="hljs-string">/usr/app/app.jar</span> <span class="hljs-string">app.jar</span>
<span class="hljs-string">ENTRYPOINT</span> [<span class="hljs-string">"java"</span>,<span class="hljs-string">"-jar"</span>, <span class="hljs-string">"app.jar"</span>]
</code></pre>
<p>Quick test, does it build?</p>
<pre><code class="lang-bash">docker build -t foo .
docker run -p 8080:8080 foo
</code></pre>
<p>Yeah! My app starts. Custom JRE for my app is small, just below 60 MB.</p>
<pre><code class="lang-bash"> docker <span class="hljs-built_in">history</span> 711bd2638b5e            
IMAGE          CREATED              CREATED BY                                      SIZE      COMMENT
...
&lt;missing&gt;      37 minutes ago       COPY /usr/app/jre /opt/java/openjdk <span class="hljs-comment"># buildk…   58.5MB    buildkit.dockerfile.v0</span>
...
</code></pre>
<p><em>What is the size of JDK/JRE in your Docker image?</em></p>
<p>Thanks!</p>
<p><strong><em>Note: The code and configuration shown above are oversimplified and not production-ready!</em></strong></p>
]]></content:encoded></item></channel></rss>