<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>Szymon Stepniak - I want to help you to become a better software developer</title>
  
  
  <link href="https://e.printstacktrace.blog/atom.xml" rel="self"/>
  
  <link href="https://e.printstacktrace.blog/"/>
  <updated>2022-07-28T08:22:54.000Z</updated>
  <id>https://e.printstacktrace.blog/</id>
  
  <author>
    <name>Szymon Stepniak</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>Things You Might Don&#39;t Know About Micronaut Framework</title>
    <link href="https://e.printstacktrace.blog/things-you-might-dont-know-about-micronaut-framework/"/>
    <id>https://e.printstacktrace.blog/things-you-might-dont-know-about-micronaut-framework/</id>
    <published>2022-07-28T08:22:54.000Z</published>
    <updated>2022-07-28T08:22:54.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Are you looking for a collection of code snippets showcasing interesting and useful features of the Micronaut Framework?You&#8217;ve found the right place!</p></div><a id="more"></a><div class="sect1"><h2 id="the-mn-files">The MN Files</h2><div class="sectionbody"><div class="paragraph"><p>A few weeks ago I started publishing eye-catching images (created using <a href="https://e.printstacktrace.blog/canva">Canva</a>) on Twitter, showcasing many interesting features of the Micronaut Framework.The idea behind this project is to share a quick tips and hints with the people browsing their Twitter feed.If you like this short form of content, then here is the collection of all published Tweets in their chronologial order:</p></div><br /><blockquote class="twitter-tweet tw-align-center"><p lang="en" dir="ltr">👽 The MN Files is a collection of code snippets showcasing interesting features of the <a href="https://twitter.com/micronautfw?ref_src=twsrc%5Etfw">@micronautfw</a> <a href="https://twitter.com/java?ref_src=twsrc%5Etfw">@java</a> framework!🚀 Here you can find all posts in chronological order:<br><br>👉 <a href="https://t.co/lJoocVjBRn">https://t.co/lJoocVjBRn</a><a href="https://twitter.com/hashtag/micronautfw?src=hash&amp;ref_src=twsrc%5Etfw">#micronautfw</a> <a href="https://twitter.com/hashtag/java?src=hash&amp;ref_src=twsrc%5Etfw">#java</a> <a href="https://twitter.com/hashtag/framework?src=hash&amp;ref_src=twsrc%5Etfw">#framework</a> <a href="https://twitter.com/hashtag/programming?src=hash&amp;ref_src=twsrc%5Etfw">#programming</a> <a href="https://twitter.com/hashtag/learing?src=hash&amp;ref_src=twsrc%5Etfw">#learing</a> <a href="https://twitter.com/hashtag/coding?src=hash&amp;ref_src=twsrc%5Etfw">#coding</a> <a href="https://twitter.com/hashtag/100daysofcode?src=hash&amp;ref_src=twsrc%5Etfw">#100daysofcode</a></p>&mdash; Szymon Stepniak (@wololock) <a href="https://twitter.com/wololock/status/1552284519293673472?ref_src=twsrc%5Etfw">July 27, 2022</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script><br/><div class="paragraph"><p>What do you think about this content form?Is it worth creating?Let me know in the comments' section below.Thanks in advance!</p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Are you looking for a collection of code snippets showcasing interesting and useful features of the Micronaut Framework?
You&amp;#8217;ve found the right place!&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Micronaut Cookbook" scheme="https://e.printstacktrace.blog/micronaut-cookbook/"/>
    
    
    <category term="java" scheme="https://e.printstacktrace.blog/t/java/"/>
    
    <category term="micronaut" scheme="https://e.printstacktrace.blog/t/micronaut/"/>
    
  </entry>
  
  <entry>
    <title>These 10 New Features Make Groovy 4.0 AWESOME!</title>
    <link href="https://e.printstacktrace.blog/these-10-new-features-make-groovy-4-0-awesome/"/>
    <id>https://e.printstacktrace.blog/these-10-new-features-make-groovy-4-0-awesome/</id>
    <published>2022-04-14T12:38:23.000Z</published>
    <updated>2022-04-14T12:38:23.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Sealed types, switch expressions, and record types.Here are just a few new features introduced in the latest Groovy 4.0 release.In this video, I want to show you ten things that make Groovy 4.0 amazing.And to keep this video short, we&#8217;re not going to dive deep into each of them.Instead, I intend to give you a quick overview of the new features.</p></div><a id="more"></a><div class="videoblock embed-responsive embed-responsive-16by9 shadow"><div class="content"><iframe src="https://www.youtube.com/embed/fTxFa2Sb4ts?rel=0" frameborder="0" allowfullscreen></iframe></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Source code repository: <a href="https://github.com/wololock/groovy-4-examples" class="bare">https://github.com/wololock/groovy-4-examples</a></td></tr></table></div><div class="sect1"><h2 id="switch-expression">Switch Expression</h2><div class="sectionbody"><div class="paragraph"><p>Groovy has always had much more powerful switch statements compared to Java. Class case values, regular expression case values, collection case values, closure case values, or at the end, equal values case. All these options made the switch statement a first-class citizen in the Groovy world. And now, following the latest updates in the Java programming language, Groovy also supports a switch expression. The main difference between a switch statement and a switch expression is that the latter introduces a syntax compatible with Java and returns a value. You can still use a variety of combinations as cases, but the new syntax will make your code a bit more elegant.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">switch (value) &#123;    case null -&gt; 'just a null'    case 0 -&gt; 'zero'    case 1 -&gt; 'one'    case &#123; it instanceof List &amp;&amp; it.empty &#125; -&gt; 'an empty list'    case List -&gt; 'a list'    case '007' -&gt; 'James Bond'    case ~/\d+/ -&gt; 'a number'    default -&gt; 'unknown'&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="records">Records</h2><div class="sectionbody"><div class="paragraph"><p>Records, a handy immutable "data carrier" type, were introduced in Java 16. Now, they are also available in Groovy. The same syntax, though Groovy also introduces a <code>@RecordType</code> annotation that you can use interchangeably. And even if this is not that a game-changer as it was for Java, it&#8217;s good to see Groovy heading up with the latest features introduced in its mother language.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">record Point(int x, int y) &#123;&#125;def p1 = new Point(0, 0)def p2 = new Point(2, 4)def p3 = new Point(0, 0)assert p1.x() == 0assert p1.y() == 0assert p2.x() == 2assert p2.y() == 4assert p1.toString() == 'Point[x=0, y=0]'assert p2.toString() == 'Point[x=2, y=4]'assert p1 == p3</code></pre></div></div></div></div><div class="sect1"><h2 id="sealed-types">Sealed Types</h2><div class="sectionbody"><div class="paragraph"><p>Another feature influenced by the latest changes in the Java programming language. Sealed types allow you to restrict which classes (or interfaces) can extend the specific sealed type. It can be done either explicitly (using the "permits" keyword) or implicitly (without any keyword) if all relevant classes are stored in the same source file. Similar to records, Groovy also introduces the <code>@Sealed</code> annotation that you can use interchangeably if this is your preference. When to use sealed types? Maybe you don&#8217;t want to allow anyone to extend your class for security reasons. Or perhaps you want to add new methods to the interface in the future, and you want to have strict control over affected subclasses. If that&#8217;s the case - sealed types might be something you want to look at.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.ToStringsealed interface Tree&lt;T&gt; &#123; &#125;@Singletonfinal class Empty implements Tree &#123;    String toString() &#123; "Empty" &#125;&#125;@ToStringfinal class Node&lt;T&gt; implements Tree&lt;T&gt; &#123;    final T value    final Tree&lt;T&gt; left, right    Node(T value, Tree&lt;T&gt; left, Tree&lt;T&gt; right) &#123;        this.value = value        this.left = left        this.right = right    &#125;&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="type-checkers">Type Checkers</h2><div class="sectionbody"><div class="paragraph"><p>Even though Groovy is mainly known for its dynamic capabilities, it allows you to be much stricter in type checking than Java. The newly added <code>groovy-typecheckers</code> optional module introduces a regex checker that can help you catch errors in your regular expressions at the compile time. Just like in this example - we have a regular expression missing a closing parenthesis. Typically, the compiler cannot detect this kind of issue, so we either find it in the unit test or at the runtime. Here I run this script in the GroovyShell, so I can catch the expected MultipleCompilationErrorsException.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.TypeChecked@TypeChecked(extensions = 'groovy.typecheckers.RegexChecker')def testRegexChecker() &#123;    def date = '2022-04-03'    assert date ==~ /(\d&#123;4&#125;)-(\d&#123;1,2&#125;)-(\d&#123;1,2&#125;/&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="built-in-macro-methods">Built-in Macro Methods</h2><div class="sectionbody"><div class="paragraph"><p>Macro methods allow you to access and manipulate the compiler AST data structures. The macro method call looks like a regular method call, but that&#8217;s not the case - it will be replaced by the generated code at the compile time. Here are a few examples of such macro methods. For instance, the <code>SV</code> method creates a string with variable names and associated values. The <code>SVI</code> one uses Groovy&#8217;s inspect method, which produces a bit different output - for instance, it does not unroll the range object as shown in this example.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def num = 42def list = [1 ,2, 3]def range = 0..5def string = 'foo'assert SV(num, list, range, string) == 'num=42, list=[1, 2, 3], range=[0, 1, 2, 3, 4, 5], string=foo'assert SVI(range) == 'range=0..5'assert NV(range) instanceof NamedValueassert NV(string).name == 'string' &amp;&amp; NV(string).val == 'foo'</code></pre></div></div></div></div><div class="sect1"><h2 id="pojo-annotation"><code>@POJO</code> annotation</h2><div class="sectionbody"><div class="paragraph"><p>If you are familiar with Groovy, you already know that every Groovy class implements the <code>GroovyObject</code> interface. There&#8217;s nothing to worry about if you only stay with your code in the Groovy ecosystem. But sometimes, you want to use Groovy to write a library code that can be used in a pure Java project as well. You can bring those two worlds together with the new ' @POJO ' annotation. Any class annotated with the <code>@POJO</code> annotation can be used without adding Groovy at the runtime. Just like the <code>PojoPoint</code> class shown in this example. Let&#8217;s compile it and run it as a Java program.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.CompileStaticimport groovy.transform.Immutableimport groovy.transform.stc.POJO@POJO@Immutable@CompileStaticclass PojoPoint &#123;    int x, y    static void main(String[] args) &#123;        PojoPoint point = new PojoPoint(1,1)        System.out.println(point.toString())    &#125;&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="groovy-contracts">Groovy Contracts</h2><div class="sectionbody"><div class="paragraph"><p>Groovy contracts might be a blessing if you are tired of writing defensive code. The <code>@Invariant</code> class annotation defines assertions that are checked during an object&#8217;s lifetime - after the constructor call, before, and after the method call. The <code>@Requires</code> annotation represents a method precondition - an assertion executed before the method call. And the <code>@Ensures</code> annotation works as a method postcondition - an assertion executed after the method call. Some may say that these annotations can be easily replaced by explicit assertions in the method&#8217;s body. And that&#8217;s true. But if you want to keep the contract and the business logic nicely separated, Groovy contracts sound like a good place to start.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.contracts.Ensuresimport groovy.contracts.Invariantimport groovy.contracts.Requires@Invariant(&#123; speed &gt;= 0 &#125;)class Rocket &#123;    int speed = 0    boolean started = false    @Requires(&#123; !started &#125;)    Rocket startEngine() &#123; tap &#123;started = true &#125;&#125;    @Requires(&#123; started &#125;)    Rocket stopEngine() &#123; tap &#123; started = false &#125;&#125;    @Requires(&#123; started &#125;)    @Ensures(&#123; old.speed &lt; speed &#125;)    Rocket accelerate(int value) &#123; tap &#123; speed += value &#125;&#125;&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="ginq">GINQ</h2><div class="sectionbody"><div class="paragraph"><p>Groovy-Integrated Query language. You will love this feature if you are a fan of SQL-like languages. GINQ allows you to query collections using a SQL-like syntax. Just like in this example. We have a JSON document containing the <code>people</code> field. We use GINQ to find all people that are 18+, in descending order, taking the first three results and modifying the returned data to be upper-cased and limited to the first two letters only. As far as I know, the Groovy team plans to extend GINQ to support SQL databases so that you can write a compile-time generated and type-checked SQL queries.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.json.JsonSlurperdef json = new JsonSlurper().parseText '''    &#123;        "people": [            &#123;"name": "Alan", "age": 11&#125;,            &#123;"name": "Mary", "age": 26&#125;,            &#123;"name": "Eric", "age": 34&#125;,            &#123;"name": "Elisabeth", "age": 14&#125;,            &#123;"name": "Marc", "age": 2&#125;,            &#123;"name": "Robert", "age": 52&#125;,            &#123;"name": "Veronica", "age": 32&#125;,            &#123;"name": "Alex", "age": 17&#125;        ]    &#125;    '''assert GQ &#123;    from f in json.people    where f.age &gt;= 18    orderby f.age in desc    limit 3    select f.name.toUpperCase().take(2)&#125;.toList() == ['RO', 'ER', 'VE']</code></pre></div></div></div></div><div class="sect1"><h2 id="toml-support">TOML Support</h2><div class="sectionbody"><div class="paragraph"><p>Groovy 3 added YAML format support, and now Groovy 4 adds TOML format support as well. Helpful if you are working with such a format in your codebase. It is worth mentioning that the output produced by the TomlBuilder class does not produce table headers but dot-separated field names instead.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.toml.TomlBuilderimport groovy.toml.TomlSlurperString input = '''# This is a TOML document (taken from https://toml.io)title = "TOML Example"[owner]name = "Tom Preston-Werner"dob = 1979-05-27T07:32:00-08:00[database]enabled = trueports = [ 8000, 8001, 8002 ]data = [ ["delta", "phi"], [3.14] ]temp_targets = &#123; cpu = 79.5, case = 72.0 &#125;[servers][servers.alpha]ip = "10.0.0.1"role = "frontend"[servers.beta]ip = "10.0.0.2"role = "backend"'''def toml = new TomlSlurper().parseText(input)assert toml.title == 'TOML Example'assert toml.owner.name == 'Tom Preston-Werner'assert toml.database.ports == [8000, 8001, 8002]assert toml.servers.alpha.ip == '10.0.0.1'assert toml.servers.beta.ip == '10.0.0.2'TomlBuilder builder = new TomlBuilder()builder &#123;    title 'This is TOML document'    servers &#123;        alpha &#123;            ip '10.0.0.1'        &#125;        beta &#123;            ip '10.0.0.2'        &#125;    &#125;&#125;assert builder.toString() =='''title = 'This is TOML document'servers.alpha.ip = '10.0.0.1'servers.beta.ip = '10.0.0.2''''</code></pre></div></div></div></div><div class="sect1"><h2 id="jdk-8-compatibility">JDK 8 Compatibility</h2><div class="sectionbody"><div class="paragraph"><p>The minimum Java version required to run Groovy 4 is JDK 8. You may ask - "but how does Groovy handle, e.g., records"? Let me show it to you. Here I have Java 17 and Groovy 4.0.1. I&#8217;m gonna compile this script to the class file, and when we open it in IntelliJ, we can see that it produces a Java native record equivalent as expected. Now I&#8217;m gonna switch to Java 8, and let&#8217;s do the same thing. When we open the class file in IntelliJ, we can see that now the generated class "emulates" a record behavior but does not use the native record syntax. And that&#8217;s the beauty of Groovy code portability - the same code and brand new language features that work even with a pretty old Java version.</p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sealed types, switch expressions, and record types.
