<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Miguel Ortega</title>
    <description>The latest articles on DEV Community by Miguel Ortega (@mikomatic).</description>
    <link>https://dev.to/mikomatic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F795067%2F653dd6eb-e6c2-424f-8f8c-cef86381333b.png</url>
      <title>DEV Community: Miguel Ortega</title>
      <link>https://dev.to/mikomatic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mikomatic"/>
    <language>en</language>
    <item>
      <title>Exploring maven incremental builds with maven-build-cache-extension</title>
      <dc:creator>Miguel Ortega</dc:creator>
      <pubDate>Wed, 15 Feb 2023 14:27:25 +0000</pubDate>
      <link>https://dev.to/mikomatic/exploring-maven-incremental-builds-with-maven-build-cache-extension-4eeo</link>
      <guid>https://dev.to/mikomatic/exploring-maven-incremental-builds-with-maven-build-cache-extension-4eeo</guid>
      <description>&lt;p&gt;With the release of maven &lt;a href="https://maven.apache.org/docs/3.9.0/release-notes.html" rel="noopener noreferrer"&gt;3.9.0&lt;/a&gt;, it is now possible to leverage the &lt;code&gt;maven-build-cache-extension&lt;/code&gt; to benefit from incremental builds in your maven project.&lt;/p&gt;

&lt;p&gt;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&gt;

&lt;blockquote&gt;
&lt;p&gt;The idea of the build cache is to calculate key from module inputs, store outputs in cache and restore them later&lt;br&gt;
transparently to the standard Maven core. In order to calculate the key cache engine analyzes source code, build flow,&lt;br&gt;
plugins and their parameters. This allows to deterministically associate each project state with unique key and&lt;br&gt;
restore&lt;br&gt;
up-to-date (not changed) projects from cache and rebuild out-of-date(changed) ones. Restoring artifacts associated&lt;br&gt;
with&lt;br&gt;
a particular project state improves build times by avoiding re-building unnecessary modules.[...]&lt;/p&gt;

&lt;p&gt;From &lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/index.html" rel="noopener noreferrer"&gt;plugin documentation&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let's dive into the easiest way to getting started.&lt;/p&gt;

&lt;h2&gt;
  
  
  Installing the extension
&lt;/h2&gt;

&lt;p&gt;As any other maven extension, you can load it via &lt;code&gt;.mvn/extensions.xml&lt;/code&gt; or by modifying your &lt;code&gt;pom.xml&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;If your using the &lt;code&gt;.mvn/extensions.xml&lt;/code&gt; approach, you can use following configuration file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;extensions&lt;/span&gt; &lt;span class="na"&gt;xmlns=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/EXTENSIONS/1.0.0"&lt;/span&gt; &lt;span class="na"&gt;xmlns:xsi=&lt;/span&gt;&lt;span class="s"&gt;"http://www.w3.org/2001/XMLSchema-instance"&lt;/span&gt;
            &lt;span class="na"&gt;xsi:schemaLocation=&lt;/span&gt;&lt;span class="s"&gt;"http://maven.apache.org/EXTENSIONS/1.0.0 http://maven.apache.org/xsd/core-extensions-1.0.0.xsd"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;extension&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.extensions&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-build-cache-extension&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/extension&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;/extensions&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This solution offers more flexibility as you can configure the extension via a &lt;code&gt;maven-build-cache-config.xml&lt;/code&gt; file stored in the &lt;code&gt;.mvn&lt;/code&gt; folder. If no configuration file is found, sensible defaults will be used.&lt;/p&gt;

&lt;p&gt;If you are using the &lt;code&gt;pom.xml&lt;/code&gt; approach, you can add the plugin to your &lt;code&gt;project-&amp;gt;build-&amp;gt;extensions&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;
&lt;span class="nt"&gt;&amp;lt;project&amp;gt;&lt;/span&gt;
    ...
    &lt;span class="nt"&gt;&amp;lt;build&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;extensions&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;extension&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.extensions&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;maven-build-cache-extension&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.0.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/extension&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/extensions&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/build&amp;gt;&lt;/span&gt;
    ...
&lt;span class="nt"&gt;&amp;lt;/project&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Running maven with the extension
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cache miss
&lt;/h3&gt;

