{"id":50,"date":"2025-09-25T02:12:14","date_gmt":"2025-09-25T02:12:14","guid":{"rendered":"https:\/\/www.tomisawesome.com\/thoughts\/?p=50"},"modified":"2025-09-25T02:24:38","modified_gmt":"2025-09-25T02:24:38","slug":"nested-string-interpolation-in-groovy","status":"publish","type":"post","link":"https:\/\/www.tomisawesome.com\/thoughts\/2025\/09\/25\/nested-string-interpolation-in-groovy\/","title":{"rendered":"Nested string interpolation in Groovy"},"content":{"rendered":"\n<p>So we&#8217;re going to go down a little rabbit hole regarding groovy string interpolation which I found odd enough to want to write up.<\/p>\n\n\n\n<p>First, some basics. Groovy uses string interpolation syntax similar to bash, which looks like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>String foo = 'I am a fancy string'\nprintln(\"My string: ${foo}\")<\/code><\/pre>\n\n\n\n<p>Pretty straightforward, with a few interesting bits. First, you may have noted that we used both single and double quotes. Single quotes give you a string literal (think Python r-strings) whereas double quotes will give you the interpolated output (think Python f-strings). <\/p>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>Quick aside, technically the interpolated output you get is a GString type, which:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Can lead to very humorous discussions with team mates (and MUCH MORE HILARIOUS conversations to those within earshot and no context)<\/li>\n\n\n\n<li>Can lead to some odd behavior when checking whether an interpolated string is in a collection<\/li>\n<\/ul>\n\n\n\n<p>Just assume GStrings are going to make your life harder than it otherwise would be. We won&#8217;t go into it because that&#8217;s now what we&#8217;re here to talk about today.<\/p>\n<\/blockquote>\n\n\n\n<h2 class=\"wp-block-heading\">The problem<\/h2>\n\n\n\n<p>So now let&#8217;s say we&#8217;ve got some environment variable that&#8217;s name is, well, variable. Perhaps this variable is dependent on system, architecture, whatever. The point is we want to be able to access the correct one.<\/p>\n\n\n\n<p>So, to access an environment variable (using a Jenkins environment as an example) is simply:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>env.ENVAR_NAME<\/code><\/pre>\n\n\n\n<p>You can put it in a string using interpolation like so:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>String myEnvar = \"${env.ENVAR_NAME}\"<\/code><\/pre>\n\n\n\n<p>Now let&#8217;s assume we want to access a home directory which is unique on each platform. We can do it a couple of ways:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>String myHome = ''\nif (platform == 'WINDOWS') {\n    myHome = \"${env.WINDOWS_HOME}\"\n} else if (platform == 'LINUX') {\n    myHome = \"${env.LINUX_HOME}\"\n} ...<\/code><\/pre>\n\n\n\n<p>We could also us a switch statement, index into a map, whatever. All of these work and are fine, but it&#8217;s a lot of boilerplate code. If only there was a way we could use nested interpolation.<\/p>\n\n\n\n<p>Which we can!<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">The solution<\/h2>\n\n\n\n<p>While this may look weird, it does in fact work:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Map foo = &#91;foo:1, bar:2]\nbaz = 'bar'\nprintln(\"Bar value: ${foo.\"${baz}\"}\")<\/code><\/pre>\n\n\n\n<blockquote class=\"wp-block-quote is-layout-flow wp-block-quote-is-layout-flow\">\n<p>I&#8217;m using baz here to refer to &#8216;bar&#8217; to ensure it&#8217;s not just using the characters of the variable name. I know it&#8217;s a bit confusing and I&#8217;m sorry.<\/p>\n<\/blockquote>\n\n\n\n<p>In fact, you can test it yourself (I like <a href=\"https:\/\/www.tutorialspoint.com\/compilers\/online-groovy-compiler.htm\" title=\"\">tutorialspoint&#8217;s groovy web runtime<\/a>):<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"619\" height=\"255\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-1.png\" alt=\"\" class=\"wp-image-52\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-1.png 619w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-1-300x124.png 300w\" sizes=\"auto, (max-width: 619px) 100vw, 619px\" \/><\/figure>\n\n\n\n<p>Now, at first glance this code looks wrong, but as you can see it does run as expected. This ends up behaving similar to nested f-strings, even if the syntax is a bit wonkier. Going back to our home example, we&#8217;d end up with code like this:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>String myHome = \"${env.\"${platform}\"_HOME}\"<\/code><\/pre>\n\n\n\n<p>I&#8217;ll admit it looks a bit odd, but ultimately it&#8217;s pretty easy to read and gives you the expected output. <\/p>\n\n\n\n<p>So nothing too crazy, but perhaps slightly counterintuitive to use the expansion outside of double quotes. The place it gets weird is if you try to treat it like a string itself.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Other (failed) approaches<\/h2>\n\n\n\n<p>At first pass, you might say &#8220;oh all it&#8217;s doing is concatenating by putting strings next to each other. You can replicate it using string concatenation.&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"648\" height=\"175\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-2.png\" alt=\"\" class=\"wp-image-53\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-2.png 648w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-2-300x81.png 300w\" sizes=\"auto, (max-width: 648px) 100vw, 648px\" \/><\/figure>\n\n\n\n<p>You would be wrong.<\/p>\n\n\n\n<p>&#8220;Okay but maybe we just need to remove the expansion indicators around baz?&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"648\" height=\"175\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-3.png\" alt=\"\" class=\"wp-image-54\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-3.png 648w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-3-300x81.png 300w\" sizes=\"auto, (max-width: 648px) 100vw, 648px\" \/><\/figure>\n\n\n\n<p>Nope.<\/p>\n\n\n\n<p>&#8220;We probably don&#8217;t even need to close the quotes first and can use it like Python&#8217;s nested f-string.&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"109\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-1024x109.png\" alt=\"\" class=\"wp-image-55\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-1024x109.png 1024w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-300x32.png 300w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-768x82.png 768w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-1536x164.png 1536w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-4-2048x219.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><\/figure>\n\n\n\n<p>Also no.<\/p>\n\n\n\n<p>&#8220;Maybe&#8230;&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"676\" height=\"184\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-5.png\" alt=\"\" class=\"wp-image-56\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-5.png 676w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-5-300x82.png 300w\" sizes=\"auto, (max-width: 676px) 100vw, 676px\" \/><\/figure>\n\n\n\n<p>No again.<\/p>\n\n\n\n<p>&#8220;Well perhaps StringBuilder&#8230;&#8221;<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"576\" height=\"243\" src=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-7.png\" alt=\"\" class=\"wp-image-58\" srcset=\"https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-7.png 576w, https:\/\/www.tomisawesome.com\/thoughts\/wp-content\/uploads\/2025\/09\/image-7-300x127.png 300w\" sizes=\"auto, (max-width: 576px) 100vw, 576px\" \/><\/figure>\n\n\n\n<p>Negatory.<\/p>\n\n\n\n<p>So, as shown there&#8217;s really only one way to make it work and it&#8217;s pretty picky about how it does so it&#8217;s good to know the exact incantation.<\/p>\n\n\n\n<p>As for why this specific approach works, I can&#8217;t find the exact documentation that describes this behavior, though there are a few examples here and there on places like stack overflow. All in all it&#8217;s an interesting rabbit hole to have followed and is nice to have in the back pocket.<\/p>\n\n\n\n<p><\/p>\n","protected":false},"excerpt":{"rendered":"<p>So we&#8217;re going to go down a little rabbit hole regarding groovy string interpolation which I found odd enough to want to write up. First, some basics. Groovy uses string interpolation syntax similar to bash, which looks like this: Pretty straightforward, with a few interesting bits. First, you may have noted that we used both [&hellip;]<\/p>\n","protected":false},"author":2,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-50","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"aioseo_notices":[],"_links":{"self":[{"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/posts\/50","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/comments?post=50"}],"version-history":[{"count":5,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/posts\/50\/revisions"}],"predecessor-version":[{"id":63,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/posts\/50\/revisions\/63"}],"wp:attachment":[{"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/media?parent=50"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/categories?post=50"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.tomisawesome.com\/thoughts\/wp-json\/wp\/v2\/tags?post=50"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}