<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>miguel codes</title><link>https://www.mortega.dev/</link><description>Recent content on miguel codes</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><lastBuildDate>Tue, 13 Jun 2023 10:26:10 +0100</lastBuildDate><atom:link href="https://www.mortega.dev/index.xml" rel="self" type="application/rss+xml"/><item><title>Document your Spring Boot properties with Jbang</title><link>https://www.mortega.dev/posts/spring-boot-property-document-maven-jbang/</link><pubDate>Tue, 13 Jun 2023 10:26:10 +0100</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/spring-boot-property-document-maven-jbang/</guid><description>
&lt;p>One of my favorites low-key features of Spring Boot is the ability to map configuration properties from
your &lt;code>application.{yml|properties}&lt;/code>
into Java Beans using &lt;code>@ConfigurationProperties&lt;/code> annotation :&lt;/p>
&lt;ul>
&lt;li>It allows type-safe configuration binding&lt;/li>
&lt;li>It allows also easy validation using — with the standard JSR-380 specification.&lt;/li>
&lt;/ul>
&lt;p>Furthermore, using &lt;code>spring-boot-configuration-processor&lt;/code>, you can generate a configuration metadata in a JSON file,
which provides useful information on how to use the properties.&lt;/p>
&lt;p>While this is mostly useful for your IDE to provide autocompletion and usage, in this post we are going to look at how
to leverage this mechanism to document your custom properties in any format using &lt;a href="https://www.jbang.dev/">jbang&lt;/a>.&lt;/p>
&lt;div class="alert alert-info" role="alert">
If you are in a hurry, you can check directly the
code &lt;a href="https://github.com/mikomatic/jbang-catalog#spring-boot-property-documenter">➡️ here&lt;/a>
&lt;/div>
&lt;aside class="article-toc" style="
background-color: var(--toc-background-color);
border-radius: 15px;
padding: 15px;
margin-bottom: 15px;
">
&lt;h4>Table of contents&lt;/h4>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#creating-a-demo-project">Creating a demo project&lt;/a>&lt;/li>
&lt;li>&lt;a href="#generating-metadata-file">Generating metadata file&lt;/a>&lt;/li>
&lt;li>&lt;a href="#documenting-our-configuration-properties">Documenting our configuration properties&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#using-the-script">Using the script&lt;/a>&lt;/li>
&lt;li>&lt;a href="#options">Options&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#references">References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/aside>
&lt;h2 id="creating-a-demo-project">
&lt;a href="#creating-a-demo-project" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Creating a demo project
&lt;/h2>
&lt;p>First let&amp;rsquo;s create a very simple Spring Boot application. I generated one
using &lt;a href="https://start.spring.io">https://start.spring.io&lt;/a> (&lt;a href="https://start.spring.io/#!type=maven-project&amp;amp;language=java&amp;amp;platformVersion=3.1.0&amp;amp;packaging=jar&amp;amp;jvmVersion=17&amp;amp;groupId=com.example&amp;amp;artifactId=demo&amp;amp;name=demo&amp;amp;description=Demo%20project%20for%20Spring%20Boot&amp;amp;packageName=com.example.demo&amp;amp;dependencies=configuration-processor,lombok">🔗 example configuration&lt;/a>).&lt;/p>
&lt;p>First let&amp;rsquo;s start by defining our custom application properties.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">
&lt;span class="nd">@Component&lt;/span>
&lt;span class="nd">@ConfigurationProperties&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;my-service&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">MyProperties&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="cm">/** enable a feature */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kt">boolean&lt;/span> &lt;span class="n">enabled&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="kd">final&lt;/span> &lt;span class="n">Other&lt;/span> &lt;span class="n">other&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="k">new&lt;/span> &lt;span class="n">Other&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="c1">// getters &amp;amp; setters omitted for brevity
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kd">public&lt;/span> &lt;span class="kd">static&lt;/span> &lt;span class="kd">class&lt;/span> &lt;span class="nc">Other&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="cm">/** another documenter property */&lt;/span>
&lt;span class="kd">private&lt;/span> &lt;span class="n">String&lt;/span> &lt;span class="n">someProperty&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="s">&amp;#34;a default value&amp;#34;&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="c1">// getters &amp;amp; setters omitted for brevity
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Let&amp;rsquo;s note a few things:&lt;/p>
&lt;ul>
&lt;li>our properties are included in a standard java bean class&lt;/li>
&lt;li>documentation is provided via our usual javadoc&lt;/li>
&lt;li>💡tip: we define our custom properties in a given namespace. This is to ensure it does not clash with
other properties from spring core or a &lt;code>starter-*&lt;/code> dependency&lt;/li>
&lt;/ul>
&lt;div class="alert alert-info" role="alert">
We can activate our bean with standard property scanning (using &lt;code>@ConfigurationPropertiesScan&lt;/code>), annotating your
properties as a &lt;code>@Component&lt;/code> &lt;em>as in this example&lt;/em>, or by explicitly declaring it
with &lt;code>@EnableConfigurationProperties&lt;/code>. &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.external-config.typesafe-configuration-properties">Check the official documentation.&lt;/a>
&lt;/div>
&lt;h2 id="generating-metadata-file">
&lt;a href="#generating-metadata-file" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Generating metadata file
&lt;/h2>
&lt;p>To generate a metadata files, let&amp;rsquo;s follow the &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.annotation-processor.configuring">instructions for maven&lt;/a>, by adding the needed dependency in
our &lt;code>pom.xml&lt;/code> (&lt;em>configuration is provided for gradle users too&lt;/em>).&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-xml" data-lang="xml">
&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.springframework.boot&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>spring-boot-configuration-processor&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;optional&amp;gt;&lt;/span>true&lt;span class="nt">&amp;lt;/optional&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now to generate the configuration metadata, a standard &lt;code>mvn compile&lt;/code> command should generate our file in the build
folder: &lt;code>META-INF/spring-configuration-metadata.json&lt;/code>.
It should look something like this :&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-json" data-lang="json">&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;groups&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;my-service&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;com.example.demo.MyProperties&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;sourceType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;com.example.demo.MyProperties&amp;#34;&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">],&lt;/span>
&lt;span class="nt">&amp;#34;properties&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="p">[&lt;/span>
&lt;span class="p">{&lt;/span>
&lt;span class="nt">&amp;#34;name&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;my-service.enabled&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;type&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;java.lang.Boolean&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;description&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;enable a feature&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;sourceType&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="s2">&amp;#34;com.example.demo.MyProperties&amp;#34;&lt;/span>&lt;span class="p">,&lt;/span>
&lt;span class="nt">&amp;#34;defaultValue&amp;#34;&lt;/span>&lt;span class="p">:&lt;/span> &lt;span class="kc">false&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;span class="p">]&lt;/span>
&lt;span class="p">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The generated file contains our property name, value and description.&lt;/p>
&lt;p>The configuration metadata format could also contain deprecation information, or even hints with possible values for a
given property. You could even provide manual hints via an additional file. Be sure to check
the &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html#appendix.configuration-metadata.format">official docs!&lt;/a>.&lt;/p>
&lt;h2 id="documenting-our-configuration-properties">
&lt;a href="#documenting-our-configuration-properties" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Documenting our configuration properties
&lt;/h2>
&lt;p>Now that we have a &lt;code>json&lt;/code> file containing all the information we need, it would be nice to have a script that can:&lt;/p>
&lt;ul>
&lt;li>parse the &lt;code>json&lt;/code> data from one or many files&lt;/li>
&lt;li>aggregate it (in case of multiple files)&lt;/li>
&lt;li>export it to a file using a templating engine (to support any format: &lt;code>markdown&lt;/code>, &lt;code>html&lt;/code>, &lt;code>asciidoc&lt;/code>)&lt;/li>
&lt;/ul>
&lt;p>This is exactly what I did using JBang, that I
discovered &lt;a href="https://www.mortega.dev/posts/how-to-automatically-rebase-all-your-merge-requests-on-gitlab-jbang-august-2022/">not so long ago&lt;/a>.&lt;/p>
&lt;p>This time around I discovered that you can publish your scripts and easily share them with the world 🎉&lt;/p>
&lt;h3 id="using-the-script">
&lt;a href="#using-the-script" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Using the script
&lt;/h3>
&lt;p>The source code is available
on &lt;a href="https://github.com/mikomatic/jbang-catalog/blob/main/springPropertyDocumenter.java">github&lt;/a>.&lt;/p>
&lt;p>Using
jbang&amp;rsquo;s &lt;a href="https://www.jbang.dev/documentation/guide/latest/alias_catalogs.html#implicit-alias-catalogs">implicit alias catalog&lt;/a>
you can call the script as easily as a single shell command.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">jbang springPropertyDocumenter@mikomatic -o generated-docs.md
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This will download the script (you have to trust the source thought 😏), parse your current folder looking
for &lt;code>META-INF/spring-configuration-metadata.json&lt;/code> files and generates a markdown version using a default template.
Then it outputs the result to &lt;code>generated-docs.md&lt;/code>&lt;/p>
&lt;p>Result should look like this (&lt;em>with your favorite markdown editor&lt;/em>):&lt;/p>
&lt;p>
&lt;img loading="lazy" src="https://www.mortega.dev/images/springDocumenter/result.png" alt="spring documenter result">
&lt;/p>
&lt;h3 id="options">
&lt;a href="#options" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Options
&lt;/h3>
&lt;p>As any command line application, usage is provided via the &lt;code>-h&lt;/code> options:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-shell" data-lang="shell">❯ jbang springPropertyDocumenter@mikomatic -h
Usage: springPropertyDocumenter &lt;span class="o">[&lt;/span>-hV&lt;span class="o">]&lt;/span> -o&lt;span class="o">=&lt;/span>&amp;lt;output&amp;gt; &lt;span class="o">[&lt;/span>-t&lt;span class="o">=&lt;/span>&amp;lt;templateFile&amp;gt;&lt;span class="o">]&lt;/span>
&lt;span class="o">[&lt;/span>-m&lt;span class="o">=&lt;/span>&amp;lt;metadataLocationFolders&amp;gt;&lt;span class="o">]&lt;/span>...
Document spring boot properties based on property metadata
-h, --help Show this &lt;span class="nb">help&lt;/span> message and exit.
-m, --metadata-location-folders&lt;span class="o">=&lt;/span>&amp;lt;metadataLocationFolders&amp;gt;
Folder&lt;span class="o">(&lt;/span>s&lt;span class="o">)&lt;/span> containing spring boot configuration
metadata files &lt;span class="o">(&lt;/span>defaults to current folder&lt;span class="o">)&lt;/span>
-o, --output&lt;span class="o">=&lt;/span>&amp;lt;output&amp;gt; Markdown file output filename
-t, --template&lt;span class="o">=&lt;/span>&amp;lt;templateFile&amp;gt;
an optional mustache template
-V, --version Print version information and exit.
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>&lt;code>-m&lt;/code>, &lt;code>--metadata-location-folders&lt;/code>: provides folders containing spring boot configuration
metadata files (defaults to current folder)&lt;/li>
&lt;li>&lt;code>-t&lt;/code>, &lt;code>--template&lt;/code>: provides a &lt;a href="https://mustache.github.io/">&lt;code>mustache&lt;/code>&lt;/a> template file if you want to customize
the output, instead of the default one.
This could be useful if you want to export you documentation in another format (&lt;code>asciidoc&lt;/code>, &lt;code>html&lt;/code> &amp;hellip;).&lt;/li>
&lt;li>&lt;code>-o&lt;/code>, &lt;code>--output&lt;/code>: the exported filename&lt;/li>
&lt;/ul>
&lt;p>I should probably document what variables are available in case of providing a custom template,
but hopefully the source code is a good starting point for the moment.&lt;/p>
&lt;h2 id="conclusion">
&lt;a href="#conclusion" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Conclusion
&lt;/h2>
&lt;p>In conclusion, the article demonstrates the power of Spring Boot&amp;rsquo;s &lt;code>@ConfigurationProperties&lt;/code> combined with
the &lt;code>spring-boot-configuration-processor&lt;/code>.&lt;/p>
&lt;p>We explored how to leverage this mechanism to document custom properties using JBang.&lt;/p>
&lt;p>This approach could greatly simplify the documentation process by automating the extraction of property information and
generating documentation in a desired format. It enhances the developer experience by providing autocomplete and usage
information in IDEs and ensures that custom properties are well-documented and easily accessible to other team members.&lt;/p>
&lt;p>Hope you found this useful, until next time 👋!&lt;/p>
&lt;h2 id="references">
&lt;a href="#references" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
References
&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html">https://docs.spring.io/spring-boot/docs/current/reference/html/configuration-metadata.html&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.baeldung.com/spring-boot-configuration-metadata">https://www.baeldung.com/spring-boot-configuration-metadata&lt;/a>&lt;/li>
&lt;/ul></description></item><item><title>Exploring maven incremental builds with maven-build-cache-extension</title><link>https://www.mortega.dev/posts/exploring-maven-build-cache-extension/</link><pubDate>Tue, 14 Feb 2023 15:46:34 +0100</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/exploring-maven-build-cache-extension/</guid><description>
&lt;p>With the release of maven &lt;a href="https://maven.apache.org/docs/3.9.0/release-notes.html">3.9.0&lt;/a>, it is now
possible to leverage the &lt;code>maven-build-cache-extension&lt;/code> to benefit from incremental builds in your maven project.&lt;/p>
&lt;p>This feature can improve build time (in your local workflow and your CI). It caches module builds, avoiding
unnecessary and/or expensive tasks:&lt;/p>
&lt;blockquote>
&lt;p>The idea of the build cache is to calculate key from module inputs, store outputs in cache and restore them later
transparently to the standard Maven core. In order to calculate the key cache engine analyzes source code, build flow,
plugins and their parameters. This allows to deterministically associate each project state with unique key and
restore
up-to-date (not changed) projects from cache and rebuild out-of-date(changed) ones. Restoring artifacts associated
with
a particular project state improves build times by avoiding re-building unnecessary modules.[&amp;hellip;]&lt;/p>
&lt;p>From &lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/index.html">plugin documentation&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>Let&amp;rsquo;s dive into the easiest way to getting started.&lt;/p>
&lt;aside class="article-toc" style="
background-color: var(--toc-background-color);
border-radius: 15px;
padding: 15px;
margin-bottom: 15px;
">
&lt;h4>Table of contents&lt;/h4>
&lt;nav id="TableOfContents">
&lt;ul>
&lt;li>&lt;a href="#installing-the-extension">Installing the extension&lt;/a>&lt;/li>
&lt;li>&lt;a href="#running-maven-with-the-extension">Running maven with the extension&lt;/a>
&lt;ul>
&lt;li>&lt;a href="#cache-miss">Cache miss&lt;/a>&lt;/li>
&lt;li>&lt;a href="#cache-hit">Cache hit&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>&lt;a href="#performance-improvements">Performance improvements&lt;/a>&lt;/li>
&lt;li>&lt;a href="#alternatives">Alternatives&lt;/a>&lt;/li>
&lt;li>&lt;a href="#troubleshooting">Troubleshooting&lt;/a>&lt;/li>
&lt;li>&lt;a href="#conclusion">Conclusion&lt;/a>&lt;/li>
&lt;li>&lt;a href="#-references">📝 References&lt;/a>&lt;/li>
&lt;/ul>
&lt;/nav>
&lt;/aside>
&lt;h2 id="installing-the-extension">
&lt;a href="#installing-the-extension" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Installing the extension
&lt;/h2>
&lt;p>As any other maven extension, you can load it via &lt;code>.mvn/extensions.xml&lt;/code> or by modifying your &lt;code>pom.xml&lt;/code>.&lt;/p>
&lt;p>If your using the &lt;code>.mvn/extensions.xml&lt;/code> approach, you can use following configuration file:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-xml" data-lang="xml">
&lt;span class="nt">&amp;lt;extensions&lt;/span> &lt;span class="na">xmlns=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/EXTENSIONS/1.0.0&amp;#34;&lt;/span> &lt;span class="na">xmlns:xsi=&lt;/span>&lt;span class="s">&amp;#34;http://www.w3.org/2001/XMLSchema-instance&amp;#34;&lt;/span>
&lt;span class="na">xsi:schemaLocation=&lt;/span>&lt;span class="s">&amp;#34;http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd&amp;#34;&lt;/span>&lt;span class="nt">&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;extension&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.apache.maven.extensions&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>maven-build-cache-extension&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>1.0.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/extension&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/extensions&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This solution offers more flexibility as you can configure the extension via a &lt;code>maven-build-cache-config.xml&lt;/code>
file stored in the &lt;code>.mvn&lt;/code> folder. If no configuration file is found, sensible defaults will be used.&lt;/p>
&lt;p>If you are using the &lt;code>pom.xml&lt;/code> approach, you can add the plugin to your &lt;code>project-&amp;gt;build-&amp;gt;extensions&lt;/code>.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-xml" data-lang="xml">
&lt;span class="nt">&amp;lt;project&amp;gt;&lt;/span>
...
&lt;span class="nt">&amp;lt;build&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;extensions&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;extension&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.apache.maven.extensions&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>maven-build-cache-extension&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>1.0.0&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/extension&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/extensions&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/build&amp;gt;&lt;/span>
...
&lt;span class="nt">&amp;lt;/project&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="running-maven-with-the-extension">
&lt;a href="#running-maven-with-the-extension" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Running maven with the extension
&lt;/h2>
&lt;h3 id="cache-miss">
&lt;a href="#cache-miss" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Cache miss
&lt;/h3>
&lt;p>When running &lt;em>any&lt;/em>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> maven goal with the extension, you should see new information printed on your maven console&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="c1"># Running a maven goal&lt;/span>
mvn clean &lt;span class="nb">test&lt;/span>
&lt;span class="o">[&lt;/span>...&lt;span class="o">]&lt;/span>
&lt;span class="c1"># Enabling cache and hash algorithm&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Cache configuration is not available at configured path C:&lt;span class="se">\&amp;lt;&lt;/span>REDACTED&amp;gt;&lt;span class="se">\.&lt;/span>mvn&lt;span class="se">\m&lt;/span>aven-build-cache-config.xml, cache is enabled with defaults
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Using XX &lt;span class="nb">hash&lt;/span> algorithm &lt;span class="k">for&lt;/span> cache
&lt;span class="o">[&lt;/span>...&lt;span class="o">]&lt;/span>
&lt;span class="c1"># For each module ...&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Going to calculate checksum &lt;span class="k">for&lt;/span> project &lt;span class="o">[&lt;/span>&lt;span class="nv">groupId&lt;/span>&lt;span class="o">=&lt;/span>com.example, &lt;span class="nv">artifactId&lt;/span>&lt;span class="o">=&lt;/span>demo&lt;span class="o">]&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Scanning plugins configurations to find input files. Probing is enabled, values will be checked &lt;span class="k">for&lt;/span> presence in file system
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Found &lt;span class="m">3&lt;/span> input files. Project dir processing: 16, plugins: &lt;span class="m">8&lt;/span> millis
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Project inputs calculated in &lt;span class="m">58&lt;/span> ms. XX checksum &lt;span class="o">[&lt;/span>596f60b3f5056d7d&lt;span class="o">]&lt;/span> calculated in &lt;span class="m">25&lt;/span> ms.
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Attempting to restore project com.example:demo from build cache
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Remote cache is incomplete or missing, trying &lt;span class="nb">local&lt;/span> build &lt;span class="k">for&lt;/span> com.example:demo
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Local build was not found by checksum 596f60b3f5056d7d &lt;span class="k">for&lt;/span> com.example:demo
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We can see that there is a lot going on here. The extension:&lt;/p>
&lt;ul>
&lt;li>detects default configuration with &lt;code>XX&lt;/code> hashing algorithm&lt;/li>
&lt;li>calculates module build execution checksum based on input files and plugin configuration&lt;/li>
&lt;li>searches a corresponding build cache&lt;/li>
&lt;li>If no cache is found, build continues (&lt;em>cache miss&lt;/em>)&lt;/li>
&lt;/ul>
&lt;p>Build cache information is stored in your &lt;code>~/.m2&lt;/code> repository, under a &lt;code>build-cache&lt;/code> folder. This can be useful to debug
any cache errors.&lt;/p>
&lt;p>For every module a
file &lt;code>buildinfo.xml&lt;/code> (&lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/build-cache-build.html">ref&lt;/a>) is
stored containing cache data (project input, maven execution) and produced artifacts.&lt;/p>
&lt;h3 id="cache-hit">
&lt;a href="#cache-hit" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Cache hit
&lt;/h3>
&lt;p>Running the same command again will re-use the previously stored build.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="c1"># Running a maven goal&lt;/span>
mvn clean &lt;span class="nb">test&lt;/span>
&lt;span class="o">[&lt;/span>...&lt;span class="o">]&lt;/span>
&lt;span class="c1"># Enabling cache and hash algorithm&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Cache configuration is not available at configured path C:&lt;span class="se">\&amp;lt;&lt;/span>REDACTED&amp;gt;&lt;span class="se">\.&lt;/span>mvn&lt;span class="se">\m&lt;/span>aven-build-cache-config.xml, cache is enabled with defaults
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Using XX &lt;span class="nb">hash&lt;/span> algorithm &lt;span class="k">for&lt;/span> cache
&lt;span class="o">[&lt;/span>...&lt;span class="o">]&lt;/span>
&lt;span class="c1"># For each module ...&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Going to calculate checksum &lt;span class="k">for&lt;/span> project &lt;span class="o">[&lt;/span>&lt;span class="nv">groupId&lt;/span>&lt;span class="o">=&lt;/span>com.example, &lt;span class="nv">artifactId&lt;/span>&lt;span class="o">=&lt;/span>demo&lt;span class="o">]&lt;/span>
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Scanning plugins configurations to find input files. Probing is enabled, values will be checked &lt;span class="k">for&lt;/span> presence in file system
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Found &lt;span class="m">3&lt;/span> input files. Project dir processing: 13, plugins: &lt;span class="m">6&lt;/span> millis
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Project inputs calculated in &lt;span class="m">39&lt;/span> ms. XX checksum &lt;span class="o">[&lt;/span>596f60b3f5056d7d&lt;span class="o">]&lt;/span> calculated in &lt;span class="m">16&lt;/span> ms.
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Attempting to restore project com.example:demo from build cache
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Remote cache is incomplete or missing, trying &lt;span class="nb">local&lt;/span> build &lt;span class="k">for&lt;/span> com.example:demo
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Local build found by checksum 596f60b3f5056d7d
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Found cached build, restoring com.example:demo from cache by checksum 596f60b3f5056d7d
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Skipping plugin execution &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&lt;/span>: resources:resources
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Skipping plugin execution &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&lt;/span>: compiler:compile
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Skipping plugin execution &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&lt;/span>: resources:testResources
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Skipping plugin execution &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&lt;/span>: compiler:testCompile
&lt;span class="o">[&lt;/span>INFO&lt;span class="o">]&lt;/span> Skipping plugin execution &lt;span class="o">(&lt;/span>cached&lt;span class="o">)&lt;/span>: surefire:test
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>We can see that maven totally skips &lt;code>compile&lt;/code> and &lt;code>test&lt;/code> execution, re-using cached data.&lt;/p>
&lt;p>This also works by a per-module cache, meaning that in a multi-module maven project, only affected modules will be
built.&lt;/p>
&lt;h2 id="performance-improvements">
&lt;a href="#performance-improvements" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Performance improvements
&lt;/h2>
&lt;p>I did some minor testing, by running &lt;code>mvn clean verify -DskipTests&lt;/code> on two projects:&lt;/p>
&lt;ul>
&lt;li>A minimal spring-boot app created via &lt;a href="https://start.spring.io/">https://start.spring.io/&lt;/a>&lt;/li>
&lt;li>Building my current project: a multi-module projet with +15 modules, ranging from spring boot apps to common libs,
generated code source, etc &amp;hellip;&lt;/li>
&lt;/ul>
&lt;div class="alert alert-info" role="alert">
💬 &lt;strong>Note&lt;/strong>: this is not a serious benchmark, and is provided just to give an example and
showcase usage. &lt;em>I&amp;rsquo;m purposely ignoring tests since I don&amp;rsquo;t have the patience to run them on my current &amp;ldquo;low resource&amp;rdquo;
laptop&lt;/em> 😅
&lt;/div>
&lt;table>
&lt;thead>
&lt;tr>
&lt;th>project&lt;/th>
&lt;th>1st&lt;/th>
&lt;th>2nd (no modification)&lt;/th>
&lt;th>3rd (modifying one module)&lt;/th>
&lt;/tr>
&lt;/thead>
&lt;tbody>
&lt;tr>
&lt;td>minimal mono-module&lt;/td>
&lt;td>~7.4 s&lt;/td>
&lt;td>~0.9 s&lt;/td>
&lt;td>&lt;code>n/a&lt;/code>&lt;/td>
&lt;/tr>
&lt;tr>
&lt;td>real life multi-module&lt;/td>
&lt;td>~120.2 s&lt;/td>
&lt;td>~16 s&lt;/td>
&lt;td>~25s&lt;/td>
&lt;/tr>
&lt;/tbody>
&lt;/table>
&lt;p>I saw x4-10 performance improvements on my build, even more pronounced when taking tests into
consideration &lt;em>which we should &lt;strong>always&lt;/strong> be doing anyway&lt;/em>. I was instantly sold 🚀.&lt;/p>
&lt;h2 id="alternatives">
&lt;a href="#alternatives" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Alternatives
&lt;/h2>
&lt;p>Before testing this plugin I used mainly two techniques to speed up my maven build (Nicolas Fränkel has a &lt;a href="https://blog.frankel.ch/faster-maven-builds/1/">nice write-up
on this&lt;/a>).&lt;/p>
&lt;ul>
&lt;li>
&lt;p>The first is the &lt;a href="https://github.com/apache/maven-mvnd">maven daemon&lt;/a>: this project aims at faster maven builds by
simplifying parallel builds. It does not provide incremental builds provided by this extension.&lt;/p>
&lt;p>The good news is that it can by used &lt;strong>in addition&lt;/strong> to this extension, for faster build extravaganza (check related
&lt;a href="https://github.com/apache/maven-mvnd/issues/788">github issue&lt;/a> - &lt;em>targeted for release &lt;code>0.10.x&lt;/code>&lt;/em>).&lt;/p>
&lt;/li>
&lt;li>
&lt;p>The second is the use of maven reactor options to build only specific modules. &lt;em>Eg.&lt;/em>
running &lt;code>mvn -pl :module1 --also-make&lt;/code> will build the specific &lt;code>module1&lt;/code> while also building required project&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> (
ignoring the rest).&lt;/p>
&lt;p>While this a neat trick that i use everyday in by local and CI build, it still does not benefit from the cache
performance improvements provided by the extension.&lt;/p>
&lt;/li>
&lt;/ul>
&lt;p>I love that with this extension I can still benefit from these tools, and still gain on massive build improvements.&lt;/p>
&lt;h2 id="troubleshooting">
&lt;a href="#troubleshooting" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Troubleshooting
&lt;/h2>
&lt;p>If you stumble upon a weird behaviour (&lt;a href="https://issues.apache.org/jira/projects/MBUILDCACHE/issues/MBUILDCACHE-38?filter=allopenissues">&lt;em>check open
issues&lt;/em> 🔗&lt;/a>),
I found the two best ways to handle errors for now are:&lt;/p>
&lt;ul>
&lt;li>Manually deleting cache folder, located at &lt;code>~/.m2/build-cache&lt;/code>&lt;/li>
&lt;li>Disabling the extension via command line &lt;code>-Dmaven.build.cache.enabled=false&lt;/code>&lt;/li>
&lt;/ul>
&lt;h2 id="conclusion">
&lt;a href="#conclusion" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Conclusion
&lt;/h2>
&lt;p>Leveraging &lt;code>maven-build-cache-extension&lt;/code> in your Maven project can significantly improve build times by
avoiding unnecessary or expensive tasks. Hopefully by following the steps outlined in this article, you can easily get
started and benefit from faster build times in your local workflow and CI.&lt;/p>
&lt;p>The extension is fairly new, there might be some edge cases to resolve, but it&amp;rsquo;s a bright step in the right direction,
while we wait for maven 4.&lt;/p>
&lt;p>If you find any issues on the plugin, don&amp;rsquo;t hesitate to get involved, raise an issue on
the &lt;a href="https://issues.apache.org/jira/projects/MBUILDCACHE/">dedicated Apache JIRA&lt;/a>. This really feels like the future of
maven ❤️.&lt;/p>
&lt;h2 id="-references">
&lt;a href="#-references" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
📝 References
&lt;/h2>
&lt;ul>
&lt;li>Github Repo: &lt;a href="https://github.com/apache/maven-build-cache-extension">https://github.com/apache/maven-build-cache-extension&lt;/a>&lt;/li>
&lt;li>Official Docs: &lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/">https://maven.apache.org/extensions/maven-build-cache-extension/&lt;/a>&lt;/li>
&lt;/ul>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>any &amp;ldquo;standard&amp;rdquo; maven goal, there seems to be some &lt;a href="https://issues.apache.org/jira/browse/MBUILDCACHE-38">issues&lt;/a>
when running custom goals.&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>For more information,
check &lt;a href="https://blog.sonatype.com/2009/10/maven-tips-and-tricks-advanced-reactor-options/">this blog post&lt;/a> or the
brand-new &lt;a href="https://maven.apache.org/guides/mini/guide-multiple-modules-4.html">official docs&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>How to Automatically Rebase All Your Merge Requests on Gitlab with Jbang</title><link>https://www.mortega.dev/posts/how-to-automatically-rebase-all-your-merge-requests-on-gitlab-jbang-august-2022/</link><pubDate>Sun, 28 Aug 2022 19:41:01 +0200</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/how-to-automatically-rebase-all-your-merge-requests-on-gitlab-jbang-august-2022/</guid><description>
&lt;p>In our current project we debated (too much :) ) about our merge strategy.&lt;/p>
&lt;p>We finally settled with a fast-forward strategy. On GitLab, when you merge a MR on main, you must rebase all the other
MRs afterwards.
If your CI takes more than a couple of minutes, it is cumbersome and take unnecessary time to integrate our code.&lt;/p>
&lt;p>I found this elegant solution
on &lt;a href="https://medium.com/ovrsea/how-to-automatically-rebase-all-your-merge-requests-on-gitlab-when-pushing-on-master-9b7c5119ac5f">Medium&lt;/a>
to automate rebase everytime someone merges on the main branch.
If you&amp;rsquo;re looking for a quick solution, be sure to check that out.&lt;/p>
&lt;p>While we liked this solution, we preferred using a label to identify MRs we wished to auto rebase.&lt;/p>
&lt;p>I thought it would be fun to replicate it using Jbang. &lt;a href="https://gist.github.com/mikomatic/8769da7f84a0da8749e9d166431d0d0a">The full code available on Github.&lt;/a>&lt;/p>
&lt;h2 id="jbang">
&lt;a href="#jbang" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Jbang
&lt;/h2>
&lt;p>Jbang is a launcher script, that makes getting started with java very simple. It&amp;rsquo;s like the standard Jshell (since JDK
11), but with super powers:&lt;/p>
&lt;ul>
&lt;li>Dependency declaration&lt;/li>
&lt;li>Include multiple files&lt;/li>
&lt;li>Easy IDE integration&lt;/li>
&lt;/ul>
&lt;p>For more information check out &lt;a href="https://www.infoq.com/news/2020/10/scripting-java-jbang/">these&lt;/a> &lt;a href="https://www.slideshare.net/RedHatDevelopers/jbang-unleash-the-power-of-java-for-shell-scripting">articles&lt;/a> or the &lt;a href="https://www.jbang.dev/documentation/guide/latest/index.html">reference documentation&lt;/a>.&lt;/p>
&lt;h2 id="the-code">
&lt;a href="#the-code" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
The Code
&lt;/h2>
&lt;p>Let&amp;rsquo;s create our script via jbang, using the cli template:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">jbang init --template&lt;span class="o">=&lt;/span>cli autorebase.java
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This enables to power of &lt;a href="https://picocli.info/">picocli&lt;/a> in our script (parameter parsing/validation).
Once the file is created we are going to use a gitlab java api client, so we must add the &lt;a href="https://github.com/gitlab4j/gitlab4j-api">Gitlab4j&lt;/a>
dependency.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="c1">//DEPS org.gitlab4j:gitlab4j-api:5.0.1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>From there the code is pretty straightforward:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java"> &lt;span class="nd">@Override&lt;/span>
&lt;span class="kd">public&lt;/span> &lt;span class="n">Integer&lt;/span> &lt;span class="nf">call&lt;/span>&lt;span class="o">()&lt;/span> &lt;span class="kd">throws&lt;/span> &lt;span class="n">Exception&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="k">try&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">GitLabApi&lt;/span> &lt;span class="n">gitLabApi&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">buildGitlabApi&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">MergeRequest&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">mergeRequestList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">gitLabApi&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getMergeRequestApi&lt;/span>&lt;span class="o">()&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">getMergeRequests&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">projectId&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">Constants&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">MergeRequestState&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">OPENED&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="n">List&lt;/span>&lt;span class="o">&amp;lt;&lt;/span>&lt;span class="n">MergeRequest&lt;/span>&lt;span class="o">&amp;gt;&lt;/span> &lt;span class="n">openedMRList&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mergeRequestList&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">stream&lt;/span>&lt;span class="o">()&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">filter&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">mr&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="o">!&lt;/span>&lt;span class="n">Boolean&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">TRUE&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">equals&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">mr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getRebaseInProgress&lt;/span>&lt;span class="o">()))&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">filter&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">mr&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">mr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getLabels&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">contains&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">AUTOREBASE_LABEL&lt;/span>&lt;span class="o">))&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">toList&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="k">for&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">MergeRequest&lt;/span> &lt;span class="n">mr&lt;/span> &lt;span class="o">:&lt;/span> &lt;span class="n">openedMRList&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="n">Long&lt;/span> &lt;span class="n">mrIid&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">mr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getIid&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="n">System&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">out&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">println&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;Rebasing open MR [%s,%s]&amp;#34;&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">formatted&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">mrIid&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">mr&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getTitle&lt;/span>&lt;span class="o">()));&lt;/span>
&lt;span class="k">if&lt;/span> &lt;span class="o">(!&lt;/span>&lt;span class="n">dryRun&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="n">gitLabApi&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">getMergeRequestApi&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">rebaseMergeRequest&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">projectId&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">mrIid&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="k">return&lt;/span> &lt;span class="n">0&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;ul>
&lt;li>1-4 : We look for all opened MR on a given project&lt;/li>
&lt;li>7-10: We filter by a specific label (but you can choose whatever fits your needs)&lt;/li>
&lt;li>10+: We iterate over the MR list to rebase them&lt;/li>
&lt;/ul>
&lt;h2 id="gitlab-ci">
&lt;a href="#gitlab-ci" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Gitlab CI
&lt;/h2>
&lt;p>The final part is integrating this script on your pipeline &lt;code>.gitlab-ci.yml&lt;/code>&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-yaml" data-lang="yaml">&lt;span class="nt">🔄 gitlab-auto-rebase-mr&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">extends&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">.jbang-job&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">script&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="l">jbang autorebase.java --gitlab-url ${GITLAB_URL} --project-id ${CI_PROJECT_ID} -t ${ACCESS_TOKEN}&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="nt">rules&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w">
&lt;/span>&lt;span class="w"> &lt;/span>- &lt;span class="nt">if&lt;/span>&lt;span class="p">:&lt;/span>&lt;span class="w"> &lt;/span>&lt;span class="l">$CI_COMMIT_BRANCH == &amp;#34;main&amp;#34;&lt;/span>&lt;span class="w">
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Of couse, your job image should contain a java + jbang.&lt;/p>
&lt;h2 id="conclusion">
&lt;a href="#conclusion" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Conclusion
&lt;/h2>
&lt;p>In this blog post we demonstrated how to integrate a simple java script in your gitlab pipeline using jbang.&lt;/p>
&lt;p>While java is still verbose compared to the bash equivalent I feel the java counterpart offers many advantages:&lt;/p>
&lt;ul>
&lt;li>easier debugging&lt;/li>
&lt;li>better ide integration&lt;/li>
&lt;li>massive ecosystem (libraries and frameworks)&lt;/li>
&lt;/ul>
&lt;p>Hopefully your next custom script will integrate a bit of java in it, I know mine will!&lt;/p></description></item><item><title>til: Generate a file with specific size in java</title><link>https://www.mortega.dev/posts/java-generate-file-junit/</link><pubDate>Tue, 10 May 2022 20:35:54 +0200</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/java-generate-file-junit/</guid><description>
&lt;p>Recently I put in place a new feature in our app that dealt with uploading file(s). It could we use a simple upload or a
multi-part upload to cloud storage.&lt;/p>
&lt;p>Testing this feature required the creation of dummy tests files with specific size in order to trigger
(or not) the multi-part upload:&lt;/p>
&lt;ul>
&lt;li>Committing dummy files to our git repository was a definite no-no.&lt;/li>
&lt;li>Files should be thrown away after running the tests.&lt;/li>
&lt;li>Files creation should as fast as possible.&lt;/li>
&lt;li>The actual content of the file is not important.&lt;/li>
&lt;/ul>
&lt;p>Junit
5&amp;rsquo;s &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-built-in-extensions-TempDirectory">TempDirectory extension&lt;/a>&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>
resolved to first 2 bullet points:&lt;/p>
&lt;p>The other requirements could be met with sparse files:&lt;/p>
&lt;blockquote>
&lt;p>Sparse files are files stored in a file system where consecutive data blocks consisting of all zero-bytes (null-bytes)
are compressed to nothing. There is often no reason to store lots of empty data, so the file system just records how
long the sequence of empty data is instead of writing it out on the storage media. This optimization can save
significant amounts of storage space for other purposes.&lt;/p>
&lt;p>&lt;a href="https://www.ctrl.blog/entry/sparse-files.html">source&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>The following code generate a sparse file, open the file for writing, seeks a given position and adds
some bytes at the end. It leverages the &lt;code>FileChannel&lt;/code> class.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;span class="lnt">20
&lt;/span>&lt;span class="lnt">21
&lt;/span>&lt;span class="lnt">22
&lt;/span>&lt;span class="lnt">23
&lt;/span>&lt;span class="lnt">24
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="nd">@Test&lt;/span>
&lt;span class="kt">void&lt;/span> &lt;span class="nf">example&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="nd">@TempDir&lt;/span> &lt;span class="n">Path&lt;/span> &lt;span class="n">tempFolder&lt;/span>&lt;span class="o">)&lt;/span>&lt;span class="kd">throws&lt;/span> &lt;span class="n">IOException&lt;/span>&lt;span class="o">{&lt;/span>
&lt;span class="c1">// The sparse option is only taken into account if the underlying filesystem
&lt;/span>&lt;span class="c1">// supports it
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kd">final&lt;/span> &lt;span class="n">OpenOption&lt;/span>&lt;span class="o">[]&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="o">={&lt;/span>
&lt;span class="n">StandardOpenOption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">WRITE&lt;/span>&lt;span class="o">,&lt;/span>
&lt;span class="n">StandardOpenOption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">CREATE_NEW&lt;/span>&lt;span class="o">,&lt;/span>
&lt;span class="n">StandardOpenOption&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">SPARSE&lt;/span>&lt;span class="o">};&lt;/span>
&lt;span class="kd">final&lt;/span> &lt;span class="n">Path&lt;/span> &lt;span class="n">hugeFile&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">tempFolder&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">resolve&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;hugefile.txt&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="k">try&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kd">final&lt;/span> &lt;span class="n">SeekableByteChannel&lt;/span> &lt;span class="n">channel&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">Files&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newByteChannel&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">hugeFile&lt;/span>&lt;span class="o">,&lt;/span>&lt;span class="n">options&lt;/span>&lt;span class="o">)){&lt;/span>
&lt;span class="c1">// or any other size
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="kt">long&lt;/span> &lt;span class="n">giB&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">1024L&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">1014L&lt;/span>&lt;span class="o">*&lt;/span>&lt;span class="n">1024L&lt;/span>&lt;span class="o">;&lt;/span>
&lt;span class="n">channel&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">position&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">giB&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="c1">// Write some random bytes
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="kd">final&lt;/span> &lt;span class="n">ByteBuffer&lt;/span> &lt;span class="n">buf&lt;/span>&lt;span class="o">=&lt;/span>&lt;span class="n">ByteBuffer&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">allocate&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">).&lt;/span>&lt;span class="na">putInt&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="n">buf&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">rewind&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="n">channel&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">write&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">buf&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;span class="c1">//Do something with my dummy file
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="o">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Since we don&amp;rsquo;t need to actually allocate disk space, large sparse files can be created in a relative short time, making
a good fit for quick &amp;ldquo;unit&amp;rdquo; testing.&lt;/p>
&lt;h3 id="additional-resources">
&lt;a href="#additional-resources" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Additional Resources:
&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://www.baeldung.com/junit-5-temporary-directory">https://www.baeldung.com/junit-5-temporary-directory&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://en.wikipedia.org/wiki/Sparse_file">https://en.wikipedia.org/wiki/Sparse_file&lt;/a>&lt;/li>
&lt;/ul>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>This feature is still experimental&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Intro to Playwright Web Automation Framework in Java</title><link>https://www.mortega.dev/posts/intro-playwright-web-automation-framework/</link><pubDate>Sun, 30 Jan 2022 18:04:33 +0100</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/intro-playwright-web-automation-framework/</guid><description>
&lt;p>In a recent project I found myself looking for a web automation framework.&lt;/p>
&lt;p>I had the following requirements:&lt;/p>
&lt;ul>
&lt;li>A dev oriented framework: so no &lt;a href="https://cucumber.io/">Cucumber&lt;/a>, &lt;a href="http://docs.fitnesse.org/FrontPage">FitNess&lt;/a> or other BDD Frameworks&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup>&lt;/li>
&lt;li>Preferably java based: I&amp;rsquo;ve had success using &lt;a href="https://robotframework.org/">Robot Framework&lt;/a> with maven, and while integration isn&amp;rsquo;t hard
, it was not seamless to say the least.&lt;/li>
&lt;li>Something easy to setup and &amp;ldquo;fast&amp;rdquo; (&lt;em>one can dream&lt;/em>)&lt;/li>
&lt;/ul>
&lt;h2 id="what-is-playwright-">
&lt;a href="#what-is-playwright-" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
What is Playwright ?
&lt;/h2>
&lt;p>Playwright is a new web application framework, created by Microsoft, allowing engineers to
test their web applications with cross-browser support. Their stable Java API landed less than a year ago.&lt;/p>
&lt;p>The feature list is huge just by reading the &lt;a href="https://playwright.dev/java/docs/intro">docs&lt;/a>, here are some of them that I found
particularly relevant:&lt;/p>
&lt;ul>
&lt;li>Auto-waiting on elements&lt;/li>
&lt;li>Network Interception&lt;/li>
&lt;li>Support for tricky scenarios like shadow-dom, iframes&lt;/li>
&lt;li>Test tracing via screenshots, videos and a specific tracing via a dedicated explorer&lt;/li>
&lt;li>API Testing (&lt;em>similar to your good old REST client for preparing server side state in the system under test&lt;/em>)&lt;/li>
&lt;li>Code generation&lt;/li>
&lt;/ul>
&lt;p>and the list goes on and on &amp;hellip;&lt;/p>
&lt;h3 id="webdriver-vs-devtools-protocol">
&lt;a href="#webdriver-vs-devtools-protocol" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
WebDriver vs DevTools Protocol
&lt;/h3>
&lt;p>One particularity of Playwright is the use of the Chrome DevTools Protocol (&lt;a href="https://developer.chrome.com/docs/devtools/overview/">CDP&lt;/a>).&lt;/p>
&lt;p>Usually Webdriver is the de-facto standard, acting as a middleman between the browser and the testing framework.&lt;/p>
&lt;p>&lt;img loading="lazy"
src="https://www.mortega.dev/./images/playwright/playwright_webdriver.png"
alt="web driver"
width=635
height="133" />&lt;/p>
&lt;p>Using the DevTools protocol allows a more direct control of browser, cutting the middleman and enabling Playwright to do some neat tricks like
interacting with the network (intercepting calls or emulating network conditions).
To my knowledge, it&amp;rsquo;s the only framework in the JVM ecosystem using CDP.&lt;/p>
&lt;h3 id="faster-tests">
&lt;a href="#faster-tests" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Faster tests
&lt;/h3>
&lt;p>Playwright comes with &lt;code>BrowserContexts&lt;/code> feature. This means each test can be isolated from each other, as if every tab
was in a new incognito mode.&lt;/p>
&lt;p>By sharing the same browsers instance, we can also speed up our test suite by not paying the costly browser instanciation.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="c1">// Create a new incognito browser context
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">BrowserContext&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">browser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newContext&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="c1">// Create a new page inside context.
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">Page&lt;/span> &lt;span class="n">page&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newPage&lt;/span>&lt;span class="o">();&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;h2 id="where-do-i-start-">
&lt;a href="#where-do-i-start-" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Where do I start ?
&lt;/h2>
&lt;p>Let&amp;rsquo;s start by adding the latest dependency to your project.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>com.microsoft.playwright&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>playwright&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${playwright.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>In the following sections we are going to talk about my favorite features.&lt;/p>
&lt;h2 id="code-generation">
&lt;a href="#code-generation" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Code generation
&lt;/h2>
&lt;p>I found the easiest way to get started was to use de code generation tool.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="c"># At the root of your project&lt;/span>
&lt;span class="n">mvn&lt;/span> &lt;span class="n">exec&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="n">java&lt;/span> &lt;span class="n">-e&lt;/span> &lt;span class="s2">&amp;#34;-Dexec.mainClass=com.microsoft.playwright.CLI&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;-Dexec.args=codegen https://google.com&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>This command will download all the need browsers (&lt;em>see configuration if you are behind a &lt;a href="https://playwright.dev/java/docs/browsers#install-behind-a-firewall-or-a-proxy">corporate proxy&lt;/a>&lt;/em>).
Then it will open your testing tab and the Playwright inspector, generating the code corresponding to the given user interactions
(even OAuth authentication).&lt;/p>
&lt;p>
&lt;img loading="lazy" src="https://www.mortega.dev/images/playwright/playwright_recorder.JPG" alt="playwright inspector" width="400" height="400">
&lt;/p>
&lt;p>Maybe it&amp;rsquo;s because I&amp;rsquo;ve been out of touch of web testing frameworks lately, but this blew me away 🔥&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup>.
It&amp;rsquo;s a great developer experience when you can benefit from code generation tools and refactor the
base code to your needs, adding needed assertions or refactoring to create readable abstractions via
a &lt;a href="https://playwright.dev/java/docs/pom">Page Object Model&lt;/a>.&lt;/p>
&lt;h2 id="network-interception">
&lt;a href="#network-interception" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Network interception
&lt;/h2>
&lt;p>You can mock API endpoints, by handling requests in your test.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;span class="lnt">3
&lt;/span>&lt;span class="lnt">4
&lt;/span>&lt;span class="lnt">5
&lt;/span>&lt;span class="lnt">6
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="c1">// Using custom testData to as a response to this API call
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">route&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;**/api/fetch_data&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">route&lt;/span> &lt;span class="o">-&amp;gt;&lt;/span> &lt;span class="n">route&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">fulfill&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Route&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">FulfillOptions&lt;/span>&lt;span class="o">()&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">setStatus&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">200&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">setBody&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">testData&lt;/span>&lt;span class="o">)));&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">navigate&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;https://example.com&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Mocking (in this case a stub) is great for out-of-process dependencies that you have no control over.&lt;/p>
&lt;blockquote>
&lt;p>Note: while writing this article I came across an excellent post describing &lt;a href="https://enterprisecraftsmanship.com/posts/when-to-mock/">when to mock&lt;/a>,
clarifying also many related terms.&lt;/p>
&lt;/blockquote>
&lt;h2 id="tracing">
&lt;a href="#tracing" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Tracing
&lt;/h2>
&lt;p>Finally, I really loved the possibility to generate a trace of your testing script.&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="n">Browser&lt;/span> &lt;span class="n">browser&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">chromium&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">launch&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="n">BrowserContext&lt;/span> &lt;span class="n">context&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">browser&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newContext&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="c1">// Setup tracing options
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">tracing&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">start&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Tracing&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">StartOptions&lt;/span>&lt;span class="o">()&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">setScreenshots&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">setSnapshots&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="kc">true&lt;/span>&lt;span class="o">));&lt;/span>
&lt;span class="n">Page&lt;/span> &lt;span class="n">page&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">newPage&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="n">page&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">navigate&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;https://playwright.dev&amp;#34;&lt;/span>&lt;span class="o">);&lt;/span>
&lt;span class="n">context&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">tracing&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">stop&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="k">new&lt;/span> &lt;span class="n">Tracing&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">StopOptions&lt;/span>&lt;span class="o">().&lt;/span>&lt;span class="na">setPath&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">Paths&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">get&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;trace.zip&amp;#34;&lt;/span>&lt;span class="o">)));&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The generated zip can be viewed via a command line&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-powershell" data-lang="powershell">&lt;span class="n">mvn&lt;/span> &lt;span class="n">exec&lt;/span>&lt;span class="err">:&lt;/span>&lt;span class="n">java&lt;/span> &lt;span class="n">-e&lt;/span> &lt;span class="s2">&amp;#34;-Dexec.mainClass=com.microsoft.playwright.CLI&amp;#34;&lt;/span> &lt;span class="s2">&amp;#34;-Dexec.args=show-trace target/trace.zip&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>or just by uploading it to &lt;a href="https://trace.playwright.dev/">https://trace.playwright.dev/&lt;/a>.&lt;/p>
&lt;p>
&lt;img loading="lazy" src="https://www.mortega.dev/images/playwright/playwright_trace.JPG" alt="playwright inspector">
&lt;/p>
&lt;ul>
&lt;li>In the upper section, you can see the timeline of your tests&lt;/li>
&lt;li>On the left and center side you can see each action in your test
&lt;ul>
&lt;li>with an attached screenshot before and after the action&lt;/li>
&lt;li>you can even see a little red dot indicating where the action took place (&lt;em>e.g. a clicked button&lt;/em>)&lt;/li>
&lt;/ul>
&lt;/li>
&lt;li>On the right section you can see network call, console logs and your tests script source code&lt;/li>
&lt;/ul>
&lt;p>Mind. Blown. 🤯&lt;/p>
&lt;h2 id="conclusion">
&lt;a href="#conclusion" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Conclusion
&lt;/h2>
&lt;p>Hopefully by now you have a nice overview of Playwright and what it can do for you.
We only scratched the surface but here are some take aways&lt;/p>
&lt;ul>
&lt;li>Great cross-browser, cross-os, cross-language library&lt;/li>
&lt;li>Simple setup and great developer experience for java&lt;/li>
&lt;li>Very complete API and awesome tooling.&lt;/li>
&lt;/ul>
&lt;p>It may reconcile me with web automation frameworks, who would have thought !&lt;/p>
&lt;p>You don&amp;rsquo;t event have to use it for your UI tests, via the Request API you can just automate your
REST calls to prepare your system for a manual test (&lt;em>e.g. if you have a staging environment that you reset regularly&lt;/em>) and
iterate from there. Or just test your API, if that fits your needs.&lt;/p>
&lt;p>The only one missing key I found is a nice reporting output, existing only in the &lt;code>node&lt;/code> implementation.&lt;/p>
&lt;p>I hacked a project integrating Playwright with Junit5 and Allure Reporting framework - code is available &lt;a href="https://github.com/mikomatic/playwright-demo">on Github&lt;/a> -
but an out-of-box reporting tool would make this incredible tool even greater.&lt;/p>
&lt;h3 id="further-reading">
&lt;a href="#further-reading" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Further reading
&lt;/h3>
&lt;ul>
&lt;li>&lt;a href="https://medium.com/slalom-build/playwright-vs-webdriver-the-future-of-browser-automation-854a7ae63218">Playwright vs WebDriver: the future of web-automation&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://applitools.com/blog/playwright-java/">Playing with Playwright&lt;/a>&lt;/li>
&lt;/ul>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>I actually don&amp;rsquo;t believe in Behaviour Driver Development testing frameworks anymore. Too often I&amp;rsquo;ve seen &lt;a href="https://cucumber.io/blog/bdd/cucumber-antipatterns-part-one/">anti-patterns&lt;/a> and miscommunications.
The business-friendly abstraction and implied additional work - translating human-readable scenarios to Java code - is not worth it in most
cases I&amp;rsquo;ve encountered. &lt;em>change my mind !&lt;/em>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>One thing I found interesting is that playwright seems to rely more on &lt;code>aria-label&lt;/code> and &lt;code>text&lt;/code> attributes more than the &lt;code>ìd&lt;/code>. Not sure
if this is true or why should it be ?&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>til: SSH Connexion troubleshooting</title><link>https://www.mortega.dev/posts/til-ssh-git-troubleshooting/</link><pubDate>Sun, 30 Jan 2022 01:13:53 +0100</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/til-ssh-git-troubleshooting/</guid><description>
&lt;p>A few weeks ago several developers on our team were unable to connect to our Gitlab instance using the SSH protocol.
Their &lt;code>git&lt;/code> commands kept asking for password with no apparent error.&lt;/p>
&lt;p>It was a bit strange because nothing had changed in their setup (or at least, that&amp;rsquo;s what they thought). Most of them
recreated their ssh keys, which seemed to correct the problem, and we all moved on - it seemed a PEBKAC&lt;sup id="fnref:1">&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref">1&lt;/a>&lt;/sup> issue.&lt;/p>
&lt;p>I didn&amp;rsquo;t gave it much thought until it happened to me too 😅.&lt;/p>
&lt;p>The &lt;a href="https://docs.gitlab.com/ee/ssh/#password-prompt-with-git-clone">gitlab documentation&lt;/a> offers a path forward:&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="hl">&lt;span class="lnt"> 9
&lt;/span>&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="c1"># Options -T to Disable pseudo-tty allocation.&lt;/span>
&lt;span class="c1"># Options -v to set verbose mode. Multiple -v options increase the verbosity. &lt;/span>
&lt;span class="c1"># The maximum is 3.&lt;/span>
ssh -Tv git@example.com
&lt;span class="c1"># ouput&lt;/span>
debug1: Next authentication method: publickey
debug1: Offering public key: /home/user/.ssh/id_rsa RSA ... agent
&lt;span class="hl">debug1: send_pubkey_test: no mutual signature algorithm
&lt;/span>debug1: No more authentication methods to try.
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>The &lt;code>no mutual signature algorithm&lt;/code> when offering my public &lt;code>ìd_rsa&lt;/code> indicates that &lt;code>ssh-rsa&lt;/code> is not enabled.&lt;/p>
&lt;p>For me this happened after updating my Git version, and after digging a bit I found out that Git updated its OpenSSH
version to 8.8 since &lt;a href="https://github.com/git-for-windows/git/releases/tag/v2.33.1.windows.1">version 2.33.1&lt;/a>. Furthermore, reading &lt;a href="https://www.openssh.com/txt/release-8.8">OpenSSH 8.8 release notes&lt;/a> I found the root
cause (&lt;em>emphasis mine&lt;/em>):&lt;/p>
&lt;blockquote>
&lt;p>This release disables RSA signatures using the SHA-1 hash algorithm by default. [&amp;hellip;]&lt;/p>
&lt;p>For most users, this change should be invisible and there is no need to replace ssh-rsa keys. [&amp;hellip;]&lt;/p>
&lt;p>&lt;em>Incompatibility is more likely when connecting to older SSH implementations that have not been upgraded
or have not closely tracked improvements in the SSH protocol.
For these cases, it may be necessary to selectively re-enable RSA/SHA1 to allow connection and/or user
authentication via the HostkeyAlgorithms and PubkeyAcceptedAlgorithms options&lt;/em>.&lt;/p>
&lt;/blockquote>
&lt;p>This seems to be exactly my case, as I was interacting with a server using OpenSSH &lt;code>6.x&lt;/code>.&lt;/p>
&lt;p>Two solutions are offered. Either generate a new key using a more robust algorithm&lt;sup id="fnref:2">&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref">2&lt;/a>&lt;/sup> (which I did)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">$ ssh-keygen -t ed25519 -C &lt;span class="s2">&amp;#34;your_email@example.com&amp;#34;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>or re-enable &lt;code>RSA SHA-1&lt;/code> support on the affected ssh client (&lt;strong>not recommended&lt;/strong>)&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt">1
&lt;/span>&lt;span class="lnt">2
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-bash" data-lang="bash">&lt;span class="c1">#In ~/.ssh/config&lt;/span>
PubkeyAcceptedAlgorithms +ssh-rsa
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>So while, the solution was actually the same as other teams members, i&amp;rsquo;m glad i actually understood the &lt;em>why&lt;/em>.&lt;/p>
&lt;h3 id="further-reading">
&lt;a href="#further-reading" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Further reading
&lt;/h3>
&lt;ul>
&lt;li>Troubleshooting
Git &lt;a href="https://confluence.atlassian.com/bitbucketserverkb/ssh-rsa-key-rejected-with-message-no-mutual-signature-algorithm-1026057701.html">on BitBucket support page&lt;/a>&lt;/li>
&lt;li>Github article
on &lt;a href="https://github.blog/2021-09-01-improving-git-protocol-security-github/">improving git protocol security&lt;/a>&lt;/li>
&lt;/ul>
&lt;section class="footnotes" role="doc-endnotes">
&lt;hr>
&lt;ol>
&lt;li id="fn:1" role="doc-endnote">
&lt;p>&lt;em>Problem Exists Between Keyboard And Chair&lt;/em>&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;li id="fn:2" role="doc-endnote">
&lt;p>Gitlab/Github recommend &lt;a href="https://docs.gitlab.com/ee/ssh/#ed25519-ssh-keys">ED25519 keys&lt;/a>&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink">&amp;#x21a9;&amp;#xfe0e;&lt;/a>&lt;/p>
&lt;/li>
&lt;/ol>
&lt;/section></description></item><item><title>Generate PDF Documents in your Spring Boot App with AsciidoctorJ</title><link>https://www.mortega.dev/posts/pdf-report-asciidoctor-java/</link><pubDate>Sun, 16 Jan 2022 10:26:10 +0100</pubDate><author>@mikomatic</author><guid>https://www.mortega.dev/posts/pdf-report-asciidoctor-java/</guid><description>
&lt;p>In a recent project I worked there was a need to generate PDF documents.&lt;/p>
&lt;p>The document itself required to display information about a complex domain hierarchical object, containing ~100+ attributes and other child objects.
Before the existing application came to place, most users were manually creating Word documents to handle this requirement (with all the copy-pasting and human errors that one can imagine).&lt;/p>
&lt;h2 id="existing-solutions">
&lt;a href="#existing-solutions" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Existing solutions
&lt;/h2>
&lt;p>The JVM ecosystem offers many possibilities to generate printable documents, to name a few:&lt;/p>
&lt;ul>
&lt;li>&lt;a href="https://eclipse.github.io/birt-website/">Birt&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://www.jaspersoft.com/products/jasperreports-library">JasperReports&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://itextpdf.com/en/products/itext-7">iText&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://pdfbox.apache.org/">PDFBox&lt;/a>&lt;/li>
&lt;/ul>
&lt;p>While these libraries certainly offer battle-tested solutions and many features (some have been around for ~20 years !) for small or even medium project I find them a bit excessive.
(&lt;em>Disclaimer: I only have experience with the first two, that you won&amp;rsquo;t find in my CV&lt;/em> 😀)&lt;/p>
&lt;p>I also have found that either the documentation is outdated, the learning curve is steep, the code is very low-level, only commercial licence is available or a combination of all the above.&lt;/p>
&lt;h2 id="why-asciidoc-can-be-good-choice">
&lt;a href="#why-asciidoc-can-be-good-choice" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Why Asciidoc can be good choice
&lt;/h2>
&lt;p>Asciidoc is a lightweight markup language, where you only focus on content rather than layout.
It&amp;rsquo;s offer a mature ecosystem to writing articles, documentation, books, and so on, with output formats ranging from &lt;code>HTML&lt;/code> to &lt;code>ePub&lt;/code> (and of course &lt;code>PDF&lt;/code>).
It can easily integrate images, diagrams, code.&lt;/p>
&lt;p>I have been using it for years for the technical documentation of personal and non-personal projects.&lt;/p>
&lt;h2 id="show-me-the-code">
&lt;a href="#show-me-the-code" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Show me the code
&lt;/h2>
&lt;blockquote>
&lt;p>If you are in a hurry you can check the code directly on &lt;a href="https://github.com/mikomatic/asciidoctorj-pdf-demo">Github&lt;/a>&lt;/p>
&lt;/blockquote>
&lt;p>Integrating &lt;code>Asciidoctor&lt;/code> into your app is as simple as adding a maven (or gradle) dependency&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-xml" data-lang="xml">&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.asciidoctor&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>asciidoctorj&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${asciidoctorj.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;dependency&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;groupId&amp;gt;&lt;/span>org.asciidoctor&lt;span class="nt">&amp;lt;/groupId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;artifactId&amp;gt;&lt;/span>asciidoctorj-pdf&lt;span class="nt">&amp;lt;/artifactId&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;version&amp;gt;&lt;/span>${asciidoctorj.pdf.version}&lt;span class="nt">&amp;lt;/version&amp;gt;&lt;/span>
&lt;span class="nt">&amp;lt;/dependency&amp;gt;&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>Now let&amp;rsquo;s see how you can generate a document :&lt;/p>
&lt;div class="highlight">&lt;div class="chroma">
&lt;table class="lntable">&lt;tr>&lt;td class="lntd">
&lt;pre class="chroma">&lt;code>&lt;span class="lnt"> 1
&lt;/span>&lt;span class="lnt"> 2
&lt;/span>&lt;span class="lnt"> 3
&lt;/span>&lt;span class="lnt"> 4
&lt;/span>&lt;span class="lnt"> 5
&lt;/span>&lt;span class="lnt"> 6
&lt;/span>&lt;span class="lnt"> 7
&lt;/span>&lt;span class="lnt"> 8
&lt;/span>&lt;span class="lnt"> 9
&lt;/span>&lt;span class="lnt">10
&lt;/span>&lt;span class="lnt">11
&lt;/span>&lt;span class="lnt">12
&lt;/span>&lt;span class="lnt">13
&lt;/span>&lt;span class="lnt">14
&lt;/span>&lt;span class="lnt">15
&lt;/span>&lt;span class="lnt">16
&lt;/span>&lt;span class="lnt">17
&lt;/span>&lt;span class="lnt">18
&lt;/span>&lt;span class="lnt">19
&lt;/span>&lt;/code>&lt;/pre>&lt;/td>
&lt;td class="lntd">
&lt;pre class="chroma">&lt;code class="language-java" data-lang="java">&lt;span class="c1">// 1. Create Asciidoctor factory
&lt;/span>&lt;span class="c1">&lt;/span>&lt;span class="k">try&lt;/span> &lt;span class="o">(&lt;/span>&lt;span class="n">Asciidoctor&lt;/span> &lt;span class="n">asciidoctor&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Asciidoctor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">Factory&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">create&lt;/span>&lt;span class="o">())&lt;/span> &lt;span class="o">{&lt;/span>
&lt;span class="c1">// 2. Define common attributes (you can even define a theme, more on that later)
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">Attributes&lt;/span> &lt;span class="n">attributes&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Attributes&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">builder&lt;/span>&lt;span class="o">()&amp;lt;&lt;/span>&lt;span class="n">2&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">attribute&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;pdf-theme&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">theme&lt;/span>&lt;span class="o">)&lt;/span> &lt;span class="c1">// optional theme
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="o">.&lt;/span>&lt;span class="na">attribute&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;doctype&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="s">&amp;#34;book&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">attribute&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;icons&amp;#34;&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="s">&amp;#34;font&amp;#34;&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="c1">//Set PDF backend
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">Options&lt;/span> &lt;span class="n">options&lt;/span> &lt;span class="o">=&lt;/span> &lt;span class="n">Options&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">builder&lt;/span>&lt;span class="o">()&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">backend&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="s">&amp;#34;pdf&amp;#34;&lt;/span>&lt;span class="o">)&amp;lt;&lt;/span>&lt;span class="n">3&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">attributes&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">attributes&lt;/span>&lt;span class="o">)&lt;/span>
&lt;span class="o">.&lt;/span>&lt;span class="na">toFile&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">outputLocation&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">toFile&lt;/span>&lt;span class="o">()).&lt;/span>&lt;span class="na">build&lt;/span>&lt;span class="o">();&lt;/span>
&lt;span class="c1">// Do the actual conversion, where `asciidocContent` is a string containing a ASCIIDOC template
&lt;/span>&lt;span class="c1">&lt;/span> &lt;span class="n">asciidoctor&lt;/span>&lt;span class="o">.&lt;/span>&lt;span class="na">convert&lt;/span>&lt;span class="o">(&lt;/span>&lt;span class="n">asciidocContent&lt;/span>&lt;span class="o">,&lt;/span> &lt;span class="n">options&lt;/span>&lt;span class="o">);&amp;lt;&lt;/span>&lt;span class="n">4&lt;/span>&lt;span class="o">&amp;gt;&lt;/span>
&lt;span class="o">}&lt;/span>
&lt;/code>&lt;/pre>&lt;/td>&lt;/tr>&lt;/table>
&lt;/div>
&lt;/div>&lt;p>That&amp;rsquo;s pretty much it !&lt;/p>
&lt;p>The &lt;code>asciidocContent&lt;/code> can come from a &amp;ldquo;static&amp;rdquo; file on your classpath (or anywhere really).
For more dynamic document, it is possible to use any templating engine (in our project we used &lt;a href="https://github.com/spullara/mustache.java">mustache&lt;/a>).&lt;/p>
&lt;p>It is possible to provide customization to the default theme, while it is not as powerful as other solutions, it can be good enough for most needs.
Several examples can be found &lt;a href="https://github.com/asciidoctor/asciidoctor-pdf/tree/main/examples">on github&lt;/a>.&lt;/p>
&lt;p>Themes can also be packaged as &lt;code>jar&lt;/code> for easier distribution.
The &lt;a href="https://github.com/asciidoctor/asciidoctor-pdf/blob/main/docs/theming-guide.adoc">documentation&lt;/a> is quite detailed.&lt;/p>
&lt;h2 id="caveats">
&lt;a href="#caveats" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Caveats
&lt;/h2>
&lt;p>While this solution has served me well, it does come with some limitations:&lt;/p>
&lt;ul>
&lt;li>Customization can be quite limited depending on your needs.&lt;/li>
&lt;li>While diagrams as text is very practical, it does currently need an external dependency (graphviz, mermaid) present on your &lt;code>PATH&lt;/code>.&lt;/li>
&lt;li>Last but not least, the dependency that wraps a JRuby runtime does not work well in UBER jars (nested jars)
&lt;ul>
&lt;li>For Spring Boot apps, this can be solved via &lt;code>requiresUnpack&lt;/code> option of &lt;code>spring-boot-maven-plugin&lt;/code>. &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.build.extract-specific-libraries-when-an-executable-jar-runs">➡️Documentation&lt;/a>&lt;/li>
&lt;li>For Quarkus, this is not possible yet &lt;a href="https://github.com/asciidoctor/asciidoctorj/issues/1047">#issue&lt;/a>&lt;/li>
&lt;/ul>
&lt;/li>
&lt;/ul>
&lt;p>A quick demo project demonstrating the possibilities of this solution, with a custom theme,
is available &lt;a href="https://github.com/mikomatic/asciidoctorj-pdf-demo">on github&lt;/a>.&lt;/p>
&lt;h3 id="further-reading">
&lt;a href="#further-reading" class="anchor">
&lt;svg class="icon" aria-hidden="true" focusable="false" height="16" version="1.1" viewBox="0 0 16 16" width="16">
&lt;path fill-rule="evenodd"
d="M4 9h1v1H4c-1.5 0-3-1.69-3-3.5S2.55 3 4 3h4c1.45 0 3 1.69 3 3.5 0 1.41-.91 2.72-2 3.25V8.59c.58-.45 1-1.27 1-2.09C10 5.22 8.98 4 8 4H4c-.98 0-2 1.22-2 2.5S3 9 4 9zm9-3h-1v1h1c1 0 2 1.22 2 2.5S13.98 12 13 12H9c-.98 0-2-1.22-2-2.5 0-.83.42-1.64 1-2.09V6.25c-1.09.53-2 1.84-2 3.25C6 11.31 7.55 13 9 13h4c1.45 0 3-1.69 3-3.5S14.5 6 13 6z">
&lt;/path>
&lt;/svg>
&lt;/a>
Further reading
&lt;/h3>
&lt;ul>
&lt;li>A good &amp;ldquo;Getting started&amp;rdquo; guide on &lt;a href="https://www.baeldung.com/asciidoctor">baeldung&lt;/a>&lt;/li>
&lt;li>&lt;a href="https://blog.ninja-squad.com/2022/01/06/generate-pdf-documents-in-java/">Another post&lt;/a> I found using &lt;code>iText&lt;/code> for &lt;em>very&lt;/em> simple documents.&lt;/li>
&lt;/ul></description></item></channel></rss>