&lt;p&gt;When running &lt;em&gt;any&lt;/em&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt; maven goal with the extension, you should see new information printed on your maven console&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Running a maven goal&lt;/span&gt;
mvn clean &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="c"&gt;# Enabling cache and hash algorithm&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Cache configuration is not available at configured path C:&lt;span class="se"&gt;\&amp;lt;&lt;/span&gt;REDACTED&amp;gt;&lt;span class="se"&gt;\.&lt;/span&gt;mvn&lt;span class="se"&gt;\m&lt;/span&gt;aven-build-cache-config.xml, cache is enabled with defaults
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Using XX &lt;span class="nb"&gt;hash &lt;/span&gt;algorithm &lt;span class="k"&gt;for &lt;/span&gt;cache
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="c"&gt;# For each module ...&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Going to calculate checksum &lt;span class="k"&gt;for &lt;/span&gt;project &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;groupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;com.example, &lt;span class="nv"&gt;artifactId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo]
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Scanning plugins configurations to find input files. Probing is enabled, values will be checked &lt;span class="k"&gt;for &lt;/span&gt;presence &lt;span class="k"&gt;in &lt;/span&gt;file system
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Found 3 input files. Project &lt;span class="nb"&gt;dir &lt;/span&gt;processing: 16, plugins: 8 millis
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Project inputs calculated &lt;span class="k"&gt;in &lt;/span&gt;58 ms. XX checksum &lt;span class="o"&gt;[&lt;/span&gt;596f60b3f5056d7d] calculated &lt;span class="k"&gt;in &lt;/span&gt;25 ms.
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Attempting to restore project com.example:demo from build cache
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Remote cache is incomplete or missing, trying &lt;span class="nb"&gt;local &lt;/span&gt;build &lt;span class="k"&gt;for &lt;/span&gt;com.example:demo
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Local build was not found by checksum 596f60b3f5056d7d &lt;span class="k"&gt;for &lt;/span&gt;com.example:demo
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that there is a lot going on here. The extension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;detects default configuration with &lt;code&gt;XX&lt;/code&gt; hashing algorithm&lt;/li&gt;
&lt;li&gt;calculates module build execution checksum based on input files and plugin configuration&lt;/li&gt;
&lt;li&gt;searches a corresponding build cache&lt;/li&gt;
&lt;li&gt;If no cache is found, build continues (&lt;em&gt;cache miss&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Build cache information is stored in your &lt;code&gt;~/.m2&lt;/code&gt; repository, under a &lt;code&gt;build-cache&lt;/code&gt; folder. This can be useful to debug any cache errors.&lt;/p&gt;

&lt;p&gt;For every module a file &lt;code&gt;buildinfo.xml&lt;/code&gt; (&lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/build-cache-build.html" rel="noopener noreferrer"&gt;ref&lt;/a&gt;) is stored containing cache data (project input, maven execution) and produced artifacts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Cache hit
&lt;/h3&gt;

&lt;p&gt;Running the same command again will re-use the previously stored build.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Running a maven goal&lt;/span&gt;
mvn clean &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="c"&gt;# Enabling cache and hash algorithm&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Cache configuration is not available at configured path C:&lt;span class="se"&gt;\&amp;lt;&lt;/span&gt;REDACTED&amp;gt;&lt;span class="se"&gt;\.&lt;/span&gt;mvn&lt;span class="se"&gt;\m&lt;/span&gt;aven-build-cache-config.xml, cache is enabled with defaults
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Using XX &lt;span class="nb"&gt;hash &lt;/span&gt;algorithm &lt;span class="k"&gt;for &lt;/span&gt;cache
&lt;span class="o"&gt;[&lt;/span&gt;...]
&lt;span class="c"&gt;# For each module ...&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Going to calculate checksum &lt;span class="k"&gt;for &lt;/span&gt;project &lt;span class="o"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;groupId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;com.example, &lt;span class="nv"&gt;artifactId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;demo]
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Scanning plugins configurations to find input files. Probing is enabled, values will be checked &lt;span class="k"&gt;for &lt;/span&gt;presence &lt;span class="k"&gt;in &lt;/span&gt;file system
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Found 3 input files. Project &lt;span class="nb"&gt;dir &lt;/span&gt;processing: 13, plugins: 6 millis
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Project inputs calculated &lt;span class="k"&gt;in &lt;/span&gt;39 ms. XX checksum &lt;span class="o"&gt;[&lt;/span&gt;596f60b3f5056d7d] calculated &lt;span class="k"&gt;in &lt;/span&gt;16 ms.
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Attempting to restore project com.example:demo from build cache
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Remote cache is incomplete or missing, trying &lt;span class="nb"&gt;local &lt;/span&gt;build &lt;span class="k"&gt;for &lt;/span&gt;com.example:demo
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Local build found by checksum 596f60b3f5056d7d
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Found cached build, restoring com.example:demo from cache by checksum 596f60b3f5056d7d
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Skipping plugin execution &lt;span class="o"&gt;(&lt;/span&gt;cached&lt;span class="o"&gt;)&lt;/span&gt;: resources:resources
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Skipping plugin execution &lt;span class="o"&gt;(&lt;/span&gt;cached&lt;span class="o"&gt;)&lt;/span&gt;: compiler:compile
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Skipping plugin execution &lt;span class="o"&gt;(&lt;/span&gt;cached&lt;span class="o"&gt;)&lt;/span&gt;: resources:testResources
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Skipping plugin execution &lt;span class="o"&gt;(&lt;/span&gt;cached&lt;span class="o"&gt;)&lt;/span&gt;: compiler:testCompile
&lt;span class="o"&gt;[&lt;/span&gt;INFO] Skipping plugin execution &lt;span class="o"&gt;(&lt;/span&gt;cached&lt;span class="o"&gt;)&lt;/span&gt;: surefire:test
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;We can see that maven totally skips &lt;code&gt;compile&lt;/code&gt; and &lt;code&gt;test&lt;/code&gt; execution, re-using cached data.&lt;/p&gt;

&lt;p&gt;This also work by a per-module cache, meaning that in a multi-module maven project, only affected modules will be built.&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance improvements
&lt;/h2&gt;

&lt;p&gt;I did some minor testing, by running &lt;code&gt;mvn clean verify -DskipTests&lt;/code&gt; on two projects:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A minimal spring-boot app created via &lt;a href="https://start.spring.io/" rel="noopener noreferrer"&gt;https://start.spring.io/&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Building my current project: a multi-module projet with +15 modules, ranging from spring boot apps to common libs,
generated code source, etc ...&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;💬 &lt;strong&gt;Note&lt;/strong&gt;: this is not a serious benchmark, and is provided just to give an example and showcase usage. &lt;em&gt;I'm purposely ignoring tests since i don't have the patience to run them on my current "low resource" laptop&lt;/em&gt; 😅&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;project&lt;/th&gt;
&lt;th&gt;1st&lt;/th&gt;
&lt;th&gt;2nd (no modification)&lt;/th&gt;
&lt;th&gt;3rd (modifying one module)&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;minimal mono-module&lt;/td&gt;
&lt;td&gt;~7.4 s&lt;/td&gt;
&lt;td&gt;~0.9 s&lt;/td&gt;
&lt;td&gt;&lt;code&gt;n/a&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;real life multi-module&lt;/td&gt;
&lt;td&gt;~120.2 s&lt;/td&gt;
&lt;td&gt;~16 s&lt;/td&gt;
&lt;td&gt;~25s&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;I saw x4-10 performance improvements on my build, that will be more significant if we take tests into consideration &lt;em&gt;which we should &lt;strong&gt;always&lt;/strong&gt; be doing anyway&lt;/em&gt;. I'm instantly sold 🚀.&lt;/p&gt;

&lt;h2&gt;
  
  
  Alternatives
&lt;/h2&gt;

&lt;p&gt;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/" rel="noopener noreferrer"&gt;nice write-up on this&lt;/a&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The first is the &lt;a href="https://github.com/apache/maven-mvnd" rel="noopener noreferrer"&gt;maven daemon&lt;/a&gt;: this project aims at faster maven builds by simplifying parallel builds. It does not provide incremental builds provided by this extension.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The good news is that it can by used &lt;strong&gt;in addition&lt;/strong&gt; to this extension, for faster build extravaganza (check related &lt;a href="https://github.com/apache/maven-mvnd/issues/788" rel="noopener noreferrer"&gt;github issue&lt;/a&gt; - &lt;em&gt;targeted for release &lt;code&gt;0.10.x&lt;/code&gt;&lt;/em&gt;).&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The second is the use of maven reactor options to build only specific modules. _Eg._running &lt;code&gt;mvn -pl :module1 --also-make&lt;/code&gt; will build the specific &lt;code&gt;module1&lt;/code&gt; while also building required project&lt;sup id="fnref2"&gt;2&lt;/sup&gt; (ignoring the rest).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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&gt;

&lt;p&gt;I love that with this extension I can still benefit from these tools, and still gain on massive build improvements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Troubleshooting
&lt;/h2&gt;

&lt;p&gt;I found the two best ways to handle errors for now are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manually deleting cache folder, located at &lt;code&gt;~/.m2/build-cache&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Disabling the extension via command line &lt;code&gt;-Dmaven.build.cache.enabled=false&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

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

&lt;p&gt;Leveraging &lt;code&gt;maven-build-cache-extension&lt;/code&gt; 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&gt;

&lt;p&gt;The extension is fairly new, there might be some edge cases to resolve, but it's a bright step in the right direction, while we wait for maven 4.&lt;/p&gt;

&lt;p&gt;If you find any issues on the plugin, don't hesitate get involved, raise on issue on the &lt;a href="https://issues.apache.org/jira/projects/MBUILDCACHE/" rel="noopener noreferrer"&gt;dedicated Apache JIRA&lt;/a&gt;. This really feels like the future of maven ❤️.&lt;/p&gt;

&lt;h2&gt;
  
  
  📝 References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Github Repo: &lt;a href="https://github.com/apache/maven-build-cache-extension" rel="noopener noreferrer"&gt;https://github.com/apache/maven-build-cache-extension&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Official Docs: &lt;a href="https://maven.apache.org/extensions/maven-build-cache-extension/" rel="noopener noreferrer"&gt;https://maven.apache.org/extensions/maven-build-cache-extension/&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;when running custom goals.&lt;br&gt;
check &lt;a href="https://blog.sonatype.com/2009/10/maven-tips-and-tricks-advanced-reactor-options/" rel="noopener noreferrer"&gt;this blog post&lt;/a&gt; or the&lt;br&gt;
brand-new &lt;a href="https://maven.apache.org/guides/mini/guide-multiple-modules-4.html" rel="noopener noreferrer"&gt;official docs&lt;/a&gt;&lt;/p&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;any "standard" maven goal, there seems to be some &lt;a href="https://issues.apache.org/jira/browse/MBUILDCACHE-38" rel="noopener noreferrer"&gt;issues&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;For more information, ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>watercooler</category>
      <category>healthydebate</category>
    </item>
    <item>
      <title>til: Generate a file with specific size in java</title>
      <dc:creator>Miguel Ortega</dc:creator>
      <pubDate>Tue, 10 May 2022 20:53:09 +0000</pubDate>
      <link>https://dev.to/mikomatic/til-generate-a-file-with-specific-size-in-java-h3c</link>
      <guid>https://dev.to/mikomatic/til-generate-a-file-with-specific-size-in-java-h3c</guid>
      <description>&lt;p&gt;Recently I wanted to implement a new feature in our codebase, that dealt with uploading a file via a simple upload or a multi-part upload to an AWS S3 storage. It was actually a fun feature to develop, I learned a lot about AWS SDK on &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/mpu-upload-object.html"&gt;uploading an object using multipart upload&lt;/a&gt; and using &lt;a href="https://docs.aws.amazon.com/AmazonS3/latest/userguide/using-presigned-url.html"&gt;presign urls&lt;/a&gt; which can be extremely useful to delegate the upload responsibility (and the dedicated resources) to the client apps.&lt;/p&gt;

&lt;p&gt;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&gt;

&lt;ul&gt;
&lt;li&gt;Committing dummy files to our git repository was a definite no-no.&lt;/li&gt;
&lt;li&gt;Files should be thrown away after running the tests.&lt;/li&gt;
&lt;li&gt;Files creation should as fast as possible.&lt;/li&gt;
&lt;li&gt;The actual content of the file is not important.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;First two requirements can be answered using Junit 5's &lt;a href="https://junit.org/junit5/docs/current/user-guide/#writing-tests-built-in-extensions-TempDirectory"&gt;TempDirectory extension&lt;/a&gt;&lt;sup id="fnref1"&gt;1&lt;/sup&gt;&lt;/p&gt;

&lt;p&gt;The other requirements could be met with sparse files:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;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&gt;

&lt;p&gt;&lt;a href="https://www.ctrl.blog/entry/sparse-files.html"&gt;source&lt;/a&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The following code generate a sparse file, open the file for writing, seeks a given position and adds somes bytes at the end. It leverages the &lt;code&gt;FileChannel&lt;/code&gt; class.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;example&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nd"&gt;@TempDir&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;tempFolder&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="kd"&gt;throws&lt;/span&gt; &lt;span class="nc"&gt;IOException&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// The sparse option is only taken into account if the underlying filesystem&lt;/span&gt;
    &lt;span class="c1"&gt;// supports it&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;OpenOption&lt;/span&gt;&lt;span class="o"&gt;[]&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="nc"&gt;StandardOpenOption&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;WRITE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; 
            &lt;span class="nc"&gt;StandardOpenOption&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;CREATE_NEW&lt;/span&gt; &lt;span class="o"&gt;,&lt;/span&gt; 
            &lt;span class="nc"&gt;StandardOpenOption&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;SPARSE&lt;/span&gt; &lt;span class="o"&gt;};&lt;/span&gt;

    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt; &lt;span class="n"&gt;hugeFile&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tempFolder&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;resolve&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"hugefile.txt"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;SeekableByteChannel&lt;/span&gt; &lt;span class="n"&gt;channel&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Files&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newByteChannel&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;hugeFile&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="c1"&gt;// or any other size&lt;/span&gt;
        &lt;span class="kt"&gt;long&lt;/span&gt; &lt;span class="n"&gt;giB&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1024L&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1014L&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1024L&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
        &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;giB&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

        &lt;span class="c1"&gt;// Write some random bytes&lt;/span&gt;
        &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt; &lt;span class="n"&gt;buf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ByteBuffer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;allocate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;putInt&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;rewind&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
        &lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;write&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;buf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="o"&gt;}&lt;/span&gt;

    &lt;span class="c1"&gt;//Do something with my dummy file&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Since we don't need to actually allocate disk space, large sparse files can be created in a relative short time, making a good fit&lt;br&gt;