Here are just a few new features introduced in the latest Groovy 4.0 release.
In this video, I want to show you ten things that make Groovy 4.0 amazing.
And to keep this video short, we&amp;#8217;re not going to dive deep into each of them.
Instead, I intend to give you a quick overview of the new features.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="youtube" scheme="https://e.printstacktrace.blog/t/youtube/"/>
    
    <category term="groovy-4.0" scheme="https://e.printstacktrace.blog/t/groovy-4-0/"/>
    
  </entry>
  
  <entry>
    <title>How to generate a random password in Groovy?</title>
    <link href="https://e.printstacktrace.blog/how-to-generate-random-password-in-groovy/"/>
    <id>https://e.printstacktrace.blog/how-to-generate-random-password-in-groovy/</id>
    <published>2022-02-14T11:44:06.000Z</published>
    <updated>2022-02-14T11:44:06.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Sometimes you need to generate a random password (or just random string of any kind.)Today I will show you how to do it with a single line of code in Groovy 3 (or newer.)</p></div><a id="more"></a><div class="paragraph"><p>The complete code that generates a random password in Groovy looks like this:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">('0'..'z').shuffled().take(10).join()</code></pre></div></div><div class="paragraph"><p>Let&#8217;s deconstruct this example and understand what each part of this code mean.</p></div><div class="paragraph"><p>The first part of that code <code>('0'..'z')</code> creates a range of characters.If we convert it to a list, we would get a list of the following characters:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, :, ;, &lt;, =, &gt;, ?, @, A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z, [, \, ], ^, _, `, a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z]</code></pre></div></div><div class="paragraph"><p>As you can see, the list contains:</p></div><div class="ulist"><ul><li><p>lower-case letters,</p></li><li><p>upper-case letters,</p></li><li><p>digits,</p></li><li><p>and several special characters.</p></li></ul></div><div class="paragraph"><p>In the next part, we call <code>shuffled()</code> method on the generated range to convert it to a list and shuffle the order of its elements.</p></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Keep in mind that the <code>shuffled()</code> method was introduced in Groovy 3.0.</td></tr></table></div><div class="paragraph"><p>Once the list of characters is shuffled, we take the first ten elements from it using the <code>take(10)</code> method call.</p></div><div class="paragraph"><p>And last but not least, we call <code>join()</code> method to create a string from a list of characters.</p></div><div class="admonitionblock warning"><table><tr><td class="icon"><i class="fa icon-warning" title="Warning"></i></td><td class="content">The example shown in this blog post does not guarantee that the generated password contains e.g. at least one special character and/or one digit.</td></tr></table></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">Groovy Shell (4.0.0, JVM: 11.0.10)Type ':help' or ':h' for help.----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------groovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; 7@R162?nIvgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; O]TR84[syYgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; vx^[rM4g&lt;dgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; 2Cv7AYgcSdgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; 7i`Zf[IRDygroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; ClKIjVTxcBgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; bU^[G5oE?Tgroovy:000&gt; ('0'..'z').shuffled().take(10).join()===&gt; tf\MmJ=SHp</code></pre></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sometimes you need to generate a random password (or just random string of any kind.)
Today I will show you how to do it with a single line of code in Groovy 3 (or newer.)&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="string" scheme="https://e.printstacktrace.blog/t/string/"/>
    
    <category term="random" scheme="https://e.printstacktrace.blog/t/random/"/>
    
    <category term="tips" scheme="https://e.printstacktrace.blog/t/tips/"/>
    
  </entry>
  
  <entry>
    <title>Merging JSON files recursively in the command-line</title>
    <link href="https://e.printstacktrace.blog/merging-json-files-recursively-in-the-command-line/"/>
    <id>https://e.printstacktrace.blog/merging-json-files-recursively-in-the-command-line/</id>
    <published>2020-11-05T22:14:35.000Z</published>
    <updated>2020-11-05T22:14:35.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Have you ever need to merge two (or more) JSON files and you wondered if you can do it in the command-line?In this blog post, I will show you how you can use <code>jq</code> command-line JSON processor to merge recursively multiple JSON files.</p></div><a id="more"></a><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Get <code>jq</code> from <a href="https://stedolan.github.io/jq/" class="bare">https://stedolan.github.io/jq/</a></td></tr></table></div><div class="sect1"><h2 id="exemplary-input-files">Exemplary input files</h2><div class="sectionbody"><div class="paragraph"><p>Below you can find two exemplary JSON files we are going to merge recursively, including combining unique elements from all arrays.</p></div><div class="listingblock"><div class="title">Listing 1. product1.json file</div><div class="content"><pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">&#123;  "id": "324a9e6f-44e8-4d70-9645-7247e5385d05",  "name": "Product old name",  "prices": [    &#123;      "amount": 19.99,      "currency": "USD"    &#125;  ],  "tags": [    "tech",    "book",    "programming"  ],  "prototype": false,  "meta": &#123;    "test": 1  &#125;,  "nested": &#123;    "map": &#123;      "1": "2",      "4": 0    &#125;,    "list": [      1,      2,      3    ]  &#125;&#125;</code></pre></div></div><div class="listingblock"><div class="title">Listing 2. product2.json file</div><div class="content"><pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">&#123;  "id": "324a9e6f-44e8-4d70-9645-7247e5385d05",  "name": "Product new name",  "vendor": "Vendor Name Inc.",  "prices": [    &#123;      "amount": 19.99,      "currency": "CAD"    &#125;  ],  "tags": [    "programming",    "learning"  ],  "meta": &#123;    "beta": 0  &#125;,  "nested": &#123;    "map": &#123;      "1": 3,      "2": 1    &#125;,    "list": [      2,      3,      4    ]  &#125;&#125;</code></pre></div></div></div></div><div class="sect1"><h2 id="merging-multiple-files-recursively">Merging multiple files recursively</h2><div class="sectionbody"><div class="paragraph"><p>And here is the <code>jq</code> code that does the job:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq -s 'def deepmerge(a;b):  reduce b[] as $item (a;    reduce ($item | keys_unsorted[]) as $key (.;      $item[$key] as $val | ($val | type) as $type | .[$key] = if ($type == "object") then        deepmerge(&#123;&#125;; [if .[$key] == null then &#123;&#125; else .[$key] end, $val])      elif ($type == "array") then        (.[$key] + $val | unique)      else        $val      end)    );  deepmerge(&#123;&#125;; .)' product1.json product2.json &gt; merged.json</code></pre></div></div><div class="paragraph"><p>We define <code>deepmerge(a;b)</code> function that merges two JSON objects recursively.</p></div></div></div><div class="sect1"><h2 id="output">Output</h2><div class="sectionbody"><div class="listingblock"><div class="title">Listing 3. merged.json file</div><div class="content"><pre class="highlightjs highlight"><code class="language-json hljs" data-lang="json">&#123;  "id": "324a9e6f-44e8-4d70-9645-7247e5385d05",  "name": "Product new name",  "prices": [    &#123;      "amount": 19.99,      "currency": "CAD"    &#125;,    &#123;      "amount": 19.99,      "currency": "USD"    &#125;  ],  "tags": [    "book",    "learning",    "programming",    "tech"  ],  "prototype": false,  "meta": &#123;    "test": 1,    "beta": 0  &#125;,  "nested": &#123;    "map": &#123;      "1": 3,      "4": 0,      "2": 1    &#125;,    "list": [      1,      2,      3,      4    ]  &#125;,  "vendor": "Vendor Name Inc."&#125;</code></pre></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Have you ever need to merge two (or more) JSON files and you wondered if you can do it in the command-line?
In this blog post, I will show you how you can use &lt;code&gt;jq&lt;/code&gt; command-line JSON processor to merge recursively multiple JSON files.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="jq cookbook" scheme="https://e.printstacktrace.blog/jq-cookbook/"/>
    
    
    <category term="merge" scheme="https://e.printstacktrace.blog/t/merge/"/>
    
    <category term="jq" scheme="https://e.printstacktrace.blog/t/jq/"/>
    
    <category term="json" scheme="https://e.printstacktrace.blog/t/json/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins Declarative Pipeline with the dynamic agent - how to configure it?</title>
    <link href="https://e.printstacktrace.blog/jenkins-declarative-pipeline-dynamic-agent/"/>
    <id>https://e.printstacktrace.blog/jenkins-declarative-pipeline-dynamic-agent/</id>
    <published>2020-09-27T07:32:11.000Z</published>
    <updated>2020-09-27T07:32:11.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>In some cases, you would like to use Jenkins declarative pipeline with the dynamic agent.For instance, you want to provide a list of available agent nodes as a parameter for the pipeline job.In this blog post, I will explain how you can configure such a behavior in just a few steps.</p></div><a id="more"></a><div class="sect1"><h2 id="jenkins-pipeline-agent-label-from-the-parameter">Jenkins Pipeline <code>agent</code> label from the parameter</h2><div class="sectionbody"><div class="listingblock"><div class="title">Listing 1. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent &#123;        label params.AGENT == "any" ? "" : params.AGENT <i class="conum" data-value="1"></i><b>(1)</b>    &#125;    parameters &#123;        choice(name: "AGENT", choices: ["any", "docker", "windows", "linux"]) <i class="conum" data-value="2"></i><b>(2)</b>    &#125;    stages &#123;        stage("Build") &#123;            steps &#123;                echo "Hello, World!"            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>There is one thing worth explaining.You can see that in the line <em class="conum" data-value="1"></em>, we check if <code>params.AGENT</code> is equal to <code>"any"</code>.If this is the case, we put an empty string instead.<em>(An empty string in this case is an equivalent of <code>agent any</code> - <a href="https://issues.jenkins.io/browse/JENKINS-43016?focusedCommentId=333915&amp;page=com.atlassian.jira.plugin.system.issuetabpanels%3Acomment-tabpanel#comment-333915">source</a>.)</em>Otherwise, Jenkins would search for the node with label <code>"any"</code> instead.</p></div><div class="paragraph"><p>In the line <em class="conum" data-value="2"></em>, we define a list of available nodes.When you start the job and choose <code>any</code> from the dropdown, any available node will be used to run the job.If you choose <code>docker</code>, <code>windows</code>, or <code>linux</code> (or any other label you will define in your pipeline), the node with that exact label will be used to run the job.And that&#8217;s it. <span class="icon"><i class="fa fa-smile-o"></i></span></p></div><div class="openblock text-center mt-5"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://www.jdoqocy.com/click-100264350-13722493" data-type="banner" data-name="oreilly-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/oreilly.png"/></a></p></div></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In some cases, you would like to use Jenkins declarative pipeline with the dynamic agent.
For instance, you want to provide a list of available agent nodes as a parameter for the pipeline job.
In this blog post, I will explain how you can configure such a behavior in just a few steps.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Jenkins Pipeline Cookbook" scheme="https://e.printstacktrace.blog/jenkins-pipeline-cookbook/"/>
    
    
    <category term="devops" scheme="https://e.printstacktrace.blog/t/devops/"/>
    
    <category term="jenkins" scheme="https://e.printstacktrace.blog/t/jenkins/"/>
    
    <category term="jenkins-pipeline" scheme="https://e.printstacktrace.blog/t/jenkins-pipeline/"/>
    
    <category term="continuous-integration" scheme="https://e.printstacktrace.blog/t/continuous-integration/"/>
    
    <category term="jenkinsfile" scheme="https://e.printstacktrace.blog/t/jenkinsfile/"/>
    
  </entry>
  
  <entry>
    <title>Groovy Ecosystem Usage Report (2020)</title>
    <link href="https://e.printstacktrace.blog/groovy-ecosystem-usage-report-2020/"/>
    <id>https://e.printstacktrace.blog/groovy-ecosystem-usage-report-2020/</id>
    <published>2020-09-13T05:40:19.000Z</published>
    <updated>2020-09-13T05:40:19.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>What is the most popular Groovy library, framework, or a tool?I <a href="https://docs.google.com/forms/d/e/1FAIpQLSd28xZnASR15OXTUnL23GjS7dguz3CCeovupr5linw510JEyA/viewanalytics">surveyed 308 Groovy community members</a>, and here are the results.</p></div><a id="more"></a><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/groovy-top-10.png"><div class="img-lazyload-container" style="width:724px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/groovy-top-10.png" height="447" width="724" style="padding-top:61.74033149171271%"></div></a></div></div></div></div><div class="sect1"><h2 id="the-most-popular-tool-gradle">🥇 The most popular tool: <span class="mark">Gradle</span></h2><div class="sectionbody"><div class="paragraph"><p><a href="https://gradle.org/">Gradle</a> (an open-source build automation tool) collected the most votes - <strong>190</strong> out of 308 (61.7%).</p></div></div></div><div class="sect1"><h2 id="the-most-popular-library-spock-framework">🥇 The most popular library: <span class="mark">Spock Framework</span></h2><div class="sectionbody"><div class="paragraph"><p><a href="http://spockframework.org/">Spock Framework</a> (a testing library for Java and Groovy applications) collected <strong>157</strong> votes (51%).</p></div></div></div><div class="sect1"><h2 id="the-most-popular-web-framework-grails">🥇 The most popular web framework: <span class="mark">Grails</span></h2><div class="sectionbody"><div class="paragraph"><p><a href="https://grails.org/">Grails Framework</a> (a Groovy-based web application framework for the JVM built on top of Spring Boot) collected <strong>116</strong> votes (37.7%)</p></div></div></div><div class="sect1"><h2 id="using-groovy-as-a-scripting-language">Using Groovy as a <span class="mark">scripting language</span></h2><div class="sectionbody"><div class="ulist"><ul><li><p><strong>163</strong> respondents (52.9%) mentioned that they use Groovy as a scripting language.</p></li><li><p><strong>131</strong> respondents (42.5%) mentioned that they use Groovy as a general-purpose language.</p></li><li><p><strong>76</strong> respondents (24.7%) mentioned that they use Groovy for building fluent DSLs.</p></li></ul></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/groovy-top-20.png"><div class="img-lazyload-container" style="width:724px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/groovy-top-20.png" height="447" width="724" style="padding-top:61.74033149171271%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="my-personal-thoughts">My personal thoughts</h2><div class="sectionbody"><div class="paragraph"><p>I was close to guess the exact <strong>TOP 3</strong> - I thought it is going to be <strong>Spock</strong>, <strong>Gradle</strong>, and <strong>Grails</strong> (in this particular order.)The <strong>Groovy Console</strong> result surprised me, and especially when it&#8217;s compared to the <code>groovysh</code> - I definitely prefer to use a command-line tool instead of GUI-based one.</p></div><div class="paragraph"><p>There is also a positive trend in the <strong>Jenkins Pipeline</strong> space - much more people use Groovy with pipelines instead of Job DSL plugin.</p></div><div class="paragraph"><p>Also, <a href="https://micronaut.io/"><strong>Micronaut</strong></a> and <a href="https://spring.io/projects/spring-boot"><strong>Spring Boot</strong></a> are head-to-head when it comes to the number of users, but it&#8217;s hard to deduct anything from those numbers.</p></div><div class="paragraph"><p>I was also surprised by the <a href="https://codenarc.github.io/CodeNarc/"><strong>CodeNarc</strong></a>, <a href="https://gebish.org/"><strong>Geb</strong></a>, and <a href="https://picocli.info/"><strong>Picocli</strong></a> small numbers.The last two are used in the specific context, which may explain the number of votes.However, <a href="https://codenarc.github.io/CodeNarc/"><strong>CodeNarc</strong></a> is a static analysis tool for Groovy, and I would love to see more people using it to improve their code quality.</p></div><div class="paragraph"><p>There are my first thoughts after looking at the survey results.<strong>And what are yours?</strong>Let me know in the comments bellow. 👍</p></div></div></div><div class="sect1"><h2 id="about-the-survey">About the survey</h2><div class="sectionbody"><div class="paragraph"><p>The survey was launched on <strong>August 28th</strong>, and it was collecting the answers up to <strong>September 11th</strong>.It contained a single multi-choice question with predefined options to choose from, plus a text input to vote for a thing that was not mentioned on the list.It was announced using many communication channels, including the Groovy mailing list, Groovy Community Slack, Twitter, LinkedIn, Reddit, etc.The survey link was clicked 520 times, and it was converted to 308 responses (59.23% conversion rate.)</p></div><div class="ulist"><ul><li><p><a href="https://docs.google.com/forms/d/e/1FAIpQLSd28xZnASR15OXTUnL23GjS7dguz3CCeovupr5linw510JEyA/viewanalytics">The survey&#8217;s chart summary (Google Form)</a></p></li><li><p><a href="https://docs.google.com/spreadsheets/d/1poYljiBY1bhb00pBZGpjc-UZ7b4bRb3RnVyg8bFJXbE/edit?usp=sharing">The survey&#8217;s raw results (Google Sheet)</a></p></li></ul></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">This survey and its results <strong>do not</strong> represent the Groovy community choices as a whole.I did my best to survey as many community members as possible.However, I couldn&#8217;t reach out to every single person.<strong>Treat this report as trends radar</strong>, based on the data provided by some percentage of the active Groovy users.</td></tr></table></div><div class="paragraph"><p>(PS: I will run a similar survey next year so that we can study changes in trends. 😉)</p></div><div class="listingblock"><div class="title">Listing 1. Survey results sorted by the number of votes</div><div class="content"><pre class="highlightjs highlight"><code class="language-plain hljs" data-lang="plain">#  | Votes | Choice======================================================================1  | 190   | Gradle2  | 163   | I use Groovy for scripting3  | 157   | Spock4  | 131   | I use Groovy as a general purpose language5  | 127   | Groovy Console6  | 116   | Grails7  | 92    | Jenkins Pipeline8  | 82    | Micronaut9  | 81    | Spring Boot10 | 76    | I use Groovy for DSLs11 | 58    | CodeNarc12 | 50    | Jenkins Job DSL13 | 50    | Geb14 | 49    | groovysh15 | 42    | Picocli16 | 35    | GPars17 | 28    | Nextflow18 | 21    | JMeter19 | 19    | Ratpack20 | 16    | SoapUI21 | 14    | Jira ScriptRunner22 | 13    | Vert.x23 | 8     | Spreadsheet Builder24 | 8     | REST-assured25 | 6     | sshoogr26 | 5     | Ersatz27 | 4     | Spring Cloud Contracts28 | 3     | BeakerX29 | 3     | Gru30 | 2     | Griffon31 | 2     | Dru32 | 2     | Gaelyk33 | 2     | GPerfUtils (gprof, gbench)34 | 1     | gradle35 | 1     | Gradle Plugin authoring36 | 1     | Grain37 | 1     | Test automation with MQ and XmlUnit and Selenium38 | 1     | Jira39 | 1     | JBake40 | 1     | docToolchain41 | 1     | Groovy + JUnit 5 + Mockito is my favorite way to write tests42 | 1     | I'm writing a content migration process for a large LMS using JSoup, apache httpcomponents, and JsonSlurper43 | 1     | writing my own AST and transpilers (metaprogramming)44 | 1     | Groovy used as a scripting language inside XWiki wiki pages45 | 1     | Vaadin, Ebean46 | 1     | groogle to work with google sheet https://groogle.gitlab.io/groogle/latest/index.html47 | 1     | gcontracts48 | 1     | I exclusively use Groovy for test automation49 | 1     | At work we use Java for the production code of our low-latency trading system, but all our tests are written in Groovy50 | 1     | Gremlin51 | 1     | GrooCSS52 | 1     | Portofino53 | 1     | It's part of our own Framework54 | 1     | Gaiden55 | 1     | Extensions for Legacy Java APIs56 | 1     | Katalon Studio57 | 1     | Kiss web framework58 | 1     | My favourite scripting language59 | 1     | Transformation scripts in Oracle SQL Developer Data Modeler60 | 1     | Bonita, template engine61 | 1     | FileBot62 | 1     | GQL (https://github.com/grooviter/gql) ASTEROID (https://github.com/grooviter/asteroid)63 | 1     | Wide use of @CompileStatic</code></pre></div></div><div class="openblock text-center mt-5"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://www.bluehost.com/track/szymonstepniak/" data-type="banner" data-name="bluehost-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/bluehost.png"/></a></p></div></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;What is the most popular Groovy library, framework, or a tool?
I &lt;a href=&quot;https://docs.google.com/forms/d/e/1FAIpQLSd28xZnASR15OXTUnL23GjS7dguz3CCeovupr5linw510JEyA/viewanalytics&quot;&gt;surveyed 308 Groovy community members&lt;/a&gt;, and here are the results.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="grails" scheme="https://e.printstacktrace.blog/t/grails/"/>
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="ratpack" scheme="https://e.printstacktrace.blog/t/ratpack/"/>
    
    <category term="gradle" scheme="https://e.printstacktrace.blog/t/gradle/"/>
    
    <category term="micronaut" scheme="https://e.printstacktrace.blog/t/micronaut/"/>
    
    <category term="spock-framework" scheme="https://e.printstacktrace.blog/t/spock-framework/"/>
    
    <category term="codenarc" scheme="https://e.printstacktrace.blog/t/codenarc/"/>
    
    <category term="geb" scheme="https://e.printstacktrace.blog/t/geb/"/>
    
    <category term="picocli" scheme="https://e.printstacktrace.blog/t/picocli/"/>
    
    <category term="spring-boot" scheme="https://e.printstacktrace.blog/t/spring-boot/"/>
    
  </entry>
  
  <entry>
    <title>How to convert JSON to CSV from the command-line?</title>
    <link href="https://e.printstacktrace.blog/how-to-convert-json-to-csv-from-the-command-line/"/>
    <id>https://e.printstacktrace.blog/how-to-convert-json-to-csv-from-the-command-line/</id>
    <published>2020-08-25T18:44:31.000Z</published>
    <updated>2020-08-25T18:44:31.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Are you looking for an easy and fast way to convert a JSON file to a CSV one?In this blog post, I will show you how you can use <code>jq</code> command-line tool to do exactly that.</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#install-jq">Install <code>jq</code></a></li><li><a href="#prepare-the-json-file">Prepare the JSON file</a></li><li><a href="#convert-a-json-file-to-the-csv-one">Convert a JSON file to the CSV one</a></li></ul></div><div class="sect1"><h2 id="install-jq">Install <code>jq</code></h2><div class="sectionbody"><div class="paragraph"><p>First, you need to install <code>jq</code> in your operating system - <a href="https://stedolan.github.io/jq/download/" class="bare">https://stedolan.github.io/jq/download/</a></p></div></div></div><div class="sect1"><h2 id="prepare-the-json-file">Prepare the JSON file</h2><div class="sectionbody"><div class="paragraph"><p>To convert JSON file to the CSV format we will need a JSON file that is represented by an array of objects.In this tutorial, I use Github&#8217;s Jobs API that returns JSON in that desired format.Below you can find a <code>curl</code> command that searches for open kotlin job positions and stores the result in the <code>jobs.json</code> file.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ curl -s "https://jobs.github.com/positions.json?description=kotlin" &gt; jobs.json</code></pre></div></div></div></div><div class="sect1"><h2 id="convert-a-json-file-to-the-csv-one">Convert a JSON file to the CSV one</h2><div class="sectionbody"><div class="paragraph"><p>And here is how we can convert input JSON file to the CSV format.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq -r 'map(&#123;id,title,url,company,location&#125;) | (first | keys_unsorted) as $keys | map([to_entries[] | .value]) as $rows | $keys,$rows[] | @csv' jobs.json &gt; jobs.csv$ cat jobs.csv"id","title","url","company","location""2ededc50-3c18-4d2c-ba54-95151ca4209a","Android Engineer","https://jobs.github.com/positions/2ededc50-3c18-4d2c-ba54-95151ca4209a","Trade Republic Bank GmbH","Berlin""2cc06700-d9ac-45ad-9bd7-a7e5d3c29b6f","Software Engineer - Android","https://jobs.github.com/positions/2cc06700-d9ac-45ad-9bd7-a7e5d3c29b6f","AiCure","Remote""242132e0-f129-4a1b-8bfa-30f2fed13123","Senior Backend Developer (Data Science)","https://jobs.github.com/positions/242132e0-f129-4a1b-8bfa-30f2fed13123","komoot","Remote Europe""fd8c3b7c-ccfe-4cc1-882c-ef9c0522903e","Software Engineer","https://jobs.github.com/positions/fd8c3b7c-ccfe-4cc1-882c-ef9c0522903e","HBM nCode Federal LLC","Starkville, MS""7d84945b-893d-493e-957a-4ed9e6f84607","Software Engineer","https://jobs.github.com/positions/7d84945b-893d-493e-957a-4ed9e6f84607","HBM nCode Federal LLC","Southfield, MI""2231d64f-e79b-4036-8601-24b2717b2896","Senior Fullstack / Flutter Developer (m/f/d)","https://jobs.github.com/positions/2231d64f-e79b-4036-8601-24b2717b2896","Superlist","Remote""caa90907-8252-4732-a815-06d96f1348bb","Senior Software Engineer - Mobile (m/f/d)","https://jobs.github.com/positions/caa90907-8252-4732-a815-06d96f1348bb","BASF Digital Farming GmbH","Köln"</code></pre></div></div><div class="paragraph"><p>Let&#8217;s deconstruct it piece by piece.</p></div><div class="ulist"><ul><li><p>We run <code>jq -r</code> to output raw strings (without double quotes.)</p></li><li><p>In the filter part, we pipe multiple filters together, starting with <code>map(&#123;id,title,url,company,location&#125;)</code>. This filter instructs <code>jq</code> which keys we want to extract from the input JSON file.</p></li><li><p>Then we use <code>(first | keys_unsorted) as $keys</code> filter which takes the first object, extracts its keys and stores them under the <code>$keys</code> variable as an array.</p></li><li><p>Next, we use <code>map([to_entries[] | .value]) as $rows</code> filter which converts every key-value entry like <code>"foo": "bar"</code> into an object like <code>&#123;"key":"foo","value":"bar"&#125;</code> so we can extract only values as an array and store it in a <code>$rows</code> variable.</p></li><li><p>Once we do it, we can use <code>$keys,$rows[]</code> filter to put keys and rows together and then pipe it with the <code>@csv</code> filter to convert JSON objects to CSV rows.</p></li></ul></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/uIKvYgix-L4" data-time="9:00" data-type="youtube" data-name="How to merge two JSON files in the command-line using jq? | #jq #json #curl"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/uIKvYgix-L4/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/uIKvYgix-L4" data-type="youtube" data-name="How to merge two JSON files in the command-line using jq? | #jq #json #curl">How to merge two JSON files in the command-line using jq? | #jq #json #curl</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">In this jq tutorial video, I show you how you can merge two JSON files in the command-line using jq JSON processor. I explain how to merge those files using a simple &quot;add&quot; filter, then I explain how to merge nested objects, as well as how to merge arrays of different objects.<a class="gatr font-weight-bold" href="https://youtu.be/uIKvYgix-L4" data-type="youtube" data-name="How to merge two JSON files in the command-line using jq? | #jq #json #curl">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div><div class="openblock text-center mt-5"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://blinkist.o6eiov.net/c/2430749/978270/10732" data-type="banner" data-name="blinkist-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/blinkist.png"/></a></p></div></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Are you looking for an easy and fast way to convert a JSON file to a CSV one?
In this blog post, I will show you how you can use &lt;code&gt;jq&lt;/code&gt; command-line tool to do exactly that.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="jq cookbook" scheme="https://e.printstacktrace.blog/jq-cookbook/"/>
    
    
    <category term="curl" scheme="https://e.printstacktrace.blog/t/curl/"/>
    
    <category term="jq" scheme="https://e.printstacktrace.blog/t/jq/"/>
    
    <category term="json" scheme="https://e.printstacktrace.blog/t/json/"/>
    
    <category term="csv" scheme="https://e.printstacktrace.blog/t/csv/"/>
    
    <category term="command-line" scheme="https://e.printstacktrace.blog/t/command-line/"/>
    
    <category term="how-to" scheme="https://e.printstacktrace.blog/t/how-to/"/>
    
  </entry>
  
  <entry>
    <title>5 Common Jenkins Pipeline Mistakes</title>
    <link href="https://e.printstacktrace.blog/5-common-jenkins-pipeline-mistakes/"/>
    <id>https://e.printstacktrace.blog/5-common-jenkins-pipeline-mistakes/</id>
    <published>2020-08-24T10:53:13.000Z</published>
    <updated>2020-08-24T10:53:13.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Do you know that you can unit test your Jenkins pipeline code?Have you ever used linter to validate the syntax of the Jenkinsfile?Do you use <code>input</code> step with a long timeout that blocks the executor and you don&#8217;t know how to improve?I answer those three questions (and two more) in the following YouTube video.</p></div><a id="more"></a><div class="videoblock embed-responsive embed-responsive-16by9 shadow"><div class="content"><iframe src="https://www.youtube.com/embed/aFRjn_4nb-Q?rel=0" frameborder="0" allowfullscreen></iframe></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Do you know that you can unit test your Jenkins pipeline code?
Have you ever used linter to validate the syntax of the Jenkinsfile?
Do you use &lt;code&gt;input&lt;/code&gt; step with a long timeout that blocks the executor and you don&amp;#8217;t know how to improve?
I answer those three questions (and two more) in the following YouTube video.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Jenkins Pipeline Cookbook" scheme="https://e.printstacktrace.blog/jenkins-pipeline-cookbook/"/>
    
    
    <category term="devops" scheme="https://e.printstacktrace.blog/t/devops/"/>
    
    <category term="jenkins" scheme="https://e.printstacktrace.blog/t/jenkins/"/>
    
    <category term="jenkins-pipeline" scheme="https://e.printstacktrace.blog/t/jenkins-pipeline/"/>
    
    <category term="cicd" scheme="https://e.printstacktrace.blog/t/cicd/"/>
    
  </entry>
  
  <entry>
    <title>How to merge two maps in Groovy?</title>
    <link href="https://e.printstacktrace.blog/how-to-merge-two-maps-in-groovy/"/>
    <id>https://e.printstacktrace.blog/how-to-merge-two-maps-in-groovy/</id>
    <published>2020-07-17T18:11:17.000Z</published>
    <updated>2020-07-17T18:11:17.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>One of the most popular map-related operation in any programming language is merging two (or more) maps.In this short blog post, I explain how to do it in the Groovy programming language, starting from the simplest <code>+</code> operation, up to more advanced use cases of merging nested maps and using runtime metaprogramming to add a <code>merge</code> method to the <code>Map</code> interface.Enjoy reading and learning!</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#merge-maps-using-operator">Merge maps using <code>+</code> operator</a></li><li><a href="#merge-maps-with-nested-maps">Merge maps with nested maps</a></li><li><a href="#merging-maps-with-nested-lists">Merging maps with nested lists</a></li><li><a href="#adding-merge-method-to-the-map-interface">Adding <code>merge</code> method to the <code>Map</code> interface</a></li><li><a href="#bonus-using-map-withdefault-method">Bonus: using <code>Map.withDefault</code> method</a></li></ul></div><div class="sect1"><h2 id="merge-maps-using-operator">Merge maps using <code>+</code> operator</h2><div class="sectionbody"><div class="paragraph"><p>The easiest way to merge two maps in Groovy is to use <code>+</code> operator.This method is straightforward - it creates a new map from the left-hand-side and right-hand-side maps.The below example illustrates it clearly.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3]def b = [a: 2, c: 4]def c = a + bassert c == [a: 2, b: 3, c: 4]</code></pre></div></div><div class="paragraph"><p>However, this method has one limitation - it doesn&#8217;t perform a so-called <em>"deep merge"</em> operation.Any nested map won&#8217;t get merged - it will only override the left-hand-side map entry with the one from the right-hand-side map.Just like in the example shown below.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3, z: [a: 10, b: 20]]def b = [a: 2, c: 4, z: [c: 30]]def c = a + bassert c == [a: 2, b: 3, c: 4, z: [c: 30]]</code></pre></div></div><div class="paragraph"><p>You can see that the final map contains the <code>z</code> key.However, the value it stores comes from the right-hand-side map only.</p></div></div></div><div class="sect1"><h2 id="merge-maps-with-nested-maps">Merge maps with nested maps</h2><div class="sectionbody"><div class="paragraph"><p>It can be fixed by implementing custom <code>merge(lhs,rhs)</code> function.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3, z: [a: 10, b: 20]]def b = [a: 2, c: 4, z: [c: 30]]def merge(Map lhs, Map rhs) &#123;    return rhs.inject(lhs.clone()) &#123; map, entry -&gt;        if (map[entry.key] instanceof Map &amp;&amp; entry.value instanceof Map) &#123;            map[entry.key] = merge(map[entry.key], entry.value)        &#125; else &#123;            map[entry.key] = entry.value        &#125;        return map    &#125;&#125;def d = merge(a, b)assert d == [a: 2, b: 3, c: 4, z: [a: 10, b: 20, c: 30]]</code></pre></div></div><div class="paragraph"><p>It starts with the <a href="http://docs.groovy-lang.org/latest/html/groovy-jdk/java/util/Collection.html#inject(java.lang.Object,%20groovy.lang.Closure)"><code>inject</code> function</a>, which is an equivalent (or close equivalent) of the <code>reduce</code> or <code>fold</code> functions from other popular programming languages.It takes two parameters - the initial value (the <code>lhs.clone()</code> in our case), and the two-argument closure that receives the current accumulator (the <code>map</code> variable from our example) and the current map entry from the iteration.</p></div><div class="admonitionblock tip"><table><tr><td class="icon"><i class="fa icon-tip" title="Tip"></i></td><td class="content">We use <code>lhs.clone()</code> to avoid creating any side effects. If we use just <code>lhs</code>, we would modify the state of the first map passed to the <code>merge</code> function.</td></tr></table></div><div class="paragraph"><p>In the next step, we need to check what is the type of the specific map key.If the same key stores a map in both maps, we call a <code>merge</code> function to create a new value as a result of merging two nested maps.Otherwise, we just copy the value from the right-hand-side map.</p></div><div class="paragraph"><p>It looks like we are ready to go, but what if maps we are merging contain e.g. a list of numbers?</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3, z: [a: 10, b: 20], y: [1,2,3,4]]def b = [a: 2, c: 4, z: [c: 30], y: [5,6,7]]def merge(Map lhs, Map rhs) &#123;    return rhs.inject(lhs.clone()) &#123; map, entry -&gt;        if (map[entry.key] instanceof Map &amp;&amp; entry.value instanceof Map) &#123;            map[entry.key] = merge(map[entry.key], entry.value)        &#125; else &#123;            map[entry.key] = entry.value        &#125;        return map    &#125;&#125;def d = merge(a, b)assert d == [a: 2, b: 3, c: 4, z: [a: 10, b: 20, c: 30], y: [5,6,7]]</code></pre></div></div><div class="paragraph"><p>Our current implementation does not support merging nested lists.Let&#8217;s fix it.</p></div></div></div><div class="sect1"><h2 id="merging-maps-with-nested-lists">Merging maps with nested lists</h2><div class="sectionbody"><div class="paragraph"><p>The solution to this problem is simple.We need to add one more condition.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3, z: [a: 10, b: 20], y: [1,2,3,4]]def b = [a: 2, c: 4, z: [c: 30], y: [5,6,7]]def merge(Map lhs, Map rhs) &#123;    return rhs.inject(lhs.clone()) &#123; map, entry -&gt;        if (map[entry.key] instanceof Map &amp;&amp; entry.value instanceof Map) &#123;            map[entry.key] = merge(map[entry.key], entry.value)        &#125; else if (map[entry.key] instanceof Collection &amp;&amp; entry.value instanceof Collection) &#123;            map[entry.key] += entry.value        &#125; else &#123;            map[entry.key] = entry.value        &#125;        return map    &#125;&#125;def d = merge(a, b)assert d == [a: 2, b: 3, c: 4, z: [a: 10, b: 20, c: 30], y: [1,2,3,4,5,6,7]]</code></pre></div></div><div class="paragraph"><p>It works! <span class="icon"><i class="fa fa-smile-o"></i></span></p></div></div></div><div class="sect1"><h2 id="adding-merge-method-to-the-map-interface">Adding <code>merge</code> method to the <code>Map</code> interface</h2><div class="sectionbody"><div class="paragraph"><p>Groovy supports <a href="https://groovy-lang.org/metaprogramming.html#_methods">runtime and compile time metaprogramming</a>.We can use it to add <code>merge(Map m)</code> method to the <code>Map</code> interface.Let&#8217;s re-use the <code>merge</code> function we&#8217;ve already implemented.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">def a = [a: 1, b: 3, z: [a: 10, b: 20], y: [1,2,3,4]]def b = [a: 2, c: 4, z: [c: 30], y: [5,6,7]]def merge(Map lhs, Map rhs) &#123;    return rhs.inject(lhs.clone()) &#123; map, entry -&gt;        if (map[entry.key] instanceof Map &amp;&amp; entry.value instanceof Map) &#123;            map[entry.key] = merge(map[entry.key], entry.value)        &#125; else if (map[entry.key] instanceof Collection &amp;&amp; entry.value instanceof Collection) &#123;            map[entry.key] += entry.value        &#125; else &#123;            map[entry.key] = entry.value        &#125;        return map    &#125;&#125;Map.metaClass.merge &lt;&lt; &#123; Map rhs -&gt; merge(delegate, rhs) &#125; <i class="conum" data-value="1"></i><b>(1)</b>def e = a.merge(b) <i class="conum" data-value="2"></i><b>(2)</b>assert e == [a: 2, b: 3, c: 4, z: [a: 10, b: 20, c: 30], y: [1,2,3,4,5,6,7]]</code></pre></div></div><div class="paragraph"><p>In the final example, we use runtime metaprogramming to create <code>merge(Map rhs)</code> method in the <code>Map</code> interface <em class="conum" data-value="1"></em>.This way we can simply call <code>a.merge(b)</code> <em class="conum" data-value="2"></em> to create a new map using deep merge operation.</p></div><div class="openblock text-center mt-5"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://click.linksynergy.com/deeplink?id=uow2LCEuhvQ&amp;mid=39197&amp;murl=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Fapache-groovy%2F" data-type="banner" data-name="groovy-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/complete-apache-groovy-course.png"/></a></p></div></div></div></div></div><div class="sect1"><h2 id="bonus-using-map-withdefault-method">Bonus: using <code>Map.withDefault</code> method</h2><div class="sectionbody"><div class="paragraph"><p>The last tip comes from the Reddit user <a href="https://www.reddit.com/r/groovy/comments/htx1d9/how_to_merge_two_maps_in_groovy/fyk4xdg/"><span class="icon color-reddit"><i class="fa fa-reddit"></i></span> <code>/u/-jp-</code></a> - thanks for the contribution!He posted a comment with an alternative solution that uses combination of <code>Map.withDefault</code> method with a <a href="https://docs.groovy-lang.org/latest/html/documentation/core-operators.html#method-pointer-operator">method pointer operator</a> that creates a closure from the <code>Map.get</code> method.It produces a slightly different solution than the one posted above.However, it&#8217;s very clever and creative way to use Groovy, so I want to share it with you as well.Here&#8217;s the original comment.</p></div><div class="quoteblock source-quote"><blockquote><div class="paragraph"><p>Depending on what semantics you want you can also use <code>withDefault</code>. This will create a view of <code>map1</code> that gets missing references from <code>map2</code>:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final map1 = [x: 1, y: 2]final map2 = [z: 3]final merged = map1.withDefault(map2.&amp;get)println "$merged.x, $merged.y, $merged.z"</code></pre></div></div></blockquote></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;One of the most popular map-related operation in any programming language is merging two (or more) maps.
In this short blog post, I explain how to do it in the Groovy programming language, starting from the simplest &lt;code&gt;+&lt;/code&gt; operation, up to more advanced use cases of merging nested maps and using runtime metaprogramming to add a &lt;code&gt;merge&lt;/code&gt; method to the &lt;code&gt;Map&lt;/code&gt; interface.
Enjoy reading and learning!&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="maps" scheme="https://e.printstacktrace.blog/t/maps/"/>
    
    <category term="merge" scheme="https://e.printstacktrace.blog/t/merge/"/>
    
    <category term="quick-tip" scheme="https://e.printstacktrace.blog/t/quick-tip/"/>
    
  </entry>
  
  <entry>
    <title>Building stackoverflow-cli with Java 11, Micronaut, Picocli, and GraalVM</title>
    <link href="https://e.printstacktrace.blog/building-stackoverflow-cli-with-java-11-micronaut-picocli-and-graalvm/"/>
    <id>https://e.printstacktrace.blog/building-stackoverflow-cli-with-java-11-micronaut-picocli-and-graalvm/</id>
    <published>2020-07-08T12:17:41.000Z</published>
    <updated>2020-07-18T08:43:49.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>In this blog post, I show you how to build <code>stackoverflow-cli</code> - a command-line application that allows you to search Stack Overflow questions directly from the terminal window.I use Java 11+, Micronaut, Picocli, and GraalVM&#8217;s <code>native-image</code>.</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#introduction">Introduction</a></li><li><a href="#installing-micronaut-cli-tool">Installing Micronaut CLI tool</a></li><li><a href="#creating-a-new-project">Creating a new project</a></li><li><a href="#enable-annotation-processing">Enable annotation processing</a></li><li><a href="#preparing-the-unit-test">Preparing the unit test</a></li><li><a href="#implementing-the-declarative-http-client">Implementing the declarative HTTP client</a></li><li><a href="#implementing-the-search-command">Implementing the <code>search</code> command</a></li><li><a href="#installing-graalvm-and-native-image">Installing GraalVM and <code>native-image</code></a></li><li><a href="#compiling-final-native-executable-file">Compiling final native executable file</a></li><li><a href="#useful-resources">Useful resources</a></li></ul></div><div class="sect1"><h2 id="introduction">Introduction</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start with explaining what the <code>stackoverflow-cli</code> application may look like.Below you can find what the final application&#8217;s help page may look like, as well as a help page for the <code>search</code> command, and an exemplary search result.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ stackoverflow-cli -hUsage: <strong>stackoverflow-cli</strong> [-hvV] [COMMAND]...  <span class="color-yellow">-h, --help</span>      Show this help message and exit.  <span class="color-yellow">-v, --verbose</span>   ...  <span class="color-yellow">-V, --version</span>   Print version information and exit.Commands:  <strong>search</strong>$ stackoverflow-cli search -hUsage: <strong>stackoverflow-cli search</strong> [-hV] [-n=<limit>] [-q=<query>] [-s=<sort>]                                [-t=<tag>]  <span class="color-yellow">-h, --help</span>             Show this help message and exit.  <span class="color-yellow">-n, --limit=&lt;limit&gt;</span>    Limit the number of results to specific number.                           Default: 10  <span class="color-yellow">-q, --query=&lt;query&gt;</span>    Search phrase  <span class="color-yellow">-s, --sort-by=&lt;sort&gt;</span>   Available options: relevance (default), activity,                           votes, creation  <span class="color-yellow">-t, --tags=&lt;tag&gt;</span>       Limit search to the specific tag  <span class="color-yellow">-V, --version</span>          Print version information and exit.$ stackoverflow-cli search -q "java stream api" -t java -n 4<strong class="color-green">✔</strong> 5|3 <strong class="color-yellow">Java Stream API filter</strong>      <a href="https://stackoverflow.com/questions/56691000/java-stream-api-filter" class="bare">https://stackoverflow.com/questions/56691000/java-stream-api-filter</a><strong class="color-green">✔</strong> 4|5 <strong class="color-yellow">Java Stream API collect method</strong>      <a href="https://stackoverflow.com/questions/56748468/java-stream-api-collect-method" class="bare">https://stackoverflow.com/questions/56748468/java-stream-api-collect-method</a><strong class="color-green">✔</strong> 0|2 <strong class="color-yellow">Java Stream API</strong>      <a href="https://stackoverflow.com/questions/51712988/java-stream-api" class="bare">https://stackoverflow.com/questions/51712988/java-stream-api</a><strong class="color-green">✔</strong> 4|2 <strong class="color-yellow">Java Stream API map argument</strong>      <a href="https://stackoverflow.com/questions/49936865/java-stream-api-map-argument" class="bare">https://stackoverflow.com/questions/49936865/java-stream-api-map-argument</a></code></pre></div></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/Xdcg4Drg1hc" data-time="36:53" data-type="youtube" data-name="Building command-line app with Java 11, Micronaut, Picocli, and GraalVM | #micronaut"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/Xdcg4Drg1hc/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/Xdcg4Drg1hc" data-type="youtube" data-name="Building command-line app with Java 11, Micronaut, Picocli, and GraalVM | #micronaut">Building command-line app with Java 11, Micronaut, Picocli, and GraalVM | #micronaut</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">In this video, I will show you how to create a standalone command-line application (CLI app) using Java 11, Micronaut, Picocli, and GraalVM. We are going to build from scratch a Java program, and in the end, we will compile it to the native binary executable file you can run without the Java Virtual Machine. This is not a deep dive tutorial. It's a quickstart introduction to the technology to give you a better understanding about what Micronaut, Picocli, and GraalVM are suitable for.<a class="gatr font-weight-bold" href="https://youtu.be/Xdcg4Drg1hc" data-type="youtube" data-name="Building command-line app with Java 11, Micronaut, Picocli, and GraalVM | #micronaut">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div></div><div class="sect1"><h2 id="installing-micronaut-cli-tool">Installing Micronaut CLI tool</h2><div class="sectionbody"><div class="paragraph"><p>The first thing we need to do is to create a new <a href="https://micronaut.io/">Micronaut</a> project.We can do it using Micronaut CLI - <code>mn</code>.Here is how you can install it using <a href="https://sdkman.io/">SDKMAN!</a> command-line tool.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ sdk install micronaut 2.0.0</code></pre></div></div></div></div><div class="sect1"><h2 id="creating-a-new-project">Creating a new project</h2><div class="sectionbody"><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Source code and resources links available in the <span class="icon"><i class="fa fa-github"></i></span> <a href="https://github.com/wololock/stackoverflow-cli">wololock/stackoverflow-cli</a> repository.</td></tr></table></div><div class="paragraph"><p>Once SDKMAN! is installed, we can create a new project.We use <code>create-cli-app</code> to generate a CLI-based project with the <a href="https://picocli.info/">Picocli</a> support added.We can define the minimum JDK version with <code>--jdk</code> option.In this example I also set the testing framework to <a href="http://spockframework.org/">Spock</a>, and I add two features: <code>graalvm</code> and <code>http-client</code>.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ mn create-cli-app --jdk=11 -t spock -f graalvm,http-client com.github.wololock.stackoverflow-cli</code></pre></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Instead of using the <code>mn</code> command-line tool, you can use <a href="https://micronaut.io/launch/">Micronaut Launch</a> web starter to bootstrap new project.</td></tr></table></div></div></div><div class="sect1"><h2 id="enable-annotation-processing">Enable annotation processing</h2><div class="sectionbody"><div class="paragraph"><p>After importing the new project to IntelliJ IDEA, go to Settings and search for "annotation processing" and enable annotation processing option as shown below.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/micronaut-picocli-graalvm/enable-annotation-processing.jpg"><div class="img-lazyload-container" style="width:1058px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/micronaut-picocli-graalvm/enable-annotation-processing.jpg" height="439" width="1058" style="padding-top:41.493383742911156%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="preparing-the-unit-test">Preparing the unit test</h2><div class="sectionbody"><div class="paragraph"><p>The application created with <code>create-cli-app</code> comes with a single unit test we can extend to test our final expectation.</p></div><div class="listingblock"><div class="title">Listing 1. src/test/groovy/com/github/wololock/StackoverflowCliCommandSpec.groovy</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">package com.github.wololockimport io.micronaut.configuration.picocli.PicocliRunnerimport io.micronaut.context.ApplicationContextimport io.micronaut.context.env.Environmentimport spock.lang.AutoCleanupimport spock.lang.Sharedimport spock.lang.Specificationclass StackoverflowCliCommandSpec extends Specification &#123;    @Shared @AutoCleanup ApplicationContext ctx = ApplicationContext.run(Environment.CLI, Environment.TEST)    void "test stackoverflow-cli search command"() &#123;        given:        ByteArrayOutputStream baos = new ByteArrayOutputStream()        PrintStream out = System.out        System.setOut(new PrintStream(baos))        String[] args = ["search", "-q", "merge maps", "-t", "java", "--verbose"] as String[]        PicocliRunner.run(StackoverflowCliCommand, ctx, args)        out.println baos.toString() <i class="conum" data-value="1"></i><b>(1)</b>        // ✔ 9|3 Merge maps in Groovy        //       https://stackoverflow.com/questions/213123/merge-maps-groovy        expect: <i class="conum" data-value="2"></i><b>(2)</b>        baos.toString() =~ $/✔? \d+\|\d+ [^\n]+\n &#123;6&#125;https://stackoverflow.com/questions/\d+/[a-z0-9\-]+/$    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>We use this test to achieve two things.Firstly, we want to <em class="conum" data-value="1"></em> print the output of the command to the standard output.And secondly, we want to <em class="conum" data-value="2"></em> test if the output matches given regular expression.</p></div></div></div><div class="sect1"><h2 id="implementing-the-declarative-http-client">Implementing the declarative HTTP client</h2><div class="sectionbody"><div class="paragraph"><p>In the next step, we can implement HTTP client that communicates with the <a href="https://api.stackexchange.com/docs/search">Stack Exchange REST API</a>.We are going to use Micronaut&#8217;s declarative HTTP client to do so.We create an interface called <code>StackOverflowHttpClient</code> and we annotate it with the <code>@Client(url)</code> annotation <em class="conum" data-value="1"></em>.The client&#8217;s URL is taken from the configuration property defined in the <code>application.yml</code> file.Next, we create the <code>search</code> method annotated with <code>@Get(path)</code> annotation <em class="conum" data-value="2"></em>.This annotation defines HTTP method (<code>GET</code> in this case) and the request&#8217;s path.We also use <code>@QueryValue</code> annotations <em class="conum" data-value="3"></em> to map querystring parameters with the method arguments.</p></div><div class="listingblock"><div class="title">Listing 2. src/main/java/com/github/wololock/api/StackOverflowHttpClient.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package com.github.wololock.api;import io.micronaut.http.annotation.Get;import io.micronaut.http.annotation.QueryValue;import io.micronaut.http.client.annotation.Client;@Client("$&#123;stackoverflow.api.url&#125;") <i class="conum" data-value="1"></i><b>(1)</b>public interface StackOverflowHttpClient &#123;    @Get("/search?site=stackoverflow") <i class="conum" data-value="2"></i><b>(2)</b>    ApiResponse&lt;Question&gt; search(            @QueryValue("intitle") String query, <i class="conum" data-value="3"></i><b>(3)</b>            @QueryValue("tagged") String tag,            @QueryValue("pagesize") int limit,            @QueryValue("sort") String sort    );&#125;</code></pre></div></div><div class="paragraph"><p>And here is the <code>application.yml</code> file that defines <code>stackoverflow.api.url</code> configuration property.</p></div><div class="listingblock"><div class="title">Listing 3. src/main/resources/application.yml</div><div class="content"><pre class="highlightjs highlight"><code class="language-yml hljs" data-lang="yml">micronaut:  application:    name: stackoverflowClistackoverflow:  api:    url: https://api.stackexchange.com/2.2</code></pre></div></div><div class="paragraph"><p>Next, we need to implement <code>ApiResponse&lt;T&gt;</code> and <code>Question</code> classes.We use them to deserialize a raw JSON response into an instance of <code>ApiResponse&lt;Question&gt;</code>.We can implement both classes as a regular Java POJO&#8217;s with getters and setters, or we can use public fields keep as simple as possible.In both cases, however, we want to add <code>@Introspected</code> annotation <em class="conum" data-value="1"></em> to instruct Micronaut to use a reflection-free Jackson module to handle serialization and deserialization.It&#8217;s a nice boost, but it is also a mandatory step if we want to use those classes in the final native executable file.</p></div><div class="paragraph"><p>Here&#8217;s an implementation of the <code>ApiResponse&lt;T&gt;</code> class.</p></div><div class="listingblock"><div class="title">Listing 4. src/main/java/com/github/wololock/api/ApiResponse.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package com.github.wololock.api;import com.fasterxml.jackson.annotation.JsonProperty;import io.micronaut.core.annotation.Introspected;import java.util.Collections;import java.util.List;@Introspected <i class="conum" data-value="1"></i><b>(1)</b>final public class ApiResponse&lt;T&gt; &#123;    public List&lt;T&gt; items = Collections.emptyList();    @JsonProperty("has_more")    public boolean hasMore;    @JsonProperty("quota_max")    public int quotaMax;    @JsonProperty("quota_remaining")    public int quotaRemaining;&#125;</code></pre></div></div><div class="paragraph"><p>And here&#8217;s an implementation of the <code>Question</code> class.</p></div><div class="listingblock"><div class="title">Listing 5. src/main/java/com/github/wololock/api/Question.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package com.github.wololock.api;import com.fasterxml.jackson.annotation.JsonProperty;import io.micronaut.core.annotation.Introspected;@Introspectedfinal public class Question &#123;    public String title;    public String link;    public int score;    @JsonProperty("answer_count")    public int answers;    @JsonProperty("is_answered")    public boolean accepted;&#125;</code></pre></div></div><div class="paragraph"><p>Keep in mind we used <code>@JsonProperty</code> over the fields that didn&#8217;t match the same naming convention between POJO class (camelCase) and the JSON body (snake_case).Alternatively, we could use <code>@JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy.class)</code> over the class to get the same result.</p></div></div></div><div class="sect1"><h2 id="implementing-the-search-command">Implementing the <code>search</code> command</h2><div class="sectionbody"><div class="paragraph"><p>Once we implemented the HTTP client, it&#8217;s time to implement the <code>search</code> command of the <code>stackoverflow-cli</code> app.We start with creating a new class - <code>SearchCommand</code>.Picocli requires, that the command class implements either <code>Runnable</code> or <code>Callable&lt;T&gt;</code> interface.The main difference is that the first one does not return any value, so if you want to return an exit code, you may want to use <code>Callable&lt;Integer&gt;</code> instead of a <code>Runnable</code> interface.</p></div><div class="paragraph"><p>Next, the newly created class has to be annotated with the <code>@Command</code> annotation <em class="conum" data-value="1"></em>.We use it to define the command name <code>search</code>, its description, and we also use the standard help mixin to instruct Picocli to generate a default help page based on the command&#8217;s options.</p></div><div class="paragraph"><p>Speaking of options, we define them as class fields and we add <code>@Option</code> annotation <em class="conum" data-value="2"></em> that specifies each option name (or names if you want to use short and long option name formats).</p></div><div class="paragraph"><p>We also inject previously created <code>StackOverflowHttpClient</code> <em class="conum" data-value="3"></em>.</p></div><div class="paragraph"><p>The desired business logic of the <code>search</code> command happens in the <code>run</code> method <em class="conum" data-value="4"></em>.We call <code>client.search(query, tag, limit, sort)</code> with the parameters retrieved from the command line.Once the data is fetched from the REST API, we display format each question the helper <code>formatQuestion</code> method, and then we print it.One thing worth mentioning is the usage of <code>Ansi.AUTO.string()</code> method with the <a href="https://picocli.info/#_ansi_colors_and_styles">ANSI colors and styles</a> applied <em class="conum" data-value="5"></em>.</p></div><div class="listingblock"><div class="title">Listing 6. src/main/java/com/github/wololock/search/SearchCommand.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package com.github.wololock.search;import com.github.wololock.api.Question;import com.github.wololock.api.SearchHttpRequest;import com.github.wololock.api.StackOverflowHttpClient;import picocli.CommandLine.Command;import picocli.CommandLine.Help.Ansi;import picocli.CommandLine.Option;import javax.inject.Inject;@Command(name = "search", description = "Search questions matching criteria.",    mixinStandardHelpOptions = true) <i class="conum" data-value="1"></i><b>(1)</b>final public class SearchCommand implements Runnable &#123;    @Option(names = &#123;"-q", "--query"&#125;, description = "Search phrase.") <i class="conum" data-value="2"></i><b>(2)</b>    String query = "";    @Option(names = &#123;"-t", "--tag"&#125;, description = "Search inside specific tag.")    String tag = "";    @Option(names = &#123;"-n", "--limit"&#125;, description = "Limit results. Default: 10")    int limit = 10;    @Option(names = &#123;"-s", "--sort-by"&#125;, description = "Available values: relevance, votes, creation, activity. Default: relevance.")    String sort = "relevance";    @Option(names = &#123;"--verbose"&#125;, description = "Print verbose output.")    boolean verbose;    @Inject    StackOverflowHttpClient client; <i class="conum" data-value="3"></i><b>(3)</b>    @Override    public void run() &#123; <i class="conum" data-value="4"></i><b>(4)</b>        var response = client.search(query, tag, limit, sort);        response.items.stream()                .map(SearchCommand::formatQuestion)                .forEach(System.out::println);        if (verbose) &#123;            System.out.printf(                    "\nItems size: %d | Quota max: %d | Quota remaining: %d | Has more: %s\n",                    response.items.size(),                    response.quotaMax,                    response.quotaRemaining,                    response.hasMore            );        &#125;        System.exit(0);    &#125;    static private String formatQuestion(final Question question) &#123;        return Ansi.AUTO.string(String.format(                "@|bold,fg(green) %s|@ %d|%d @|bold,fg(yellow) %s|@\n      %s", <i class="conum" data-value="5"></i><b>(5)</b>                question.accepted ? "✔" : "",                question.score,                question.answers,                question.title,                question.link        ));    &#125;&#125;</code></pre></div></div><div class="admonitionblock tip"><table><tr><td class="icon"><i class="fa icon-tip" title="Tip"></i></td><td class="content">When using Micronaut&#8217;s declarative HTTP client, it is worth adding <code>System.exit(0)</code> at the end of the command&#8217;s <code>run()</code> method.This way we force to <strong>shut down the application instantly</strong>, without waiting 2 seconds until Netty gracefully shuts down all event loop groups.</td></tr></table></div><div class="paragraph"><p>After implementing the command class, we need to register it as a subcommand in the main application class.We do it by adding <code>SearchCommand.class</code> to the <code>subcommands</code> array of the <code>@Command</code> annotation in the main class <em class="conum" data-value="1"></em>.</p></div><div class="listingblock"><div class="title">Listing 7. src/main/java/com/github/wololock/StackoverflowCliCommand.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">package com.github.wololock;import com.github.wololock.search.SearchCommand;import io.micronaut.configuration.picocli.PicocliRunner;import io.micronaut.context.ApplicationContext;import picocli.CommandLine;import picocli.CommandLine.Command;import picocli.CommandLine.Option;import picocli.CommandLine.Parameters;@Command(name = "stackoverflow-cli", description = "...",        mixinStandardHelpOptions = true, subcommands = &#123;SearchCommand.class&#125;) <i class="conum" data-value="1"></i><b>(1)</b>public class StackoverflowCliCommand implements Runnable &#123;    @Option(names = &#123;"-v", "--verbose"&#125;, description = "...")    boolean verbose;    public static void main(String[] args) throws Exception &#123;        PicocliRunner.run(StackoverflowCliCommand.class, args);    &#125;    public void run() &#123;        // business logic here        if (verbose) &#123;            System.out.println("Hi!");        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>After that, we can run the unit test to see the results.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/micronaut-picocli-graalvm/running-unit-test.jpg"><div class="img-lazyload-container" style="width:1280px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/micronaut-picocli-graalvm/running-unit-test.jpg" height="487" width="1280" style="padding-top:38.046875%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="installing-graalvm-and-native-image">Installing GraalVM and <code>native-image</code></h2><div class="sectionbody"><div class="paragraph"><p>The last step is to compile the application to the native executable file.Before we do so, we need to install <a href="https://www.graalvm.org/">GraalVM</a> and the <code>native-image</code> tool.Below you can find step by step guide using SDKMAN!.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash"><strong>$ sdk install java 20.1.0.r11-grl</strong>Downloading: java 20.1.0.r11-grlIn progress...&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35&#35 100,00%Repackaging Java 20.1.0.r11-grl...Done repackaging...<span class="color-green">Installing: java 20.1.0.r11-grl</span><span class="color-green">Done installing!</span><span class="color-yellow">Do you want java 20.1.0.r11-grl to be set as default? (Y/n): n</span><strong>$ sdk use java 20.1.0.r11-grl</strong><span class="color-green">Using java version 20.1.0.r11-grl in this shell.</span><strong>$ java -version</strong>openjdk version "11.0.7" 2020-04-14OpenJDK Runtime Environment GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02)OpenJDK 64-Bit Server VM GraalVM CE 20.1.0 (build 11.0.7+10-jvmci-20.1-b02, mixed mode)<strong>$ gu install native-image</strong>Downloading: Component catalog from www.graalvm.orgProcessing Component: Native ImageDownloading: Component native-image: Native Image  from github.comInstalling new component: Native Image (org.graalvm.native-image, version 20.1.0)<strong>$ native-image --version</strong>GraalVM Version 20.1.0 (Java Version 11.0.7)</code></pre></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">Depending on when you read this blog post, version <code>20.1.0.r11-grl</code> may not exist anymore in the SDKMAN. Run <code>sdk list java</code> to find the latest GraalVM version available.</td></tr></table></div></div></div><div class="sect1"><h2 id="compiling-final-native-executable-file">Compiling final native executable file</h2><div class="sectionbody"><div class="paragraph"><p>Once we installed GraalVM, <code>native-image</code>, and we switched the Java to the GraalVM&#8217;s one, we can firstly assemble our application to the JAR file.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash"><strong>$ ./gradlew --no-daemon assemble</strong>To honour the JVM settings for this build a new JVM will be forked. Please consider using the daemon: https://docs.gradle.org/6.5/userguide/gradle_daemon.html.Daemon will be stopped at the end of the build stopping after processing> Task :compileJavaNote: Writing native-image.properties file to destination: META-INF/native-image/com.github.wololock/stackoverflow-cli/native-image.propertiesNote: Writing reflection-config.json file to destination: META-INF/native-image/com.github.wololock/stackoverflow-cli/reflection-config.jsonNote: Writing resource-config.json file to destination: META-INF/native-image/com.github.wololock/stackoverflow-cli/resource-config.jsonNote: Creating bean classes for 3 type elementsNote: ReflectConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/reflect-config.jsonNote: ResourceConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/resource-config.jsonNote: ProxyConfigGen writing to: CLASS_OUTPUT/META-INF/native-image/picocli-generated/proxy-config.json<strong class="color-green">BUILD SUCCESSFUL</strong> in 10s10 actionable tasks: 10 executed</code></pre></div></div><div class="paragraph"><p>Next, we can run <code>native-image</code> compilation.It takes some time, and may utilize all available cores on your laptop, so be aware of it. <span class="icon"><i class="fa fa-smile-o"></i></span></p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash"><strong>$ native-image --no-server -cp build/libs/stackoverflow-cli-&#42-all.jar</strong>[stackoverflow-cli:614922]    classlist:   4,204.35 ms,  1.18 GB[stackoverflow-cli:614922]        (cap):   1,080.85 ms,  1.18 GB[stackoverflow-cli:614922]        setup:   3,027.05 ms,  1.18 GBWARNING GR-10238: VarHandle for static field is currently not fully supported. Static field private static volatile java.lang.System$Logger jdk.internal.event.EventHelper.securityLogger is not properly marked for Unsafe access![stackoverflow-cli:614922]     (clinit):   1,542.14 ms,  4.85 GB[stackoverflow-cli:614922]   (typeflow):  31,615.83 ms,  4.85 GB[stackoverflow-cli:614922]    (objects):  33,949.48 ms,  4.85 GB[stackoverflow-cli:614922]   (features):   3,764.04 ms,  4.85 GB[stackoverflow-cli:614922]     analysis:  74,941.32 ms,  4.85 GB[stackoverflow-cli:614922]     universe:   2,433.46 ms,  4.85 GB[stackoverflow-cli:614922]      (parse):   6,762.73 ms,  4.85 GB[stackoverflow-cli:614922]     (inline):   9,066.65 ms,  5.18 GB[stackoverflow-cli:614922]    (compile):  54,236.90 ms,  4.74 GB[stackoverflow-cli:614922]      compile:  73,993.64 ms,  4.74 GB[stackoverflow-cli:614922]        image:   7,939.30 ms,  4.82 GB[stackoverflow-cli:614922]        write:   1,066.98 ms,  4.82 GB[stackoverflow-cli:614922]      [total]: 167,963.48 ms,  4.82 GB<strong>$ ls -lah stackoverflow-cli</strong>-rwxrwxr-x 1 wololock wololock 54M 07-08 11:26 <span class="color-green">stackoverflow-cli</span></code></pre></div></div><div class="paragraph"><p>Once the native binary file is created (54 megabytes, it&#8217;s not mistaken), we can run a final test to search questions matching <code>stream foreach</code> phrase inside the <code>java</code> tag, and we can limit the output to 4 most relevant questions.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash"><strong>$ ./stackoverflow-cli search --verbose -n 4 -t java -q "stream foreach"</strong><strong class="color-green">✔</strong> 313|13 <span class="color-yellow">Break or return from Java 8 stream forEach?</span>      https://stackoverflow.com/questions/23308193/break-or-return-from-java-8-stream-foreach<strong class="color-green">✔</strong> 26|4 <span class="color-yellow">Incrementing counter in Stream foreach Java 8</span>      https://stackoverflow.com/questions/38568129/incrementing-counter-in-stream-foreach-java-8<strong class="color-green">✔</strong> 46|4 <span class="color-yellow">Java 8 Lambda Stream forEach with multiple statements</span>      https://stackoverflow.com/questions/31130457/java-8-lambda-stream-foreach-with-multiple-statements 0|0 <span class="color-yellow">Java stream forEach</span>      https://stackoverflow.com/questions/44951862/java-stream-foreachItems size: 4 | Quota max: 300 | Quota remaining: 292 | Has more: true</code></pre></div></div></div></div><div class="sect1"><h2 id="useful-resources">Useful resources</h2><div class="sectionbody"><div class="ulist"><ul><li><p><a href="https://micronaut-projects.github.io/micronaut-picocli/latest/guide/">Micronaut Picocli Configuration</a></p></li><li><p><a href="https://picocli.info/quick-guide.html">Picocli Quick Guide</a></p></li><li><p><a href="https://www.youtube.com/watch?v=Xdcg4Drg1hc">"Building command-line app with Java 11, Micronaut, Picocli, and GraalVM" video</a></p></li></ul></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In this blog post, I show you how to build &lt;code&gt;stackoverflow-cli&lt;/code&gt; - a command-line application that allows you to search Stack Overflow questions directly from the terminal window.
I use Java 11+, Micronaut, Picocli, and GraalVM&amp;#8217;s &lt;code&gt;native-image&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Micronaut Cookbook" scheme="https://e.printstacktrace.blog/micronaut-cookbook/"/>
    
    
    <category term="java" scheme="https://e.printstacktrace.blog/t/java/"/>
    
    <category term="micronaut" scheme="https://e.printstacktrace.blog/t/micronaut/"/>
    
    <category term="graalvm" scheme="https://e.printstacktrace.blog/t/graalvm/"/>
    
    <category term="native-image" scheme="https://e.printstacktrace.blog/t/native-image/"/>
    
    <category term="picocli" scheme="https://e.printstacktrace.blog/t/picocli/"/>
    
  </entry>
  
  <entry>
    <title>How to catch curl response in Jenkins Pipeline?</title>
    <link href="https://e.printstacktrace.blog/how-to-catch-curl-response-in-jenkins-pipeline/"/>
    <id>https://e.printstacktrace.blog/how-to-catch-curl-response-in-jenkins-pipeline/</id>
    <published>2020-06-22T18:20:41.000Z</published>
    <updated>2020-06-22T18:20:41.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>In this blog post, I explain why you may want to use <code>curl</code> command in your Jenkinsfile, how to catch <code>curl</code> response and store it in a variable, as well as how to read HTTP response status code and extract some data from the JSON document. Enjoy!</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#why-use-curl-instead-of-any-java-http-client">Why use <code>curl</code> instead of any Java HTTP client?</a></li><li><a href="#capturing-sh-output-with-returnstdout-option">Capturing <code>sh</code> output with <code>returnStdout</code> option</a></li><li><a href="#using-authentication">Using authentication</a></li><li><a href="#capturing-http-response-status-code-and-response-body">Capturing HTTP response status code and response body</a></li><li><a href="#using-groovy-json-jsonslurperclassic-to-parse-the-response">Using <code>groovy.json.JsonSlurperClassic</code> to parse the response</a></li><li><a href="#using-jq-to-parse-the-response">Using <code>jq</code> to parse the response</a></li><li><a href="#bonus-a-few-more-useful-curl-parameters">Bonus: a few more useful <code>curl</code> parameters</a><ul class="sectlevel2"><li><a href="#how-to-set-the-acceptapplicationjson-header">How to set the <code>Accept:application/json</code> header?</a></li><li><a href="#how-to-store-and-send-cookies-from-a-file">How to store and send cookies from a file?</a></li><li><a href="#how-to-handle-redirects-e-g-from-http-to-https">How to handle redirects (e.g. from <code>http</code> to <code>https</code>)?</a></li><li><a href="#how-to-retry-on-connection-refused">How to retry on connection refused?</a></li></ul></li></ul></div><div class="sect1"><h2 id="why-use-curl-instead-of-any-java-http-client">Why use <code>curl</code> instead of any Java HTTP client?</h2><div class="sectionbody"><div class="paragraph"><p>I guess you may ask this question: <em>"why to bother with curl while there is Java or Groovy HTTP client I can use in the pipeline script block?"</em>.This question might look reasonable.However, there is one important fact you need to consider.Any Groovy (or Java) code you put into the script block is always <a href="https://www.jenkins.io/blog/2017/02/01/pipeline-scalability-best-practice/#fundamentals">executed on the Jenkins master server</a>.No matter what agent/node you use to execute the specific stage(s).Now imagine the following scenario.Let&#8217;s say you have a dedicated node in your Jenkins infrastructure to run some REST API calls to perform a production deployment.And let&#8217;s assume that only this specific node can make such an HTTP call for security reasons.If you implement the deployment part by using some Groovy code (e.g., something as simple as <code>java.net.HttpURLConnection</code>), it will fail.The server that de facto makes an HTTP call, in this case, is not your dedicated node, but the Jenkins master server.The same one that is not allowed to make any connection to your production server(s).However, if you use the pipeline&#8217;s <code>sh</code> step to execute the <code>curl</code> command instead, the HTTP request will be sent from the designated Jenkins node.This way, you can design a topology where only a specific node can perform strategic REST API calls.</p></div><div class="paragraph"><p>Now when you know <em>"why"</em>, let&#8217;s take a look at the <em>"how"</em> part.</p></div><div class="openblock text-center mt-4"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://www.bluehost.com/track/szymonstepniak/" data-type="banner" data-name="bluehost-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/bluehost.png"/></a></p></div></div></div></div></div><div class="sect1"><h2 id="capturing-sh-output-with-returnstdout-option">Capturing <code>sh</code> output with <code>returnStdout</code> option</h2><div class="sectionbody"><div class="paragraph"><p>The way to execute curl command is to use <code>sh</code> (or <code>bat</code> if you are on the Windows server) step.You need to know that the <code>sh</code> step by default does not return any value, so if you try to assign it&#8217;s output to a variable, you will get the <code>null</code> value.To change this behavior, you need to set the <code>returnStdout</code> parameter to <code>true</code>.In the following example, I use my local Jenkins installation to get the build information of one of my Jenkins pipelines using the JSON API endpoint.</p></div><div class="listingblock"><div class="title">Listing 1. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Using curl example") &#123;            steps &#123;                script &#123;                    final String url = "http://localhost:8080/job/Demos/job/maven-pipeline-demo/job/sdkman/2/api/json"                    final String response = sh(script: "curl -s $url", <strong class="highlightcode">returnStdout: true</strong>).trim()                    echo response                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/01-basic-curl.jpg"><div class="img-lazyload-container" style="width:1126px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/01-basic-curl.jpg" height="385" width="1126" style="padding-top:34.19182948490231%"></div></a></div></div></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">It is useful to call <code>trim()</code> on the output from the <code>sh</code> step to remove any new line characters.</td></tr></table></div></div></div><div class="sect1"><h2 id="using-authentication">Using authentication</h2><div class="sectionbody"><div class="paragraph"><p>The endpoint I was trying to call in the previous example requires authentication.We can extend this example by adding <code>-u username:password</code> option and using <a href="https://plugins.jenkins.io/credentials-binding/">Credentials Binding plugin</a> to store those credentials.</p></div><div class="admonitionblock warning"><table><tr><td class="icon"><i class="fa icon-warning" title="Warning"></i></td><td class="content"><div class="paragraph"><p>Storing sensitive credentials securely is <strong>very difficult</strong>, and it is out of this blog post&#8217;s scope.However, here are a few hints that may help you avoid some real problems.</p></div><div class="ulist"><ul><li><p>Limit the scope of credentials as much as possible.</p></li><li><p>Group Jenkins jobs using folders and control who (a person, or a team) has permissions to those folders using <a href="https://plugins.jenkins.io/role-strategy/">Role-based Authorization Strategy plugin</a>.</p></li><li><p>Avoid using credentials when possible. If you have to use them, prefer using tokens with the least permissions granted.</p></li><li><p>If possible, store credentials outside Jenkins. If using private/public key pairs is an option, consider using remote storage (like AWS KMS.)For the most sensitive credentials, you can use a dedicated node combined with a Jenkins job (or a pipeline) that can run on it exclusively.Such a job can be modified only by administrators, and the node cannot be used by other regular Jenkins users.Other pipelines/jobs may trigger this dedicated job to perform a sensitive task that uses a key (from remote storage) to authenticate.This way, you can minimize the risk of leaking sensitive credentials.</p></li></ul></div><hr><div class="exampleblock small"><div class="content"><div class="paragraph"><p>You can read more about securing sensitive credentials here:</p></div><div class="ulist"><ul><li><p><a href="https://support.cloudbees.com/hc/en-us/articles/115002880172-How-to-manage-sensitive-credentials-">How to manage sensitive credentials?</a></p></li><li><p><a href="https://codurance.com/2019/05/30/accessing-and-dumping-jenkins-credentials/">Accessing and dumping Jenkins credentials</a></p></li></ul></div></div></div></td></tr></table></div><div class="paragraph"><p>In this example, I use the <a href="https://support.cloudbees.com/hc/en-us/articles/115003090592-How-to-re-generate-my-Jenkins-user-token">user&#8217;s API Token</a> instead of a real password.It works like a regular password, but it can be easily revoked, and the new one can be re-generated if needed.Also, you can use <code>usernameColonPassword</code> to bind <code>username:password</code> to the specific variable easily.</p></div><div class="listingblock"><div class="title">Listing 2. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Using curl example") &#123;            steps &#123;                script &#123;                    final String url = "http://localhost:8080/job/Demos/job/maven-pipeline-demo/job/sdkman/2/api/json"                    withCredentials([usernameColonPassword(credentialsId: "jenkins-api-token", variable: "API_TOKEN")]) &#123;                        final String response = sh(script: "curl -s <strong class="highlightcode">-u $API_TOKEN</strong> $url", returnStdout: true).trim()                        echo response                    &#125;                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/03-auth.jpg"><div class="img-lazyload-container" style="width:1122px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/03-auth.jpg" height="398" width="1122" style="padding-top:35.47237076648842%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="capturing-http-response-status-code-and-response-body">Capturing HTTP response status code and response body</h2><div class="sectionbody"><div class="paragraph"><p>In many cases, you want to react to different HTTP response status codes.For instance, you want to check if the response returned <code>200</code> or <code>400</code> status code.Let&#8217;s extend the previous example to fit into such a use case.We can pass <code>-w' \n%&#123;response_code&#125;'</code> option to change the output format to the one that contains the response body in the first line, and the status code in the second line.Then we can split the output by the new line character, and we can use {multiple-assignment}[Groovy multiple assignment] feature to assign the response body and the status to the two separate variables.</p></div><div class="listingblock"><div class="title">Listing 3. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Using curl example") &#123;            steps &#123;                script &#123;                    final String url = "http://localhost:8080/job/Demos/job/maven-pipeline-demo/job/sdkman/2/api/json"                    withCredentials([usernameColonPassword(credentialsId: "jenkins-api-token", variable: "API_TOKEN")]) &#123;                        final <strong class="highlightcode">def (String response, int code)</strong> =                            sh(script: "curl -s <strong class="highlightcode">-w '\\n%&#123;response_code&#125;'</strong> -u $API_TOKEN $url", returnStdout: true)                                .trim()                                <strong class="highlightcode">.tokenize("\n")</strong>                        echo "HTTP response status code: $code"                        if (code == 200) &#123;                            echo response                        &#125;                    &#125;                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/04-status-code.jpg"><div class="img-lazyload-container" style="width:1128px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/04-status-code.jpg" height="427" width="1128" style="padding-top:37.854609929078016%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="using-groovy-json-jsonslurperclassic-to-parse-the-response">Using <code>groovy.json.JsonSlurperClassic</code> to parse the response</h2><div class="sectionbody"><div class="paragraph"><p>Once we have the response body and the status code extracted, we can parse the JSON body and extract the value we are interested in.The Jenkins build&#8217;s JSON API response contains a list of different actions.Let&#8217;s say we want to extract the value of <code>executingTimeMillis</code> stored as an action of type <code>jenkins.metrics.impl.TimeInQueueAction</code>.</p></div><div class="paragraph"><p>If you know Groovy, I&#8217;m guessing you might already think about using <code>groovy.json.JsonSlurper</code> to solve the problem.It is not a terrible idea, but it has two downsides you need to take into account.Firstly, it makes Jenkins master server involved in parsing the JSON response, even if the stage gets executed on a different node.And secondly, <code>groovy.json.JsonSlurper</code> produces a lazy map which is not-serializable.The first thing might or might not be a problem - it mainly depends on your Jenkins topology architecture.If you use multiple agents to execute stages and have a lot of different Jenkins jobs on your server, it makes sense to utilize Jenkins master server resources more effectively.Using <code>groovy.json.JsonSlurper</code> to parse small JSON documents might not be a bottleneck.Still, if hundreds or thousands of Jenkins jobs start to parse large JSON documents, it may become an issue.The second problem can be solved easily - instead of using <code>groovy.json.JsonSlurper</code>, use <code>groovy.json.JsonSlurperClassic</code>.This class produces a regular hash map that can be serialized, and that way, you can avoid pipeline serialization problems.</p></div><div class="listingblock"><div class="title">Listing 4. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Using curl example") &#123;            steps &#123;                script &#123;                    final String url = "http://localhost:8080/job/Demos/job/maven-pipeline-demo/job/sdkman/2/api/json"                    withCredentials([usernameColonPassword(credentialsId: "jenkins-api-token", variable: "API_TOKEN")]) &#123;                        final def (String response, int code) =                            sh(script: "curl -s -w '\\n%&#123;response_code&#125;' -u $API_TOKEN $url", returnStdout: true)                                .trim()                                .tokenize("\n")                        echo "HTTP response status code: $code"                        if (code == 200) &#123;                            def json = new groovy.json.JsonSlurperClassic().parseText(response)                            def executingTimeMillis = json.actions.find &#123; it._class == "jenkins.metrics.impl.TimeInQueueAction" &#125;.executingTimeMillis                            echo "executingTimeMillis = $executingTimeMillis"                        &#125;                    &#125;                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="admonitionblock warning"><table><tr><td class="icon"><i class="fa icon-warning" title="Warning"></i></td><td class="content"><div class="paragraph"><p>By default, using <code>groovy.json.JsonSlurper</code> class and its <code>parseText(String str)</code> method are not allowed, <strong>requiring the administrator&#8217;s manual approval</strong> the first time you are trying to use it.If you run this example and see an error like the one below, you need to go to your Jenkins' ScriptApproval page and approve using both the class and the <code>parseText()</code> method.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid d-inline-block m-0"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/05-jsonslurper.jpg"><div class="img-lazyload-container" style="width:1045px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/05-jsonslurper.jpg" height="180" width="1045" style="padding-top:17.22488038277512%"></div></a></div></div></div></div></td></tr></table></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/06-jsonslurper-response.jpg"><div class="img-lazyload-container" style="width:1046px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/06-jsonslurper-response.jpg" height="248" width="1046" style="padding-top:23.709369024856596%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="using-jq-to-parse-the-response">Using <code>jq</code> to parse the response</h2><div class="sectionbody"><div class="paragraph"><p>There is an alternative, and it is usually a more recommended way to parse and extract values from the JSON data - the <code>jq</code> command-line tool.All you have to do is to make sure that the <code>jq</code> is installed on the Jenkins node that executes the pipeline.</p></div><div class="paragraph"><p>Let&#8217;s take a look at how we can use it to extract the same information from the JSON response.</p></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content"><div class="paragraph"><p>In the below example, I want re-use the <code>response</code> variable that stores the JSON response body, and keep checking the <code>code</code> variable if the status was <code>200</code>.To make this use case working, I need to echo the response body and pipe it with the <code>jq</code> command.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "echo '$response' | jq -r ...", returnStdout: true)</code></pre></div></div><div class="paragraph"><p>However, it is a common pattern to pipe <code>curl</code> output directly as an input for the <code>jq</code> command:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "curl -s ... | jq -r ...", returnStdout: true)</code></pre></div></div></td></tr></table></div><div class="listingblock"><div class="title">Listing 5. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Using curl example") &#123;            steps &#123;                script &#123;                    final String url = "http://localhost:8080/job/Demos/job/maven-pipeline-demo/job/sdkman/2/api/json"                    withCredentials([usernameColonPassword(credentialsId: "jenkins-api-token", variable: "API_TOKEN")]) &#123;                        final def (String response, int code) =                            sh(script: "curl -s -w '\\n%&#123;response_code&#125;' -u $API_TOKEN $url", returnStdout: true)                                .trim()                                .tokenize("\n")                        echo "HTTP response status code: $code"                        if (code == 200) &#123;                            def executingTimeMillis = sh(script: "echo '$response' | jq -r '.actions[] | select(._class == \"jenkins.metrics.impl.TimeInQueueAction\") | .executingTimeMillis'", returnStdout: true).trim()                            echo "executingTimeMillis = $executingTimeMillis"                        &#125;                    &#125;                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/curl-jenkins-pipeline/07-jq.jpg"><div class="img-lazyload-container" style="width:1048px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jenkins-pipeline/07-jq.jpg" height="289" width="1048" style="padding-top:27.576335877862597%"></div></a></div></div></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">You can learn more about <code>jq</code> from my <strong class="mark">jq cookbook</strong> <a href="https://e.printstacktrace.blog/jq-cookbook/" title="" target="">blog posts series</a>.</td></tr></table></div></div></div><div class="sect1"><h2 id="bonus-a-few-more-useful-curl-parameters">Bonus: a few more useful <code>curl</code> parameters</h2><div class="sectionbody"><div class="paragraph"><p>The list of all available options and parameters that the <code>curl</code> command supports is enormous.You can find all of them in the <code>man curl</code> manual page.Below you can find a few more useful options you may want to use in your Jenkins pipeline.</p></div><div class="sect2"><h3 id="how-to-set-the-acceptapplicationjson-header">How to set the <code>Accept:application/json</code> header?</h3><div class="paragraph"><p>Some REST API endpoints support multiple content types.If this is your case, you will need to explicitly say what the content type you can accept is.For the JSON content type, you will need to pass the <code>-H "Accept: application/json"</code> parameter.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "curl -s <strong class="highlightcode">-H 'Accept:application/json'</strong> $url", returnStdout: true).trim()</code></pre></div></div></div><div class="sect2"><h3 id="how-to-store-and-send-cookies-from-a-file">How to store and send cookies from a file?</h3><div class="paragraph"><p>Cookies are useful for storing temporary information.By default, <code>curl</code> makes all requests in a stateless manner.If you want to store cookies coming with the response, and then send them back with the following request, you may want to add <code>-c cookies.txt -b cookies.txt</code> parameters.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "curl -s <strong class="highlightcode">-c cookies.txt -b cookies.txt</strong> $url", returnStdout: true).trim()</code></pre></div></div></div><div class="sect2"><h3 id="how-to-handle-redirects-e-g-from-http-to-https">How to handle redirects (e.g. from <code>http</code> to <code>https</code>)?</h3><div class="paragraph"><p>If you want to instruct the <code>curl</code> command to follow redirects, you need to add <code>-L</code> parameter.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "curl -s <strong class="highlightcode">-L</strong> $url", returnStdout: true).trim()</code></pre></div></div></div><div class="sect2"><h3 id="how-to-retry-on-connection-refused">How to retry on connection refused?</h3><div class="paragraph"><p>The first <a href="https://en.wikipedia.org/wiki/Fallacies_of_distributed_computing">fallacy of the distributed computing</a> says that <em>"The network is reliable."</em>We all know it is not.The <code>curl</code> command allows you to perform a simple retry mechanism on connection refused error.For instance, if you want to retry up to 10 times with the 6 seconds delay between every retry, you need to add the following parameters: <code>--retry-connrefused --retry 10 --retry-delay 6</code></p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">sh(script: "curl -s <strong class="highlightcode">--retry-connrefused --retry 10 --retry-delay 6</strong> $url", returnStdout: true).trim()</code></pre></div></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/Ei_Nk14vruE" data-time="13:45" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/Ei_Nk14vruE/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/Ei_Nk14vruE" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline">Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">Jenkins Pipeline as a code is a new standard for defining continuous integration and delivery pipelines in Jenkins. The scripted pipeline was the first implementation of the pipeline as a code standard. The later one, the declarative pipeline, slowly becomes a standard of how Jenkins users define their pipeline logic. In this tutorial video, I explain what are four major differences between Jenkins declarative pipeline and the scripted one. Enjoy and don't forget to like and comment on the video!<a class="gatr font-weight-bold" href="https://youtu.be/Ei_Nk14vruE" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;In this blog post, I explain why you may want to use &lt;code&gt;curl&lt;/code&gt; command in your Jenkinsfile, how to catch &lt;code&gt;curl&lt;/code&gt; response and store it in a variable, as well as how to read HTTP response status code and extract some data from the JSON document. Enjoy!&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Jenkins Pipeline Cookbook" scheme="https://e.printstacktrace.blog/jenkins-pipeline-cookbook/"/>
    
    
    <category term="curl" scheme="https://e.printstacktrace.blog/t/curl/"/>
    
    <category term="jenkins" scheme="https://e.printstacktrace.blog/t/jenkins/"/>
    
    <category term="jenkins-pipeline" scheme="https://e.printstacktrace.blog/t/jenkins-pipeline/"/>
    
    <category term="jenkinsfile" scheme="https://e.printstacktrace.blog/t/jenkinsfile/"/>
    
    <category term="jq" scheme="https://e.printstacktrace.blog/t/jq/"/>
    
    <category term="json" scheme="https://e.printstacktrace.blog/t/json/"/>
    
  </entry>
  
  <entry>
    <title>Atomic Habits - book review</title>
    <link href="https://e.printstacktrace.blog/atomic-habits-book-review/"/>
    <id>https://e.printstacktrace.blog/atomic-habits-book-review/</id>
    <published>2020-06-02T09:22:10.000Z</published>
    <updated>2020-09-09T16:26:20.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Sticking to the process of achieving my goals has always been one of the most difficult obstacles to overcome, both on my way to becoming a better professional, and in my private life.I have found that in some areas I need to make an extraordinary amount of effort to keep myself on the right track, no matter how <strong>S.M.A.R.T.</strong>(<em>Specific, Measurable, Achievable, Relevant, Time-based</em>) my goals are.I couldn’t understand why in some cases I was able to make the desired progress, while in others I kept failing.Was it a matter of talent I was missing, or a skill I haven’t acquired yet?I have seen the dots, but I didn’t yet know how to connect them.</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#the-hidden-enemy">The hidden enemy</a></li><li><a href="#the-power-of-atomic-habits">The Power of Atomic Habits</a></li><li><a href="#the-four-laws-of-behavior-change">The Four Laws of Behavior Change</a></li><li><a href="#repetition-is-the-key-to-success">Repetition is the key to success</a></li><li><a href="#the-conclusion">The conclusion</a></li><li><a href="#bonus-atomic-habits-ideas-for-software-developers">Bonus: Atomic Habits ideas for Software Developers</a></li></ul></div><div class="sect1"><h2 id="the-hidden-enemy">The hidden enemy</h2><div class="sectionbody"><div class="paragraph"><p>I guess you heard it many times before.Almost every piece of advice on <em>"how to set goals"</em> or <em>"how to build a habit"</em> contains: <em>"set S.M.A.R.T. goals," "make a plan," "stick with it,"</em> and so on.However, there is one thing that almost no advice takes into account - <strong>the Resistance</strong>, better known as <strong>procrastination</strong>.In his book <a href="https://amzn.to/2MgCKMD">"The War of Art,"</a> Steven Pressfield describes Resistance as a destructive force that keeps you from doing the right thing.He explains that for him, as a professional writer, all that counts is to sit down and write.The amount of pages or their quality does not matter that much.The most important thing is to overcome the Resistance, so at the end of the day, the score is 1:0 for Steven.The Resistance loses, but it will try the next day.And the next.Again, and again.</p></div><div class="paragraph"><p>However, not all knowledge workers can apply the same pattern to their work - obviously, as a <a href="https://e.printstacktrace.blog/programmers-bookshelf/">software developer</a>, I can’t just write down any code that comes to my mind and expect my career to flourish.So even though Pressfield aptly defines the enemy, he does not provide the ultimate solution to my biggest problem.But it seems that I have finally found someone else who does.</p></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content"><div class="paragraph"><div class="title"><h2 class="mt-3">What is an atomic habit?</h2></div><p>"An <strong>atomic habit</strong> is a regular practice or routine that is not only small and easy to do, but also the source of incredible power; a component of the system of compound growth." - James Clear</p></div></td></tr></table></div></div></div><div class="sect1"><h2 id="the-power-of-atomic-habits">The Power of Atomic Habits</h2><div class="sectionbody"><div class="paragraph"><p>It’s difficult to summarize <a href="https://amzn.to/3coH0EF">"Atomic Habits"</a> in a single sentence, but if I had to try, I would pick one of the subtitles from the first chapter: <strong>"Forget about goals, focus on systems instead."</strong><a href="https://jamesclear.com/">James Clear</a> explains that goals are outcome-oriented, and what is obvious, yet still surprising - both winners and losers have the same goals.What makes the difference is <strong>the system</strong>, a process of introducing small incremental changes that eventually manifest themselves in a new habit.The rule of <strong>getting 1% better every day</strong> illustrates it very well.As James Clear states, <em>"Habits are the compound interest of self-improvement."</em>You won’t see drastic progress from day to day.But still, this 1% accumulates to a significant result over a couple of months or a year.Learning one small thing or one new idea doesn’t make you a genius, but, as the author states, <em>"a commitment to lifelong learning can be transformative."</em></p></div><div class="paragraph"><p>If this is so obvious, then why do we keep failing in building positive habits?James Clear explains that <strong>we often expect progress to be linear</strong>, but it almost never is.We make small changes, we don’t see the expected results, and we instantly get discouraged and stop.However, to make a habit stick, it needs to persist long enough to break through the so-called <strong>Plateau of Latent Potential</strong>.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/the-plateau-of-latent-potential.jpg"><div class="img-lazyload-container" style="width:412px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/the-plateau-of-latent-potential.jpg" height="324" width="412" style="padding-top:78.64077669902912%"></div></a></div></div></div></div><div class="paragraph"><p>This is where implementing atomic habits makes a significant difference.If your goal is to read 12 books in a year (<em>the outcome</em>), you should focus on <strong>becoming a reader</strong> (<em>the identity</em>) who reads every day for 30 minutes.If your goal is to have a popular blog (<em>the outcome</em>), James Clear suggests focusing on <strong>becoming a writer</strong> (<em>the identity</em>) who writes daily for an hour or two.If you want to become an outstanding software developer (<em>the outcome</em>), you should focus on <strong>becoming a lifelong learner</strong> (<em>the identity</em>) who finds a way to learn something valuable daily.</p></div><div class="paragraph"><p>When you start tracking the progress of those small habits, you feed your brain with the dopamine it asks for.And at some point in time, probably later than you expect, you are going to break through <strong>the Plateau of Latent Potential</strong>.You will then realize that it wasn’t the goal that helped you do so, but rather <strong>the system of consistent small improvements</strong> that you have successfully adopted.</p></div></div></div><div class="sect1"><h2 id="the-four-laws-of-behavior-change">The Four Laws of Behavior Change</h2><div class="sectionbody"><div class="paragraph"><p>While the first part of the "Atomic Habits" explains the power of systematic improvements (the <em>"why?"</em> part), James Clear does not stop there.In the remaining parts, he introduces <strong>"The Four Laws of Behavior Change."</strong></p></div><div class="ulist ulist2"><ul><li><p><strong>Make It Obvious.</strong> (Train your brain to predict certain outcomes on an autopilot. Use time and location as cues. Make your environment to support a new habit.)</p></li><li><p><strong>Make It Attractive.</strong> (Dopamine improves motivation. Make habits more attractive by pairing an action you want with an action you need to do.)</p></li><li><p><strong>Make It Easy.</strong> (Focus on practicing, not planning. Repetition creates a habit. Reduce the friction associated with a habit. Starting a new habit should take less than two minutes to do.)</p></li><li><p><strong>Make It Satisfying.</strong> (Reward every action that strengthens a habit. "What is immediately rewarded is repeated." Track habits every day. Allow yourself to break a habit for one day, but never miss twice.)</p></li></ul></div><div class="paragraph"><p>The inversion of those laws for breaking bad habits is the following:</p></div><div class="ulist ulist2"><ul><li><p><strong>Make It Invisible.</strong> (Reduce exposure to the bad habits triggers.)</p></li><li><p><strong>Make It Unattractive.</strong> (Focus on highlighting the benefits of avoiding your bad habits.)</p></li><li><p><strong>Make It Difficult.</strong> (Increase friction associated with a bad habit.)</p></li><li><p><strong>Make It Unsatisfying.</strong> (Make the cost of your bad habit public and painful.)</p></li></ul></div><div class="paragraph"><p>This is what makes "Atomic Habits" stand out from other books on the subject, even the iconic ones, such as <a href="https://amzn.to/2MinoXY">"The Power of Habit"</a> by Charles Duhigg.It provides a framework that is ready to implement, and a guidebook to navigate through the process of change.But no manual, not even the best one, makes the desired impact if it is not put into real action.And the best way to practice any habit is to do it through <strong>repetition</strong>.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid d-inline-block rounded"><div class="content"><a class="image" href="/images/atomic-habits/book.jpg"><div class="img-lazyload-container" style="width:1280px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/atomic-habits/book.jpg" height="320" width="1280" style="padding-top:25%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="repetition-is-the-key-to-success">Repetition is the key to success</h2><div class="sectionbody"><div class="paragraph"><p>If you’re like me, you value quality over quantity.However, chapter eleven opens with a story that proves that <strong>the quantity is your best ally</strong> when it comes to building a habit.It tells the story of Jerry Uelsmann, a professor at the University of Florida, who divided students into two groups on his first day of the film photography class.One group was the <em>"quantity"</em> group.Their final grade was based solely on the number of photos they submitted.The second group was the <em>"quality"</em> group.They were asked to submit a single photo they thought the best.At the end of the semester, it turned out that <strong>all the best photos were submitted</strong> by the <em>"quantity"</em> group.They <strong>felt safe</strong> to fail, <strong>experiment</strong>, and learn from <strong>every mistake</strong>.And eventually, they learned much more effectively through <strong>repetition</strong>.The <em>"quality"</em> group, on the other hand, was busy speculating what the perfect photo looks like, and they didn’t have time to learn through experimentation.</p></div><div class="paragraph"><p>This brings us back to Steven Pressfield and his way of dealing with the Resistance by creating a <strong>routine of writing</strong>.As it turns out, it all comes down to repetitively exercising what you want to excel at.If Pressfield spent most of the time overthinking his results, he wouldn’t be able to produce any good story - just like the students from the “quality” group weren’t able to take the perfect photo.It helped me realize that most of my failures in the area of building new habits were caused by overcomplicating the process.I focused mostly on doing things right, instead of <strong>doing the right things</strong>.As James Clear tries to convince, the right thing doesn’t have to be spectacular right away.Doing <strong>small things consistently</strong> day after day is the way to build a <strong>habit that sticks</strong>, and to <strong>produce results</strong> that can change your life.What we need to understand and appreciate is the importance of small steps that we consciously decide to take - they eventually lead to success.</p></div><div class="paragraph"><p><div class="get-the-book my-5" data-debug="Displayed book: Atomic Habits: An Easy &amp; Proven Way to Build Good Habits &amp; Break Bad Ones"><div class="container"><div class="row"><div class="col-12"><div class="row"><div class="col-md-3 text-center"><div class="px-4"><div class="shadow d-inline-flex img-fluid mb-5"><div class="img-lazyload-container" style="width:199px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/books/atomic_habits.jpg" height="300" width="199" style="padding-top:150.75376884422113%"></div></div></div></div><div class="col-md-9"><h3 class="text-md-left text-center">Atomic Habits: An Easy &amp; Proven Way to Build Good Habits &amp; Break Bad Ones</h3><p class="description"><span>No matter your goals, Atomic Habits offers a proven framework for improving - every day. James Clear, one of the world's leading experts on habit formation, reveals practical strategies that will teach you exactly how to form good habits, break bad ones, and master the tiny behaviors that lead to remarkable results.</span><span class="small">(source: <a href="https://amzn.to/3coH0EF">amazon.com</a>)</span></p><a class="btn btn-warning mr-2 mb-2 btn-sm shadow-sm" href="https://amzn.to/35mMf7v"><i class="fa fa-amazon"></i><span class="ml-2">Kindle ebook</span></a><a class="btn btn-warning mr-2 mb-2 btn-sm shadow-sm" href="https://amzn.to/3coH0EF"><i class="fa fa-amazon"></i><span class="ml-2">Buy a paperback</span></a><a class="btn btn-warning mr-2 mb-2 btn-sm shadow-sm" href="https://amzn.to/3ng73DL"><i class="fa fa-amazon"></i><span class="ml-2">Try Audible audiobook <span class="small">(30-days free trial)</span></span></a><a class="btn btn-warning mr-2 mb-2 btn-sm shadow-sm" href="https://amzn.to/2N6HYP3"><i class="fa fa-amazon"></i><span class="ml-2">Try Kindle Unlimited</span></a><div class="mt-4 affiliate-info"><i class="fas fa-info-circle mr-2"></i><span>The above links are <b>affiliate links</b>. If you decide to buy a book using my affiliate link, I will get a small percentage from your purchase. The affiliate link does not affect the final price of the product. Using it costs you nothing.</span></div></div></div></div></div></div></div><script type="application/ld+json">{"@context":"https://schema.org","@type":"Book","name":"Atomic Habits: An Easy & Proven Way to Build Good Habits & Break Bad Ones","image":"https://e.printstacktrace.blog/images/books/atomic_habits.jpg","author":{"@type":"Person","name":"James Clear"},"datePublished":"2018-10-18","publisher":{"@type":"Organization","name":"Random House Business"},"inLanguage":"en-US","isbn":"978-1847941831","review":{"@type":"Review","author":{"@id":"https://e.printstacktrace.blog/#/schema/person/wololock"},"datePublished":"2020-06-02","reviewRating":{"@type":"Rating","ratingValue":5},"reviewBody":"We usually expect to see linear growth when building new habits. It doesn't work like that. We do not realize that making small improvements, like 1% every day, results in making 37-times better progress after a year. James Clear extends the idea of the habit loop (cue, craving, response, reward) and guides you on how to build an efficient habits building system. The book is pleasant to read, very \"actionable\", and is based on research, and not only \"because I think so.\" Highly recommended!"}}</script></p></div></div></div><div class="sect1"><h2 id="the-conclusion">The conclusion</h2><div class="sectionbody"><div class="olist arabic"><ol class="arabic"><li><p>The effects of small habits <strong>compound</strong> over time. Apply <strong class="mark">1% better every day</strong> rule to get <strong>37 times</strong> better over a year.</p></li><li><p><strong class="mark">Forget about goals and focus on systems</strong> that support the process of constant improvement instead. Winners and losers have the same goals - they just implemented different systems.</p></li><li><p>To make the habit stick, <strong>repeat</strong> small steps over and over again. Be aware of <strong>the Plateau of Latent Potential</strong>. The outcome is a side effect of <strong class="mark">the identity change</strong>.</p></li><li><p>Implement <strong class="mark">"The Four Laws of Behavior Change"</strong> for building desired habits and breaking the bad ones.</p></li></ol></div><div class="paragraph"><p>I would strongly recommend reading <a href="https://amzn.to/3coH0EF">“Atomic Habits”</a> to everyone.The book is full of both scientific research and anecdotal evidence, which makes reading it fun, and applying the framework simple.I haven’t covered all the examples and ideas in this blog post, but I hope it encourages you to experiment with your own atomic habits.I strongly believe that everyone will profit from applying the tips included in this book into their own lives.</p></div></div></div><div class="sect1"><h2 id="bonus-atomic-habits-ideas-for-software-developers">Bonus: Atomic Habits ideas for Software Developers</h2><div class="sectionbody"><div class="olist arabic"><ol class="arabic"><li><p>Invest some time to learn how to use your IDE (or an editor) most effectively. Experiment with using one new shortcut every week. For instance, if you keep using a mouse to navigate in your project, find a shortcut (or a macro) that will speed up the process, and keep using it instead.</p></li><li><p>Apply the famous "Boy Scout Rule" and always leave your code better than you found it. Avoid huge refactorings. It’s better to constantly improve the code base with small chunks.</p></li><li><p>Join a group or a community that supports your activities. Consider contributing to an open-source project. Your contributions don’t have to be spectacular - almost every open-source project appreciates small contributions like documentation updates, fixing typos, improving code samples, etc.</p></li><li><p>Try to watch one presentation from your favorite conference on YouTube every week. Take notes while watching, and repeat it every week. After a year you will be 52 presentations smarter.</p></li><li><p>Synthesize and publish your notes on a blog. List a few things you find most interesting, and explain what you have learned from the presentation. After a year you will have a blog with at least 52 valuable blog posts.</p></li><li><p>Don’t be afraid to experiment. Every mistake is an experience you can learn from. Try different ways to learn new programming languages, frameworks, technologies. If you’re a backend developer, maybe it is not a bad idea to get a new perspective and learn some fancy frontend technology. Or a mobile app. Or an assembly language for your laptop’s processor. The sky is the limit.</p></li></ol></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Sticking to the process of achieving my goals has always been one of the most difficult obstacles to overcome, both on my way to becoming a better professional, and in my private life.
I have found that in some areas I need to make an extraordinary amount of effort to keep myself on the right track, no matter how &lt;strong&gt;S.M.A.R.T.&lt;/strong&gt;
(&lt;em&gt;Specific, Measurable, Achievable, Relevant, Time-based&lt;/em&gt;) my goals are.
I couldn’t understand why in some cases I was able to make the desired progress, while in others I kept failing.
Was it a matter of talent I was missing, or a skill I haven’t acquired yet?
I have seen the dots, but I didn’t yet know how to connect them.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Programmer&#39;s Bookshelf" scheme="https://e.printstacktrace.blog/programmers-bookshelf/"/>
    
    
    <category term="learning" scheme="https://e.printstacktrace.blog/t/learning/"/>
    
    <category term="book" scheme="https://e.printstacktrace.blog/t/book/"/>
    
    <category term="review" scheme="https://e.printstacktrace.blog/t/review/"/>
    
    <category term="self-improvement" scheme="https://e.printstacktrace.blog/t/self-improvement/"/>
    
    <category term="non-fiction" scheme="https://e.printstacktrace.blog/t/non-fiction/"/>
    
    <category term="affiliate" scheme="https://e.printstacktrace.blog/t/affiliate/"/>
    
    <category term="mind-mapping" scheme="https://e.printstacktrace.blog/t/mind-mapping/"/>
    
    <category term="atomic-habits" scheme="https://e.printstacktrace.blog/t/atomic-habits/"/>
    
    <category term="habits" scheme="https://e.printstacktrace.blog/t/habits/"/>
    
  </entry>
  
  <entry>
    <title>Parsing JSON in command-line with jq: basic filters and functions (part 1)</title>
    <link href="https://e.printstacktrace.blog/parsing-json-in-command-line-with-jq-basic-filters-and-functions-part-1/"/>
    <id>https://e.printstacktrace.blog/parsing-json-in-command-line-with-jq-basic-filters-and-functions-part-1/</id>
    <published>2020-05-24T16:28:32.000Z</published>
    <updated>2020-05-25T08:31:56.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Have you ever wondered, what is the most convenient way to parse JSON data in the Unix/Linux command line?For instance, how to parse some information from the <code>curl</code> JSON response?Grep?No, thank you.There is a better tool for that.And it&#8217;s called <code>jq</code>.</p></div><a id="more"></a><div class="paragraph"><p>Welcome to the first episode of the <strong>jq cookbook</strong> series!The first episode is not about theory - we jump to the first exercise in just a second.I strongly encourage you to <a href="https://stedolan.github.io/jq/download/">install <code>jq</code> command-line tool</a> in your operating system and follow me.There are some exciting things to learn today, and we are starting right now!</p></div><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#introduction">Introduction</a></li><li><a href="#invoking-jq">Invoking <code>jq</code></a></li><li><a href="#processing-input-from-command-other-than-curl">Processing input from command other than <code>curl</code></a></li><li><a href="#extracting-fields-from-the-array">Extracting fields from the array</a></li><li><a href="#filtering-null-values-with-selectexpr-filter">Filtering <code>null</code> values with <code>select(expr)</code> filter</a></li><li><a href="#sorting-by-field-with-sort_byexpr-filter">Sorting by field with <code>sort_by(expr)</code> filter</a></li><li><a href="#limiting-the-size-of-the-result-with-limitnexpr-filter">Limiting the size of the result with <code>limit(n;expr)</code> filter</a></li><li><a href="#grouping-by-using-group_byexpr-filter">Grouping by using <code>group_by(expr)</code> filter</a></li><li><a href="#putting-it-all-together">Putting it all together</a></li></ul></div><div class="sect1"><h2 id="introduction">Introduction</h2><div class="sectionbody"><div class="paragraph"><p>In this blog post, I will use <a href="https://openlibrary.org/dev/docs/api/search">openlibrary.org public Search API</a>.The reason for that is simple - it returns quite complex and interesting data we can process, and it still mimics a real-life example.I want to use this API to run a full-text search query.I&#8217;m going to use my last name as a search term - it is not a very popular Polish last name, so there shouldn&#8217;t be many results associated with it.</p></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">The JSON response used in this example is 3631 lines long.To keep all code samples clear, I will limit the JSON output to max. 10-15 lines.The original document can be found here - <span class="icon"><i class="fa fa-github"></i></span> <a href="https://gist.github.com/wololock/0ba478cfeb73b36c41c4f8c814111db3">openlibrary.json</a>.</td></tr></table></div><div class="listingblock"><div class="title">Listing 1. An example of openlibrary.org full-text search query</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ curl -s "http://openlibrary.org/search.json?q=st%C4%99pniak"&#123; "start": 0, "num_found": 44, "numFound": 44, "docs": [    ...  ]&#125;</code></pre></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">My last name contains Polish letter <code>ę</code>, which gets encoded as <code>%C4%99</code>.</td></tr></table></div></div></div><div class="sect1"><h2 id="invoking-jq">Invoking <code>jq</code></h2><div class="sectionbody"><div class="paragraph"><p>Once we have the JSON document to process, it&#8217;s time to invoke <code>jq</code> processor.The simplest way to do it is to pipe the output from the <code>curl</code> command to the <code>jq</code> input.</p></div><div class="listingblock"><div class="title">Listing 2. Pipe from <code>curl</code> to <code>jq</code> example.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ curl -s "http://openlibrary.org/search.json?q=st%C4%99pniak" | jq .</code></pre></div></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">The <code>.</code> in the <code>jq</code> filter means <strong>identity</strong>.</td></tr></table></div><div class="paragraph"><p>This simple pipe doesn&#8217;t do much, and <code>jq</code> just prints the JSON "as is."However, there is one difference when compared with the plain <code>curl</code> response.<code>jq</code> by default prints JSON document using "pretty" format, and it adds some basic colors.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/curl-jq-basic-example.jpg"><div class="img-lazyload-container" style="width:1280px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/curl-jq-basic-example.jpg" height="351" width="1280" style="padding-top:27.421875%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="processing-input-from-command-other-than-curl">Processing input from command other than <code>curl</code></h2><div class="sectionbody"><div class="paragraph"><p>Using pipes comes with huge benefits - it doesn&#8217;t matter what command produces the input.It means that we can forward the <code>curl</code> command output to a file, and then use this file to produce input for the <code>jq</code> processor.This might be very useful when we do experiments, and we don&#8217;t want to download the same JSON document over and over again.</p></div><div class="listingblock"><div class="title">Listing 3. Forwarding <code>curl</code> output to the openlibrary.json file</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ curl -s "http://openlibrary.org/search.json?q=st%C4%99pniak" &gt; openlibrary.json</code></pre></div></div><div class="paragraph"><p>Once we captured the input, we can e.g., pipe commands <code>cat</code> and <code>jq</code> to produce the same result, but this time without involving the call over the network.</p></div><div class="listingblock"><div class="title">Listing 4. Pipe from <code>cat</code> to <code>jq</code> example.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ cat openlibrary.json | jq .</code></pre></div></div><div class="paragraph"><p>However, using <code>cat</code> is not necessary if we want to read the input from a file - <code>jq</code> takes a file as an argument as well.In the all upcoming examples, I will use this syntax to save a few characters.</p></div><div class="listingblock"><div class="title">Listing 5. Taking input from the file example.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq . openlibrary.json</code></pre></div></div></div></div><div class="sect1"><h2 id="extracting-fields-from-the-array">Extracting fields from the array</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start processing the data with some real filters.The document we took from the openlibrary.org Search API has a list under the <code>docs</code> key.This list contains all documents matching the search term.Every document is described using multiple fields, but we are interested in the <code>title</code>, <code>author_name</code>, and <code>publish_year</code> only.</p></div><div class="paragraph"><p>The first filter we want to use is called <strong>array/object value iterator <code>.[]</code></strong>.This filter extracts values from the given array (or object), so the next filter in the pipeline gets applied to each element.In our case, we want to apply <code>.docs[]</code> filter to extract documents.</p></div><div class="listingblock"><div class="title">Listing 6. Applying array value iterator on the <code>docs</code> key.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '.docs[]' openlibrary.json&#123;  "title_suggest": "Słownik tajemnych gwar przestępczych",  "edition_key": [    "OL1174034M"  ],  "isbn": [    "9781859170069",    "1859170064"  ],  ....</code></pre></div></div><div class="paragraph"><p>The next step is to extract only specific fields.<code>jq</code> filters support piping to apply new filters on top of the results from the previous ones.It uses the same <code>|</code> symbol, but pay attention to one significant difference - piping filters happens inside the <code>jq</code> argument string.</p></div><div class="listingblock"><div class="title">Listing 7. Piping <code>jq</code> filters to extract specific fields.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '.docs[] | &#123;title,author_name,publish_year&#125;' openlibrary.json&#123;  "title": "Słownik tajemnych gwar przestępczych",  "author_name": [    "Klemens Stępniak"  ],  "publish_year": [    1993  ]&#125;&#123;  "title": "EU adjustment to eastern enlargement",  "author_name": null,  "publish_year": [    1998  ]&#125;...</code></pre></div></div><div class="paragraph"><p>Looks good.Every document got extracted to the new one, containing only three fields.However, <code>author_name</code> and <code>publish_year</code> seem to be arrays.We can update the filter to extract the first value from those arrays and store them as scalar values instead.</p></div><div class="listingblock"><div class="title">Listing 8. Transforming arrays to scalar values example.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125;' openlibrary.json&#123;  "title": "Słownik tajemnych gwar przestępczych",  "author_name": "Klemens Stępniak",  "publish_year": 1993&#125;&#123;  "title": "EU adjustment to eastern enlargement",  "author_name": null,  "publish_year": 1998&#125;...</code></pre></div></div><div class="paragraph"><p>I guess you already noticed that we can transform objects either by just referring to the field name (e.g. <code>title</code>) or by constructing the new field explicitly (e.g. <code>author_name: .author_name[0]</code>.)</p></div></div></div><div class="sect1"><h2 id="filtering-null-values-with-selectexpr-filter">Filtering <code>null</code> values with <code>select(expr)</code> filter</h2><div class="sectionbody"><div class="paragraph"><p>When I looked at the response, I noticed that some documents were missing either <code>author_name</code> or <code>publish_year</code>.We can exclude such documents from the final result using the <code>select(expr)</code> filter.</p></div><div class="listingblock"><div class="title">Listing 9. Filtering objects containing <code>null</code> values.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)' openlibrary.json&#123;  "title": "Słownik tajemnych gwar przestępczych",  "author_name": "Klemens Stępniak",  "publish_year": 1993&#125;&#123;  "title": "Integracja regionalna i transfer kapitału",  "author_name": "Andrzej Stępniak",  "publish_year": 1996&#125;...</code></pre></div></div></div></div><div class="sect1"><h2 id="sorting-by-field-with-sort_byexpr-filter">Sorting by field with <code>sort_by(expr)</code> filter</h2><div class="sectionbody"><div class="paragraph"><p>Now let&#8217;s say we want to get the result in a specific order.What if we want to sort the final result by <code>publish_year</code>?<code>jq</code> offer the <code>sort_by(expr)</code> filter, but before we apply it, we need to make one modification.The <code>sort_by(expr)</code> filter works on arrays, and our current output is not an array.If you look closely, you will see that this is just a set of JSON documents printed one after another.We can transform it into a JSON array by wrapping our current long filter with square brackets - an array constructor.</p></div><div class="paragraph"><p>Compare the following example with the previous one and spot the difference.</p></div><div class="listingblock"><div class="title">Listing 10. Transforming set of JSON objects into a JSON array of objects using <code>[]</code> constructor.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)]' openlibrary.json[  &#123;    "title": "Słownik tajemnych gwar przestępczych",    "author_name": "Klemens Stępniak",    "publish_year": 1993  &#125;,  &#123;    "title": "Integracja regionalna i transfer kapitału",    "author_name": "Andrzej Stępniak",    "publish_year": 1996  &#125;,  ...]</code></pre></div></div><div class="paragraph"><p>Once we have a JSON array constructed, we can pipe <code>sort_by(.publish_year)</code> into our filter.</p></div><div class="listingblock"><div class="title">Listing 11. Sorting a JSON array of objects by specific field using <code>sort_by(expr)</code>.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | sort_by(.publish_year)' openlibrary.json[  &#123;    "title": "Rada Portu i Dróg Wodnych w Wolnym Mieście Gdańsku.  Gdańsku",    "author_name": "Henryk Stępniak",    "publish_year": 1971  &#125;,  &#123;    "title": "Potrącenie w systemie polskiego prawa cywilnego",    "author_name": "Lechosław Stępniak",    "publish_year": 1975  &#125;,  &#123;    "title": "Uzdatnianie złomu poprodukcyjnego",    "author_name": "Stanisław Stępniak",    "publish_year": 1978  &#125;,  ...]</code></pre></div></div><div class="paragraph"><p>By default, <code>sort_by(expr)</code> sorts objects in the ascending order.To sort objects in the descending order, we can pipe <code>reverse</code> filter after the <code>sort_by(expr)</code> one.</p></div><div class="listingblock"><div class="title">Listing 12. Sorting in the descending order.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | sort_by(.publish_year) | reverse' openlibrary.json[  &#123;    "title": "Dziedzictwo archiwalne we współpracy Polski i Ukrainy",    "author_name": "Władysław Stępniak",    "publish_year": 2010  &#125;,  &#123;    "title": "Misja Służby Więziennej a jej zadania wobec aktualnej polityki karnej i oczekiwań społecznych",    "author_name": "Polski Kongres Penitencjarny (4th 2008 Poznań, Poland)",    "publish_year": 2008  &#125;,  &#123;    "title": "Czarny Bór",    "author_name": "Władysław Stępniak",    "publish_year": 2007  &#125;,  ...]</code></pre></div></div></div></div><div class="sect1"><h2 id="limiting-the-size-of-the-result-with-limitnexpr-filter">Limiting the size of the result with <code>limit(n;expr)</code> filter</h2><div class="sectionbody"><div class="paragraph"><p>As the next step, we may want to limit the size of the result.Let&#8217;s say we want to get the newest three publications as a result.We can use <code>limit(n;expr)</code> filter, but we need to be aware of one thing.To limit the size of an array using this filter, we need to use an expression that extracts values from an array for iteration - <code>.[]</code>.Once <code>limit(n;expr)</code> limits the number of objects, it produces the output in the same format.It means that we don&#8217;t get an array but rather a set of separate results instead.</p></div><div class="listingblock"><div class="title">Listing 13. Applying <code>limit(3;.[])</code> filter.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | sort_by(.publish_year)| reverse | limit(3;.[])' openlibrary.json&#123;  "title": "Dziedzictwo archiwalne we współpracy Polski i Ukrainy",  "author_name": "Władysław Stępniak",  "publish_year": 2010&#125;&#123;  "title": "Misja Służby Więziennej a jej zadania wobec aktualnej polityki karnej i oczekiwań społecznych",  "author_name": "Polski Kongres Penitencjarny (4th 2008 Poznań, Poland)",  "publish_year": 2008&#125;&#123;  "title": "Czarny Bór",  "author_name": "Władysław Stępniak",  "publish_year": 2007&#125;</code></pre></div></div><div class="paragraph"><p>If we want to represent the result as a JSON array, we need to wrap <code>limit(n;expr)</code> filter with <code>[]</code> to construct an array again.</p></div><div class="listingblock"><div class="title">Listing 14. Final JSON array of the three latest documents.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | sort_by(.publish_year)| reverse | [limit(3;.[])]' openlibrary.json[  &#123;    "title": "Dziedzictwo archiwalne we współpracy Polski i Ukrainy",    "author_name": "Władysław Stępniak",    "publish_year": 2010  &#125;,  &#123;    "title": "Misja Służby Więziennej a jej zadania wobec aktualnej polityki karnej i oczekiwań społecznych",    "author_name": "Polski Kongres Penitencjarny (4th 2008 Poznań, Poland)",    "publish_year": 2008  &#125;,  &#123;    "title": "Czarny Bór",    "author_name": "Władysław Stępniak",    "publish_year": 2007  &#125;]</code></pre></div></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/EIhLl9ebeiA" data-time="9:44" data-type="youtube" data-name="Parsing JSON from curl with jq: sort_by, group_by, limit (part 1) | #jq #json #curl"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/EIhLl9ebeiA/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/EIhLl9ebeiA" data-type="youtube" data-name="Parsing JSON from curl with jq: sort_by, group_by, limit (part 1) | #jq #json #curl">Parsing JSON from curl with jq: sort_by, group_by, limit (part 1) | #jq #json #curl</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">In this video, I show you how to use the jq command-line to parse and transform a JSON response to the desired format. I use openlibrary.org Search API to get a complex JSON document (~3600 lines) using curl command for further parsing using jq. In the first step, we extract three fields for each document (title, author, publishing year) and then we continue transforming the JSON - we sort it, we limit the number of elements, and last but not least, we group elements by the author name.<a class="gatr font-weight-bold" href="https://youtu.be/EIhLl9ebeiA" data-type="youtube" data-name="Parsing JSON from curl with jq: sort_by, group_by, limit (part 1) | #jq #json #curl">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div></div><div class="sect1"><h2 id="grouping-by-using-group_byexpr-filter">Grouping by using <code>group_by(expr)</code> filter</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s say we want to find out how many publications each author created.<code>jq</code> offers built-in <code>group_by(expr)</code> filter we can use.In our case, we will use <code>group_by(.author_name)</code> to group all publications by their authors.</p></div><div class="listingblock"><div class="title">Listing 15. Grouping results using <code>group_by(.author_name)</code> filter.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | group_by(.author_name)' openlibrary.json[  [    &#123;      "title": "Integracja regionalna i transfer kapitału",      "author_name": "Andrzej Stępniak",      "publish_year": 1996    &#125;,    &#123;      "title": "Promocja polskich podmiotów inwestycyjnych na obszarze Wspólnot Europejskich",      "author_name": "Andrzej Stępniak",      "publish_year": 1993    &#125;,    &#123;      "title": "Kwestia narodowa a społeczna na Śląsku Cieszyńskim pod koniec XIX i w początkach XX wieku, do 1920 roku",      "author_name": "Andrzej Stępniak",      "publish_year": 1986    &#125;,    &#123;      "title": "Polska-WE",      "author_name": "Andrzej Stępniak",      "publish_year": 1993    &#125;  ],  [    &#123;      "title": "Kula jako symbol vanitas",      "author_name": "Beata Purc-Stępniak",      "publish_year": 2004    &#125;  ],  ....]</code></pre></div></div><div class="paragraph"><p>As you can see now, <code>group_by(.author_name)</code> produced an array of arrays.It&#8217;s hard to say how many publications each author did from this document, so we might need to transform this output a bit.</p></div><div class="paragraph"><p>The next filter we need to apply is <code>.[]</code> to start iterating over the grouped arrays.We will pipe this iteration with a transformation that extracts <code>author_name</code> field from the first result using <code>&#123;author_name: .[0].author_name&#125;</code> filter.And as we seen before, <code>.[] | &#123;author_name: .[0].author_name&#125;</code> combination produces a set of results, so we need to wrap it with <code>[]</code> to construct an array.</p></div><div class="listingblock"><div class="title">Listing 16. Extracting <code>author_name</code> from the grouped result.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | group_by(.author_name)| [.[] | &#123;author_name: .[0].author_name&#125;]' openlibrary.json[  &#123;    "author_name": "Andrzej Stępniak"  &#125;,  &#123;    "author_name": "Beata Purc-Stępniak"  &#125;,  &#123;    "author_name": "Colloquia Jerzy Skowronek dedicata (10th 2004 Warsaw, Poland)"  &#125;,  ....]</code></pre></div></div><div class="paragraph"><p>That&#8217;s a good step forward - now we have author names extracted from the grouped result.The next step is to update the transformation filter to also produce the <code>count</code> field - a field containing the number of publications.There is a simple way to do it.The current transformation filter, <code>[.[] | &#123;author_name: .[0].author_name&#125;]</code>, iterates the nested arrays that contain all documents associated with the same author.It means that <code>.</code> (identity) in this filter refers to a list of objects, and that is why we extract the author name using <code>.[0].author_name</code> expression, which means: take the first object from the array and return its <code>author_name</code> value.We can use this array to count its size.In the <code>jq</code>, we have a filter called <code>length</code> that can be piped with an array to return its size.So in our case, an expression <code>. | lenght</code> used in the context of this transformation will return the size of an array, which represents the number of publications.</p></div><div class="listingblock"><div class="title">Listing 17. Extracting the number of publications using <code>length</code> filter.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | group_by(.author_name)| [.[] | &#123;author_name: .[0].author_name, count: . | length&#125;]' openlibrary.json[  &#123;    "author_name": "Andrzej Stępniak",    "count": 4  &#125;,  &#123;    "author_name": "Beata Purc-Stępniak",    "count": 1  &#125;,  &#123;    "author_name": "Colloquia Jerzy Skowronek dedicata (10th 2004 Warsaw, Poland)",    "count": 1  &#125;,  ....]</code></pre></div></div><div class="paragraph"><p>Voila! <span class="icon"><i class="fa fa-smile-o"></i></span></p></div></div></div><div class="sect1"><h2 id="putting-it-all-together">Putting it all together</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s finish this experiment by putting all commands we have learned today together.We can add <code>sort_by(expr)</code> and <code>limit(n;expr)</code> to the latest query to produce the final result - an array of three authors that published the most documents.</p></div><div class="listingblock"><div class="title">Listing 18. Extracting the number of publications using <code>length</code> filter.</div><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ jq '[.docs[] | &#123;title,author_name: .author_name[0], publish_year: .publish_year[0]&#125; | select(.author_name!=null and .publish_year!=null)] | group_by(.author_name)| [.[] | &#123;author_name: .[0].author_name, count: . | length&#125;] | sort_by(.count) | reverse | [limit(3;.[])]' openlibrary.json[  &#123;    "author_name": "Władysław Stępniak",    "count": 11  &#125;,  &#123;    "author_name": "Andrzej Stępniak",    "count": 4  &#125;,  &#123;    "author_name": "Henryk Stępniak",    "count": 3  &#125;]</code></pre></div></div><div class="paragraph"><p>And that&#8217;s it for today.In the next episode, we will dive deeper into more advanced filters and functions that <code>jq</code> offers.Stay tuned! <span class="icon"><i class="fa fa-smile-o"></i></span></p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Have you ever wondered, what is the most convenient way to parse JSON data in the Unix/Linux command line?
For instance, how to parse some information from the &lt;code&gt;curl&lt;/code&gt; JSON response?
Grep?
No, thank you.
There is a better tool for that.
And it&amp;#8217;s called &lt;code&gt;jq&lt;/code&gt;.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="jq cookbook" scheme="https://e.printstacktrace.blog/jq-cookbook/"/>
    
    
    <category term="devops" scheme="https://e.printstacktrace.blog/t/devops/"/>
    
    <category term="rest" scheme="https://e.printstacktrace.blog/t/rest/"/>
    
    <category term="http" scheme="https://e.printstacktrace.blog/t/http/"/>
    
    <category term="curl" scheme="https://e.printstacktrace.blog/t/curl/"/>
    
    <category term="jq" scheme="https://e.printstacktrace.blog/t/jq/"/>
    
    <category term="json" scheme="https://e.printstacktrace.blog/t/json/"/>
    
    <category term="linux" scheme="https://e.printstacktrace.blog/t/linux/"/>
    
  </entry>
  
  <entry>
    <title>How to time out Jenkins Pipeline stage and keep the pipeline running?</title>
    <link href="https://e.printstacktrace.blog/how-to-time-out-jenkins-pipeline-stage-and-keep-the-pipeline-running/"/>
    <id>https://e.printstacktrace.blog/how-to-time-out-jenkins-pipeline-stage-and-keep-the-pipeline-running/</id>
    <published>2020-04-16T07:28:38.000Z</published>
    <updated>2020-04-16T14:21:32.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>The declarative Jenkins Pipeline allows us to define timeout either at the pipeline level or the specific stage.This feature prevents Jenkins&#8217;s job from getting stuck.However, in some cases, we want to accept that one stage may timeout, but we want to keep the remaining stages running.</p></div><a id="more"></a><div class="sect1"><h2 id="using-stage-timeout">Using stage <code>timeout</code></h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start with a simple example.In this demo, I use a pipeline with two stages, <code>A</code> and <code>B</code>.</p></div><div class="listingblock"><div class="title">Listing 1. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("A") &#123;            options &#123;                timeout(time: 3, unit: "SECONDS")            &#125;            steps &#123;                echo "Started stage A"                sleep(time: 5, unit: "SECONDS")            &#125;        &#125;        stage("B") &#123;            steps &#123;                echo "Started stage B"            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>As you may expect, running this pipeline causes timeout in the <code>A</code> stage, and the remaining stage <code>B</code> does not get triggered.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/pipeline-timeout-01.png"><div class="img-lazyload-container" style="width:996px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/pipeline-timeout-01.png" height="502" width="996" style="padding-top:50.401606425702816%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="using-catcherror-workflow-step">Using <code>catchError</code> workflow step</h2><div class="sectionbody"><div class="paragraph"><p>In most cases, this is OK.The remaining stage of the pipeline depends on the success of the previous one.But what if we want to treat <code>A</code> stage as <em>optional</em> and keep the pipeline running even if it times out?Let&#8217;s see if we can use <a href="https://jenkins.io/doc/pipeline/steps/workflow-basic-steps/#catcherror-catch-error-and-set-build-result-to-failure"><code>catchError</code> workflow step</a> to handle the exception for us.</p></div><div class="listingblock"><div class="title">Listing 2. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("A") &#123;            options &#123;                timeout(time: 3, unit: "SECONDS")            &#125;            steps &#123;                catchError(buildResult: 'SUCCESS', stageResult: 'ABORTED') &#123; <i class="conum" data-value="1"></i><b>(1)</b>                    echo "Started stage A"                    sleep(time: 5, unit: "SECONDS")                &#125;            &#125;        &#125;        stage("B") &#123;            steps &#123;                echo "Started stage B"            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>In this example, we wrapped steps that potentially may timeout with the <code>catchError</code> <em class="conum" data-value="1"></em>.We want to mark the stage as <code>ABORTED</code>, but we want to keep the build status as <code>SUCCESS</code>.This way, we don&#8217;t want to affect the final build result with the timeout of the <code>A</code> stage.Let&#8217;s look at what happens when we run the pipeline now.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/pipeline-timeout-02.png"><div class="img-lazyload-container" style="width:990px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/pipeline-timeout-02.png" height="430" width="990" style="padding-top:43.43434343434344%"></div></a></div></div></div></div><div class="paragraph"><p>Looks good, but there is one small issue.We can see that stage <code>B</code> was executed, however, the job status is set to <code>ABORTED</code>.It turns out that, according to the documentation, we cannot set the build result to <code>SUCCESS</code> from results like <code>UNSTABLE</code> or <code>ABORTED</code>.</p></div><div class="quoteblock"><blockquote><div class="paragraph"><p>If an error is caught, the overall build result will be set to this value. Note that the build result can only get worse, so you cannot change the result to <code>SUCCESS</code> if the current result is <code>UNSTABLE</code> or worse. Use <code>SUCCESS</code> or <code>null</code> to keep the build result from being set when an error is caught.</p></div></blockquote></div></div></div><div class="sect1"><h2 id="using-try-catch-inside-script-step">Using <code>try-catch</code> inside <code>script</code> step</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s see if replacing <code>catchError</code> with good old <code>try-catch</code> can help us in this situation.</p></div><div class="listingblock"><div class="title">Listing 3. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("A") &#123;            options &#123;                timeout(time: 3, unit: "SECONDS")            &#125;            steps &#123;                script &#123; <i class="conum" data-value="2"></i><b>(2)</b>                    try &#123;                        echo "Started stage A"                        sleep(time: 5, unit: "SECONDS")                    &#125; catch (Throwable e) &#123;                        echo "Caught $&#123;e.toString()&#125;"                        currentBuild.result = "SUCCESS" <i class="conum" data-value="1"></i><b>(1)</b>                    &#125;                &#125;            &#125;        &#125;        stage("B") &#123;            steps &#123;                echo "Started stage B"            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>In this example, we want to catch any potential exception and force the <code>SUCCESS</code> build status <em class="conum" data-value="1"></em>.Keep in mind, that we had to put the code into the <code>script</code> step <em class="conum" data-value="2"></em> to be able to use <code>try-catch</code> block.Let&#8217;s run the pipeline and see how it goes.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/pipeline-timeout-03.png"><div class="img-lazyload-container" style="width:979px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/pipeline-timeout-03.png" height="532" width="979" style="padding-top:54.341164453524%"></div></a></div></div></div></div><div class="paragraph"><p>We can see the expected build status, but there is one issue with this approach.The <code>A</code> stage is marked as <code>SUCCESS</code> which might be confusing.It hides the information about the timeout, so we need to click on the stage and check if there was a timeout or not every time we run the pipeline.There should be a better way to handle it.</p></div></div></div><div class="sect1"><h2 id="using-catcherror-together-with-try-catch">Using <code>catchError</code> together with <code>try-catch</code></h2><div class="sectionbody"><div class="paragraph"><p>Luckily, there is a better solution.We can combine both <code>catchError</code> with the <code>try-catch</code>.Let&#8217;s take a look at the final example.</p></div><div class="listingblock"><div class="title">Listing 4. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("A") &#123;            options &#123;                timeout(time: 3, unit: "SECONDS")            &#125;            steps &#123;                script &#123;                    Exception caughtException = null                    catchError(buildResult: 'SUCCESS', stageResult: 'ABORTED') &#123; <i class="conum" data-value="1"></i><b>(1)</b>                        try &#123; <i class="conum" data-value="2"></i><b>(2)</b>                            echo "Started stage A"                            sleep(time: 5, unit: "SECONDS")                        &#125; catch (org.jenkinsci.plugins.workflow.steps.FlowInterruptedException e) &#123;                            error "Caught $&#123;e.toString()&#125;" <i class="conum" data-value="3"></i><b>(3)</b>                        &#125; catch (Throwable e) &#123;                            caughtException = e                        &#125;                    &#125;                    if (caughtException) &#123;                        error caughtException.message                    &#125;                &#125;            &#125;        &#125;        stage("B") &#123;            steps &#123;                echo "Started stage B"            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>In this example, we use <code>catchError</code> to control the stage result in case of an error <em class="conum" data-value="1"></em>.The code that may potentially timeout is wrapped with the <code>try-catch</code> <em class="conum" data-value="2"></em> so we can control the exception.We can be very specific - in this case we catch the <code>FlowInterruptedException</code> to mark the current stage as <code>ABORTED</code> <em class="conum" data-value="3"></em>, but we also store any other exception as <code>caughtException</code>.If any exception other than <code>FlowInterruptedException</code> occurs, we execute <code>error</code> step with an exception message to fail the <code>A</code> stage.Let&#8217;s run and see the final result.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/pipeline-timeout-04.png"><div class="img-lazyload-container" style="width:978px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/pipeline-timeout-04.png" height="527" width="978" style="padding-top:53.88548057259713%"></div></a></div></div></div></div><div class="paragraph"><p>And here is what happens if the code that may timeout throws a different error (for instance, some shell command may return exit code 1.)</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/pipeline-timeout-05.png"><div class="img-lazyload-container" style="width:1043px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/pipeline-timeout-05.png" height="524" width="1043" style="padding-top:50.23969319271333%"></div></a></div></div></div></div><div class="paragraph"><p>Voilà!Now it works just as we expect.The <code>A</code> stage gets executed, and it is acceptable to timeout.The pipeline continues in such a case, and its final result depends on the remaining stages. <span class="icon"><i class="fa fa-thumbs-o-up"></i></span></p></div><div class="paragraph text-center mt-4"><p><a class="gatr" href="https://click.linksynergy.com/deeplink?id=uow2LCEuhvQ&amp;mid=39197&amp;murl=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Flearn-devops-ci-cd-with-jenkins-using-pipelines-and-docker%2F" data-type="banner" data-name="jenkins-docker-udemy-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/jenkins-docker-udemy.png"/></a></p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;The declarative Jenkins Pipeline allows us to define timeout either at the pipeline level or the specific stage.
This feature prevents Jenkins&amp;#8217;s job from getting stuck.
However, in some cases, we want to accept that one stage may timeout, but we want to keep the remaining stages running.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Jenkins Pipeline Cookbook" scheme="https://e.printstacktrace.blog/jenkins-pipeline-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="devops" scheme="https://e.printstacktrace.blog/t/devops/"/>
    
    <category term="jenkins" scheme="https://e.printstacktrace.blog/t/jenkins/"/>
    
    <category term="jenkins-pipeline" scheme="https://e.printstacktrace.blog/t/jenkins-pipeline/"/>
    
    <category term="cicd" scheme="https://e.printstacktrace.blog/t/cicd/"/>
    
    <category term="continuous-integration" scheme="https://e.printstacktrace.blog/t/continuous-integration/"/>
    
    <category term="jenkinsfile" scheme="https://e.printstacktrace.blog/t/jenkinsfile/"/>
    
  </entry>
  
  <entry>
    <title>My first 200 YouTube subscribers - Thank You!</title>
    <link href="https://e.printstacktrace.blog/my-first-200-youtube-subscribers-thank-you/"/>
    <id>https://e.printstacktrace.blog/my-first-200-youtube-subscribers-thank-you/</id>
    <published>2020-04-09T11:02:16.000Z</published>
    <updated>2020-11-05T14:01:43.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Many content creators celebrate their 10k, 100k, or 1M subscribers/followers milestones.There is nothing wrong with that.Achieving such numbers require tons of work, time, effort, sometimes even luck.Those numbers do not show up overnight.People deserve celebrating those moments and sharing their happiness with their community.Today I want to share my own success with you.The number that wouldn&#8217;t be possible without your trust and support.</p></div><a id="more"></a><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">At the end of this blog post, I share a few tips that <strong>helped me grow my YouTube channel</strong> up to 200 subscribers and beyond. Enjoy!</td></tr></table></div><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#achievement-unlocked">Achievement unlocked!</a></li><li><a href="#early-adopters">Early adopters</a></li><li><a href="#the-future">The future</a></li><li><a href="#how-to-grow-a-youtube-channel-from-0-to-200-subscribers-and-beyond">How to grow a YouTube channel from 0 to 200 subscribers and beyond?</a><ul class="sectlevel2"><li><a href="#research-before-you-press-record">Research before you press record</a></li><li><a href="#follow-youtube-video-formula-hook-content-cta">Follow YouTube video formula: Hook, Content, CTA</a></li><li><a href="#improve-your-seo">Improve your SEO</a></li><li><a href="#design-thumbnails-that-stand-out">Design thumbnails that stand out</a></li><li><a href="#engage-with-your-community">Engage with your community</a></li><li><a href="#be-patient-and-consistent">Be patient and consistent</a></li></ul></li></ul></div><div class="sect1"><h2 id="achievement-unlocked">Achievement unlocked!</h2><div class="sectionbody"><div class="paragraph"><p>My <a href="https://e.printstacktrace.blog/youtube?utm_source=post">YouTube channel</a> just reached <strong>200 subscribers</strong>.Is it a lot? Not necessarily.It&#8217;s not even round number, like 256, or 512.But it means a lot to me.There are two hundred people who found my videos, either useful or entertaining.I&nbsp;hope you are one of them.I&nbsp;didn&#8217;t plan to write a blog post about it, but then I thought that my fellow subscribers <strong>deserve big and honest THANK YOU!</strong> <span class="icon"><i class="fa fa-thumbs-o-up"></i></span></p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block clearfix"><div class="content"><a class="image" href="/images/yt-200-subs.png"><div class="img-lazyload-container" style="width:1252px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/yt-200-subs.png" height="472" width="1252" style="padding-top:37.69968051118211%"></div></a></div></div></div></div><div class="paragraph"><p>I&#8217;ve seen many times people thanking the follower who made their 1k, 10k, or any other milestone.This is a very gentle way to welcome someone who just joined your subscribers or followers group.But every time I see it, I ask myself: <em>"Why they don&#8217;t thank their, let&#8217;s say, first 10 followers?"</em>Social media are a game of numbers.Those numbers work as social proof - it&#8217;s easier to keep growing when you already have an audience.Anyone who starts and grows more or less organically needs to be patient and stay consistent, probably more than ever.And don&#8217;t get me wrong - I don&#8217;t try to blame it.I&nbsp;accept and agree with it.</p></div></div></div><div class="sect1"><h2 id="early-adopters">Early adopters</h2><div class="sectionbody"><div class="paragraph"><p>The beginnings in any discipline are tough.We&nbsp;learn new things, we experiment and measure results, and then we try to improve if something doesn&#8217;t work.It&nbsp;is the time when we keep making mistakes, keep gaining confidence, and keep building trust.This is why, in my opinion, early adopters, people who trusted you when you just started (and don&#8217;t have much to offer), deserve acknowledgment.I&nbsp;don&#8217;t know all your names.YouTube notifies only about those of you who keep their subscriptions public.I&nbsp;keep all emails received when the new subscriber shows up.<strong>Marcin, Søren, Paweł, Tucker, Naresha, Robert, Anton, Jay, Marouane, Kenneth, Jason, Sergiy, Giulio, Federico, Scott, Łukasz, Diego</strong>, and all the rest of you - I&#8217;m happy and grateful you trusted me and shown your support.It&nbsp;motivates me to keep doing the work, and I hope I won&#8217;t disappoint you.I&nbsp;don&#8217;t know if I ever grow my YouTube channel to 10k, 100k, or more, but I know one thing - I will never forget that you were the early adopters.I&nbsp;salute to you!</p></div></div></div><div class="sect1"><h2 id="the-future">The future</h2><div class="sectionbody"><div class="paragraph"><p>Some of you know that I love recording, editing, and producing videos.I&nbsp;spent countless hours in Adobe Premiere to learn the video editing craft, and I can tell you, it became my favorite hobby.I&nbsp;prefer quality over quantity, and I know that sometimes I put probably too much attention and effort into polishing some small details.Just like with the "型 Programming Kata" series, where I spent around two and a half hours to produce 40 seconds intro that I am so proud of.From the economic stand of point, it was probably not the best time investment, but hey - I don&#8217;t care.I&nbsp;enjoyed creating it like a child in Disneyland.</p></div><div class="paragraph"><p>So, what is the future of the YouTube channel?I&nbsp;will keep posting videos weekly.You can expect both long and short ones.I&nbsp;want to use the short form to share with you some useful tips and tricks that are ready to apply.This way you can learn something new without dedicating a lot of time.In&nbsp;the long-form videos, I will try to go a little deeper into some specific problems to solve.And everything is going to more or less Java oriented, with a strong accent on the Groovy ecosystem.But not only that.So, consider <a href="https://e.printstacktrace.blog/youtube?utm_source=post">subscribing</a> if you haven&#8217;t already.And&nbsp;remember - any kind of engagement (likes, comments, subscriptions) helps.I want to thank you for that!</p></div></div></div><div class="sect1"><h2 id="how-to-grow-a-youtube-channel-from-0-to-200-subscribers-and-beyond">How to grow a YouTube channel from 0 to 200 subscribers and beyond?</h2><div class="sectionbody"><div class="paragraph"><p>I know how difficult it is to start a YouTube channel and see its slow growth.I know how disappointing it is to publish a new video and see just a few views in the first hours.If you&#8217;re going through the same struggles - <strong>you&#8217;re not alone</strong>.</p></div><div class="paragraph"><p>In this section I would like to share with you a few tips that made a significant difference in my early days of the YouTube channel development.I learned them the hard way, and I still keep applying those tips up to this day.I hope you will benefit from them as well.</p></div><div class="sect2"><h3 id="research-before-you-press-record">Research before you press record</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="/images/youtube-research-before-you-press-record.jpg"><div class="img-lazyload-container" style="width:800px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-research-before-you-press-record.jpg" height="270" width="800" style="padding-top:33.75%"></div></a></div></div></div></div><div class="paragraph"><p>Do you know that YouTube is the <strong>2nd largest search engine</strong> on the Internet?It&#8217;s designed to help viewers to find (and suggest) a relevant content.The more <em>"searchable"</em> your video is, the better.</p></div><div class="paragraph"><p>So when you have an idea to create a video on a specific topic, go to YouTube and start searching for a similar content.Do what your ideal visitor would do.For instance, if I want to create a video on playing guitar, I would search <em>"how to play guitar"</em> and see what YouTube recommends to people who are looking for that specific topic.Then I would use those recommendations to create a video on a topic that gets recommended to most people.</p></div></div><div class="sect2"><h3 id="follow-youtube-video-formula-hook-content-cta">Follow YouTube video formula: Hook, Content, CTA</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="/images/youtube-formula.png"><div class="img-lazyload-container" style="width:800px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-formula.png" height="270" width="800" style="padding-top:33.75%"></div></a></div></div></div></div><div class="paragraph"><p>There are many ways to structure your video, and different formulas may work better for different niches.The one that works best for me is: <strong>Hook</strong>, <strong>Content</strong>, <strong>CTA</strong>.</p></div><div class="ulist"><ul><li><p><strong>The Hook</strong>:Use the first 15 seconds to attract your viewers.Explain in a single sentence why they should continue watching the video, and how it will impact their life.</p></li><li><p><strong>The Content</strong>:This is the part where you deliver your message.You can split it into chunks and present with the following flow: <strong>Intro</strong> (the <em>"why?"</em>), <strong>Basic Theory</strong> (the <em>"what?"</em>), and <strong>Practice</strong> (the <em>"how?"</em>).This way your message will be clear to both, novice and more advanced viewers.</p></li><li><p><strong>CTA (Call To Action)</strong>:In this part you can ask viewers to subscribe to your channel, watch the next video, or check the details in the description.<strong>Never ask to subscribe upfront!</strong>People don&#8217;t know you yet, you haven&#8217;t deliver anything so far, and you ask for a favor.Deliver first, and then ask.</p></li></ul></div></div><div class="sect2"><h3 id="improve-your-seo">Improve your SEO</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="https://e.printstacktrace.blog/vidiq"><div class="img-lazyload-container" style="width:800px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-vidiq-seo-keyword-research-tool.png" height="270" width="800" style="padding-top:33.75%"></div></a></div></div></div></div><div class="paragraph"><p>When you are at the beginning of your journey, every small thing matter.Established YouTube channels don&#8217;t have to pay much attention about keywords, titles, or descriptions - they have a solid following which guarantees that every video will be watched by thousands.<strong>That&#8217;s not your case.</strong></p></div><div class="paragraph"><p>The small YouTube channels have to spend more time on a proper SEO (search engine optimization.)I use <a href="https://e.printstacktrace.blog/vidiq"><strong>vidIQ</strong></a> - one of the best tools designed to maximize organic reach of YouTube videos.</p></div></div><div class="sect2"><h3 id="design-thumbnails-that-stand-out">Design thumbnails that stand out</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="https://e.printstacktrace.blog/canva"><div class="img-lazyload-container" style="width:800px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-thumbnail-creator-canva.png" height="270" width="800" style="padding-top:33.75%"></div></a></div></div></div></div><div class="paragraph"><p>No matter how valuable your video is - if it does not attract the audience with a good-looking thumbnail, chances are no one will watch it.I&#8217;m not a graphic designer, and I guess neither you.</p></div><div class="paragraph"><p>I use <a href="https://e.printstacktrace.blog/canva"><strong>Canva</strong></a> - an online graphic designing tool with hundreds of thousands ready to use templates.You can design your own from scratch, or you can browse and find a template that will catch an attention and drive more audience to your channel.</p></div></div><div class="sect2"><h3 id="engage-with-your-community">Engage with your community</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="/images/youtube-community.png"><div class="img-lazyload-container" style="width:797px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-community.png" height="268" width="797" style="padding-top:33.626097867001256%"></div></a></div></div></div></div><div class="paragraph"><p>One of the biggest benefits of being a small YouTuber is the fact that you can read and answer <strong>every</strong> comment or message you receive from your audience.Don&#8217;t lose this opportunity to create a strong relationship with your viewers.</p></div><div class="paragraph"><p>Answer to every comment, ask follow-up questions, find out what your small audience is mostly interested in and deliver the content they expect.</p></div></div><div class="sect2"><h3 id="be-patient-and-consistent">Be patient and consistent</h3><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block mt-0"><div class="content"><a class="image" href="/images/youtube-patience.png"><div class="img-lazyload-container" style="width:797px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/youtube-patience.png" height="268" width="797" style="padding-top:33.626097867001256%"></div></a></div></div></div></div><div class="paragraph"><p>I know it sounds cliche, but it couldn&#8217;t be closer to truth.Find your niche, upload on a regular schedule (once a week, once a two weeks, once a month - it doesn&#8217;t matter), monitor your analytics, see what works best, experiment, and be patient.YouTube is a marathon, and you never know when (and which video) will suddenly build your recognition.</p></div></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Many content creators celebrate their 10k, 100k, or 1M subscribers/followers milestones.
There is nothing wrong with that.
Achieving such numbers require tons of work, time, effort, sometimes even luck.
Those numbers do not show up overnight.
People deserve celebrating those moments and sharing their happiness with their community.
Today I want to share my own success with you.
The number that wouldn&amp;#8217;t be possible without your trust and support.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Blog Reports" scheme="https://e.printstacktrace.blog/blog-reports/"/>
    
    
    <category term="learning" scheme="https://e.printstacktrace.blog/t/learning/"/>
    
    <category term="blog" scheme="https://e.printstacktrace.blog/t/blog/"/>
    
    <category term="report" scheme="https://e.printstacktrace.blog/t/report/"/>
    
    <category term="youtube" scheme="https://e.printstacktrace.blog/t/youtube/"/>
    
    <category term="canva" scheme="https://e.printstacktrace.blog/t/canva/"/>
    
    <category term="vidiq" scheme="https://e.printstacktrace.blog/t/vidiq/"/>
    
  </entry>
  
  <entry>
    <title>Groovy dynamic Maps, generic type erasure, and raw types - an interesting use case to learn from</title>
    <link href="https://e.printstacktrace.blog/groovy-dynamic-maps-generic-type-erasure-and-raw-types/"/>
    <id>https://e.printstacktrace.blog/groovy-dynamic-maps-generic-type-erasure-and-raw-types/</id>
    <published>2020-04-02T10:18:26.000Z</published>
    <updated>2020-04-02T10:18:26.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Dynamic type inference in Groovy might be tricky.Add generic type erasure to it, and you can find yourself in trouble.In this blog post, I would like to show you such use case and explain what happens under the hood.Enjoy reading! <span class="icon"><i class="fa fa-smile-o"></i></span></p></div><a id="more"></a><div class="sect1"><h2 id="an-example">An example</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start with a simple example.</p></div><div class="listingblock"><div class="title">Listing 1. SomeClass.groovy</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">class SomeClass &#123;    static final Map&lt;String, String&gt; PLAYERS = [        "Football": ["Adam", "John", "Travis", "Elliot"],        "Basketball": ["Samantha", "Greg", "Fabien", "Jessica"]    ]    static void main(String[] args) &#123;        println PLAYERS        println PLAYERS.dump()        println PLAYERS.Football        println PLAYERS.Football.dump()    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>Here is a Groovy class with a <code>PLAYERS</code> map.Its type was defined as <code>Map&lt;String,String&gt;</code>, but for some reason (maybe by accident), someone assigned a list of strings to each key.If you open such a file in the IDE like IntelliJ IDEA, it will show you a warning like this one.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/groovy-dynamic-map-warning.png"><div class="img-lazyload-container" style="width:1488px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/groovy-dynamic-map-warning.png" height="218" width="1488" style="padding-top:14.650537634408604%"></div></a></div></div></div></div><div class="paragraph"><p>But the fact is, we can run <code>SomeClass.main()</code> method and get the following output.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-text hljs" data-lang="text">[Football:[Adam, John, Travis, Elliot], Basketball:[Samantha, Greg, Fabien, Jessica]]&lt;java.util.LinkedHashMap@34f13c4f head=Football=[Adam, John, Travis, Elliot] tail=Basketball=[Samantha, Greg, Fabien, Jessica] accessOrder=false table=[Basketball=[Samantha, Greg, Fabien, Jessica], Football=[Adam, John, Travis, Elliot], null, null] entrySet=[Football=[Adam, John, Travis, Elliot], Basketball=[Samantha, Greg, Fabien, Jessica]] size=2 modCount=2 threshold=3 loadFactor=0.75 keySet=null values=null&gt;[Adam, John, Travis, Elliot]&lt;java.util.ArrayList@42c8f129 elementData=[Adam, John, Travis, Elliot] size=4 modCount=4&gt;</code></pre></div></div><div class="paragraph"><p>It doesn&#8217;t make much sense, right?Not necessarily.</p></div></div></div><div class="sect1"><h2 id="generic-type-erasure-and-raw-types">Generic type erasure and raw types</h2><div class="sectionbody"><div class="paragraph"><p>Java generic types <a href="https://docs.oracle.com/javase/tutorial/java/generics/erasure.html">gets erased at the compile time</a>.It means that the Java Runtime Environment loses information about the generic type, and essentially, the <code>PLAYERS</code> map from our example becomes something similar to <code>Map&lt;Object,Object&gt;</code>.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ javap -s -p SomeClassCompiled from "SomeClass.groovy"public class SomeClass implements groovy.lang.GroovyObject &#123;  private static final java.util.Map&lt;java.lang.String, java.lang.String&gt; PLAYERS;    descriptor: Ljava/util/Map;  ...</code></pre></div></div><div class="paragraph"><p>Java also allows assigning "raw" types to the parameterized type.For instance, you can create a raw <code>List</code>, add some values of type <code>String</code> to it (or even mix different types), and assign such a list to the <code>List&lt;Integer&gt;</code> variable.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">final List foo = new ArrayList();foo.add("abc");foo.add("def");foo.add("ghj");foo.add(123);final List&lt;Integer&gt; numbers = foo;System.out.println(numbers); // prints: [abc, def, ghj, 123]</code></pre></div></div><div class="paragraph"><p>Your IDE will throw a few warnings, like <code>Unchecked call to 'add(E)' as a member of raw type 'java.util.List'</code>, but it won&#8217;t stop you from doing so.The above code compiles and runs without any issue.</p></div></div></div><div class="sect1"><h2 id="what-happens-in-the-groovy-use-case">What happens in the Groovy use case?</h2><div class="sectionbody"><div class="paragraph"><p>Now when we know about generic type erasure and raw types, we can take a look at what happens in the Groovy use case under the hood.We can compile <code>SomeClass.groovy</code> file with the Groovy compiler.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovyc SomeClass.groovy</code></pre></div></div><div class="paragraph"><p>And then we can open the <code>SomeClass.class</code> file in the IntelliJ IDEA to see its disassembled form.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">//// Source code recreated from a .class file by IntelliJ IDEA// (powered by Fernflower decompiler)//import groovy.lang.GroovyObject;import groovy.lang.MetaClass;import groovy.transform.Generated;import groovy.transform.Internal;import java.util.Map;import org.codehaus.groovy.runtime.ScriptBytecodeAdapter;import org.codehaus.groovy.runtime.callsite.CallSite;public class SomeClass implements GroovyObject &#123;    private static final Map&lt;String, String&gt; PLAYERS; <i class="conum" data-value="1"></i><b>(1)</b>    @Generated    public SomeClass() &#123;        CallSite[] var1 = $getCallSiteArray();        super();        MetaClass var2 = this.$getStaticMetaClass();        this.metaClass = var2;    &#125;    public static void main(String... args) &#123;        CallSite[] var1 = $getCallSiteArray();        var1[0].callStatic(SomeClass.class, PLAYERS);        var1[1].callStatic(SomeClass.class, var1[2].call(PLAYERS));        var1[3].callStatic(SomeClass.class, var1[4].callGetProperty(PLAYERS));        var1[5].callStatic(SomeClass.class, var1[6].call(var1[7].callGetProperty(PLAYERS)));    &#125;    @Generated    @Internal    public MetaClass getMetaClass() &#123;        MetaClass var10000 = this.metaClass;        if (var10000 != null) &#123;            return var10000;        &#125; else &#123;            this.metaClass = this.$getStaticMetaClass();            return this.metaClass;        &#125;    &#125;    @Generated    @Internal    public void setMetaClass(MetaClass var1) &#123;        this.metaClass = var1;    &#125;    static &#123;        Map var0 = ScriptBytecodeAdapter.createMap(new Object[]&#123;"Football", ScriptBytecodeAdapter.createList(new Object[]&#123;"Adam", "John", "Travis", "Elliot"&#125;), "Basketball", ScriptBytecodeAdapter.createList(new Object[]&#123;"Samantha", "Greg", "Fabien", "Jessica"&#125;)&#125;);        PLAYERS = var0; <i class="conum" data-value="2"></i><b>(2)</b>    &#125;    @Generated    public static Map&lt;String, String&gt; getPLAYERS() &#123;        return PLAYERS;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>The disassembled code shows what the Groovy class looks like from the Java perspective.We can see that <code>PLAYERS</code> map <em class="conum" data-value="1"></em> is the same <code>Map&lt;String,String&gt;</code> type.It gets initialized in the static constructor <em class="conum" data-value="2"></em> by assigning a map created by <code>ScriptBytecodeAdapter.createMap()</code> function.It returns a raw <code>Map</code> type and accepts <code>Object[]</code> - an array of any objects.What it shows is that in the dynamically compiled Groovy, it doesn&#8217;t matter what specific map we define on the right side of the assignment expression.The bytecode it produces takes all entries and treat them as they were of <code>Object</code> type, and produces a raw <code>Map</code> as a result.</p></div><div class="paragraph"><p>Groovy also does all the necessary casts for you.If we have to rewrite Groovy&#8217;s <code>SomeClass</code> to its Java equivalent, we would need to either treat anything that is returned by the <code>PLAYERS.get()</code> as <code>Object</code>, or make all required casts by hand.</p></div><div class="listingblock"><div class="title">Listing 2. SomeJavaClass.java</div><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">import java.util.Arrays;import java.util.HashMap;import java.util.List;import java.util.Map;final class SomeJavaClass &#123;    private static final Map&lt;String, String&gt; PLAYERS;    static &#123;        final Map map = new HashMap();        map.put("Football", Arrays.asList("Adam", "John", "Travis", "Elliot"));        map.put("Basketball", Arrays.asList("Samantha", "Greg", "Fabien", "Jessica"));        PLAYERS = map;    &#125;    public static void main(String[] args) &#123;        final Object footballPlayersObject = PLAYERS.get("Football");        System.out.println(footballPlayersObject); // prints: [Adam, John, Travis, Elliot]        System.out.println(footballPlayersObject.getClass()); // prints: class java.util.Arrays$ArrayList        final List&lt;String&gt; footballPlayersList = (List) ((Object) PLAYERS.get("Football"));        System.out.println(footballPlayersList); // prints: [Adam, John, Travis, Elliot]        System.out.println(footballPlayersList.getClass()); // prints: class java.util.Arrays$ArrayList    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>Groovy handles all that.It uses <code>AbstractCallSite.callGetProperty()</code> method that accpets <code>Object</code> parameter and returns an <code>Object</code>.Also, if we do the following in our Groovy example:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final List&lt;String&gt; footballPlayers = PLAYERS.Football</code></pre></div></div><div class="paragraph"><p>it would get compiled to the following Java equivalent:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-java hljs" data-lang="java">List footballPlayers = (List)ScriptBytecodeAdapter.castToType(var1[0].callGetProperty(PLAYERS), List.class);</code></pre></div></div></div></div><div class="sect1"><h2 id="so-is-it-good-or-bad">So, is it good or bad?</h2><div class="sectionbody"><div class="paragraph"><p>As always - it depends.With great power comes great responsibility.Luckily, Groovy also offers solutions if you are looking for some more secure type checking or even static compilation.</p></div><div class="paragraph"><p>If you want to take advantage of Groovy&#8217;s dynamic compilation, but you want to improve type checking, you can consider using <code>@groovy.transform.TypeChecked</code> annotation.When we add it to the <code>SomeClass</code>, IDE will mark <code>PLAYERS</code> variable red and say <strong>Cannot assign <code>LinkedHashMap&lt;String, List&lt;String&gt;&gt;</code> to <code>Map&lt;String, String&gt;</code></strong>.Also, when we try to compile the class with <code>groovyc</code>, we will end up seeing the following error.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovyc SomeClass.groovyorg.codehaus.groovy.control.MultipleCompilationErrorsException: startup failed:SomeClass.groovy: 6: [Static type checking] - Incompatible generic argument types. Cannot assign java.util.LinkedHashMap &lt;java.lang.String, java.util.List&gt; to: java.util.Map &lt;String, String&gt; @ line 6, column 48.   Map&lt;String, String&gt; PLAYERS = [                                 ^SomeClass.groovy: 12: [Static type checking] - Cannot assign value of type java.lang.String to variable of type java.util.List &lt;String&gt; @ line 12, column 46.   ist&lt;String&gt; footballPlayers = PLAYERS.Fo                                 ^2 errors</code></pre></div></div><div class="paragraph"><p>Alternatively, if you don&#8217;t use any of the Groovy&#8217;s dynamic features, you can enable static compilation with <code>@groovy.transform.CompileStatic</code> annotation.It enables static type checking and produces the bytecode that is much closer to what Java compiler produces.</p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Dynamic type inference in Groovy might be tricky.
Add generic type erasure to it, and you can find yourself in trouble.
In this blog post, I would like to show you such use case and explain what happens under the hood.
Enjoy reading! &lt;span class=&quot;icon&quot;&gt;&lt;i class=&quot;fa fa-smile-o&quot;&gt;&lt;/i&gt;&lt;/span&gt;&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="java" scheme="https://e.printstacktrace.blog/t/java/"/>
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="type-erasure" scheme="https://e.printstacktrace.blog/t/type-erasure/"/>
    
    <category term="generics" scheme="https://e.printstacktrace.blog/t/generics/"/>
    
    <category term="bytecode" scheme="https://e.printstacktrace.blog/t/bytecode/"/>
    
  </entry>
  
  <entry>
    <title>Groovy 3 @NullCheck annotation - less code and less NPE</title>
    <link href="https://e.printstacktrace.blog/groovy-3-nullcheck-annotation-less-code-and-less-npe/"/>
    <id>https://e.printstacktrace.blog/groovy-3-nullcheck-annotation-less-code-and-less-npe/</id>
    <published>2020-02-14T13:34:38.000Z</published>
    <updated>2020-02-14T13:34:38.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Groovy 3 helps you write less, but more secure code.Today I want to show you one of the features added in the latest release - <code>@NullCheck</code> annotation.</p></div><a id="more"></a><div class="sect1"><h2 id="what-is-nullcheck">What is <code>@NullCheck</code>?</h2><div class="sectionbody"><div class="paragraph"><p>The <code>@groovy.transform.NullCheck</code> belongs to the category of annotations that trigger specific AST transformations at the compilation time.This specific annotation can be added to class, constructor, or method.When it is present, it adds if-statement that checks if a variable (or variables) is not <code>null</code>, and throws <code>IllegalArgumentException</code> otherwise.</p></div><div class="paragraph"><p>Here is Groovy =&lt;2.5 class example that does all <code>null</code> checks manually.</p></div><div class="listingblock"><div class="title">Listing 1. Groovy 2.5 solution.</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.CompileStatic@CompileStaticclass Foo &#123;    private final String str    Foo(final String str) &#123;        if (str == null) &#123; <i class="conum" data-value="1"></i><b>(1)</b>            throw new IllegalArgumentException("str cannot be null")        &#125;        this.str = str    &#125;    String bar(final BigDecimal value) &#123;        if (value == null) &#123; <i class="conum" data-value="2"></i><b>(2)</b>            throw new IllegalArgumentException("value cannot be null")        &#125;        return str.toUpperCase() + " = " + value.toString()    &#125;&#125;assert new Foo("test").bar(BigDecimal.TEN) == "TEST = 10"new Foo("test").bar(null) <i class="conum" data-value="3"></i><b>(3)</b></code></pre></div></div><div class="colist arabic"><table><tr><td><i class="conum" data-value="1"></i><b>1</b></td><td>Explicit null check in the constructor.</td></tr><tr><td><i class="conum" data-value="2"></i><b>2</b></td><td>Explicit null check in the method body.</td></tr><tr><td><i class="conum" data-value="3"></i><b>3</b></td><td>Calling <code>bar(null)</code> to get <code>IllegalArgumentException</code>.</td></tr></table></div><div class="paragraph"><p>When you run such a Groovy script, you will see <code>IllegalArgumentException</code> as expected.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovy test.groovyCaught: java.lang.IllegalArgumentException: value cannot be nulljava.lang.IllegalArgumentException: value cannot be nullat Foo.bar(test.groovy:17)at Foo$bar.call(Unknown Source)at test.run(test.groovy:26)</code></pre></div></div><div class="paragraph"><p>When using Groovy 3 (and higher) and <code>@NullCheck</code> annotation we can get replace all explicit checks with a single annotation.The AST transformation that runs at the compile-time produces the same bytecode as in the explicit Groovy 2.5 use case.</p></div><div class="listingblock"><div class="title">Listing 2. Groovy 3+ solution.</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.CompileStaticimport groovy.transform.NullCheck@CompileStatic@NullCheck <i class="conum" data-value="1"></i><b>(1)</b>class Foo &#123;    private final String str    Foo(final String str) &#123;        this.str = str    &#125;    String bar(final BigDecimal value) &#123;        return str.toUpperCase() + " = " + value.toString()    &#125;&#125;assert new Foo("test").bar(BigDecimal.TEN) == "TEST = 10"new Foo(null).bar(BigDecimal.ONE) <i class="conum" data-value="2"></i><b>(2)</b></code></pre></div></div><div class="colist arabic"><table><tr><td><i class="conum" data-value="1"></i><b>1</b></td><td><code>@NullCheck</code> at the class level affects all constructors and methods.</td></tr><tr><td><i class="conum" data-value="2"></i><b>2</b></td><td>This time we call a constructor with a <code>null</code> argument to get <code>IllegalArgumentException</code>.</td></tr></table></div><div class="paragraph"><p>Running the following example in the command line produces the expected result.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovy test.groovyCaught: java.lang.IllegalArgumentException: str cannot be nulljava.lang.IllegalArgumentException: str cannot be nullat Foo.&lt;init&gt;(test.groovy)at test.run(test.groovy:20)</code></pre></div></div></div></div><div class="sect1"><h2 id="combining-nullcheck-with-other-annotations">Combining <code>@NullCheck</code> with other annotations</h2><div class="sectionbody"><div class="paragraph"><p>Starting from Groovy 3.0.2, the <code>@NullCheck</code> annotation offers <code>includeGenerated</code> option.This option allows to use the annotation in combination with other AST transformations like <code>@Immutable</code> or <code>@TupleConstructor</code>.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">import groovy.transform.CompileStaticimport groovy.transform.Immutableimport groovy.transform.NullCheck@CompileStatic@NullCheck(includeGenerated = true)@Immutableclass Foo &#123;    final String str    String bar(final BigDecimal value) &#123;        return str.toUpperCase() + " = " + value.toString()    &#125;&#125;assert new Foo("test").bar(BigDecimal.TEN) == "TEST = 10"new Foo(null).bar(BigDecimal.ONE)</code></pre></div></div><div class="paragraph"><p>Output:</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovy test.groovyCaught: java.lang.IllegalArgumentException: args cannot be nulljava.lang.IllegalArgumentException: args cannot be nullat Foo.&lt;init&gt;(test.groovy)at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)at java.base/jdk.internal.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)at java.base/jdk.internal.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)at test.run(test.groovy:18)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)</code></pre></div></div><div class="paragraph"><p>If we skip setting <code>includeGenerated</code> to true, the <code>@NullCheck</code> annotation won&#8217;t be applied and we will see <code>NullPointerException</code>instead of the <code>IllegalArgumentException</code>.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">$ groovy test.groovyCaught: java.lang.NullPointerExceptionjava.lang.NullPointerExceptionat Foo.bar(test.groovy:12)at Foo$bar.call(Unknown Source)at test.run(test.groovy:18)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)</code></pre></div></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/yN6EbeevV2w" data-time="4:20" data-type="youtube" data-name="Groovy Tutorial | Avoiding NPE with @NullCheck annotation | #groovylang"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/yN6EbeevV2w/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/yN6EbeevV2w" data-type="youtube" data-name="Groovy Tutorial | Avoiding NPE with @NullCheck annotation | #groovylang">Groovy Tutorial | Avoiding NPE with @NullCheck annotation | #groovylang</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">@NullCheck is class, method, or constructor annotation which indicates that each parameter should be checked to ensure it isn't null. If placed at the class level, all explicit methods and constructors will be checked. This feature was added in Groovy 3.0<a class="gatr font-weight-bold" href="https://youtu.be/yN6EbeevV2w" data-type="youtube" data-name="Groovy Tutorial | Avoiding NPE with @NullCheck annotation | #groovylang">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Groovy 3 helps you write less, but more secure code.
Today I want to show you one of the features added in the latest release - &lt;code&gt;@NullCheck&lt;/code&gt; annotation.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="annotation" scheme="https://e.printstacktrace.blog/t/annotation/"/>
    
    <category term="groovy-3" scheme="https://e.printstacktrace.blog/t/groovy-3/"/>
    
  </entry>
  
  <entry>
    <title>Groovy 3 String GDK improvements - takeRight, takeBetween, and a few others</title>
    <link href="https://e.printstacktrace.blog/groovy-3-string-gdk-improvements-takeright-takebetween-and-a-few-others/"/>
    <id>https://e.printstacktrace.blog/groovy-3-string-gdk-improvements-takeright-takebetween-and-a-few-others/</id>
    <published>2020-02-12T11:15:33.000Z</published>
    <updated>2020-02-12T11:15:33.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>Groovy 3 was released a few days ago<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>, and it introduced <a href="https://groovy-lang.org/releasenotes/groovy-3.0.html#Groovy3.0releasenotes-GDKimprovements">a lot of important new features</a> to the language.Today I want to show you a few useful improvements in the GDK.We will take a closer look into methods like <code>takeRight</code>, <code>takeAfter</code>, <code>takeBetween</code>, and a few others that were added to the <code>java.lang.String</code> class.</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#string-takerightint"><code>String.takeRight(int)</code></a></li><li><a href="#string-takeafterstr"><code>String.takeAfter(str)</code></a></li><li><a href="#string-takebeforestr"><code>String.takeBefore(str)</code></a></li><li><a href="#string-takebetweenfromto"><code>String.takeBetween(from,to)</code></a></li><li><a href="#string-droprightint"><code>String.dropRight(int)</code></a></li><li><a href="#string-startswithignorecasestr-and-similar"><code>String.startsWithIgnoreCase(str)</code> and similar</a></li></ul></div><div class="admonitionblock note"><table><tr><td class="icon"><i class="fa icon-note" title="Note"></i></td><td class="content">In this blog post we use <code>String</code> as a base class, but most (if not all) of presented methods are working with <code>String</code>, <code>CharSequence</code> and <code>GString</code> classes.</td></tr></table></div><div class="sect1"><h2 id="string-takerightint"><code>String.takeRight(int)</code></h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start with the first one - <code>takeRight</code>.This method allows you to extract <code>n</code> last characters from a given String (or all characters if the number is larger then the string length.)</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Groovy"assert text.takeRight(0) == ""assert text.takeRight(1) == "y"assert text.takeRight(3) == "ovy"assert text.takeRight(20) == "Groovy"</code></pre></div></div></div></div><div class="sect1"><h2 id="string-takeafterstr"><code>String.takeAfter(str)</code></h2><div class="sectionbody"><div class="paragraph"><p>This method allows you to extract the text that exists after the first occurrence of the <code>str</code>.Keep in mind that it is case-sensitive, so it looks for the exact match.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Groovy"assert text.takeAfter("G") == "roovy"assert text.takeAfter("g") == ""assert text.takeAfter("Gro") == "ovy"assert text.takeAfter("Groovy") == ""</code></pre></div></div></div></div><div class="sect1"><h2 id="string-takebeforestr"><code>String.takeBefore(str)</code></h2><div class="sectionbody"><div class="paragraph"><p>It is similar to <code>takeAfter</code>, but here it extracts the text that exists before the first occurrence of <code>str</code>.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Groovy"assert text.takeBefore("G") == ""assert text.takeBefore("g") == ""assert text.takeBefore("ovy") == "Gro"assert text.takeBefore("o") == "Gr"assert text.takeBefore("Groovy") == ""</code></pre></div></div></div></div><div class="sect1"><h2 id="string-takebetweenfromto"><code>String.takeBetween(from,to)</code></h2><div class="sectionbody"><div class="paragraph"><p>This method allows you to extract the text that exists between the first occurrence of <code>from</code> and <code>to</code>.It can be used with a single parameter, then <code>to</code> becomes <code>from</code>.There is also a third optional parameter - <code>occurrence</code> which defines which occurrence should be taken into account (default: the first occurrence of <code>from</code> and <code>to</code>).</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Lorem ipsum dolor sit amet"assert text.takeBetween("i") == "psum dolor s"assert text.takeBetween("i", "r") == "psum dolo"assert text.takeBetween("i", "a") == "psum dolor sit "assert text.takeBetween("l","o") == ""assert text.takeBetween("m")  == " ipsu"assert text.takeBetween("m", 1) == ""assert text.takeBetween("i", "m", 0) == "psu"assert text.takeBetween("i", "m", 1) == "t a"assert text.takeBetween("i", "m", 2) == ""</code></pre></div></div></div></div><div class="sect1"><h2 id="string-droprightint"><code>String.dropRight(int)</code></h2><div class="sectionbody"><div class="paragraph"><p>This is an equivalent of <code>String.drop(int)</code> method, but in this case it produces a new String that drops <code>n</code> characters from the right side.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Hello, World!"assert text.dropRight(4) == "Hello, Wo"assert text.dropRight(0) == "Hello, World!"assert text.dropRight(-10) == "Hello, World!"assert text.dropRight(20) == ""</code></pre></div></div></div></div><div class="sect1"><h2 id="string-startswithignorecasestr-and-similar"><code>String.startsWithIgnoreCase(str)</code> and similar</h2><div class="sectionbody"><div class="paragraph"><p>Groovy also adds "ignore case" variants to three popular String methods:</p></div><div class="ulist"><ul><li><p><code>String.startsWithIgnoreCase(str)</code></p></li><li><p><code>String.endsWithIgnoreCase(str)</code></p></li><li><p><code>String.containsIgnoreCase(str)</code></p></li></ul></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">final String text = "Hello, World!"assert text.startsWithIgnoreCase("he") == trueassert text.startsWithIgnoreCase("HE") == trueassert text.startsWithIgnoreCase("HEE") == falseassert text.endsWithIgnoreCase("D!") == trueassert text.endsWithIgnoreCase("LD!") == trueassert text.endsWithIgnoreCase("LLD!") == falseassert text.containsIgnoreCase("HELL") == trueassert text.containsIgnoreCase("OLD") == falseassert text.containsIgnoreCase("OrLd") == true</code></pre></div></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/TjLDHIa-HVE" data-time="12:41" data-type="youtube" data-name="Groovy 3 Quick Review | #groovylang"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/TjLDHIa-HVE/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/TjLDHIa-HVE" data-type="youtube" data-name="Groovy 3 Quick Review | #groovylang">Groovy 3 Quick Review | #groovylang</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">In this video, I check what features are added in the recent Groovy 3 release.<a class="gatr font-weight-bold" href="https://youtu.be/TjLDHIa-HVE" data-type="youtube" data-name="Groovy 3 Quick Review | #groovylang">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div></div><div id="footnotes"><hr><div class="footnote" id="_footnotedef_1"><a href="#_footnoteref_1">1</a>. February 10th, 2020</div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;Groovy 3 was released a few days ago&lt;sup class=&quot;footnote&quot;&gt;[&lt;a id=&quot;_footnoteref_1&quot; class=&quot;footnote&quot; href=&quot;#_footnotedef_1&quot; title=&quot;View footnote.&quot;&gt;1&lt;/a&gt;]&lt;/sup&gt;, and it introduced &lt;a href=&quot;https://groovy-lang.org/releasenotes/groovy-3.0.html#Groovy3.0releasenotes-GDKimprovements&quot;&gt;a lot of important new features&lt;/a&gt; to the language.
Today I want to show you a few useful improvements in the GDK.
We will take a closer look into methods like &lt;code&gt;takeRight&lt;/code&gt;, &lt;code&gt;takeAfter&lt;/code&gt;, &lt;code&gt;takeBetween&lt;/code&gt;, and a few others that were added to the &lt;code&gt;java.lang.String&lt;/code&gt; class.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Groovy Cookbook" scheme="https://e.printstacktrace.blog/groovy-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="string" scheme="https://e.printstacktrace.blog/t/string/"/>
    
    <category term="groovy-3" scheme="https://e.printstacktrace.blog/t/groovy-3/"/>
    
  </entry>
  
  <entry>
    <title>Jenkins Scripted Pipeline vs. Declarative Pipeline - the 4 practical differences</title>
    <link href="https://e.printstacktrace.blog/jenkins-scripted-pipeline-vs-declarative-pipeline-the-4-practical-differences/"/>
    <id>https://e.printstacktrace.blog/jenkins-scripted-pipeline-vs-declarative-pipeline-the-4-practical-differences/</id>
    <published>2020-01-22T00:31:55.000Z</published>
    <updated>2020-05-08T19:17:26.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>If you read this blog post, there is a high chance you&#8217;re looking for information about practical differences between scripted and declarative pipeline, correct?You couldn&#8217;t find a better place then.I&#8217;m going to show you the four most practical differences between those two.Stay with me for a few minutes and enjoy the ride!</p></div><a id="more"></a><div id="toc" class="toc"><div id="toctitle" class="title">Table of Contents</div><ul class="sectlevel1"><li><a href="#the-introduction">The introduction</a><ul class="sectlevel2"><li><a href="#1-pipeline-code-validation-at-startup">1. Pipeline code validation at startup</a></li><li><a href="#2-restart-from-stage">2. Restart from stage</a></li><li><a href="#3-declarative-pipeline-options-block">3. Declarative pipeline <code>options</code> block</a></li><li><a href="#4-skipping-stages-with-when-block">4. Skipping stages with <code>when</code> block.</a></li></ul></li><li><a href="#conclusion">Conclusion</a></li></ul></div><div class="sect1"><h2 id="the-introduction">The introduction</h2><div class="sectionbody"><div class="paragraph"><p>But let&#8217;s start with the following question - why are there two pipeline types in the first place?The scripted pipeline was the first implementation of the pipeline as a code in Jenkins.Even though it uses the underlying pipeline subsystem, it was designed more or less as <strong>a general-purpose DSL built with Groovy</strong>.<sup class="footnote">[<a id="_footnoteref_1" class="footnote" href="#_footnotedef_1" title="View footnote.">1</a>]</sup>It means that it does not come with a fixed structure, and it is up to you how you will define your pipeline logic.The declarative pipeline, on the other hand, is more opinionated, and its structure is well-defined.<sup class="footnote">[<a id="_footnoteref_2" class="footnote" href="#_footnotedef_2" title="View footnote.">2</a>]</sup>It may look a bit limiting, but in practice, you can achieve the same things using the scripted or declarative pipeline. So which one to choose?</p></div><div class="paragraph"><p>If you ask me this question and expect an answer different from <em>"it depends,"</em> I would say use the declarative pipeline. And here&#8217;s why.</p></div><div class="sect2"><h3 id="1-pipeline-code-validation-at-startup">1. Pipeline code validation at startup</h3><div class="paragraph"><p>Let&#8217;s consider the following pipeline code.</p></div><div class="listingblock"><div class="title">Listing 1. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Build") &#123;            steps &#123;                echo "Some code compilation here..."            &#125;        &#125;        stage("Test") &#123;            steps &#123;                echo "Some tests execution here..."                echo 1            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>If we try to execute the following pipeline, the validation will quickly fail the build.The <strong>echo</strong> step can be triggered only with the <code>String</code> parameter, so we get the error like.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-declarative-pipeline-validation.png"><div class="img-lazyload-container" style="width:1169px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-declarative-pipeline-validation.png" height="777" width="1169" style="padding-top:66.46706586826348%"></div></a></div></div></div></div><div class="paragraph"><p>Notice that the pipeline didn&#8217;t execute any stage and just failed.This might save us a lot of time - imagine executing the <strong>Build</strong> stage for a couple of minutes, just to get the information that the <strong>echo</strong> step expects to get <code>java.lang.String</code> instead of <code>java.lang.Integer</code>.</p></div><div class="paragraph"><p>Now let&#8217;s take a look at the scripted pipeline equivalent of that example.</p></div><div class="listingblock"><div class="title">Listing 2. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">node &#123;    stage("Build") &#123;        echo "Some code compilation here..."    &#125;    stage("Test") &#123;        echo "Some tests execution here..."        echo 1    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>This pipeline executes the same stages, and the same steps.There is one significant difference however.Let&#8217;s execute it and see what result it produces.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-scripted-pipeline-echo-error.png"><div class="img-lazyload-container" style="width:1186px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-scripted-pipeline-echo-error.png" height="541" width="1186" style="padding-top:45.61551433389545%"></div></a></div></div></div></div><div class="paragraph"><p>It failed as expected.But this time the <strong>Build</strong> stage was executed, as well as the first step from the <strong>Test</strong> stage.As you can see, there was no validation of the pipeline code.The declarative pipeline in this case handles such use case much better.</p></div></div><div class="sect2"><h3 id="2-restart-from-stage">2. Restart from stage</h3><div class="paragraph"><p>Another cool feature that only declarative pipeline has is "Restart from stage" one.Let&#8217;s fix the pipeline from the previous example and see if we can restart the <strong>Test</strong> stage only.</p></div><div class="listingblock"><div class="title">Listing 3. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    stages &#123;        stage("Build") &#123;            steps &#123;                echo "Some code compilation here..."            &#125;        &#125;        stage("Test") &#123;            steps &#123;                echo "Some tests execution here..."            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>Let&#8217;s execute it.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-declarative-pipeline-restart-stage.png"><div class="img-lazyload-container" style="width:1181px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-declarative-pipeline-restart-stage.png" height="445" width="1181" style="padding-top:37.67993226079594%"></div></a></div></div></div></div><div class="paragraph"><p>Here you can see that the <strong>Test</strong> stage is selected.There is an option called "Restart Test" right above the steps list on the right side.Let&#8217;s click on it and see the result.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-declarative-pipeline-restarted.png"><div class="img-lazyload-container" style="width:1174px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-declarative-pipeline-restarted.png" height="442" width="1174" style="padding-top:37.64906303236798%"></div></a></div></div></div></div><div class="paragraph"><p>As you can see, Jenkins skipped the <strong>Build</strong> stage (it used the workspace from the previous build) and started the next pipeline execution from the <strong>Test</strong> stage.It might be useful when you execute some external tests and they fail because of some issues with the remote environment.You can fix the problem with the test environment and re-run the stage again, without the need to rebuild all artifacts.(The code of the app hasn&#8217;t change in such case.)</p></div><div class="paragraph"><p>Now, let&#8217;s look at the scripted pipeline example.</p></div><div class="listingblock"><div class="title">Listing 4. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">node &#123;    stage("Build") &#123;        echo "Some code compilation here..."    &#125;    stage("Test") &#123;        echo "Some tests execution here..."    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>Let&#8217;s execute it.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-scripted-pipeline-no-restart-option.png"><div class="img-lazyload-container" style="width:1162px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-scripted-pipeline-no-restart-option.png" height="447" width="1162" style="padding-top:38.468158347676415%"></div></a></div></div></div></div><div class="paragraph"><p>No restart option as you can see.The declarative pipeline vs. scripted pipeline - 2:0.</p></div><div class="paragraph"><p><div class="youtube-widget py-3"><div class="container"><div class="row youtube-row"><div class="col-12 col-lg-4 text-lg-left text-center mb-lg-0 mb-4"><a class="thumb gatr d-inline-block" href="https://youtu.be/Ei_Nk14vruE" data-time="13:45" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline"><img class="img-fluid shadow" data-lazy="https://i3.ytimg.com/vi/Ei_Nk14vruE/mqdefault.jpg"/></a></div><div class="col-12 col-lg-8"><h5 class="m-0 p-0 mb-3 text-lg-left text-center"><a class="gatr" href="https://youtu.be/Ei_Nk14vruE" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline">Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline</a></h5><ul class="youtube-meta"><li><i class="fa fa-youtube mr-1"></i><span>YouTube</span></li><li>5k views</li><li>2.5k subscribers</li></ul><p class="sans-serif small text-lg-left text-justify">Jenkins Pipeline as a code is a new standard for defining continuous integration and delivery pipelines in Jenkins. The scripted pipeline was the first implementation of the pipeline as a code standard. The later one, the declarative pipeline, slowly becomes a standard of how Jenkins users define their pipeline logic. In this tutorial video, I explain what are four major differences between Jenkins declarative pipeline and the scripted one. Enjoy and don't forget to like and comment on the video!<a class="gatr font-weight-bold" href="https://youtu.be/Ei_Nk14vruE" data-type="youtube" data-name="Jenkins Declarative Pipeline vs Scripted Pipeline - 4 key differences | #jenkinspipeline">Watch&nbsp;now&nbsp;&raquo;</a></p></div></div></div></div></p></div></div><div class="sect2"><h3 id="3-declarative-pipeline-options-block">3. Declarative pipeline <code>options</code> block</h3><div class="paragraph"><p>The third feature is supported by both pipeline types, however the declarative pipeline handles it a bit better in my opinion.Let&#8217;s say we have the following features to add to the previous pipeline.</p></div><div class="ulist"><ul><li><p>The timestamps in console log.</p></li><li><p>The ANSI color output.</p></li><li><p>The 1-minute timeout for the <strong>Build</strong> stage, and 2 minutes timeout for the <strong>Test</strong> stage.</p></li></ul></div><div class="paragraph"><p>Here is what does the declarative pipeline look like.</p></div><div class="listingblock"><div class="title">Listing 5. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    options &#123;        timestamps()        ansiColor("xterm")    &#125;    stages &#123;        stage("Build") &#123;            options &#123;                timeout(time: 1, unit: "MINUTES")            &#125;            steps &#123;                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'            &#125;        &#125;        stage("Test") &#123;            options &#123;                timeout(time: 2, unit: "MINUTES")            &#125;            steps &#123;                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>Let&#8217;s run it.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-declarative-pipeline-options.png"><div class="img-lazyload-container" style="width:1169px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-declarative-pipeline-options.png" height="483" width="1169" style="padding-top:41.31736526946108%"></div></a></div></div></div></div><div class="paragraph"><p>Here is the console log.</p></div><div class="listingblock"><div class="content"><pre class="highlightjs highlight"><code class="language-bash hljs" data-lang="bash">Started by user Szymon StepniakRunning in Durability level: MAX_SURVIVABILITY[Pipeline] Start of Pipeline[Pipeline] nodeRunning on Jenkins in /home/wololock/.jenkins/workspace/pipeline-sandbox[Pipeline] &#123;[Pipeline] timestamps[Pipeline] &#123;[Pipeline] ansiColor[Pipeline] &#123;[Pipeline] stage[Pipeline] &#123; (Build)[Pipeline] timeout15:10:04  Timeout set to expire in 1 min 0 sec[Pipeline] &#123;[Pipeline] sh15:10:04  + printf '\e[31mSome code compilation here...\e[0m\n'15:10:04  Some code compilation here...[Pipeline] &#125;[Pipeline] // timeout[Pipeline] &#125;[Pipeline] // stage[Pipeline] stage[Pipeline] &#123; (Test)[Pipeline] timeout15:10:04  Timeout set to expire in 2 min 0 sec[Pipeline] &#123;[Pipeline] sh15:10:05  + printf '\e[31mSome tests execution here...\e[0m\n'15:10:05  Some tests execution here...[Pipeline] &#125;[Pipeline] // timeout[Pipeline] &#125;[Pipeline] // stage[Pipeline] &#125;[Pipeline] // ansiColor[Pipeline] &#125;[Pipeline] // timestamps[Pipeline] &#125;[Pipeline] // node[Pipeline] End of PipelineFinished: SUCCESS</code></pre></div></div><div class="paragraph"><p>In the declarative pipeline, options are separated from the pipeline script logic.The scripted pipeline also supports <code>timestamps</code>, <code>ansiColor</code> and <code>timeout</code> options, but it requires a different code.Here is the same pipeline expressed using the scripted pipeline.</p></div><div class="listingblock"><div class="title">Listing 6. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">node &#123;    timestamps &#123;        ansiColor("xterm") &#123;            stage("Build") &#123;                timeout(time: 1, unit: "MINUTES") &#123;                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'                &#125;            &#125;            stage("Test") &#123;                timeout(time: 2, unit: "MINUTES") &#123;                    sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>I guess you see the problem.Here we used only <code>timestamps</code> and <code>ansiColor</code> Jenkins plugins.Imagine adding one or two more plugins.Declarative vs. scripted, 3:0.</p></div></div><div class="sect2"><h3 id="4-skipping-stages-with-when-block">4. Skipping stages with <code>when</code> block.</h3><div class="paragraph"><p>The last thing I would like to mention in this blog post is the <code>when</code> block that the declarative pipeline supports.Let&#8217;s improve the previous example and add a following condition:</p></div><div class="ulist"><ul><li><p>Execute <strong>Test</strong> stage only if <code>env.FOO</code> equals <code>bar</code>.</p></li></ul></div><div class="paragraph"><p>Here is what the declarative pipeline code looks like.</p></div><div class="listingblock"><div class="title">Listing 7. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">pipeline &#123;    agent any    options &#123;        timestamps()        ansiColor("xterm")    &#125;    stages &#123;        stage("Build") &#123;            options &#123;                timeout(time: 1, unit: "MINUTES")            &#125;            steps &#123;                sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'            &#125;        &#125;        stage("Test") &#123;            when &#123;                environment name: "FOO", value: "bar"            &#125;            options &#123;                timeout(time: 2, unit: "MINUTES")            &#125;            steps &#123;                sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>And let&#8217;s execute it.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-declarative-pipeline-when.png"><div class="img-lazyload-container" style="width:1179px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-declarative-pipeline-when.png" height="935" width="1179" style="padding-top:79.30449533502969%"></div></a></div></div></div></div><div class="paragraph"><p>The <strong>Test</strong> stage was skipped as expected.Now let&#8217;s try to do the same thing in the scripted pipeline example.</p></div><div class="listingblock"><div class="title">Listing 8. Jenkinsfile</div><div class="content"><pre class="highlightjs highlight"><code class="language-groovy hljs" data-lang="groovy">node &#123;    timestamps &#123;        ansiColor("xterm") &#123;            stage("Build") &#123;                timeout(time: 1, unit: "MINUTES") &#123;                    sh 'printf "\\e[31mSome code compilation here...\\e[0m\\n"'                &#125;            &#125;            if (env.FOO == "bar") &#123;                stage("Test") &#123;                    timeout(time: 2, unit: "MINUTES") &#123;                        sh 'printf "\\e[31mSome tests execution here...\\e[0m\\n"'                    &#125;                &#125;            &#125;        &#125;    &#125;&#125;</code></pre></div></div><div class="paragraph"><p>As you can see, we had to use if-condition to check if <code>env.FOO</code> equals <code>bar</code>, and only then add the <strong>Test</strong> stage.(It&#8217;s not a real skipping in this case unfortunately.)Let&#8217;s run it and see what is the result.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block p-2"><div class="content"><a class="image" href="/images/jenkins-scripted-pipeline-stage-skip.png"><div class="img-lazyload-container" style="width:1191px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/jenkins-scripted-pipeline-stage-skip.png" height="453" width="1191" style="padding-top:38.0352644836272%"></div></a></div></div></div></div><div class="paragraph"><p>This is not the same result.In the scripted pipeline use case, the <strong>Test</strong> stage is not even rendered.This might introduce some unnecessary confusion, the declarative pipeline handles it much better in my opinion.Declarative vs. scripted, 4:0.</p></div></div></div></div><div class="sect1"><h2 id="conclusion">Conclusion</h2><div class="sectionbody"><div class="paragraph"><p>Here are my top 4 differences between the declarative and scripted Jenkins pipeline.These are not the only differences, and I guess your list may look a little different.What is your choice?Do you prefer the declarative pipeline, or the scripted one?Please share your thoughts in the section down below.</p></div><div class="openblock text-center mt-4"><div class="content"><div class="paragraph"><p><a class="gatr" href="https://click.linksynergy.com/deeplink?id=uow2LCEuhvQ&amp;mid=39197&amp;murl=https%3A%2F%2Fwww.udemy.com%2Fcourse%2Flearn-devops-ci-cd-with-jenkins-using-pipelines-and-docker%2F" data-type="banner" data-name="jenkins-docker-udemy-01" target="_blank"><img class="d-none img-fluid d-lg-inline-block" data-lazy="/images/yml/v2/jenkins-docker-udemy.png"/></a></p></div></div></div></div></div><div id="footnotes"><hr><div class="footnote" id="_footnotedef_1"><a href="#_footnoteref_1">1</a>. <a href="https://jenkins.io/doc/book/pipeline/syntax/#scripted-pipeline" class="bare">https://jenkins.io/doc/book/pipeline/syntax/#scripted-pipeline</a></div><div class="footnote" id="_footnotedef_2"><a href="#_footnoteref_2">2</a>. <a href="https://jenkins.io/doc/book/pipeline/syntax/#declarative-pipeline" class="bare">https://jenkins.io/doc/book/pipeline/syntax/#declarative-pipeline</a></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;If you read this blog post, there is a high chance you&amp;#8217;re looking for information about practical differences between scripted and declarative pipeline, correct?
You couldn&amp;#8217;t find a better place then.
I&amp;#8217;m going to show you the four most practical differences between those two.
Stay with me for a few minutes and enjoy the ride!&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Jenkins Pipeline Cookbook" scheme="https://e.printstacktrace.blog/jenkins-pipeline-cookbook/"/>
    
    
    <category term="groovy" scheme="https://e.printstacktrace.blog/t/groovy/"/>
    
    <category term="jenkins" scheme="https://e.printstacktrace.blog/t/jenkins/"/>
    
    <category term="jenkins-pipeline" scheme="https://e.printstacktrace.blog/t/jenkins-pipeline/"/>
    
    <category term="cicd" scheme="https://e.printstacktrace.blog/t/cicd/"/>
    
    <category term="pipeline" scheme="https://e.printstacktrace.blog/t/pipeline/"/>
    
  </entry>
  
  <entry>
    <title>The blog development report - year 2019</title>
    <link href="https://e.printstacktrace.blog/the-blog-development-report-year-2019/"/>
    <id>https://e.printstacktrace.blog/the-blog-development-report-year-2019/</id>
    <published>2020-01-12T10:01:25.000Z</published>
    <updated>2020-01-12T10:01:25.000Z</updated>
    
    <content type="html"><![CDATA[<div class="paragraph"><p>2019 was a fantastic year for my blog.I didn&#8217;t manage to write articles every month, but it didn&#8217;t stop the growth.Today I would like to share with you some numbers.And the reason is simple - you&#8217;ve made those numbers possible, so I think you deserve to get some "behind the scenes" facts.</p></div><a id="more"></a><div class="sect1"><h2 id="113802-unique-visitors">113,802 unique visitors</h2><div class="sectionbody"><div class="paragraph"><p>Let&#8217;s start with the number of unique visitors.In 2019, the blog was visited by <strong>113,802</strong> unique visitors.The month with the lowest number of visitors was February - <strong>2,581</strong> unique visitors.The month with the highest number of visitors was December - <strong>17,443</strong> unique visitors.</p></div><div class="paragraph"><p>The blog has a very high bounce rate of <strong>91,61%</strong>, which means that only <strong>8,39%</strong> of visitors continue browsing the blog after opening the first page.This number does not surprise me - a vast majority of people find the blog using a search engine, and they visit it to find a specific solution to their problem.They find what they looked for, and they get back to their business.However, I would like to lower the bounce rate in 2020 to something like 80-85%.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-unique-visitors.png"><div class="img-lazyload-container" style="width:924px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-unique-visitors.png" height="708" width="924" style="padding-top:76.62337662337663%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="organic-search-produced-918-traffic">Organic Search produced 91,8% traffic</h2><div class="sectionbody"><div class="paragraph"><p>There is no surprise here.The Google search engine is responsible for <strong>91,8%</strong> traffic on the blog.The second top channel is direct traffic with <strong>4,5%</strong> efficiency.The third top channel is the Social Media traffic with just <strong>1,6%</strong> efficiency.</p></div><div class="paragraph"><p>There is one interesting observation.The most engaging traffic comes from referrals - the bounce rate, in this case, is <strong>78,04%</strong>.The least engaging channel is, of course, Organic Search with <strong>92,22%</strong> bounce rate.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-acquisition-summary.png"><div class="img-lazyload-container" style="width:931px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-acquisition-summary.png" height="775" width="931" style="padding-top:83.2438238453276%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="partitioning-lists-in-java-8-wins">Partitioning lists in Java 8 wins</h2><div class="sectionbody"><div class="paragraph"><p>Now let&#8217;s take a look at the most popular blog posts in 2019.Here is the TOP 3:</p></div><div class="ulist"><ul><li><p><a href="/divide-a-list-to-lists-of-n-size-in-Java-8/" title="Divide a list to lists of n size in Java 8">Divide a list to lists of n size in Java 8</a> - <strong>42,078</strong> pageviews</p></li><li><p><a href="/groovy-regular-expressions-the-definitive-guide/" title="Groovy Regular Expressions - The Definitive Guide">Groovy Regular Expressions - The Definitive Guide</a> - <strong>37,119</strong> pageviews</p></li><li><p><a href="/how-to-avoid-no-tests-were-found-when-using-junit5-with-groovy/" title="How to avoid &quot;No tests were found&quot; when using JUnit 5 with Groovy?">How to avoid &quot;No tests were found&quot; when using JUnit 5 with Groovy?</a> - <strong>11,556</strong> pageviews</p></li></ul></div><div class="paragraph"><p>There is one honorable mention - <a href="/jenkins-pipeline-environment-variables-the-definitive-guide/" title="Jenkins Pipeline Environment Variables - The Definitive Guide">Jenkins Pipeline Environment Variables - The Definitive Guide</a> which generated <strong>5,334</strong> pageviews, but this blog post was published on November 2nd, so it is one of the candidates for the 2020 TOP 3 ranking winner.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-pageviews-summary.png"><div class="img-lazyload-container" style="width:980px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-pageviews-summary.png" height="841" width="980" style="padding-top:85.81632653061224%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="the-top-search-keyword-groovy-regex">The top search keyword - <code>groovy regex</code></h2><div class="sectionbody"><div class="paragraph"><p>And here is a summary from the Search Console.At the beginning of 2019, the blog was not ranked high in the Google index.It started changing in the middle of the year, and I started seeing more and more traffic coming from Google.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-google-search-summary.png"><div class="img-lazyload-container" style="width:779px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-google-search-summary.png" height="477" width="779" style="padding-top:61.23234916559692%"></div></a></div></div></div></div><div class="paragraph"><p>And here are the TOP 10 search keywords for my blog:</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-top-search-keywords.png"><div class="img-lazyload-container" style="width:770px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-top-search-keywords.png" height="732" width="770" style="padding-top:95.06493506493506%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="top-country-united-states">Top country - United States</h2><div class="sectionbody"><div class="paragraph"><p>Almost <strong>20%</strong> of the traffic comes from the United States.The second country in that ranking is India with almost <strong>9%</strong> score.The third place belongs to Germany with <strong>6.5%</strong> score.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-geo-summary.png"><div class="img-lazyload-container" style="width:833px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-geo-summary.png" height="522" width="833" style="padding-top:62.665066026410564%"></div></a></div></div></div></div><div class="paragraph"><p>And here is the TOP 10:</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-top-10-countries.png"><div class="img-lazyload-container" style="width:867px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-top-10-countries.png" height="574" width="867" style="padding-top:66.20530565167243%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="internet-explorer-used-by-119-of-users">Internet Explorer used by 1,19% of users</h2><div class="sectionbody"><div class="paragraph"><p>And last but not least - the web browser ranking.Google Chrome wins with almost <strong>80%</strong> score.Firefox is used by <strong>12%</strong> of readers, which means that the first two browsers are used by <strong>~92%</strong> of readers.Here is the full ranking.</p></div><div class="openblock text-center"><div class="content"><div class="imageblock img-fluid shadow d-inline-block"><div class="content"><a class="image" href="/images/2019-webbrowser-summary.png"><div class="img-lazyload-container" style="width:853px;background:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAAABGdBTUEAAK/INwWK6QAAABl0RVh0U29mdHdhcmUAQWRvYmUgSW1hZ2VSZWFkeXHJZTwAAAC9UExURVlZWdPT07KysmRkZIWFhfT09JmZmWZmZm9vb39/fxkZGUxMTDMzM3p6epCQkKamppubm729venp6cjIyN7e3tbW1s/Pz8LCwnx8fLS0tFZWVoiIiI+Pj6GhoeTk5Glpabu7u93d3evr66CgoJSUlKqqqsnJyeDg4Hd3d8PDw+Xl5bi4uNHR0dvb26Ojo6urq+fn51hYWDg4OCgoKHBwcK2traenp0FBQe7u7vHx8U5OTre3t8zMzHV1df///7GrnpQAAAA/dFJOU///////////////////////////////////////////////////////////////////////////////////AI4mfBcAAAUGSURBVHja7NoJb6M4GMZxY0NCD64kve/pMZ2d3Z297+X7f6zFNmBAMUXa6URl/q9UJSWPUPzrizFWRUlNLgEBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYYIEFAVhggQUWWGBBABZYYIEFFlgQgAUWWGCBBRYEYIEFFlhggQUBWGCBBRZYYEEAFlhggQUWWBCABRZYYIEFFgRggQUWWGCBBQFYYIEFFlhgQQAWWGCBBRZYEIAFFlhggQUWBGCBBRZYn6cCIcRXgvX/h9qcIVBqDdbEM8RCxGCB9QqXYRwHYDHBgwXWl8eKZKiESHI3Ba1kWs3fKixcaJUl1YyeBm7Ocq+yLItUiVBGnXxenSHJolIKEcwHq6ikbOX1YGVzQCTN8LPmSLreghUl9sN4Uw7yajMrLC0TZ1ImzqY6FEop0+pIaEN5HaoOxVuwEqFyc4I46uSlzOLqgxlh6UaR9l3VYWl9Fdoxb1Q90KJtu41pwwFW/WHhTtW8i7TafLCqRsk6bsGw63L9qurXRmuIlbT9lDQnlXU+nBFW1Q2qnZbDprWa2tjR90LZFqx1/+Td/HpGWLlrLDvIwTcx6dQ1Vrntbig68cDms3JwbA5Y1azs1ger6sNV/bbIw1jU81MvNAGrl58RVn8ozW+btF08iGFoAlYvP3csfVur1gJBEIA1uBmue5dhZDOyO2epbmgCVi8/I6x0MMHH9pjsTfBhNzQBq5uPZoQlB0uH3DZG4EZqQ26fL3sZq5uf09Ih6qw3i/pm6BZO0qZX7rrUS68Xsbr5ZE4rePMk08pk9aUZugfqppvs6AM1Acvlo/StP+6EbW06z8hJqxbYp2BZPQUnFsLsKuhQdaHqn5ewbF7KXIn0jWO5MqOQ7RaNLPtbNMmmhimj0GUmYLl8Gs0Lq4wyPbTu1l2QKqHSouzs3OlDIslW5SQsnY/NXmFplyNvEuuLV/Tau9BzwiraDUSwXmysztYWWNtL1psXeumgIrDGaqXvBfUuvtqUYI3V2t1wk1e2msFluJJm6zDJXv/fIfjPP7DAAgsssCiwwAILLLDAosACCyywwAKLAgsssMACC6zt9fDz/v75tyOB+98PD2+ORgKffjw4OP1uJPDxl+Xy8v1I4MPF3t7VNyOB4/vF4uzdzrG+39f1kz/w66Guv/yBvw90KX/gZKkr8Qf+2dOV+gNHC12/7RxrabD2/a31bLAO/a11YbAO/K21MFhLf2s9Gqw9f2vdGqzFu11jnVusE2/gxmI9eQOnFuvYG7i0WH7uK4t15w2cWazrXWP9a7H8f/bQYvm/6IPF+sF/pVssf19Ii/WH/0K2WH/uGuvEWC39gSdj9Twy+Rqri5EZx1gt/IE7Y/XoD1wbq9vd3w1PlufnD2OBp+ebm/uxwPHF6emnscDR4vLy41jg7vHq6sNY4Pr27OyYdRaLUrDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssMCiwAILLLDAAosCCyywwAILLAossMACCyywKLDAAgsssL6u+k+AAQCR9eHtLKvLfwAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center;"><img data-original="/images/2019-webbrowser-summary.png" height="574" width="853" style="padding-top:67.29191090269636%"></div></a></div></div></div></div></div></div><div class="sect1"><h2 id="whats-coming-in-2020">What&#8217;s coming in 2020?</h2><div class="sectionbody"><div class="paragraph"><p>Don&#8217;t expect any revolution.I want to keep blogging about the things I&#8217;m genuinely interested in.I want to blog more often, but the quality of the content is more important to me than the quantity.There is one thing I can guarantee you - the blog remains ad-free.I love the User Experience I&#8217;ve built here, and I don&#8217;t want to change that.There will be some alternative (and optional) ways to support my work, but ads are not in this category.</p></div><div class="paragraph"><p>My goal for 2020 is to put more content on my small <a href="https://www.youtube.com/c/eprintstacktrace">YouTube channel</a>.I recorded a few new videos already, so you should see a video posted every week.<em>(Consider subscribing if you&#8217;re not doing it already. <span class="icon"><i class="fa fa-smile-o"></i></span>)</em></p></div><div class="paragraph"><p>And that&#8217;s it for today.I wish you a fruitful year 2020!Define your own rules and live according to them, and everything will go smoothly.Take care, and see you in the next blog post!</p></div><div class="paragraph"><p>PS: If there is something that surprised you most regarding the 2019 performance of this blog, please let me know in the comments section down below.</p></div></div></div>]]></content>
    
    
    <summary type="html">&lt;div class=&quot;paragraph&quot;&gt;
&lt;p&gt;2019 was a fantastic year for my blog.
I didn&amp;#8217;t manage to write articles every month, but it didn&amp;#8217;t stop the growth.
Today I would like to share with you some numbers.
And the reason is simple - you&amp;#8217;ve made those numbers possible, so I think you deserve to get some &quot;behind the scenes&quot; facts.&lt;/p&gt;
&lt;/div&gt;</summary>
    
    
    
    <category term="Blog Reports" scheme="https://e.printstacktrace.blog/blog-reports/"/>
    
    
    <category term="2019" scheme="https://e.printstacktrace.blog/t/2019/"/>
    
    <category term="summary" scheme="https://e.printstacktrace.blog/t/summary/"/>
    
    <category term="blog" scheme="https://e.printstacktrace.blog/t/blog/"/>
    
    <category term="google-analytics" scheme="https://e.printstacktrace.blog/t/google-analytics/"/>
    
    <category term="report" scheme="https://e.printstacktrace.blog/t/report/"/>
    
  </entry>
  
</feed>