for quick "unit" testing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Additional Resources:
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.baeldung.com/junit-5-temporary-directory"&gt;https://www.baeldung.com/junit-5-temporary-directory&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://en.wikipedia.org/wiki/Sparse_file"&gt;https://en.wikipedia.org/wiki/Sparse_file&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;This feature is still experimental ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>java</category>
      <category>junit</category>
      <category>nio</category>
    </item>
    <item>
      <title>Intro to Playwright Web Automation Framework in Java</title>
      <dc:creator>Miguel Ortega</dc:creator>
      <pubDate>Mon, 31 Jan 2022 19:34:45 +0000</pubDate>
      <link>https://dev.to/mikomatic/intro-to-playwright-web-automation-framework-in-java-k0m</link>
      <guid>https://dev.to/mikomatic/intro-to-playwright-web-automation-framework-in-java-k0m</guid>
      <description>&lt;p&gt;In a recent project I found myself looking for a web automation framework.&lt;/p&gt;

&lt;p&gt;I had the following requirements:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A dev oriented framework: so no &lt;a href="https://cucumber.io/"&gt;Cucumber&lt;/a&gt;, &lt;a href="http://docs.fitnesse.org/FrontPage"&gt;FitNess&lt;/a&gt; or other BDD Frameworks&lt;sup id="fnref1"&gt;1&lt;/sup&gt;
&lt;/li&gt;
&lt;li&gt;Preferably java based: I've had success using &lt;a href="https://robotframework.org/"&gt;Robot Framework&lt;/a&gt; with maven, and while integration isn't hard, it was not seamless to say the least.&lt;/li&gt;
&lt;li&gt;Something easy to setup and "fast" (&lt;em&gt;one can dream&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What is Playwright ?
&lt;/h2&gt;

&lt;p&gt;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&gt;

&lt;p&gt;The feature list is huge just by reading the &lt;a href="https://playwright.dev/java/docs/intro"&gt;docs&lt;/a&gt;, here are some of them that I found particularly relevant:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Auto-waiting on elements&lt;/li&gt;
&lt;li&gt;Network Interception&lt;/li&gt;
&lt;li&gt;Support for tricky scenarios like shadow-dom, iframes&lt;/li&gt;
&lt;li&gt;Test tracing via screenshots, videos and a specific tracing via a dedicated explorer&lt;/li&gt;
&lt;li&gt;API Testing (&lt;em&gt;similar to your good old REST client for preparing server side state in the system under test&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Code generation&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;and the list goes on and on ... &lt;/p&gt;

&lt;h3&gt;
  
  
  WebDriver vs DevTools Protocol
&lt;/h3&gt;

&lt;p&gt;One particularity of Playwright is the use of the Chrome DevTools Protocol (&lt;a href="https://developer.chrome.com/docs/devtools/overview/"&gt;CDP&lt;/a&gt;). &lt;/p&gt;

&lt;p&gt;Usually Webdriver is the de-facto standard, acting as a middleman between the browser and the testing framework.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--1m3-BpxY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0ivoscpl7tfdrjn36w8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--1m3-BpxY--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g0ivoscpl7tfdrjn36w8.png" alt="web driver" width="635" height="133"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;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).&lt;br&gt;
To my knowledge, it's the only framework in the JVM ecosystem using CDP.&lt;/p&gt;
&lt;h3&gt;
  
  
  Faster tests
&lt;/h3&gt;

&lt;p&gt;Playwright comes with &lt;code&gt;BrowserContexts&lt;/code&gt; feature. This means each test can be isolated from each other, as if every tab was in a new incognito mode.&lt;/p&gt;

&lt;p&gt;By sharing the same browsers instance, we can also speed up our test suite by not paying the costly browser instanciation.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Create a new incognito browser context&lt;/span&gt;
&lt;span class="nc"&gt;BrowserContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="c1"&gt;// Create a new page inside context.&lt;/span&gt;
&lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newPage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Where do I start ?
&lt;/h2&gt;

&lt;p&gt;Let's start by adding the latest dependency to your project.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;com.microsoft.playwright&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;playwright&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${playwright.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the following sections we are going to talk about my favorite features.&lt;/p&gt;

&lt;h2&gt;
  
  
  Code generation
&lt;/h2&gt;

&lt;p&gt;I found the easiest way to get started was to use de code generation tool.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="c"&gt;# At the root of your project&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="n"&gt;mvn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec:java&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-Dexec.mainClass=com.microsoft.playwright.CLI"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-Dexec.args=codegen https://google.com"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This command will download all the need browsers (&lt;em&gt;see configuration if you are behind a &lt;a href="https://playwright.dev/java/docs/browsers#install-behind-a-firewall-or-a-proxy"&gt;corporate proxy&lt;/a&gt;&lt;/em&gt;).&lt;br&gt;
Then it will open your testing tab and the Playwright inspector, generating the code corresponding to the given user interactions&lt;br&gt;
(even OAuth authentication).&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--C0QIiVle--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/echn2bvhj5edci34i2ty.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--C0QIiVle--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/echn2bvhj5edci34i2ty.JPG" alt="playwright inspector" width="880" height="880"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Maybe it's because I've been out of touch of web testing frameworks lately, but this blew me away 🔥&lt;sup id="fnref2"&gt;2&lt;/sup&gt;. &lt;br&gt;
It'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"&gt;Page Object Model&lt;/a&gt;.&lt;/p&gt;
&lt;h2&gt;
  
  
  Network interception
&lt;/h2&gt;

&lt;p&gt;You can mock API endpoints, by handling requests in your test.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Using custom testData to as a response to this API call&lt;/span&gt;
&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;route&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"**/api/fetch_data"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;fulfill&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Route&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;FulfillOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setStatus&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
  &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;testData&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;

&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://example.com"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Mocking (in this case a stub) is great for out-of-process dependencies that you have no control over.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: while writing this article I came across an excellent post describing &lt;a href="https://enterprisecraftsmanship.com/posts/when-to-mock/"&gt;when to mock&lt;/a&gt;, &lt;br&gt;
clarifying also many related terms.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Tracing
&lt;/h2&gt;

&lt;p&gt;Finally, I really loved the possibility to generate a trace of your testing script.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;Browser&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;chromium&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;launch&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="nc"&gt;BrowserContext&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newContext&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// Setup tracing options&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;start&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;StartOptions&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setScreenshots&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setSnapshots&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;Page&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;newPage&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;navigate&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"https://playwright.dev"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;tracing&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;stop&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Tracing&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;StopOptions&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;setPath&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Paths&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"trace.zip"&lt;/span&gt;&lt;span class="o"&gt;)));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generated zip can be viewed via a command line&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight powershell"&gt;&lt;code&gt;&lt;span class="n"&gt;mvn&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;exec:java&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nt"&gt;-e&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-Dexec.mainClass=com.microsoft.playwright.CLI"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"-Dexec.args=show-trace target/trace.zip"&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or just by uploading it to &lt;a href="https://trace.playwright.dev/"&gt;https://trace.playwright.dev/&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--fvq2C2Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgsuutvjvjxf5ouh9feu.JPG" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--fvq2C2Rq--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sgsuutvjvjxf5ouh9feu.JPG" alt="playwright recorder" width="880" height="350"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;In the upper section, you can see the timeline of your tests&lt;/li&gt;
&lt;li&gt;On the left and center side you can see each action in your test

&lt;ul&gt;
&lt;li&gt;with an attached screenshot before and after the action&lt;/li&gt;
&lt;li&gt;you can even see a little red dot indicating where the action took place (&lt;em&gt;e.g. a clicked button&lt;/em&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;li&gt;On the right section you can see network call, console logs and your tests script source code&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Mind. Blown. 🤯&lt;/p&gt;

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

&lt;p&gt;Hopefully by now you have a nice overview of Playwright and what it can do for you.&lt;br&gt;
We only scratched the surface but here are some take aways&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Great cross-browser, cross-os, cross-language library&lt;/li&gt;
&lt;li&gt;Simple setup and great developer experience for java &lt;/li&gt;
&lt;li&gt;Very complete API and awesome tooling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It may reconcile me with web automation frameworks, who would have thought !&lt;/p&gt;

&lt;p&gt;You don'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&gt;e.g. if you have a staging environment that you reset regularly&lt;/em&gt;) and iterate from there. Or just test your API, if that fits your needs.&lt;/p&gt;

&lt;p&gt;The only one missing key I found is a nice reporting output, existing only in the &lt;code&gt;node&lt;/code&gt; implementation.&lt;/p&gt;

&lt;p&gt;I hacked a project integrating Playwright with   Junit5 and Allure Reporting framework - code is available [on Github] &lt;a href="https://github.com/mikomatic/playwright-demo"&gt;https://github.com/mikomatic/playwright-demo&lt;/a&gt;) - but an out-of-box reporting tool would make this incredible tool even greater.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://medium.com/slalom-build/playwright-vs-webdriver-the-future-of-browser-automation-854a7ae63218"&gt;Playwright vs WebDriver: the future of web-automation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://applitools.com/blog/playwright-java/"&gt;Playing with Playwright&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;I actually don't believe in Behaviour Driver Development testing frameworks anymore. Too often I've seen &lt;a href="https://cucumber.io/blog/bdd/cucumber-antipatterns-part-one/"&gt;anti-patterns&lt;/a&gt; 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've encountered. &lt;em&gt;change my mind !&lt;/em&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;One thing I found interesting is that playwright seems to rely more on &lt;code&gt;aria-label&lt;/code&gt; and &lt;code&gt;text&lt;/code&gt; attributes more than the &lt;code&gt;ìd&lt;/code&gt;. Not sure if this is true or why should it be ? ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>java</category>
      <category>playwright</category>
    </item>
    <item>
      <title>til: SSH Connexion troubleshooting</title>
      <dc:creator>Miguel Ortega</dc:creator>
      <pubDate>Sun, 30 Jan 2022 10:21:51 +0000</pubDate>
      <link>https://dev.to/mikomatic/til-ssh-connexion-troubleshooting-5e5a</link>
      <guid>https://dev.to/mikomatic/til-ssh-connexion-troubleshooting-5e5a</guid>
      <description>&lt;p&gt;A few weeks ago several developers on our team were unable to connect to our Gitlab instance using the SSH protocol.&lt;br&gt;
Their &lt;code&gt;git&lt;/code&gt; commands kept asking for password with no apparent error.&lt;/p&gt;

&lt;p&gt;It was a bit strange because nothing had changed in their setup (or at least, that'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="fnref1"&gt;1&lt;/sup&gt; issue.&lt;/p&gt;

&lt;p&gt;I didn't gave it much thought until it happened to me too 😅.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://docs.gitlab.com/ee/ssh/#password-prompt-with-git-clone"&gt;gitlab documentation&lt;/a&gt; offers a path forward:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# Options -T to Disable pseudo-tty allocation.&lt;/span&gt;
&lt;span class="c"&gt;# Options -v to set verbose mode. Multiple -v options increase the verbosity.  &lt;/span&gt;
&lt;span class="c"&gt;# The maximum is 3.&lt;/span&gt;
ssh &lt;span class="nt"&gt;-Tv&lt;/span&gt; git@example.com

&lt;span class="c"&gt;# ouput&lt;/span&gt;
debug1: Next authentication method: publickey
debug1: Offering public key: /home/user/.ssh/id_rsa RSA ... agent
debug1: send_pubkey_test: no mutual signature algorithm
debug1: No more authentication methods to try.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;no mutual signature algorithm&lt;/code&gt; when offering my public &lt;code&gt;ìd_rsa&lt;/code&gt; indicates that &lt;code&gt;ssh-rsa&lt;/code&gt; is not enabled.&lt;/p&gt;

&lt;p&gt;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"&gt;version 2.33.1&lt;/a&gt;. Furthermore, reading &lt;a href="https://www.openssh.com/txt/release-8.8"&gt;OpenSSH 8.8 release notes&lt;/a&gt; I found the root&lt;br&gt;
cause (&lt;em&gt;emphasis mine&lt;/em&gt;):&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;This release disables RSA signatures using the SHA-1 hash algorithm by default. [...]&lt;/p&gt;

&lt;p&gt;For most users, this change should be invisible and there is no need to replace ssh-rsa keys. [...]&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Incompatibility is more likely when connecting to older SSH implementations that have not been upgraded&lt;br&gt;
or have not closely tracked improvements in the SSH protocol.&lt;br&gt;
For these cases, it may be necessary to selectively re-enable RSA/SHA1 to allow connection and/or user&lt;br&gt;
authentication via the HostkeyAlgorithms and PubkeyAcceptedAlgorithms options&lt;/em&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;This seems to be exactly my case, as I was interacting with a server using OpenSSH &lt;code&gt;6.x&lt;/code&gt;. &lt;/p&gt;

&lt;p&gt;Two solutions are offered. Either generate a new key using a more robust algorithm&lt;sup id="fnref2"&gt;2&lt;/sup&gt; (which I did)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;ssh-keygen &lt;span class="nt"&gt;-t&lt;/span&gt; ed25519 &lt;span class="nt"&gt;-C&lt;/span&gt; &lt;span class="s2"&gt;"your_email@example.com"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;or re-enable &lt;code&gt;RSA SHA-1&lt;/code&gt; support on the affected ssh client (&lt;strong&gt;not recommended&lt;/strong&gt;)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#In ~/.ssh/config&lt;/span&gt;
PubkeyAcceptedAlgorithms +ssh-rsa
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So while, the solution was actually the same as other teams members, i'm glad i actually understood the &lt;em&gt;why&lt;/em&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Some References:
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Troubleshooting
Git &lt;a href="https://confluence.atlassian.com/bitbucketserverkb/ssh-rsa-key-rejected-with-message-no-mutual-signature-algorithm-1026057701.html"&gt;on BitBucket support page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Github article
on &lt;a href="https://github.blog/2021-09-01-improving-git-protocol-security-github/"&gt;improving git protocol security&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;ol&gt;

&lt;li id="fn1"&gt;
&lt;p&gt;&lt;em&gt;Problem Exists Between Keyboard And Chair&lt;/em&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;li id="fn2"&gt;
&lt;p&gt;Gitlab/Github recommend &lt;a href="https://docs.gitlab.com/ee/ssh/#ed25519-ssh-keys"&gt;ED25519 keys&lt;/a&gt; ↩&lt;/p&gt;
&lt;/li&gt;

&lt;/ol&gt;

</description>
      <category>git</category>
      <category>ssh</category>
      <category>todayilearned</category>
    </item>
    <item>
      <title>Generate PDF Documents in your Spring Boot App with AsciidoctorJ</title>
      <dc:creator>Miguel Ortega</dc:creator>
      <pubDate>Mon, 17 Jan 2022 08:41:45 +0000</pubDate>
      <link>https://dev.to/mikomatic/generate-pdf-documents-in-your-spring-boot-app-with-asciidoctorj-2fa9</link>
      <guid>https://dev.to/mikomatic/generate-pdf-documents-in-your-spring-boot-app-with-asciidoctorj-2fa9</guid>
      <description>&lt;p&gt;In a recent project I worked there was a need to generate PDF documents.&lt;/p&gt;

&lt;p&gt;The document itself required to display information about a complex domain hierarchical object, containing ~100+ attributes and other child objects.&lt;br&gt;
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&gt;
&lt;h2&gt;
  
  
  Existing solutions
&lt;/h2&gt;

&lt;p&gt;The JVM ecosystem offers many possibilities to generate printable documents, to name a few:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://eclipse.github.io/birt-website/"&gt;Birt&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.jaspersoft.com/products/jasperreports-library"&gt;JasperReports&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://itextpdf.com/en/products/itext-7"&gt;iText&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pdfbox.apache.org/"&gt;PDFBox&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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;br&gt;
(&lt;em&gt;Disclaimer: I only have experience with the first two, that you won't find in my CV&lt;/em&gt; 😀)&lt;/p&gt;

&lt;p&gt;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&gt;
&lt;h2&gt;
  
  
  Why Asciidoc can be good choice
&lt;/h2&gt;

&lt;p&gt;Asciidoc is a lightweight markup language, where you only focus on content rather than layout.&lt;br&gt;
It's offer a mature ecosystem to writing articles, documentation, books, and so on, with output formats ranging from &lt;code&gt;HTML&lt;/code&gt; to &lt;code&gt;ePub&lt;/code&gt; (and of course &lt;code&gt;PDF&lt;/code&gt;).&lt;br&gt;
It can easily integrate images, diagrams, code.&lt;/p&gt;

&lt;p&gt;I have been using it for years for the technical documentation of personal and non-personal projects.&lt;/p&gt;
&lt;h2&gt;
  
  
  Show me the code
&lt;/h2&gt;



&lt;p&gt;If you are in a hurry you can check the code directly on &lt;a href="https://github.com/mikomatic/asciidoctorj-pdf-demo"&gt;Github&lt;/a&gt;&lt;/p&gt;



&lt;p&gt;Integrating &lt;code&gt;Asciidoctor&lt;/code&gt; into your app is as simple as adding a maven (or gradle) dependency&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.asciidoctor&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;asciidoctorj&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${asciidoctorj.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;

&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.asciidoctor&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;asciidoctorj-pdf&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;${asciidoctorj.pdf.version}&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now let's see how you can generate a document :&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Asciidoctor&lt;/span&gt; &lt;span class="n"&gt;asciidoctor&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Asciidoctor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;Factory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;create&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="c1"&gt;//&amp;lt;1&amp;gt;&lt;/span&gt;

    &lt;span class="nc"&gt;Attributes&lt;/span&gt; &lt;span class="n"&gt;attributes&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Attributes&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;&lt;span class="c1"&gt;//&amp;lt;2&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdf-theme"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;theme&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// optional theme&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"doctype"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"book"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attribute&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"icons"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"font"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="nc"&gt;Options&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Options&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;builder&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;backend&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"pdf"&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="c1"&gt;//&amp;lt;3&amp;gt;&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;attributes&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
          &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toFile&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;outputLocation&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toFile&lt;/span&gt;&lt;span class="o"&gt;()).&lt;/span&gt;&lt;span class="na"&gt;build&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;asciidoctor&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;convert&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;asciidocContent&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;options&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;&lt;span class="c1"&gt;//&amp;lt;4&amp;gt;&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;Create Asciidoctor factory&lt;/li&gt;
&lt;li&gt;Define common attributes (you can even define a theme, more on that later)&lt;/li&gt;
&lt;li&gt;Set PDF backend&lt;/li&gt;
&lt;li&gt;Do the actual conversion, where &lt;code&gt;asciidocContent&lt;/code&gt; is a string containing a ASCIIDOC template&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That's pretty much it !&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;asciidocContent&lt;/code&gt; can come from a "static" file on your classpath (or anywhere really).&lt;br&gt;
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"&gt;mustache&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;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.&lt;br&gt;
Several examples can be found &lt;a href="https://github.com/asciidoctor/asciidoctor-pdf/tree/main/examples"&gt;on github&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Themes can also be packaged as &lt;code&gt;jar&lt;/code&gt; for easier distribution.&lt;br&gt;
The &lt;a href="https://github.com/asciidoctor/asciidoctor-pdf/blob/main/docs/theming-guide.adoc"&gt;documentation&lt;/a&gt; is quite detailed.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caveats
&lt;/h2&gt;

&lt;p&gt;While this solution has served me well, it does come with some limitations:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Customization can be quite limited depending on your needs.&lt;/li&gt;
&lt;li&gt;While diagrams as text is very practical, it does currently need an external dependency (graphviz, mermaid) present on your &lt;code&gt;PATH&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Last but not least, the dependency that wraps a JRuby runtime does not work well in UBER jars (nested jars)

&lt;ul&gt;
&lt;li&gt;For Spring Boot apps, this can be solved via &lt;code&gt;requiresUnpack&lt;/code&gt; option of &lt;code&gt;spring-boot-maven-plugin&lt;/code&gt;. &lt;a href="https://docs.spring.io/spring-boot/docs/current/reference/htmlsingle/#howto.build.extract-specific-libraries-when-an-executable-jar-runs"&gt;➡️Documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;For Quarkus, this is not possible yet &lt;a href="https://github.com/asciidoctor/asciidoctorj/issues/1047"&gt;#issue&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;a quick demo project demonstrating the possibilities of this solution, with a custom theme,&lt;br&gt;
is available &lt;a href="https://github.com/mikomatic/asciidoctorj-pdf-demo"&gt;on github&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Further reading
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;A good "Getting started" guide on &lt;a href="https://www.baeldung.com/asciidoctor"&gt;baeldung&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.ninja-squad.com/2022/01/06/generate-pdf-documents-in-"&gt;https://blog.ninja-squad.com/2022/01/06/generate-pdf-documents-in-&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>java</category>
      <category>asciidoc</category>
      <category>springboot</category>
      <category>pdf</category>
    </item>
  </channel>
</rss>
