<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Julien's DevRel corner]]></title><description><![CDATA[DevRel, Thoughts, Climate and Code]]></description><link>https://lengrand.fr/</link><image><url>https://lengrand.fr/favicon.png</url><title>Julien&apos;s DevRel corner</title><link>https://lengrand.fr/</link></image><generator>Ghost 6.39</generator><lastBuildDate>Tue, 09 Jun 2026 06:35:43 GMT</lastBuildDate><atom:link href="https://lengrand.fr/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[My blog was hacked and Claude and I just fixed it]]></title><description><![CDATA[My self-hosted Ghost blog got silently hacked via a malicious script injected directly into the database, targeting only Windows users. I traced the source of the issue and fixed it quickly with the help of Claude Code.]]></description><link>https://lengrand.fr/my-blog-was-hacked-and-claude-and-i-just-fixed-it/</link><guid isPermaLink="false">6a0b7f71da13e397be65e81a</guid><category><![CDATA[security]]></category><category><![CDATA[selfhosted]]></category><category><![CDATA[web-dev]]></category><category><![CDATA[ghost]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Mon, 18 May 2026 22:02:58 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203515-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203515-1.png" alt="My blog was hacked and Claude and I just fixed it"><p></p><h2 id="how-it-started">How it started</h2><p>It all started from a message on Linkedin yesterday &quot;Hey, you&apos;ve been hacked I&apos;m getting fake captchas on your blog&quot;. </p><p>Obviously I checked and saw nothing wrong. But I also remembered about someone telling me something very similar a couple weeks back on Mastodon. So I checked from all of the devices I could think of in the house and couldn&apos;t reproduce either, even with a VPN from the location of the user who reported the issue. </p><p>At this point, I dismissed it as something coming from the user side. Maybe they had an infected browser extension running. Just as a safe check, I decided to send a call for help <a href="https://bsky.app/profile/lengrand.fr/post/3mm2z3sxsq42w?ref=lengrand.fr">on social media</a> to ask people to check the blog and let me know if they saw anything weird. No new reports, either....</p><h2 id="starting-the-investigation">Starting the investigation</h2><p>After a couple hours I still had an uneasy feeling, so I started reading online and found <a href="https://www.reddit.com/r/brave/comments/1rco58g/does_anyone_know_why_this_annoying_captcha_keeps/?ref=lengrand.fr">the exact issue</a> the users were reporting. I fired up Claude Code on my server and started investigating to see if I could find something.</p><p>For reference I run a self-hosted Ghost instance on Digital Ocean (currently migrating to Hetzner, but that&apos;s another story) on top of an Ubuntu with Nginx as reverse proxy. </p><p>I spent the next hour or so poking at my server with Claude and we arrived at the same conclusion :</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2026/05/image.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="2000" height="473" srcset="https://lengrand.fr/content/images/size/w600/2026/05/image.png 600w, https://lengrand.fr/content/images/size/w1000/2026/05/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/05/image.png 1600w, https://lengrand.fr/content/images/size/w2400/2026/05/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The error is on the other side.</span></figcaption></figure><p></p><h2 id="i-can-reproduce-%F0%9F%98%96">I can reproduce &#x1F616;! </h2><p></p><p>But that&apos;s when a third report from an old friend finally came in that it all finally clicked in my head. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/05/image-1.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="1082" height="630" srcset="https://lengrand.fr/content/images/size/w600/2026/05/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2026/05/image-1.png 1000w, https://lengrand.fr/content/images/2026/05/image-1.png 1082w" sizes="(min-width: 720px) 720px"></figure><p>This all seems to be very Windows related! So I fired up by gaming rig and voila : here&apos;s the popup</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203515-2.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="1720" height="525" srcset="https://lengrand.fr/content/images/size/w600/2026/05/Screenshot-2026-05-18-203515-2.png 600w, https://lengrand.fr/content/images/size/w1000/2026/05/Screenshot-2026-05-18-203515-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/05/Screenshot-2026-05-18-203515-2.png 1600w, https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203515-2.png 1720w" sizes="(min-width: 720px) 720px"></figure><p>With the ugly scary <strong>don&apos;t do it</strong> follow up message: </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/05/image-2.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="850" height="942" srcset="https://lengrand.fr/content/images/size/w600/2026/05/image-2.png 600w, https://lengrand.fr/content/images/2026/05/image-2.png 850w" sizes="(min-width: 720px) 720px"></figure><p></p><p>The good news : I could reproduce. The bad news ? Well my server is actually infected (remember this next time you&apos;re tempted to trust AI to make conclusions for you :)).</p><h2 id="time-to-investigate">Time to investigate</h2><p>Now that I could reproduce myself, I had access to the source of the generated popup</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203534.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="1369" height="433" srcset="https://lengrand.fr/content/images/size/w600/2026/05/Screenshot-2026-05-18-203534.png 600w, https://lengrand.fr/content/images/size/w1000/2026/05/Screenshot-2026-05-18-203534.png 1000w, https://lengrand.fr/content/images/2026/05/Screenshot-2026-05-18-203534.png 1369w" sizes="(min-width: 720px) 720px"></figure><p>So it was a matter of finding where this was embedded. The popup seems to be created in a <em><code>rcoverlay</code></em> id. Let&apos;s dive deeper into what&apos;s in there. </p><p>I made a curl request with a Windows user-agent to see if anything suspicious could be found </p><pre><code class="language-bash">  curl -s -A &quot;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/124.0.0.0 Safari/537.36&quot; \
    https://lengrand.fr/ways-in-which-genai-has-changed-my-coding-so-far/ | grep -i &quot;rc_overlay\|_rc_\|robot\|script&quot;</code></pre><p>There we go, it looks like somehow a script is injected into the footer of all of my posts and activated only on Windows machines. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/05/image-3.png" class="kg-image" alt="My blog was hacked and Claude and I just fixed it" loading="lazy" width="2000" height="319" srcset="https://lengrand.fr/content/images/size/w600/2026/05/image-3.png 600w, https://lengrand.fr/content/images/size/w1000/2026/05/image-3.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/05/image-3.png 1600w, https://lengrand.fr/content/images/size/w2400/2026/05/image-3.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>BTW, if we forget the fact that Claude is often wrong about its conclusions, it&apos;s definitely been a great companion and helped me speed up my research by a bunch!</p><p>Now that we know what to look for, it gets easier. Nothing in the ghost install files, nothing on the reverse proxy side, the next logical target is the database. And as expected : </p><pre><code class="language-bash">$ mysql ghost_prod -e &quot;SELECT COUNT(*) FROM posts WHERE codeinjection_foot LIKE &apos;%eralfduolccitats%&apos;;&quot;
  +----------+
  | COUNT(*) |
  +----------+
  |      148 |
  +----------+

  mysql ghost_prod -e &quot;SELECT id, slug, codeinjection_foot FROM posts WHERE codeinjection_foot LIKE &apos;%eralfduolccitats%&apos; LIMIT 1&quot;;</code></pre><p>Bingo, the malicious script tag has been added to all of my articles inside the database directly, in the <code>codeinjection_foot</code> column.</p><p>That&apos;s sneaky, because the code injection settings of my ghost admin interface is where I looked for clues at the very beginning of my research. But since those changes were directly written in the database so my instance looked clean at first glance.</p><h2 id="and-now-the-fix">And now, the fix</h2><p>Now that we know where the problem is, the fix comes rather easy. I don&apos;t actually have any footer injection on my instance, so I&apos;ve simply set the columns to <code>NULL</code>for all of the articles in my database.</p><pre><code class="language-bash">$ mysql ghost_prod -e &quot;UPDATE posts SET codeinjection_foot = NULL WHERE codeinjection_foot LIKE &apos;%eralfduolccitats%&apos;;&quot;
$ mysql ghost_prod -e &quot;SELECT COUNT(*) FROM posts WHERE codeinjection_foot LIKE &apos;%eralfduolccitats%&apos;;&quot;
  +----------+
  | COUNT(*) |
  +----------+
  |        0 |
  +----------+</code></pre><p>(Bit of a scary command to run, I can&apos;t think the snapshot capabilities of Digital Ocean enough). If you run this yourself, make sure to make backups first!</p><h2 id="cleanup">Cleanup</h2><p>The last steps were to make sure the issue wasn&apos;t happening any more, and plugging any hole I could think of. So I </p><ul><li>updated my server and the ghost install</li><li>changed my password</li><li>checked for unknown users in my ghost instance</li><li>rotated all my keys</li></ul><p>The issue seems to have happened somewhere 2 days ago, date at which all of my articles were updated. They do have an update time in the database, which makes me believe that the changes were done via ghost itself, rather than a direct access to the server </p><pre><code>SELECT slug, updated_at FROM posts WHERE updated_at &gt;= &apos;2026-05-16&apos; ORDER BY updated_at DESC LIMIT 20;

  +-------------------------------------------------+---------------------+
  | slug                                            | updated_at          |
  +-------------------------------------------------+---------------------+
  | computer-vision-companies                       | 2026-05-16 21:06:13 |
  | about                                           | 2026-05-16 21:06:02 |
  | terms                                           | 2026-05-16 21:05:50 |
  | first-message                                   | 2026-05-16 21:05:35 |
  | synchronize-config-files-between-computers      | 2026-05-16 21:05:24 |
  | activate-numpad-on-startup                      | 2026-05-16 21:05:11 |
  | converting-a-flv-file-to-avi                    | 2026-05-16 21:05:01 |
  | my-pics-on-deviantart                           | 2026-05-16 21:04:49 |
  | why-i-dont-use-adblocker-and-co                 | 2026-05-16 21:04:38 |
  | keysonic-keyboard-and-linux-problems            | 2026-05-16 21:04:26 |
  | opencv-rect-expects-four-integers               | 2026-05-16 21:04:15 |
  | android-arm-optimized-computer-vision-library   | 2026-05-16 21:04:03 |
  | pombo-how-to-get-your-stolen-computer-back      | 2026-05-16 21:03:52 |
  | classification-hu-and-zernike-moments-matlab    | 2026-05-16 21:03:40 |
  | get-the-power-of-matlab-in-command-line         | 2026-05-16 21:03:30 |
  | errors-on-linux-boot-with-a-radeon-hd           | 2026-05-16 21:03:17 |
  | cool-computer-vision-project-shredded-documents | 2026-05-16 21:03:07 |
  | compiling-opencv-for-linux-debian               | 2026-05-16 21:02:54 |
  | simple-region-growing-implementation-in-python  | 2026-05-16 21:02:43 |
  | pythonunittest-assertraises-raises-error        | 2026-05-16 21:02:32 |
  +-------------------------------------------------+---------------------+</code></pre><p>I can&apos;t know for sure where the problem is coming from, but my suspicions come from the (unofficial) Ghost MCP server I installed a few months back (and never did anything with). I removed it and deleted the associated keys. </p><h2 id="some-learnings">Some learnings</h2><p>I&apos;ll make it short but :</p><ul><li>Don&apos;t be too quick thinking the issue is on the other side. The problem was coming from me all along</li><li>You surely can&apos;t trust AI&apos;s conclusions, but Claude has been insanely helpful along the way. I&apos;ve saved hours with Claude helping me search for the next obvious issues, and proposing fixes. </li><li>This sounds obvious, but in retrospect I should have been more careful installing third party randomware on my production server. I&apos;m lucky the issue was actually quite contained</li><li>A story for tomorrow, but it&apos;s probably a good trigger to migrate anyways and go <a href="https://monokai.com/articles/how-i-moved-my-digital-stack-to-europe/?ref=lengrand.fr">European</a>.</li></ul><p>A big thank you for all of you who helped me, and especially <a href="https://www.linkedin.com/in/amlewandowska/?ref=lengrand.fr">Anna</a> and <a href="https://www.linkedin.com/in/ruurd/?ref=lengrand.fr">Ruurd</a> &#x2764;&#xFE0F;. </p>]]></content:encoded></item><item><title><![CDATA[Ways in which GenAI has changed the way I write code so far]]></title><description><![CDATA[AI has fundamentally transformed my developer workflow, from stack choices and IDE preferences to how git is used. I using Claude and orchestration tools like Maestro to guide implementation while tests serve as guardrails.]]></description><link>https://lengrand.fr/ways-in-which-genai-has-changed-my-coding-so-far/</link><guid isPermaLink="false">69f329677acd40276ed5b1ce</guid><category><![CDATA[ai]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Thu, 30 Apr 2026 11:19:11 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2026/04/Screenshot-2026-04-30-at-13.13.19.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2026/04/Screenshot-2026-04-30-at-13.13.19.png" alt="Ways in which GenAI has changed the way I write code so far"><p><em>TL;DR : Projects where I use AI had a massive influence on my workflow, and it changed my default stack, IDE, how I use git and more. </em></p><p>A few months ago, I wrote about <a href="https://lengrand.fr/ways-in-which-genai-has-changed-my-tech-life-so-far/">the impact that GenAI had in my Tech life so far</a>. This article is about how it impacted the way I write code.</p><p>First, a disclaimer : This articles describes about how I write code <em>at home</em>, and does not necessarily reflect my work environment.  </p><h2 id="what-used-to-be-ideas-can-now-be-pet-projects">What used to be ideas can now be pet projects</h2><p>As pretty much all developers, I constantly have ideas of things to build. And just like many of you, most of those ideas are fun / good but they&apos;d also simply be too long to implement. The flow of ideas is faster than the time I have to build them. </p><p>Because of how good models have become though this has massively changed : Those ideas actually <em>can</em> be implemented now, usually in a matter of hours. So my coding has turned into 2 types of work : </p><ul><li>Manual coding, the old way, when I want control / have fun / learn stuff</li><li>Semi &quot;vibe-coding&quot;, where I let Claude (<a href="https://lengrand.fr/my-experience-using-junie-for-the-past-few-months/">after trying Junie</a>) handle most of the implementation and I guide him instead. I will challenge it on architecture decisions and use a large set of tests as guardrail.</li></ul><h2 id="the-agent-orchestrator-as-a-base-layer">The Agent Orchestrator as a base layer</h2><p>For the past year or so, I&apos;ve been using <a href="https://runmaestro.ai/?ref=lengrand.fr">Maestro</a> as my main AI orchestration tool. I like Maestro a lot, because it gets a few things right that many other similar tools don&apos;t for me: </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2026/04/image.png" class="kg-image" alt="Ways in which GenAI has changed the way I write code so far" loading="lazy" width="2000" height="1096" srcset="https://lengrand.fr/content/images/size/w600/2026/04/image.png 600w, https://lengrand.fr/content/images/size/w1000/2026/04/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/04/image.png 1600w, https://lengrand.fr/content/images/size/w2400/2026/04/image.png 2400w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The main Maestro UI</span></figcaption></figure><ul><li>It organizes everything in projects, which avoids having to constantly search which tab to open</li><li>It works using Auto-run docs, which is pretty much a light version of spec-driven development, but without all the framework hassle.<ul><li>You tell it what you want to do, and it will come back with a proposal of implementation in steps, as a set of markdown files with TODO lists.</li></ul></li><li>You can either tell it to change the files, or modify them yourself.</li><li>All items are done in a fresh new context, to avoid context drift and pollution. No more restarting Claude Code sessions all the time.</li><li>But what I love most is that you can set a set of guardrails as Markdown files, and let it iterate over its plan as long as the guardrails aren&apos;t satisfied. It&apos;s pretty much a simplified <a href="https://awesomeclaude.ai/ralph-wiggum?ref=lengrand.fr">Ralph Wiggum loop</a>, without any fluff. </li><li>Because my projects tend to be very similar, I put those implementation files in a separate repository and share those guardrails across projects. </li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2026/04/image-1.png" class="kg-image" alt="Ways in which GenAI has changed the way I write code so far" loading="lazy" width="1376" height="972" srcset="https://lengrand.fr/content/images/size/w600/2026/04/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2026/04/image-1.png 1000w, https://lengrand.fr/content/images/2026/04/image-1.png 1376w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Setting up Maestro to do its thing, with my Kotlin light guardrails in place</span></figcaption></figure><p></p><h2 id="second-ai-as-a-reviewer">Second AI as a reviewer</h2><p>Because I tend to let AI generate the huge majority of my code, I use a second AI as reviewer of the created Pull Requests. I have been using Claude Code locally a lot, but I love the team behind <a href="https://kilo.ai/?ref=lengrand.fr">Kilo.AI</a> and they&apos;ve been my tool of choice for pull requests reviewing. I like that I can select what sort of review I want, and how extensive. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2026/04/image-2.png" class="kg-image" alt="Ways in which GenAI has changed the way I write code so far" loading="lazy" width="2000" height="1057" srcset="https://lengrand.fr/content/images/size/w600/2026/04/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2026/04/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/04/image-2.png 1600w, https://lengrand.fr/content/images/2026/04/image-2.png 2158w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The KiloAI Code Reviewer</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2026/04/image-4.png" class="kg-image" alt="Ways in which GenAI has changed the way I write code so far" loading="lazy" width="1788" height="956" srcset="https://lengrand.fr/content/images/size/w600/2026/04/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2026/04/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/04/image-4.png 1600w, https://lengrand.fr/content/images/2026/04/image-4.png 1788w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Kilo helping me review Claude Code generated code</span></figcaption></figure><h2 id="from-git-branches-to-git-worktrees">From Git branches, to Git worktrees</h2><p>If you start using an agent orchestrator, you very quickly start telling it to work on several things at the same time. Maybe some UI cosmetics, an implementation of preview deployments and a new feature at the same time.</p><p>And git branches simply won&apos;t cut it any more, because you&apos;re pretty much constantly concurrently working on several branches at once. That&apos;s why I switched to <a href="https://git-scm.com/docs/git-worktree?ref=lengrand.fr">git worktrees</a> instead. </p><p>Said simply, git worktrees are branches but in local folders. This allows you to make concurrent changes as well as test on multiple branches at once. I like to keep things under control, so I usually don&apos;t work on more than 3/4 things at once,  but that&apos;s still a whole lot of concurrent changes.</p><h2 id="from-kotlin-to-typescript">From Kotlin to Typescript</h2><p>If you&apos;ve read me before, you know I&apos;m a huge <a href="https://lengrand.fr/celebrating-kotlin-2-0/">Kotlin</a> fan. Kotlin is a fun, powerful, consistent and pleasant language to work with. I&apos;m also usually a minimalist, so my UIs are typically built in barebones web components and I like to avoid frameworks. </p><p>If you let AI handle most of your coding though, those taste become irrelevant and one thing is more important than everything else : How good can my agent work with my stack. </p><p>For this simple reason, all of my vibe coded projects are written in Typescript on the backend and usually React on the frontend. Not because I like those (I don&apos;t), but simply because of the absolute insane amount of training data available on this stack, making it easier for LLMs to write in. </p><p><strong>Your stack doesn&apos;t matter. How good your LLM is with it does....</strong></p><h3 id="no-more-heavy-ides">No more heavy IDEs</h3><p>Just like with Kotlin, I&apos;ve been a massive JetBrains fan for a very long time. The company itself, but also their IDEs. I spent years paying for subscriptions from my pocket simply to support the company because of how good their products are. </p><p>But in an environment where most of your code is written by someone else, where you constantly have 3/4 editors open in different worktrees and where you open and close editors 20 times a day, heavy IDEs just don&apos;t cut it any more. </p><p>Last week I realized that I hadn&apos;t opened IntelliJ in over a month... I&apos;m genuinely slightly worried about JetBrains and I hope they&apos;re not seeing that effect in their revenue...</p><p>The support for worktrees in IntelliJ is also very lacking. Thankfully, there&apos;s a great <a href="https://plugins.jetbrains.com/plugin/23813-git-worktree?ref=lengrand.fr">plugin</a> for this on the marketplace. Thanks Lukasz!</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2026/04/image-5.png" class="kg-image" alt="Ways in which GenAI has changed the way I write code so far" loading="lazy" width="2000" height="994" srcset="https://lengrand.fr/content/images/size/w600/2026/04/image-5.png 600w, https://lengrand.fr/content/images/size/w1000/2026/04/image-5.png 1000w, https://lengrand.fr/content/images/size/w1600/2026/04/image-5.png 1600w, https://lengrand.fr/content/images/size/w2400/2026/04/image-5.png 2400w" sizes="(min-width: 720px) 720px"></figure><h2 id="from-tool-of-choice-to-maximizing-freedom">From tool of choice, to maximizing freedom</h2><p><a href="https://www.reddit.com/r/ClaudeAI/comments/1srzhd7/psa_claude_pro_no_longer_lists_claude_code_as_an/?ref=lengrand.fr">As the era of subsidized AI</a> <a href="https://github.blog/news-insights/company-news/github-copilot-is-moving-to-usage-based-billing/?ref=lengrand.fr">comes to an end</a> and <a href="https://www.reddit.com/r/ClaudeCode/comments/1s2lye7/claude_code_limits_were_silently_reduced_and_its/?ref=lengrand.fr">the major GenAI companies keep restricting their quotas</a>, it&apos;s become important for me to rely less on a &quot;favourite tool&quot; but find ways to keep my options open instead.</p><p>I&apos;ve been an avid Claude Code user for a long time, but lately I&apos;ve switched to more open alternatives, like <a href="https://kilo.ai/?ref=lengrand.fr">Kilo</a> and <a href="https://openrouter.ai/?ref=lengrand.fr">OpenRouter</a>. That way, I&apos;m less impacted by sudden changes. Let&apos;s see how expensive all those capabilities will become over time, as investors start expecting their money back...</p><h2 id="actually-talking-to-my-computer">Actually talking to my computer</h2><p>Until a year ago, the only times I was talking to my computer was to curse at it. I was looking at those developers from the Bay Area whispering to their computer ion a very opiniated way.... Thing is, after a few months writing prompts and build several features concurrently, it turns out that speaking to your computer actually makes sense. The models have become really good at transcribing my voice, even with my French accent, and you can iterate faster by speaking.</p><p>It&apos;s still not something I do a lot, maybe 20% of the time, but I know I have the choice any time and I switch depending on what I want to do</p><h2 id="custom-skills">Custom skills</h2><p>While I have never been a big user of custom prompts, or custom agents, I do find  skills very useful in my day to day life. Especially together with Maestro, and for things that are adjacent to coding. Skills are a good way for me to extract the knowledge of someone teaching me something, and get GenAI to propose something constructed based on this. </p><p>It&apos;s both useful as an input for my work, but also as a way to teach myself the actual skill over time. Things like monetization, landing page improvement, or marketing plan are all skills that I&apos;ve created based on tutorials I found online.</p><p><a href="https://github.com/jlengrand/my-skills?ref=lengrand.fr">You can find my custom skills here.</a></p><h2 id="closing">Closing</h2><p>I don&apos;t quite know how the future looks like, and whether that way of working is here to stay. I do really appreciate the feeling of being able to implement a larger set of the ideas I have been having over the years. </p><p>But I also know that as codebases get larger and larger, it becomes harder to keep AI in check and keep the quality of the final product at a sufficient level of quality. </p><p>I still write code the old fashioned way too, because I find joy in it. I also do get joy to be able to build a complete working idea in a couple hours. Let&apos;s se in a year or two how expensive all this tooling has become.</p>]]></content:encoded></item><item><title><![CDATA[Ways in which GenAI has changed my (tech) life so far]]></title><description><![CDATA[GenAI changed everything: harder to find quality content, social media flooded with bots, lost communities. But maybe it's making us more human again? #GenAI #TechCommunity]]></description><link>https://lengrand.fr/ways-in-which-genai-has-changed-my-tech-life-so-far/</link><guid isPermaLink="false">693479897acd40276ed5b004</guid><category><![CDATA[devrel]]></category><category><![CDATA[ai]]></category><category><![CDATA[blogging]]></category><category><![CDATA[communities]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Sun, 07 Dec 2025 11:35:32 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2025/12/file-20250527-56-2cuf2n.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2025/12/file-20250527-56-2cuf2n.jpg" alt="Ways in which GenAI has changed my (tech) life so far"><p>Just like many (most?) of you, I am using AI every day. I like it for <em>some</em> things, like giving me insights on topics and explore new ideas or create learning plans. I&apos;ve been way less successful for other things (well, most things actually).<br>Heck, I&apos;m even currently leading AI adoption for engineers in my current job, so let&apos;s say I&apos;m pretty invested.<br>In this article though, I want to discuss a few of the ways AI has drastically impacted me and my life so far.</p><h2 id="its-generally-harder-to-find-quality-technical-content-online">It&apos;s generally harder to find quality technical content online</h2><p>Because AI is able to generate tons of content in minimal time, many content farms out there generate massive amounts of data and flood the market with it. Some <a href="https://ahrefs.com/blog/what-percentage-of-new-content-is-ai-generated/?ref=lengrand.fr">sources</a> even report that 3/4 of websites online already contain generated content.<br>It makes it increasingly difficult to use search engines to find quality technical information.<br>This is a not a new phenomenon in DevRel, it&apos;s well known that many of the large actors out there externalise the &quot;lower quality / more generally&quot; content to third parties. But with the arrival of LLMs, it has scaled up tremendously.<br>I simply find myself relying less and less on &quot;insert your search engine&quot; to get proper technical information. Instead, I directly go to specialised places like Discord, Reddit or simply the documentation. </p><p>This is also true for smaller scale content publishers and personal blogs. For as little as 50$ a month you can have a <a href="https://www.outrank.so/?ref=lengrand.fr">completely automated blog generating content for itself</a>, including social media promotion.</p><h2 id="the-signal-ratio-on-social-media-has-become-abysmal">The signal / ratio on social media has become abysmal</h2><p>Social Media has always been a double edged sword for me. Even 10 years ago, unless you&apos;d curate your timeline like a madman, you&apos;d have to sift through a lot of irrelevant content to find a few gems. But those gems were worth it. As a long time Kotlin developer, I&apos;ve found most of &quot;my people&quot; online first by sharing and reacting to Kotlin content. Today, those gems are still there but the amount of low quality / irrelevant content has spiked up to incredible amounts.<br>I feel like it&apos;s more blatant on Youtube, play around with shorts (&#x1F922;) for 30 minutes and your timeline will be full of AI generated videos that voice over successful reddit posts from completely automated accounts. Worse, the content is very often plain wrong.<br>Not only that, AI is also being pushed on people using social media. Insert a few words, and LinkedIn will transform those into a full fledged 500 words post babbling about said words.</p><h2 id="everything-looks-the-same">Everything looks the same</h2><p>Because of how easy it is to generate content, as well as how aggressively it is pushed everywhere (Gmail, Outlook, LinkedIn, ..., basically everywhere!) the share of people using it is increasingly large.<br>This leads to posts that basically all look the same. Same formatting, same verbosity, same use of emojis, bullet lists everywhere with separated sections AND SAME LIFELESS TONE... The online world is really losing its flavour. Essentially, AI generated content has no soul.<br>It also means that shallower content will tend to be posted more often, because of how cheap it is to generate posts. Which increases the signal/noise ratio issue I mentioned above. </p><h2 id="i-lost-my-online-community">I lost my (online) community</h2><p>One can find many semi automated accounts posting engagement traps to and this simply pushes the more technical audience away.</p><p>The social media world has also become a lot more fragmented the last few years. First because of the Twitter acquisition (leading to many people being on different platforms) but also lately because the <a href="https://www.aljazeera.com/news/2025/11/24/are-tech-companies-using-your-private-data-to-train-ai-models?ref=lengrand.fr">ever more aggressive chase for data</a> to feed the almighty models. Add to this that by being on a platform, you have to coexist with some <a href="https://www.theguardian.com/technology/2025/nov/21/elon-musk-grok-ai-bias-ranks-richest-man-fittest-smartest?ref=lengrand.fr">outrageously voluntarily biased models</a> and the use of social media becomes very unattractive.</p><p>Because of all these reasons, a large share of the people I used to follow are now using social media for &quot;push only&quot;, meaning that they use cross-posters and do not interact with their replies. Others have left the game altogether. </p><p>And given that discussions (for me) are where connection gets created, I simply find it harder to find inspiring people (whose behaviour or work give me energy) online.</p><h2 id="weve-become-so-much-more-used-to-stealing-other-peoples-content">We&apos;ve become so much more used to stealing other people&apos;s content</h2><p>One of the things that bothers me the most is that we all know that  those large models are only possible because we have been essentially been stealing people&apos;s data at an unprecedented scale.<br>In a world where some of the most used Open-Source projects struggle to survive for lack of sponsorship, all of us techies are using AI assistants that have been fed the entirety of GitHub. Anthropic has <a href="https://www.theguardian.com/technology/2025/sep/05/anthropic-settlement-ai-book-lawsuit?ref=lengrand.fr">just lost a lawsuit</a> because they used books to train their models without authorisation. The same is true for images, music, video generation, . . . you name it.</p><p>We developers are at the same time lamenting for the lack of sponsoring from the big corps while using stolen data on a daily basis.</p><h2 id="it-all-feels-unsustainable">It all feels unsustainable</h2><p>I am no expert in economics, so please don&apos;t take my word for it but given the already insane amount of revenue generated by AI the past years and the sheer size of the losses that those companies are still making, I find it extremely hard to believe that the current pricing models of AI tooling is sustainable (<a href="https://fortune.com/2025/09/23/ai-boom-unsustainable-tech-spending-parabolic-deutsche-bank/?ref=lengrand.fr">and I&apos;m not the only one</a>).<br>A few years back, Netflix cost 12 dollars. The convenience and the size of the catalog made me a very happy user. Eight years down the line, it is now close to 25 dollars (and climbing) and I have to pay 4 similar services for the same catalog.</p><p>What&apos;s even more, the insane levels of <a href="https://www.npr.org/2025/11/23/nx-s1-5615410/ai-bubble-nvidia-openai-revenue-bust-data-centers?ref=lengrand.fr">investments loops</a> being made make me genuinely worried the whole industry ends up crashing hard.</p><p>It&apos;s like driving a Ferrari at 250km/h on the highway : It feels good and exhilarating, but it&apos;s genuinely a bit hard to not have a large part of your brain wondering what will happen when a mistake happens....</p><h2 id="its-easy-to-be-lazy">It&apos;s easy to be lazy</h2><p>I use AI everyday, and it can really be amazing. A pet-project that used to take a week now takes 1/2 day. It&apos;s also really great at helping me dive into an existing project or domain quickly. Or update dependencies for older maintenance projects. It&apos;s also a rather good rubber ducky (even if it tends to agree with you too much for my taste) and it&apos;s a useful creative companion at giving project or gift ideas.</p><p>What I find difficult though is how easy it can be to fall into the trap of &quot;just do it for me please&quot;.</p><ul><li>This blog post you have to write ? &quot;Do it and I&apos;ll do some changes&quot;.</li><li>This slide deck that is expected of you &quot;Oh, I&apos;ll just need a couple hours and AI will do most of the work for me&quot;.</li><li>Darn I&apos;m stuck on <a href="https://adventofcode.com/?ref=lengrand.fr">Advent of Code</a>, Claude, do your magic.</li></ul><p>It takes effort to stay sharp. And now, we constantly have a backup plan next to us to do our work. It&apos;s easier to just let it slip and requires (me) more discipline and motivation to stay relevant. </p><p>I am sometimes really glad I am not a student today, because I feel GenAI would have done me a lot of harm back then. The grind is useful to get past a certain level of understanding.<br>It was always possible, you simply needed to copy the work of someone else or use a mechanical turk platforms. But now it&apos;s always available on all your devices, for free. And it <a href="https://techcrunch.com/2025/02/10/is-ai-making-us-dumb/?ref=lengrand.fr">turns out, research seems to agree</a>.</p><h2 id="hiring-online-has-become-harder">Hiring online has become harder</h2><p>A few months back, I was hiring for a couple technical positions. I made the mistake to post the openings on LinkedIn.</p><p>LinkedIn has always been a bit of a hit and miss for hiring, but I&apos;ve had some success in the past finding very relevant people way out of my network (Shoutout to my former Adyen team!!). Things seem to be different since the GPT era though....<br>Within one hour, I had received over 300 responses. Most clearly generated and not a single one relevant to the position. I closed the gates as the requests were still pouring in and decided to use my local network again.<br>I am unsure exactly what happens here, having not used the platform to search for a job myself but I believe that many people use tools like n8n to build <a href="https://n8n.io/workflows/5906-automated-job-applications-and-status-tracking-with-linkedin-indeed-and-google-sheets/?ref=lengrand.fr">automated pipelines</a> those days generating applications responses on the fly .</p><p>I have to admit I don&apos;t quite understand this to be honest, I&apos;ve always been more of the &quot;apply to 2 jobs but go 300% for it&quot; kind of person. What are your chances to get an interview based on a fully generated application? The current market is quite overcrowded though, so I guess I can still understand why people do this...</p><h2 id="and-so-what-now">And so what now?</h2><p>This article ended up being much longer than I expected. I don&apos;t intend to do any low key AI bashing here. I am using AI everyday myself and am genuinely excited about the possibilities that technology can bring us in the future. I have Claude writing a new pet-project for me literally as I am writing this.</p><p>I&apos;ll write more about it soon but as a community person, I have never seen such engagement from a community than with AI tooling. There is deep, genuine interest; people experiment a lot and fast and this creates a lot of positive excitement. Optimists and Skeptics alike, close to everyone feels involved.</p><p><br>That being said, I also see that my behaviour has a changed a lot the past couple years :</p><ul><li>I mostly lost interest in social media, for the reasons mentioned above. I am almost push only now and it has become infrequent.</li><li>I am slowly migrating out of GitHub and my pet projects have become private by default.</li><li>I am much more skeptical of any news I see and verify almost everything I read (which is not a bad thing in itself).</li><li>I am always &quot;on alert&quot; for anything I see / read / hear to see if it is generated content.</li><li>Most visibly AI generated content has become a red flag for me and a sign of &quot;low effort&quot;. I don&apos;t dismiss it but I do alter my expectations about the content / person / company in front of me based on this.</li></ul><p>Most interestingly though, I see renewed interest in smaller, gated communities. Physical Meetups, local groups, closed content publications, indie writing, ...<br>I actually feel like we&apos;re coming back to where it all started for me 15 years ago. Locally, with people I know, making genuine connections. AI can&apos;t replace that, and in some ways, it might be making me more human again...</p><p>To be continued!</p>]]></content:encoded></item><item><title><![CDATA[Using Coolify for subdomain direct redirect without a app resource]]></title><description><![CDATA[Coolify's dynamic proxy configuration lets you redirect subdomains to external URLs without creating full resources. Using a simple YAML file with Traefik configuration, you can set up SSL-enabled redirects directly through Coolify's interface.]]></description><link>https://lengrand.fr/using-coolify-for-subdomain-direct-redirect-without-a-app-resource/</link><guid isPermaLink="false">68c70d1e7acd40276ed5af73</guid><category><![CDATA[coolify]]></category><category><![CDATA[traefik]]></category><category><![CDATA[devops]]></category><category><![CDATA[DNS]]></category><category><![CDATA[habits]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Sun, 14 Sep 2025 19:07:33 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1525011268546-bf3f9b007f6a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGFycm93fGVufDB8fHx8MTc1Nzg3NjgzNHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1525011268546-bf3f9b007f6a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGFycm93fGVufDB8fHx8MTc1Nzg3NjgzNHww&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Using Coolify for subdomain direct redirect without a app resource"><p>You should know by now that I&apos;m a pretty big fan of <a href="https://lengrand.fr/tag/coolify/">Coolify</a>. I use it to manage all of my applications. </p><p>One of the things I love the most about Coolify is how it automagically handles DNS for you. Write where you want your app to be hosted and boom, it&apos;ll set it up for you. Dont, and it will generate one for you. You can read more about it <a href="https://lengrand.fr/hosting-kotlin-applications-using-coolify/">here</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/09/image-1.png" class="kg-image" alt="Using Coolify for subdomain direct redirect without a app resource" loading="lazy" width="1402" height="806" srcset="https://lengrand.fr/content/images/size/w600/2025/09/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2025/09/image-1.png 1000w, https://lengrand.fr/content/images/2025/09/image-1.png 1402w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Deciding where your app will be available is as easy as writing it down</span></figcaption></figure><p>Today, I faced a new use case for the first time : I had a Claude artifact that was hosted somewhere already, but wanted one of my subdomains to redirect to it.</p><p>I know I could do it directly via my registrar&apos;s DNS interface, but because all of my apps are handled via Coolify I wanted to keep it as such if possible. </p><p>The issue is, to access this domain configuration setting from Coolify, you have to create a resource (git repo, Dockerfile, . . . ).</p><p>Turns out there is another way : the <strong>dynamic proxy configuration</strong>.</p><p>My use case is as such : I want to have <a href="https://blogprocessor.lengrand.quest/?ref=lengrand.fr">https://blogprocessor.lengrand.quest/</a> redirect to <a href="https://claude.ai/public/artifacts/b21d3e9c-b05f-4fe3-923e-63e1144cfff8?ref=lengrand.fr">https://claude.ai/public/artifacts/b21d3e9c-b05f-4fe3-923e-63e1144cfff8</a>. To do this : </p><ul><li>Navigate to <strong>Servers &gt; [YourServer] &gt; Proxy &gt; Dynamic Configuration</strong> in your Coolify dashboard.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/09/image-2.png" class="kg-image" alt="Using Coolify for subdomain direct redirect without a app resource" loading="lazy" width="2000" height="558" srcset="https://lengrand.fr/content/images/size/w600/2025/09/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2025/09/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/09/image-2.png 1600w, https://lengrand.fr/content/images/2025/09/image-2.png 2078w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The server proxy dynamic configuration page </span></figcaption></figure><ul><li>Create a new YAML file (in my case <code>claude.yaml</code>) with the following structure:</li></ul><pre><code class="language-yaml">http:
  routers:
    blogprocessor-redirect:
      rule: Host(`blogprocessor.lengrand.quest`)
      service: blogprocessor-redirect-service
      middlewares:
        - blogprocessor-redirect-middleware
      tls:
        certResolver: letsencrypt
  middlewares:
    blogprocessor-redirect-middleware:
      redirectRegex:
        regex: &apos;^https://blogprocessor\.lengrand\.quest/?(.*)&apos;
        replacement: &apos;https://claude.ai/public/artifacts/b21d3e9c-b05f-4fe3-923e-63e1144cfff8&apos;
        permanent: true
  services:
    blogprocessor-redirect-service:
      loadBalancer:
        servers:
          - url: &apos;http://127.0.0.1:9999&apos;</code></pre><p>(Note : I am using the traefik proxy here, but you can use Caddy too) </p><p>The actual content of the configuration is pretty simple :</p><ul><li>A Middleware that describes what kind of redirection I want</li><li>A router to be able to handle tls</li><li>A (dummy) service because somehow the router requires a service and a middleware</li></ul><p>I have tried to get rid of the dummy service but couldn&apos;t find a way to get the TLS handling without it. The good news however is that I can keep using that file as I create more Claude artifacts without having to create much more configuration.</p><p>If you find a way, please let me know!</p><p>I want to thank <a href="https://cinzya.gg/?ref=lengrand.fr">Cinzya</a>, one of the moderators of the Coolify server for pointing me in the right direction, she&apos;s been super helpful!</p>]]></content:encoded></item><item><title><![CDATA[Extracting moments of interest from Dash Cam footage]]></title><description><![CDATA[DIY Dashcam Incident Logging with Apple Shortcuts
After buying my first car at nearly 40, I needed a way to mark interesting dashcam moments while driving. My solution? A simple Apple Shortcut that voice-captures timestamp and location data into a note when I say "record incident."]]></description><link>https://lengrand.fr/extracting-moments-of-interest-from-dash-cam-footage/</link><guid isPermaLink="false">68c3c7ac7acd40276ed5af03</guid><category><![CDATA[apple]]></category><category><![CDATA[dashcam]]></category><category><![CDATA[automation]]></category><category><![CDATA[dyi]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Fri, 12 Sep 2025 08:21:23 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1624602482469-3cd73308e649?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGRhc2hjYW18ZW58MHx8fHwxNzU3NjY1MjIyfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1624602482469-3cd73308e649?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fGRhc2hjYW18ZW58MHx8fHwxNzU3NjY1MjIyfDA&amp;ixlib=rb-4.1.0&amp;q=80&amp;w=2000" alt="Extracting moments of interest from Dash Cam footage"><p>Earlier this year, I&apos;ve bought my very first car. (Managed to spend almot 20 years of my adult life without owning one, isn&apos;t that a bit impressive? &#x1F60A;).</p><p>The first thing I did was to buy a <a href="https://www.amazon.nl/dp/B098WVKF19?ref=lengrand.fr">dashcam</a>. I went for a rather simple model, but with both a front and back camera. Being a rather new (active) driver, I wanted to make sure that everything that could happen was recorded <em>just in case</em>.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/09/image.png" class="kg-image" alt="Extracting moments of interest from Dash Cam footage" loading="lazy" width="1500" height="1389" srcset="https://lengrand.fr/content/images/size/w600/2025/09/image.png 600w, https://lengrand.fr/content/images/size/w1000/2025/09/image.png 1000w, https://lengrand.fr/content/images/2025/09/image.png 1500w" sizes="(min-width: 720px) 720px"></figure><p>The dashcam itself is great, and it turns out there isn&apos;t more than a couple days passing by without an incident that I&apos;d like to record. People tend to be very dangerous on the road, and I&apos;m honestly surprised how we can give a ton of steel to everyone and send them on their merry way to the same locations every day, on a schedule without causing more mayhem.</p><p>One of the limitations of that dashcam is that there is no easy way to &quot;mark&quot; moments of interest to extract them later. Just like most dash cams, the camera simply has a SD Card inside it and continuously records the road on a rolling stream.</p><p>My first thought was to shout &quot;RECORD&quot; every time I saw something I wanted to extract later and post process the video files searching for those moments. That gave me the opportunity to play with <a href="https://github.com/openai/whisper?ref=lengrand.fr">OpenAI&apos;s whisper</a>. But it turns out that that microphone from the dash cam is of mediocre quality, and I also have a bad tendency to play music extremely loud when I drive &#x1F605;. It&apos;s also <em>extremely</em> power and resource consuming because it requires transcripting the entirety of the video files before searching for the keyword occurrences.</p><p>I gave it another thought and realized that I could quickly build a very efficient poor man&apos;s version of the system I wanted using <a href="https://support.apple.com/guide/shortcuts/welcome/ios?ref=lengrand.fr">Apple Shortcuts</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/09/4EF4A625-79B3-49E6-B408-C8ADC8108304_1_101.jpeg" class="kg-image" alt="Extracting moments of interest from Dash Cam footage" loading="lazy" width="1206" height="2622" srcset="https://lengrand.fr/content/images/size/w600/2025/09/4EF4A625-79B3-49E6-B408-C8ADC8108304_1_101.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2025/09/4EF4A625-79B3-49E6-B408-C8ADC8108304_1_101.jpeg 1000w, https://lengrand.fr/content/images/2025/09/4EF4A625-79B3-49E6-B408-C8ADC8108304_1_101.jpeg 1206w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Creating a new shortcut</span></figcaption></figure><p>This is how my current process looks like : </p><ul><li>Create variables with the current data and location</li><li>Create a string of text with both variables concatenated</li><li>Append this text to an existing note.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/09/51F61D52-3794-4180-9BB6-D4335394C4FF_1_101.jpeg" class="kg-image" alt="Extracting moments of interest from Dash Cam footage" loading="lazy" width="1206" height="2622" srcset="https://lengrand.fr/content/images/size/w600/2025/09/51F61D52-3794-4180-9BB6-D4335394C4FF_1_101.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2025/09/51F61D52-3794-4180-9BB6-D4335394C4FF_1_101.jpeg 1000w, https://lengrand.fr/content/images/2025/09/51F61D52-3794-4180-9BB6-D4335394C4FF_1_101.jpeg 1206w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Creating a dedicated shortcut</span></figcaption></figure><p>We constantly talk about AI those days, and I realised that I barely use Siri except to ask it to set an alarm or a timer while cooking. Turns out the shortcut application is very powerful, being able to open and close applications, run loops and much, much more. It even has a sort of scripting language.</p><p>The last thing left to do is to find a good name for the shortcut. I called it &quot;Record incident&quot; so it&apos;s generic enough, while still very easily understandable by Siri on top of music.</p><p>Because of the location variable (which I added as a bonus), Apple will ask for additional permissions the first time shortcut is being run but that&apos;s only a good thing in my opinion.</p><p> </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/09/14E60348-E42D-46BD-A5D3-534724D1D4E9_1_101.jpeg" class="kg-image" alt="Extracting moments of interest from Dash Cam footage" loading="lazy" width="1206" height="2622" srcset="https://lengrand.fr/content/images/size/w600/2025/09/14E60348-E42D-46BD-A5D3-534724D1D4E9_1_101.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2025/09/14E60348-E42D-46BD-A5D3-534724D1D4E9_1_101.jpeg 1000w, https://lengrand.fr/content/images/2025/09/14E60348-E42D-46BD-A5D3-534724D1D4E9_1_101.jpeg 1206w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">My new shortcut, available</span></figcaption></figure><p>That&apos;s it for now! I now have a special notes on my apple devices that lists all of the moments of interests that I can easily trigger by voice and without being dangerous while driving.</p><p>The next step is to write a short script that will parse those values and create small clips from the video stream around those moments.</p><p>Always fun to discover new capabilities, and find cheap and efficient ways to achieve my goals &#x1F60A;.</p>]]></content:encoded></item><item><title><![CDATA[Hosting Bugsink on Coolify]]></title><description><![CDATA[A simple Docker Compose setup to host Bugsink using Coolify that supports HTTPS]]></description><link>https://lengrand.fr/hosting-bugsink-on-coolify/</link><guid isPermaLink="false">686c25eb458af7216de9027a</guid><category><![CDATA[bugsink]]></category><category><![CDATA[coolify]]></category><category><![CDATA[testing]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Mon, 07 Jul 2025 20:33:23 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2025/07/Screenshot-2025-07-07-at-22.32.08.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2025/07/Screenshot-2025-07-07-at-22.32.08.png" alt="Hosting Bugsink on Coolify"><p><em>TL;DR : There is a bug in Coolify currently preventing to use the default Bugsink deployment template. This post describes a method to deploy via Docker Compose while waiting for a fix</em>.</p><h2 id="some-context">Some context</h2><p>As some of you may know already, I&apos;ve been moving off the public cloud the past year and started deploying my applications on my own server instead using <a href="https://coolify.io/?ref=lengrand.fr">Coolify</a>. I do this for many reasons, one of them being that I want to support indie projects more and I really like Andras&apos; open approach to building his company.</p><p>Since last summer, I&apos;ve been following Klaas&apos; journey into building <a href="https://www.bugsink.com/?ref=lengrand.fr"><strong>Bugsink</strong></a>; a simple but effective error tracker. (<em>Disclaimer : I have been doing some advising work for Klaas and am biased here &#x1F60A;, but my opinion is 100% honest here</em>).</p><h2 id="the-main-deployment-template">The main deployment template</h2><p>Coolify has a module system that you can use to install services from templates. I have learnt about it a bit, since I contributed <a href="https://github.com/coollabsio/coolify/pull/5044?ref=lengrand.fr">the first version of the Bugsink template</a> to it &#x1F60A;. The template uses a Docker Compose deployment in the background.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/07/image.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="1808" height="840" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/07/image.png 1600w, https://lengrand.fr/content/images/2025/07/image.png 1808w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Installing Bugsink on Coolify is only one click away</span></figcaption></figure><p>Pretty handy when working on a new project. Pick service, click, use. </p><p><a href="https://www.bugsink.com/blog/coolify/?ref=lengrand.fr">The bugsink blog actually has coolify deployment page</a>, but the problem is that the latest version of Coolify has a issue where some environment variables can&apos;t get updated properly after the original deployment. This makes it impossible to deploy Bugsink properly in my case.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://github.com/coollabsio/coolify/issues/6035?ref=lengrand.fr"><div class="kg-bookmark-content"><div class="kg-bookmark-title">[Bug]: Bugsink environment variables issue. &#xB7; Issue #6035 &#xB7; coollabsio/coolify</div><div class="kg-bookmark-description">Error Message and Logs On bugsink it looks like coolify doesnt update the env variables (just once on the creation of the container). When updating the Domain in the General tab, the FDQN env varia&#x2026;</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://lengrand.fr/content/images/icon/pinned-octocat-093da3e6fa40.svg" alt="Hosting Bugsink on Coolify"><span class="kg-bookmark-author">GitHub</span><span class="kg-bookmark-publisher">Bastih18</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://opengraph.githubassets.com/c258159075f1efcb5262c17bf2736c0c7b9865f606b590222e36ea437e19e19b/coollabsio/coolify/issues/6035" alt="Hosting Bugsink on Coolify" onerror="this.style.display = &apos;none&apos;"></div></a></figure><p>While waiting for the bug to be fixed, I dived deeper into the Docker Compose file and ran my own version. </p><h2 id="current-deployment">Current deployment</h2><p>I started from <a href="https://www.bugsink.com/docs/docker-compose-install/?ref=lengrand.fr">the Bugsink Docker compose documentation page</a>, but I ran into multiple issues specific with Coolify with the setup : </p><ul><li>I needed to setup port redirection because I didn&apos;t want my DSN address to contain any port (meaning I had to use <a href="https://coolify.io/docs/knowledge-base/docker/compose?ref=lengrand.fr#coolify-s-magic-environment-variables">Coolify&apos;s magic variables</a>)</li><li>I wanted HTTPS to work as expected </li><li>I also wanted my <code>BASE_URL</code> to be properly recognized by bugsink, while it is refusing any request that is not from an authorized domain.</li></ul><p>This is the Docker Compose file that I currently use :</p><pre><code class="language-yaml">services:
  mysql:
    image: &apos;mysql:latest&apos;
    restart: unless-stopped
    environment:
      - &apos;MYSQL_ROOT_PASSWORD=${SERVICE_PASSWORD_ROOT}&apos;
      - &apos;MYSQL_DATABASE=${MYSQL_DATABASE:-bugsink}&apos;
      - &apos;MYSQL_USER=${SERVICE_USER_BUGSINK}&apos;
      - &apos;MYSQL_PASSWORD=${SERVICE_PASSWORD_BUGSINK}&apos;
    volumes:
      - &apos;my-datavolume:/var/lib/mysql&apos;
    healthcheck:
      test:
        - CMD
        - mysqladmin
        - ping
        - &apos;-h&apos;
        - 127.0.0.1
      interval: 5s
      timeout: 20s
      retries: 10
  web:
    image: bugsink/bugsink
    restart: unless-stopped
    environment:
      - SECRET_KEY=$SERVICE_PASSWORD_64_BUGSINK
      - &apos;CREATE_SUPERUSER=admin:$SERVICE_PASSWORD_BUGSINK&apos;
      - SERVICE_FQDN_BUGSINK_8000
      - &apos;DATABASE_URL=mysql://${SERVICE_USER_BUGSINK}:$SERVICE_PASSWORD_BUGSINK@mysql:3306/${MYSQL_DATABASE:-bugsink}&apos;
      - BEHIND_HTTPS_PROXY=true
      - BASE_URL=$BASE_URL
    depends_on:
      mysql:
        condition: service_healthy
    healthcheck:
      test:
        - CMD-SHELL
        - &apos;python -c &apos;&apos;import requests; requests.get(&quot;http://localhost:8000/&quot;).raise_for_status()&apos;&apos;&apos;
      interval: 5s
      timeout: 20s
      retries: 10
</code></pre><p>There are only a noticeable changes : </p><ul><li>I added the <code>BEHIND_HTTPS_PROXY</code> variable, because you don&apos;t want to run a bug tracker on plain HTTP.</li><li>I separated the generated qualified domain name that Coolify uses and the <code>BASE_URL</code> variables. </li></ul><p>Now, before deploying there is two more small manual steps to do : </p><ul><li>Set the desired domain name in the Bugsink web settings (you can use the default generated if you want, I decided to roll my own).</li></ul><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/07/image-1.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="2000" height="595" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image-1.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/07/image-1.png 1600w, https://lengrand.fr/content/images/size/w2400/2025/07/image-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><ul><li>Set the <code>BASE_URL</code> environment variable to the same value</li></ul><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/07/image-2.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="2000" height="300" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/07/image-2.png 1600w, https://lengrand.fr/content/images/size/w2400/2025/07/image-2.png 2400w" sizes="(min-width: 720px) 720px"></figure><p><em>Note : you need to align the port that bugsink is running on, the port in the domain settings in bugsink, set the rediretion in the DockerFile using the FQDN environment variable and omit it in the base url variable for everything to work as expected</em>.</p><h2 id="testing-is-all">Testing is all</h2><p>The last thing to do is to deploy, and test an error against a project in Bugsink. For this, you can create a project in the application until you hit the SDK setup page.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/07/image-4.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="2000" height="879" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/07/image-4.png 1600w, https://lengrand.fr/content/images/2025/07/image-4.png 2226w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Using an obfuscated domain name on purpose here :)</span></figcaption></figure><p>Once this is done, you need to trigger an error in a project, <a href="https://www.bugsink.com/sentry-sdk-compatible/?ref=lengrand.fr">Bugsink&apos;s documentation</a> explain how very well. There are several ways to do this but in the past I faced network and setup issues. </p><p>To make sure I am testing only Bugsink and not my application setup, I created a small project called <a href="https://sentryerrorgenerator.lengrand.quest/?ref=lengrand.fr">Sentry Error Generator</a> (This application does not log anything, but be aware this is a sensitive action to do). Go to the website, enter the setup URL Bugsink gave you and press generate error. You should see it in Bugsink.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/07/image-5.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="1286" height="776" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image-5.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image-5.png 1000w, https://lengrand.fr/content/images/2025/07/image-5.png 1286w" sizes="(min-width: 720px) 720px"></figure><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/07/image-6.png" class="kg-image" alt="Hosting Bugsink on Coolify" loading="lazy" width="1728" height="216" srcset="https://lengrand.fr/content/images/size/w600/2025/07/image-6.png 600w, https://lengrand.fr/content/images/size/w1000/2025/07/image-6.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/07/image-6.png 1600w, https://lengrand.fr/content/images/2025/07/image-6.png 1728w" sizes="(min-width: 720px) 720px"></figure><p>Congratulations, you are now collecting your application&apos;s errors (for free, if you have followed this guide! Oh, and if <a href="https://www.bugsink.com/blog/saving-costs-on-sentry/?ref=lengrand.fr">your current  bug tracker too expensive</a> and want a smaller and simpler alternative but with no maintenance, <a href="https://www.bugsink.com/?ref=lengrand.fr#pricing">Bugsink has some hosted and supported options too</a> <em>(As I&apos;ve said earlier, I&apos;m biased!)</em>.</p>]]></content:encoded></item><item><title><![CDATA[KotlinConf 2025 is a real bowl of fresh air for backend Devs]]></title><description><![CDATA[With their keynote that JetBrains intentionally started putting backend developers in the spotlight again. They made big, impactful announcements, both at an ecosystem and language levels and I can't wait to try out all of the new goodies. ]]></description><link>https://lengrand.fr/kotlinconf-2025-is-a-real-bowl-of-fresh-air-for-backend-devs/</link><guid isPermaLink="false">682f23027cfc0773d1ba372d</guid><category><![CDATA[kotlin]]></category><category><![CDATA[jvm]]></category><category><![CDATA[conference]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Thu, 22 May 2025 14:34:02 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2025/06/Screenshot-2025-06-23-at-09.46.41.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2025/06/Screenshot-2025-06-23-at-09.46.41.png" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"><p><em>TL;DR : After years of focus on the compiler and KMP, The JetBrains folks are coming it out with tons of announcements for server folks too, and that feels great</em></p><h2 id="the-past-few-years">The past few years</h2><p>You probably know me by now, I&apos;ve been liking Kotlin a lot for a long time. Back in 2019 already we were the first at ING <a href="https://medium.com/ing-blog/introducing-kotlin-at-ing-a-long-but-rewarding-story-1bfcd3dc8da0?ref=lengrand.fr">moving some of of our production code from Java to Kotlin</a>. Over the past years though, <a href="https://www.reddit.com/r/Kotlin/comments/1gq7zwg/comment/lww1spb/?context=3&amp;ref=lengrand.fr">I&apos;ve also been vocal online</a> about my worries that all of the love was going towards KMP. And Google deciding to <a href="https://bsky.app/profile/jlengrand.bsky.social/post/3llqnajudqf2u?ref=lengrand.fr">sunset the (non Android) Kotlin category</a> has just been another pointer in the same direction. I&apos;ve even mentioned this exact thing already right after visiting KotlinConf 2023.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://lengrand.fr/kotlinconf/"><div class="kg-bookmark-content"><div class="kg-bookmark-title">My KotlinConf experience</div><div class="kg-bookmark-description">My experience and impressions attending KotlinConf a couple weeks back. And some thoughts on the future of Kotlin.</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://lengrand.fr/content/images/size/w256h256/2023/07/terminal.png" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"><span class="kg-bookmark-author">Julien&apos;s DevRel corner</span><span class="kg-bookmark-publisher">Julien</span></div></div><div class="kg-bookmark-thumbnail"><img src="https://lengrand.fr/content/images/2023/05/IMG_7028-1.jpeg" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"></div></a></figure><p>Even with Java releasing more often, and getting more and more new features every iteration I&apos;m still much happier using Kotlin. But for pure backend developers is the developer experience still that differentiating that it makes it worth switching? After all, Java got pretty good records those days. A much better functional programming experience. And virtual threads. The list goes on....</p><p><em>Note : This article is my personal impression based on my usage of the language. Depending what you&apos;re working on, your opinion may vary &#x1F60A;. I&apos;ve also included many screenshots from the Keynote itself, </em><a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;ref=lengrand.fr"><em>please watch it</em></a><em> for the complete announcements! </em></p><p><em>Note 2 : There&apos;s also been lots of great AI related announcements, but I&apos;ll intentionally skip them in this article. I&apos;m generally happy JetBrains picks up the AI wave and innovates while staying open. </em><a href="https://lengrand.fr/my-experience-using-junie-for-the-past-few-months/"><em>I&apos;m a mostly happy user</em></a><em> of their product myself.</em></p><h2 id="new-language-features">New language features</h2><p>As a backend engineer, I think the main improvements I&apos;ve personally seen in Kotlin that made a difference to me the past years are <a href="https://lengrand.fr/measuring-time-and-durations-in-kotlin/">the time API</a> (back in 2021!) and the K2 compiler. Of course, the K2 compiler is a huge (and needed) improvement that came with Kotlin 2.0. But it also didn&apos;t bring a huge amount of new language features. </p><p>Now, <strong>today&apos;s keynote was a VERY welcome change to this</strong>. Others will do a complete breakdown better than me but here some actual new language features I&apos;m excited about!</p><ul><li>Name-based destructuring to ensure I am grabbing the right properties. An actual improvements compared to the current version.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-16.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1620" height="474" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-16.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-16.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-16.png 1600w, https://lengrand.fr/content/images/2025/05/image-16.png 1620w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot from Day 1 Keynote: </span><a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&amp;ref=lengrand.fr"><span style="white-space: pre-wrap;">https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb</span></a></figcaption></figure><ul><li>Rich errors : an improvement to error handling, sitting on top of the already great null safety features of the language. Not only do we now have complete return types but they also get propagated so we can neatly handle all error cases in a single location.</li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-17.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1632" height="562" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-17.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-17.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-17.png 1600w, https://lengrand.fr/content/images/2025/05/image-17.png 1632w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot from Day 1 Keynote: </span><a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&amp;ref=lengrand.fr"><span style="white-space: pre-wrap;">https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb</span></a></figcaption></figure><ul><li>Even Kotlin compiler plugins are getting more powerful, with Power Assert offering an expressive and clear error message. Look at this, we&apos;re almost topping the already amazing <a href="https://guide.elm-lang.org/error_handling/?ref=lengrand.fr">Elm error handling</a> capabilities. </li></ul><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-18.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1646" height="674" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-18.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-18.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-18.png 1600w, https://lengrand.fr/content/images/2025/05/image-18.png 1646w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Screenshot from Day 1 Keynote: </span><a href="https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb&amp;ref=lengrand.fr"><span style="white-space: pre-wrap;">https://www.youtube.com/live/PYAPymKRKVA?si=lKlYZoRzImgKMptb</span></a></figcaption></figure><p>This is developer experience like I haven&apos;t seen in a long time from the teams at JetBrains, and it genuinely makes me happy.</p><h2 id="tooling-ecosystem-and-strategic-partnerships">Tooling ecosystem, and strategic partnerships</h2><p>I&apos;ve already mentioned lately that I was a big fan of <a href="https://kotlinlang.org/docs/kotlin-notebook-overview.html?ref=lengrand.fr">Kotlin notebooks</a>. I find them generally amazing, because they combine the unique experience of Python Notebooks with the gigantic JVM ecosystem (and without the Python horrible dependency management issues).</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://bsky.app/profile/jlengrand.bsky.social/post/3lgg7aa5yxe2l?ref=lengrand.fr"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Julien Lengrand-Lambert &#x1F951;&#x1F44B; (@jlengrand.bsky.social)</div><div class="kg-bookmark-description">Been doing some data analysis lately and #Kotlin notebooks, together with dataframe, kandy and sqlite are a really freaking powerful combination! Can&#x2019;t wait to show you the results :)</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://web-cdn.bsky.app/static/apple-touch-icon.png" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"><span class="kg-bookmark-author">Bluesky Social</span></div></div></a></figure><p>I was legit &#x1F92F; this morning when I saw that the folks at JetBrains combine this already amazing experience together with the ecosystem that pretty much every JVM dev uses on the planet : Spring!</p><p>The screenshots aren&apos;t doing justice to the announcement, so please just go and <a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;ref=lengrand.fr">have a look at the Keynote</a> yourself. The latest version of notebooks can attach itself to a running Spring kernel and have access to all of its context. And that even for applications that aren&apos;t using Kotlin yet.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-19.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1056" height="796" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-19.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-19.png 1000w, https://lengrand.fr/content/images/2025/05/image-19.png 1056w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Kotlin notebook, straight from a running Spring kernel</span></figcaption></figure><p>Again, this is next level for me in terms of developer experience : combining the flexibility of notebooks for experimentation together with the sturdiness of a production like application. With the kandy and dataframe data manipulation capabilities, I can definitely see this speed up my day to day development speed.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-20.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1232" height="650" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-20.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-20.png 1000w, https://lengrand.fr/content/images/2025/05/image-20.png 1232w" sizes="(min-width: 720px) 720px"></figure><p>And of course, JetBrains also announcing a strategic partnership with Spring on stage shows that they clearly intend to give us &quot;corporate&quot; backend developers some serious love. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-24.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1216" height="246" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-24.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-24.png 1000w, https://lengrand.fr/content/images/2025/05/image-24.png 1216w" sizes="(min-width: 720px) 720px"></figure><h2 id="growing-kotlin-foundation">Growing Kotlin Foundation</h2><p>For a long time, Kotlin was completely in the hands of JetBrains. This has massively changed over the past years with the creation of the Kotlin Foundation. </p><p>As a professional having to pick a technology, it is crucial to look at how promising its future looks. Seeing that Kotlin is jointly supported by several companies,  and especially companies that aren&apos;t making a living of the language makes it a much safer business case to adopt inside a company. </p><p>Seeing two more companies (and none other than Meta) join the foundation gives a great idea of how bright of a future Kotlin has &#x1F60A;.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-21.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1634" height="628" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-21.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-21.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-21.png 1600w, https://lengrand.fr/content/images/2025/05/image-21.png 1634w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Kotlin foundation members</span></figcaption></figure><h2 id="the-kotlin-built-java-library-ecosystem">The Kotlin built Java library ecosystem </h2><p>A few weeks back, I was mentioning that for the first time I found a library that was completely built in Kotlin, but had a clear Java compatibility too. If you&apos;re interested, I&apos;m talking about the <a href="https://github.com/uakihir0/kbsky?ref=lengrand.fr">KBsky</a>.</p><figure class="kg-card kg-bookmark-card kg-card-hascaption"><a class="kg-bookmark-container" href="https://bsky.app/profile/jlengrand.bsky.social/post/3lmk4xy6s2o2q?ref=lengrand.fr"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Julien Lengrand-Lambert &#x1F951;&#x1F44B; (@jlengrand.bsky.social)</div><div class="kg-bookmark-description">Interestingly, this is the first time ever that I find a more mature ecosystem of libraries for my need in #kotlin than in #java quite interesting isn&#x2019;t it. I was looking for ATProto libraries, it seems the main java was got deprecated in favor of a kotlin version? https://github.com/uakihir0/bsky4j</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://web-cdn.bsky.app/static/apple-touch-icon.png" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"><span class="kg-bookmark-author">Bluesky Social</span></div></div></a><figcaption><p><span style="white-space: pre-wrap;">A Java library, built in Kotlin</span></p></figcaption></figure><p>Today, I learnt that the two most successful JVM AI libraries (the OpenAI Java SDK and the Anthropic Java SDK) both are actually written in Kotlin.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-22.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1600" height="590" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-22.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-22.png 1000w, https://lengrand.fr/content/images/2025/05/image-22.png 1600w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">See Keynote for complete info : </span><a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;ref=lengrand.fr"><span style="white-space: pre-wrap;">https://www.youtube.com/watch?v=PYAPymKRKVA</span></a></figcaption></figure><p> </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-23.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1620" height="634" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-23.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-23.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-23.png 1600w, https://lengrand.fr/content/images/2025/05/image-23.png 1620w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">See Keynote for complete info : </span><a href="https://www.youtube.com/watch?v=PYAPymKRKVA&amp;ref=lengrand.fr"><span style="white-space: pre-wrap;">https://www.youtube.com/watch?v=PYAPymKRKVA</span></a></figcaption></figure><p>I find this a great sign for the language. Of course, the amount of Java developers out there is much larger than people writing Kotlin and it makes sense for library builders to want to maximize their reach. But them choosing Kotlin to write the library speaks volume about the quality of the language, but is also a great sign for its future. </p><h2 id="the-official-kotlin-language-server">The official KOTLIN LANGUAGE SERVER</h2><p>This is, <a href="https://bsky.app/profile/jlengrand.bsky.social/post/3lpqo5stye22h?ref=lengrand.fr">by far</a>, the announcement I am most excited about and I genuinely didn&apos;t see it coming. JetBrains announced the official release of their Kotlin Language Server protocol today. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-25.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1530" height="590" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-25.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-25.png 1000w, https://lengrand.fr/content/images/2025/05/image-25.png 1530w" sizes="(min-width: 720px) 720px"></figure><p>For those unaware, the <a href="https://microsoft.github.io/language-server-protocol/?ref=lengrand.fr">language server</a> protocol &quot;defines the protocol used between an editor or IDE and a language server that provides language features like auto complete, go to definition, find all references etc.&quot;. Having an official language server protocol implementation basically means that all IDEs (from vim to IntelliJ, or SublimeText) can support the language equally well.</p><p>The fact that JetBrains is behind Kotlin always made it rather logical for them to not Open-Source a language server. After all, they&apos;re selling their IDEs and that&apos;d pretty much mean they&apos;re competing against themselves. And it&apos;s always been one of my biggest issues with the language.</p><figure class="kg-card kg-bookmark-card"><a class="kg-bookmark-container" href="https://bsky.app/profile/jlengrand.bsky.social/post/3lfadcojxvk2a?ref=lengrand.fr"><div class="kg-bookmark-content"><div class="kg-bookmark-title">Julien Lengrand-Lambert &#x1F951;&#x1F44B; (@jlengrand.bsky.social)</div><div class="kg-bookmark-description">Agreed. Absolutely love IntelliJ and the company behind it, but I don&#x2019;t like have to start the powerhouse all the time for some simple scripting. Let me use IntelliJ for feature full, multiplatform stuff and give me a powerful language server for the simple backend / scripting thing please</div><div class="kg-bookmark-metadata"><img class="kg-bookmark-icon" src="https://web-cdn.bsky.app/static/apple-touch-icon.png" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs"><span class="kg-bookmark-author">Bluesky Social</span></div></div></a></figure><p>Yes, IntelliJ is a great IDE. I use it every single day, have paid for my license for years and I love it. But in simply just don&apos;t believe in the success of gated products. More and more people use VSCode every day. People now use Cursor. Or SublimeText. Some still use vim. Forcing folks to use a specific IDE just feels wrong and tames the excitement using a new language should bring.</p><p>Yes, there&apos;s been <a href="https://github.com/fwcd/kotlin-language-server?ref=lengrand.fr">community driven language server implementations</a> for Kotlin, but they&apos;ve always been struggling with support. And literally being a company building IDEs for a living, JetBrains always felt like the best party to handle this properly. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-26.png" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1716" height="324" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-26.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-26.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-26.png 1600w, https://lengrand.fr/content/images/2025/05/image-26.png 1716w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The main kotlin language server implementation struggling with support</span></figcaption></figure><p><strong>I&apos;m over the moon that JetBrains has the courage to open up and even lead the language server efforts in the future. It shows a strong trust in the future of the language and that can only help drive adoption across the industry.</strong></p><h2 id="ing-as-use-case">ING as use case </h2><p>As I mentioned already, I was part of the first team to start using Kotlin at ING back in 2019. Five years later, adoption has grown a lot internally and I was super happy to see JetBrains offer us to be one of the leading industry use cases for their server side Keynote.</p><p>Kotlin powers some of our critical services today, and it&apos;s a great symbol of how good and powerful the language is. I can&apos;t wait to see where we&apos;ll be in 5 years.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/62178106-7465-4119-9D30-9CECCBFAF0D5_1_101.jpeg" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="2000" height="920" srcset="https://lengrand.fr/content/images/size/w600/2025/05/62178106-7465-4119-9D30-9CECCBFAF0D5_1_101.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2025/05/62178106-7465-4119-9D30-9CECCBFAF0D5_1_101.jpeg 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/62178106-7465-4119-9D30-9CECCBFAF0D5_1_101.jpeg 1600w, https://lengrand.fr/content/images/2025/05/62178106-7465-4119-9D30-9CECCBFAF0D5_1_101.jpeg 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Simone, one of my colleagues, talking about Kotlin adoption at ING. Well done Simone!</span></figcaption></figure><p>I&apos;ve always been a fan of Kotlin, and it&apos;s been an honour to be able to contribute to the content of the program, no matter how small my contribution was.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/A7B5746C-C34F-4D16-AA7A-DEEEA886A3FA_1_105_c.jpeg" class="kg-image" alt="KotlinConf 2025 is a real bowl of fresh air for backend Devs" loading="lazy" width="1024" height="768" srcset="https://lengrand.fr/content/images/size/w600/2025/05/A7B5746C-C34F-4D16-AA7A-DEEEA886A3FA_1_105_c.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2025/05/A7B5746C-C34F-4D16-AA7A-DEEEA886A3FA_1_105_c.jpeg 1000w, https://lengrand.fr/content/images/2025/05/A7B5746C-C34F-4D16-AA7A-DEEEA886A3FA_1_105_c.jpeg 1024w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Recording the video, behind the scenes</span></figcaption></figure><h2 id="conclusion">Conclusion</h2><p>There&apos;s much more I&apos;d like to talk about (amper, klibs, kmp, ...) but I&apos;ll keep it to the essential : It feels to me with their keynote that JetBrains intentionally started putting backend developers in the spotlight again. They made big, impactful announcements, both at an ecosystem and language levels and I can&apos;t wait to try out all of the new goodies. </p><p>Well done JetBrains, well done! </p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[My experience using Junie for the past few months]]></title><description><![CDATA[Junie is one of the best coding agent I've been trying out so far. Very well integrated with IntelliJ, great for Kotlin, and the test first focus makes it quite good at coming out with good results. However, I do miss the capability to only accept part of a solution and it can be very slowwwwww. ]]></description><link>https://lengrand.fr/my-experience-using-junie-for-the-past-few-months/</link><guid isPermaLink="false">681f952e7cfc0773d1ba35d2</guid><category><![CDATA[ai]]></category><category><![CDATA[jetbrains]]></category><category><![CDATA[kotlin]]></category><category><![CDATA[agent]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Sun, 11 May 2025 11:38:55 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2025/05/jetbrains-junie-intelligenza-artificiale-agente-ai-per-programmare-ufficiale-696x392.webp" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2025/05/jetbrains-junie-intelligenza-artificiale-agente-ai-per-programmare-ufficiale-696x392.webp" alt="My experience using Junie for the past few months"><p>I&apos;ve been a big fan of all things JetBrains for a long time. The company itself, for many reasons but also their high quality products (and languages, but you know that about me already &#x1F61D;). As a fan, I&apos;ve jumped on the occasion to be using <a href="https://www.jetbrains.com/junie/?ref=lengrand.fr">Junie</a> (JetBrains coding agent) as soon as it came out. This post summarises my experience, in no particular order of features.</p><p><em>NOTE: The examples below are a compilation created over a long period of time. They are illustrations of the issues I&apos;ve met but aren&apos;t meant to be exact examples. </em></p><h2 id="quick-intro">Quick intro</h2><p><a href="https://www.jetbrains.com/junie/?ref=lengrand.fr">Junie</a> is JetBrains equivalent of Copilot (or Windsurf / Cursor together with the IDE). It is only available inside the IntelliJ based IDEs (Rider, Pycharm, .... included) and is available through an additional &quot;AI&quot; monthly paid fee (10$/month at the time of writing). They have a limited free plan too. </p><p>You can install Junie from the <a href="https://plugins.jetbrains.com/plugin/26104-jetbrains-junie?ref=lengrand.fr">Marketplace</a>. Once installed it appears as a window in your IDE</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="1125" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image.png 1600w, https://lengrand.fr/content/images/2025/05/image.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The Junie window inside IntelliJ</span></figcaption></figure><p>Junie has 2 modes at the moment : </p><ul><li>Code : You ask Junie something, it will most likely write code / create / edit files in your project.</li><li>Ask : In case you don&apos;t want changes, but rather ask a question or chat with the AI, you can switch to Ask mode. Handy if you want to weight choices in your project, or ask about potential issues / optimisations. </li></ul><p>I&apos;m very glad they added this mode actually, I tend to use the AI a lot to help me with reasoning and it is very frustrating to have half a dozen files changes only when asking of a refactoring makes sense. </p><p>You can also activate the &quot;brave mode&quot; option, which will allow the AI to run commands in your terminal without your explicit permission every time. I was a little worried at the beginning but so far nothing crazy has happened. I&apos;ll report the first time Junie decides to <code>$ rm -rf /</code>, that&apos;ll sure be fun and I can only blame myself then.</p><h2 id="the-many-ai-products-naming-and-differentiations">The many AI products, naming and differentiations</h2><p>I&apos;m a little confused between the different types of AI products IntelliJ is offering at the moment, which are most relevant to me, but also what are their differentiations. I just found out writing this blog that they have <a href="https://www.jetbrains.com/ai/?ref=lengrand.fr">a dedicated page</a> for this actually.</p><p>To me, it&apos;s still really not clear what the difference between Junie and <a href="https://www.jetbrains.com/ai-assistant/?ref=lengrand.fr">the AI assistant</a> is. It looks like Junie is the AI assistant, but with more bells and whistles? Except Junie is not customisable (models, prompt library, ...), while the assistant is? Then there&apos;s also Mellum, the JetBrains LLM. It feels like I have to learn a whole lot of new product names with little differentiation and it&apos;s a bit more than I want to have to remember. </p><p>The <a href="https://www.jetbrains.com/ai-ides/buy/?section=personal&amp;billing=monthly&amp;ref=lengrand.fr">pricing page</a> with features also basically tells you you get everything as part of the subscription anyways, which is good news! (unless you&apos;re enterprise, then you&apos;re screwed). Oh, and one thing to mention : pretty cool to see a flat price for the agent, when most of the competition mentions token limit pricing instead!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-14.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="743" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-14.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-14.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-14.png 1600w, https://lengrand.fr/content/images/2025/05/image-14.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The features table of the JetBrains AI products package</span></figcaption></figure><p>I understand the market is moving very fast and is honestly quite a mess at the moment, but I&apos;d appreciate a &quot;use me if, don&apos;t use me if&quot; part that&apos;s a bit clearer. </p><h2 id="junie-is-very-eager-to-solve-my-issues">Junie is VERY eager to solve my issues</h2><p>I like to use my coding agent as if I had a junior pair programmer with me. I usually don&apos;t ask it for a complete project / solution, but rather first look at a list of things we need to achieve for a particular outcome, before diving into each piece of it separately. That way seems to be an optimal way to speed up delivery, while keeping an actual control over quality and overall architecture. </p><p>In my experience so far, Junie tends to REALLY want to be doing a lot of work and I sometimes have a hard time telling it to only do one thing. </p><p>I may ask it to create a single test, and I&apos;ll have a complete test class created, the implementation will be refactored and it will also go on and upgrade some of my dependencies while it&apos;s at it. </p><p>It can be frustrating, because it&apos;s not what I asked for, and it will take me time to refuse part of the solution... Here is a concrete example where I ask Junie to create a test for a single class method. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-1.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="1245" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-1.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-1.png 1600w, https://lengrand.fr/content/images/2025/05/image-1.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Asking to test a method, only to have my implementation changed and refactored</span></figcaption></figure><p>By the way, this was my method at the time &#x1F605;. Yup, it was basically empty. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-2.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1736" height="226" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-2.png 1600w, https://lengrand.fr/content/images/2025/05/image-2.png 1736w" sizes="(min-width: 720px) 720px"></figure><p>And this is the summary of what Junie did by the end of its quest. Not only did it create a test, it also decided to create the actual implementation of the method. As I&apos;d tell my junior if they were not an AI : very industrious, but not what I asked for buddy....</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-3.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1402" height="352" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-3.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-3.png 1000w, https://lengrand.fr/content/images/2025/05/image-3.png 1402w" sizes="(min-width: 720px) 720px"></figure><p>I refused Junie&apos;s solution, and asked it again. This time being more precise about what I DIDN&apos;T want (modifying the implementation): </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2025/05/image-4.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="1208" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-4.png 1600w, https://lengrand.fr/content/images/2025/05/image-4.png 2000w" sizes="(min-width: 720px) 720px"></figure><p>This time, the results were as expected. But it took me 2 tries, and my sentence in my request actually was longer.  </p><p><em>EDIT : </em><a href="https://bsky.app/profile/maritvandijk.bsky.social/post/3lovtike75k2h?ref=lengrand.fr"><em>The awesome Marit Van Dijk mentioned the allowlist on Bluesky</em></a><em>, which I didn&apos;t know about! </em></p><h2 id="junie-can-get-some-basic-stuff-wrong">Junie can get some basic stuff wrong</h2><p>This was a bit surprising to me. I&apos;ve seen Junie get imports wrong for <a href="https://assertj.github.io/doc/?ref=lengrand.fr">AssertJ</a> for example. To the extent that the code wouldn&apos;t compile. (Also, I would have liked it to ask me to add AssertJ as a dependency instead of just decide to start using it &#x1F605;). It&apos;s quickly and easily fixed, since it will try to build by itself, fail and iterate. But still, I wonder where that comes from. </p><p>For smaller libraries, I&apos;ve also seen Junie struggle to use basic objects. In the below example, it wants to use reflection and complex shenanigans where a simple instantiation just works out of the box. The actual implementation uses <code>val feature = RichtextFacetMention()</code> . It also tries to access fields using <code>getDeclaredField()</code> where accessors are perfectly fine <code>facet.features = <em>mutableListOf</em>(feature)</code>. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-15.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="910" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-15.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-15.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-15.png 1600w, https://lengrand.fr/content/images/2025/05/image-15.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Junie getting lost with basic POJOs</span></figcaption></figure><p>I&apos;m not exactly sure why this is happening and I honestly also haven&apos;t dived very deep into it. I wonder if that isn&apos;t because <a href="https://github.com/uakihir0/kbsky?ref=lengrand.fr">kbsky</a> is both multiplatform as well as Java compatible and there is some confusing generation happening to get that compatibility. </p><p>To fix this, I&apos;ve had to write some of the code myself so the agent can learn from it. It&apos;s become pretty good at it now, but it also means that I don&apos;t trust the agent with the code as much as I&apos;d like to &#x1F60A;.  </p><h2 id="trust-but-verify">Trust, but verify</h2><p>Related, but tangential : It happens very often that Junie does some extra things for me that are completely unrelated to the task at hand. It can be changing some formatting, or updating a version, or changing a Gradle option. The changes aren&apos;t bad per se, but this also means that I have to be very careful about checking the output of the task when Junie is done. Those changes also can be in places that aren&apos;t really suitable for tests, making them harder to spot in any other way than a thorough code review.</p><p>Here is an example where Junie decided that checking for null fields wasn&apos;t enough. The fields also shouldn&apos;t be empty. This isn&apos;t a bad change per se, however it wasn&apos;t really done at the right moment. At least, it does inform me at the end of the task though so that&apos;s that. Not quite welcome if it had slipped, but also not terrible. I&apos;m on the edge on this one, I like the changes but it also makes me feel I can&apos;t quite trust the agent. What if those changes had large impact on my clients? </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-8.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="634" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-8.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-8.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-8.png 1600w, https://lengrand.fr/content/images/2025/05/image-8.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Junie decides that null isn&apos;t enough. Fields also shouldn&apos;t be empty.</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-9.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1378" height="604" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-9.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-9.png 1000w, https://lengrand.fr/content/images/2025/05/image-9.png 1378w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Junie informs me of the change</span></figcaption></figure><h2 id="junie-can-be-opiniated-about-the-names-locations-of-my-files">Junie can be opiniated about the names / locations of my files</h2><p>I&apos;ve had a few times where Junie would take my file, take its content and decide to place it somewhere else. It&apos;s also explicit about it, telling me my file is now deprecated. Even though the agent may be correct overall (maybe my naming isn&apos;t great. Maybe the file should be in another place), I don&apos;t quite like that it makes tracking history more difficult in the future. It also makes reviewing the changes harder. As usual, I&apos;d rather have this done in a separate step, not at the same time as functional changes. </p><p>Funnily enough, this can even happen mid task, where Junie moves the file around, finds a solution to the problem and moves everything back. I&apos;d love to know more about the reasoning behind this ^^. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-11.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="694" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-11.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-11.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-11.png 1600w, https://lengrand.fr/content/images/2025/05/image-11.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Clearly Junie didn&apos;t like the name of my file &#x1F60A;</span></figcaption></figure><h2 id="i-really-miss-the-option-to-refuse-part-of-the-solution">I really miss the option to refuse part of the solution</h2><p>At the moment, what I miss THE MOST, BY FAR about Junie compared to other similar agent is the option to accept / refuse part of a solution. When Junie thinks it&apos;s done, it will tell you. You can then decide to accept, refuse or tell it to try again.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-12.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1708" height="722" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-12.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-12.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-12.png 1600w, https://lengrand.fr/content/images/2025/05/image-12.png 1708w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Junie waiting for a decision </span></figcaption></figure><p>Now, I may be 90% OK with the solution, but want to remove the gradle options that it also added at the same time. I&apos;ve had a time where it added tests, and also a <a href="https://www.jitpack.io/?ref=lengrand.fr">Jitpack</a> configuration. I mean, thank you, but no?</p><p>Cursor is more fine grained in that regard, and will let you individually accept / refuse changes : </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-13.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1500" height="684" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-13.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-13.png 1000w, https://lengrand.fr/content/images/2025/05/image-13.png 1500w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Accept / Reject feature in cursor (Screenshot courtosy of </span><a href="https://www.datacamp.com/tutorial/cursor-ai-code-editor?ref=lengrand.fr"><span style="white-space: pre-wrap;">datacamp</span></a><span style="white-space: pre-wrap;">) </span></figcaption></figure><p>When I asked about this on the discord, the official answer was to use the commit window of IntelliJ to do this. This is a valid, but subpar answer imho. It also makes it more difficult for me to check whether the refused changes are breaking the complete task in any way. I have to switch to manual mode for this. There must be a way to do better.</p><h2 id="ask-mode-responses-are-great">Ask mode responses are great!</h2><p>The scratch files as an output to the ask mode are great. When you chat with Junie in ask mode, the output is a Markdown file that is placed in your <a href="https://www.jetbrains.com/help/idea/scratches.html?ref=lengrand.fr">scratches</a> folder. I really like this for several reasons :</p><ul><li>The output is structured and readable</li><li> It is easily shareable</li><li>I can reuse this as input for later tasks.</li></ul><p>I haven&apos;t tried this yet, but I also think that makes it for a very good start of keeping <a href="https://github.com/joelparkerhenderson/architecture-decision-record?ref=lengrand.fr">a log of decisions</a> if they were placed somewhere together with my source code.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-10.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="2000" height="721" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-10.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-10.png 1000w, https://lengrand.fr/content/images/size/w1600/2025/05/image-10.png 1600w, https://lengrand.fr/content/images/2025/05/image-10.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">A structured answer to a question I asked Junie</span></figcaption></figure><h2 id="junie-is-extremely-slow-in-my-experience-compared-to-other-tools-ive-used">Junie is extremely slow in my experience compared to other tools I&apos;ve used</h2><p>This is my main gripe with Junie at the moment honestly. Even for simple-ish requests (think, write a simple method to filter a data class ), Junie will take between 3 and 4 minutes to complete. I haven&apos;t benchmarked this, but it felt much longer than most other coding agents I&apos;ve tried. </p><p>This is understandable, given how it works. It will first make a plan, verify its plan across many files of the project, create the implementation, make sure the code builds, write tests for the method, run those tests. It usually will discover a couple bugs that way, reiterate and keep looping until it finally succeeds. This is pretty much how I, mere human, would also do it. (This is while using Brave mode by the way).</p><p>My issue with this is that during that whole time, I am not actually actively involved in the process. I will be needed soon to verify the implementation because I&apos;m not actually pair programming and seeing the code being written live I cannot be in a &quot;copilot seat&quot;. The pilot actually closes the door of the cockpit and reopens it when it thinks it&apos;s ready. </p><p>My issues with this is that it <em>really</em> disrupts my flow. During that time, I cannot quite start doing something else to keep the context fresh in my head. I also do not want to start checking email / slack because I want to keep in the flow. I haven&apos;t found a good way yet to use that time in a way that isn&apos;t disrupting. It has made me actually decide to not use Junie for many things several times. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-6.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="854" height="752" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-6.png 600w, https://lengrand.fr/content/images/2025/05/image-6.png 854w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Now that compiling Kotlin is fast again in IntelliJ I wait for Junie instead &#x1F604;</span></figcaption></figure><p>When spending 4/5 hours in the IDE, this spinning wheel really started grinding my gears after a while &#x1FAE4;.</p><p></p><h2 id="random-401s-when-leaving-the-ide-open-too-long">Random 401s when leaving the IDE open too long</h2><p>Not sure whether others experience this too, but Junie regularly lost contact with its servers and the only way to fix this was to restart IntelliJ completely. I never close my IDE, and rarely restart my computer and after a few days Junie tends to just give me an unknown error that won&apos;t go away even when reloading the plugin. Not a huge issue, CMD+Q CMD-Space are only a couple keystrokes but still, it&apos;s a mild annoyance. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2025/05/image-7.png" class="kg-image" alt="My experience using Junie for the past few months" loading="lazy" width="1372" height="220" srcset="https://lengrand.fr/content/images/size/w600/2025/05/image-7.png 600w, https://lengrand.fr/content/images/size/w1000/2025/05/image-7.png 1000w, https://lengrand.fr/content/images/2025/05/image-7.png 1372w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Random errors at time, forcing me to restart the IDE.</span></figcaption></figure><h2 id="the-need-for-structured-junie-guidelines">The need for structured Junie guidelines?</h2><h2 id> </h2><p>There are many <a href="https://docs.anthropic.com/en/prompt-library/library?ref=lengrand.fr">prompt</a> <a href="https://library.maastrichtuniversity.nl/apps-tools/ai-prompt-library/?ref=lengrand.fr">libraries</a> <a href="https://promptlibrary.org/?ref=lengrand.fr">out</a> there. I asked on the Junie discord where people were sharing their Junie guidelines. To my surprise, that doesn&apos;t really seem to be a thing today, and everyone mentioned they either didn&apos;t use any or they were too project specific. However, <a href="https://www.jetbrains.com/guide/ai/article/junie/intellij-idea/?ref=lengrand.fr">the first thing that the Junie guide mentions is to create those guidelines</a>.</p><p>This makes me wonder whether there is a need for a Junie guidelines library, or at least a place where people can share how they use this file. Because at the moment I feel like I&apos;m underutilising the tool and I could pick up great ideas from others.  </p><h2 id="in-summary-some-personal-tips">In summary, some personal tips :</h2><ul><li>Use a <a href="https://www.jetbrains.com/guide/ai/article/junie/intellij-idea/?ref=lengrand.fr">guidelines</a> file to personalize Junie as you want it.</li><li>Chat about large pieces of work with Junie, and then ask it to do the work piece by piece for better implementation results.</li><li>Don&apos;t ask Junie to just &quot;upgrade&quot; your project because it will be late on versions. Instead, check first and be specific about what you want.</li><li>Don&apos;t hesitate to switch between ask and code modes consciously, for best results. </li><li>If you find a way to stay actively in the flow while Junie is doing its thing, please let me know.</li><li>I really hope Junie becomes faster over time, because during long coding days it&apos;s sometimes a make or break situation for me, and I&apos;m likely to start using another agent in the future simply because of this.</li></ul>]]></content:encoded></item><item><title><![CDATA[You probably shouldn't hire a Developer Advocate yet]]></title><description><![CDATA[Many companies with job ads out for Developer Advocates don't know that's not what they should be hiring for (yet). More often than not, what they need first is some internal mindset change. And even then, a Developer Experience will go a long way to achieve the first stages of a healthy community.]]></description><link>https://lengrand.fr/you-probably-shouldnt-hire-a-developer-advocate-yet/</link><guid isPermaLink="false">67250be86ff08319f98bb09a</guid><category><![CDATA[devrel]]></category><category><![CDATA[strategy]]></category><category><![CDATA[community]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Sun, 03 Nov 2024 17:46:31 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2024/11/1500x500.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2024/11/1500x500.jpg" alt="You probably shouldn&apos;t hire a Developer Advocate yet"><p><em>TL;DR : Many companies searching for Developer Advocates probably shouldn&apos;t be hiring yet. More often than not, what they need first is some internal mindset change. And even then, Developer Experience will go a long way to achieve the first stages of a healthy community.</em></p><p>Many companies searching for Developer Advocates probably shouldn&apos;t be hiring yet. More often than not, what they need first is some internal mindset change. And even then, a Developer Experience will go a long way to achieve the first stages of a healthy community.</p><p>Note : I don&apos;t intend to describe what a Developer Advocate does in this post. <a href="https://www.swyx.io/measuring-devrel?ref=lengrand.fr">Others</a> have done it <a href="https://dev.to/wassimchegham/what-the-heck-is-a-developer-advocate-4l2a?ref=lengrand.fr">better than I could</a>. This article also both discusses internal as well as external Developer Advocacy challenges.</p><p>I&apos;ve been officially in the world of Developer Relations for just about 4 years now and I&apos;ve had multiple occasions where people would come up to me and asking me for advice on how to find their first Developer Advocate.<br>And even if there&apos;s many ways I could help them with that, my answer after chatting for some time is &quot;well you probably shouldn&apos;t&quot;. This blog is a list of a few of the reasons that keep coming back and my thoughts on it</p><p>&quot;Why do you folks want to hire a Developer Advocate?&quot;</p><h2 id="the-company-struggles-with-talent-retention">The company struggles with talent retention</h2><p>They struggle to keep their best profiles, who go on to find better pastures. They want to hire a developer advocate to improve this.</p><p>This one is always super interesting to me. When digging up more, it turns out that the reasons are typically pretty clear :</p><ul><li>It&apos;s a shame, we&apos;re a great company but we&apos;re losing out best people because the competition pays more.</li><li>Our best elements are leaving because they reach a ceiling and cannot be promoted any more unless they move to management</li><li>Our developers wants to write / speak at conferences / organise events but we don&apos;t have budget for them / our policies don&apos;t allow it.</li><li>Our developers build what they&apos;re told, it&apos;s the product team who decides <strong>what</strong> to build.</li></ul><p>A lot of the answers above cannot directly be solved by a Developer Advocate, and maybe even make them worse.<strong> A lot of the keys needed to solve the issues above lie in the HR realm.</strong><br>Improve your compensation packages, open up more growth opportunities for your tech profiles, rework your product / tech work relationship, ...<br>Yes, those may be longer projects and / or more expensive but believe me they&apos;re better than putting lipstick on a pig. Your engineers are smart and you&apos;ll do more harm than good.</p><h2 id="the-company-struggles-with-internal-upskilling">The company struggles with internal upskilling</h2><p>&quot;Our developers aren&apos;t upskilling and staying up to date with market trends and we need someone to bring that knowledge inside and motivate people to learn&quot;.</p><p>I find that one super interesting. As a developer in a company, I&apos;d be tremendously pissed if someone was paid to go to conferences in my stead and report to me on what I should learn.<br>Again, I find that sort of discussion fascinating :</p><ul><li>Do you not have a good internal candidate for this already? Why not?</li><li>Are your developers not upskilling, or are you simply not aware of it?</li><li>Humans are typically curious by nature. What makes it that they&apos;re not learning ? Do you have budget / opportunities in place? What does the workload look like?</li><li>And picking from the amazing &quot;<a href="https://www.amazon.com/Accelerate-Software-Performing-Technology-Organizations/dp/1942788339?ref=lengrand.fr">Accelerate</a>&quot; book : <strong>Do you have a culture of safety and experimentation in place? Do you lead by example</strong>? When was the last time you went on a conference or shared knowledge yourself?</li></ul><h2 id="the-company-struggles-with-hiring-a-players">The company struggles with hiring &quot;A&quot; players</h2><p>They want to speed up hiring and capture better profiles.</p><p>Typically my first question is &quot;what makes your current profiles not good enough&quot;? And we&apos;re typically back to the upskilling / retention discussion. <strong>What is an A player anyway?</strong></p><p>One of the things that I consistently discover and find super interesting is that there always seems to be a disconnect between the Marketing team who will be responsible for Tech Branding and the actual techies on the floor. And the problem only grows the larger the company is.<br>I&apos;ve literally been in discussions where people would say &quot;we need to hire influencers in the space&quot; only to answer &quot;really? How about this person in your company already who wrote 2 books on topic X. Or this person who spoke at 4 conferences the past year. Or this lady who happens to be a Java Champion? Or this other who literally organises part of the event you&apos;re sponsoring every year on their own time?&quot;</p><p>Sure you can hire a Developer Advocate to find out those internal profiles and nurture them, but<strong> it seems cheaper to make sure whoever is responsible for Tech Branding also happens to be close to the communities they want to reach</strong>, or even better be part of it &#x1F60A;.</p><p>Another point of discussion I raise usually is : <em>why</em> do you want A players? And <em>what does that mean</em>? <strong>Are your problems hard enough that you will be able to keep them sharp?</strong><br>I don&apos;t know anyone who will say openly that they want to hire less than great developers. Still, not all companies are born equal. Refining <em>what you mean exactly</em> will go a long way in finding the right profiles, and will also help line up expectations for new hires. <strong>There&apos;s nothing more damaging to your technical brand than advertising something and getting people who convert to realise they&apos;ve been fed incorrect information</strong>. Especially &quot;A players&quot;.</p><h2 id="consultancy-company-wants-to-be-more-visible-in-the-market">(Consultancy) Company wants to be more visible in the market</h2><p>This one is actually more frequent than I was expecting. (Consultancy) company, who makes a living selling hours or projects (not products) wants someone to speak at conferences / podcasts / write for them in order to increase their brand visibility (aka thought leadership).</p><p>That can make a lot of sense, and there&apos;s great examples of this on the market. That being said, it depends on the strategy being used. You want to be more visible, great! But <strong>visible to who</strong>? Who is the decision maker at your future customer? Which kind of events do those people participate in, where do they get their influences from, and from what kind of people ?<br>A lot of the times, the answer is probably close to C-level or directors. And I would then argue that your own C-level / directors probably would be the best leaders to create influence outside the company in that setting. They are the ones shaping the vision of the company, and spreading it out. At least that&apos;s where it should start from.</p><h2 id="small-company-has-a-technical-product-and-wants-to-market-it-to-techies">(Small) company has a (technical) product and wants to market it to techies</h2><p>Now, we&apos;re getting closer from the realm of what your typical Developer Advocate excels at. The request makes sense, but let&apos;s dive into the topic a little deeper.</p><p>The question &quot;What kind of activities do you want your Developer Advocate to do?&quot; are usually answered with :</p><ul><li>Go to conferences / man conference booths and talk about our products</li><li>Make videos and blogs about the product / new features</li><li>Respond to questions on Stack Overflow / Social Media</li><li>...</li></ul><p>With the typical goal of largely <strong>increasing the number of new accounts created / monthly entering developers.</strong></p><p>Now, that&apos;s where I love to discuss more about the product in itself. All the activities described make a lot of sense, but each activity also <a href="https://lengrand.fr/aaarrp-metrics-as-a-way-to-manage-expectations-up/">targets a specific part of the product funnel</a>. And before promoting the heck out of a project, you want to know if people will love the project.</p><p>Because <a href="https://www.swyx.io/measuring-devrel?ref=lengrand.fr">your real success metric actually probably is the number of monthly active developers</a> (ideally paying). If most of them are leaking out right after entering, you&apos;re going to struggle being successful.</p><p>Many developers simply don&apos;t go to conferences, and aren&apos;t going to read about you until they have a specific issue to fix. Conferences are also incidentally the most expensive activity one can think of, in straight up money but also time!</p><p>At this point we&apos;re entering the wonderful and exciting world of &#x2728;Developer Experience&#x2728;. </p><ul><li>Do you offer free accounts?</li><li>Are you using the expected standards in the industry in terms of APIs, WebHooks, ....</li><li>Do you have sample data for folks to get onboard quickly?</li><li>How accessible is your roadmap?</li><li>Are you already active in the community of the domain you&apos;re in ? Do you conform to their expectations? (Example : If you&apos;re into APM, do you support OpenTelemetry? If you&apos;re doing PostgreSQL, are you an active contributer?)</li></ul><p>The outcome of that discussion gives you a pretty clear idea of how far they are in the process, and how much can achieve joining them.</p><h2 id="in-conclusion">In conclusion</h2><p>I find all those discussions fascinating. They&apos;re all very important problems and they&apos;re typically things that executives spend a lot of brain cycles on. What I find most interesting is that <strong>very often those people <em>already</em> know what their real issue is</strong>. <strong>But it seems harder or more expensive to fix the root cause so they want to hire a Developer Advocate instead to tackle the issue.</strong></p><p>It&apos;s definitely possible to hire a consultant to help you shape your strategy, setup an action plan or more. I find it interesting that &quot;hire a developer advocate&quot; became some kind of catch all in a way. A superpower that will help solve underlying issues. This could absolutely work, but if the hire has the direct mandate to solve those issues and they typically cross many departments.</p><p>One more thing that I remark is that <strong>&quot;Developer Advocate&quot; often seems conflated with &quot;Developer Relations&quot;</strong> and many of the other fields adjacent to it. Developer Education, Developer Experience, Technical Product Management, ... these are all very valid needs, but they&apos;re not quite the same as &quot;Developer Advocacy&quot;.</p><p>Not an issue per se, it&apos;s all part of the journey to dive into this. Hiring someone without giving them the keys for being successful, in a field that is generally known for being hard to measure without going through the journey is a waste though (but that&apos;s a topic for a whole other post)!</p><p><br>Wanna chat, do you agree, disagree or think you have another challenge? Just hit me up &#x1F60A;! </p><p><em>Thanks </em><a href="https://floord.github.io/?ref=lengrand.fr"><em>Floor</em></a><em> for the insanely fast review </em></p>]]></content:encoded></item><item><title><![CDATA[Impressions on the remarkable 2, one month in]]></title><description><![CDATA[This article describes my experience with the Remarkable 2 after a month of usage. I am generally happy with it, though many things are lacking in my opinion.]]></description><link>https://lengrand.fr/impressions-on-the-remarkable-2-one-month-in/</link><guid isPermaLink="false">671ab3d86ff08319f98baf0d</guid><category><![CDATA[remarkable]]></category><category><![CDATA[eink]]></category><category><![CDATA[productivity]]></category><category><![CDATA[work]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Fri, 25 Oct 2024 16:44:02 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2024/10/IMG_9222-1.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2024/10/IMG_9222-1.jpeg" alt="Impressions on the remarkable 2, one month in"><p><em>TL;DR : I love the remarkable for reading articles and writing notes on books and I love the computer / website / tablet sync, but I  find the ecosystem relatively poor and I still prefer to take my notes on paper. I haven&apos;t tried the handwriting to text capabilities.</em></p><p><em>BTW, this isn&apos;t a sponsored post in any way (though do feel free to send your proposals, good folks at Remarkable xD).</em></p><p>I&apos;ve been eyeing the <a href="https://remarkable.com/?ref=lengrand.fr">Remarkable</a> tablet for a very long time. I&apos;m an avid reader, and I <a href="https://lengrand.fr/why-i-still-take-notes-on-paper/">still take a lot of notes on paper</a>. I also love to write and highlight things in the books I read, and I&apos;ve been a <a href="https://nl.kobobooks.com/collections/ereaders?ref=lengrand.fr">Kobo</a> user since pretty much forever. I&apos;ve never bought one though, because I just found it way too pricy for the capabilities.</p><h2 id="why-now">Why now</h2><p>But when they announced the remarkable pro couple months ago, I decided it was the right time to give it a shot. Many of the remarkable fans jumped on the new product and there was a lot of the second model online for relatively cheap.</p><p>I bought a 6 months old, still under warranty, close to new condition remarkable with 2 covers, the pen and pen tips for 375 euros (about a 45% discount on the store price). </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/10/IMG_9222.jpeg" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="1280" height="960" srcset="https://lengrand.fr/content/images/size/w600/2024/10/IMG_9222.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2024/10/IMG_9222.jpeg 1000w, https://lengrand.fr/content/images/2024/10/IMG_9222.jpeg 1280w" sizes="(min-width: 720px) 720px"></figure><h2 id="first-impressions">First impressions</h2><p>I like the tablet as much as I was expecting to. The product looks and feels qualitative. It&apos;s a pleasure to hold the pen. The interface is snappy. The magnet and covers are great and it transports easily. </p><h3 id="reading">Reading </h3><p>The (active) reading experience is the aspect of the tablet I love the most. I really like my Kobo, especially as it fits in my trouser backpocket but reading larger format books or PDFs in general is a pain. </p><p>The experience on the remarkable is ... well ... remarkable. The screen is high quality enough that I feel like I&apos;m pretty much reading a paper A4 format book. Highlighting snaps to sentences and it feels great when reading. Its also very nice to quickly be able to tag pages, write some notes at the bottom, ... Basically stuff I do with a paper book but in a way that I can reuse easier later. </p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/_5bLF92oHXg?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Highlighting article - Remarkable article"></iframe></figure><p>I have an iPad as well, but in my experience eink displays are just so much more pleasant to read on the difference is extremely noticeable. I&apos;ll talk more about it later but reading shorter articles / publications is the thing I think the remarkable excels at. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/10/IMG_9225.jpeg" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="960" height="1280" srcset="https://lengrand.fr/content/images/size/w600/2024/10/IMG_9225.jpeg 600w, https://lengrand.fr/content/images/2024/10/IMG_9225.jpeg 960w" sizes="(min-width: 720px) 720px"></figure><p>I do dearly miss the presence of a backlight, like any e-reader has since forever. I can live without it, but being able to read books in the dark is one of the unique selling points of ebooks compared to good old paper and I find it a shame not to find it there.</p><h3 id="drms">DRMs </h3><p>One small, but relevant item I learnt that hard way is that the tablet does not support files with DRM. That is logical, given the product is then encrypted to the dive but that also means that you cannot buy an ebook anywhere and expect it to be readable on your tablet. For example for me in the Netherlands <a href="https://bol.com/?ref=lengrand.fr">Bol.com</a> is a no go. (And most cases you can&apos;t get ebooks reimbursed, given they&apos;re digital products).</p><h3 id="writing">Writing</h3><p>In my opinion, the writing experience is .... alright. I&apos;ve tried the iPad / <a href="https://paperlike.com/?ref=lengrand.fr">paperlike</a> combo in the past and didn&apos;t quite like it. At least the remarkable pen feels great (it&apos;s not smooth) and the tablet is very thin compared to an iPad so you feel like your hand is on a notebook. But it&apos;s still a screen, and in my personal experience the input lag is slightly noticeable which isn&apos;t pleasant. </p><p>I also don&apos;t quite like the fact that the angle you put the pen on the tablet changes the thickness of the writing. I get it, but the effect is bolder than what I&apos;d get with a real pen and it annoys me. </p><p>I&apos;ll come back to it later in the UI part but I&apos;m left handed and it&apos;s SUPER annoying  that the &quot;close&quot; button is basically located where you hand lies on the tablet. I was going crazy closing my documents by mistake. Turns out <a href="https://www.reddit.com/r/RemarkableTablet/comments/l0ls1a/page_closing_button_x_and_left_handed_mode/?ref=lengrand.fr">it&apos;s a known problem</a> and the only actual &quot;solution&quot; is to actively hide the UI...</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/1Z1fW-Objv8?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Left hand closing page - Remarkable article"></iframe><figcaption><p><span style="white-space: pre-wrap;">My left hand closing the page (hard to simulate with a phone in my hand :P)</span></p></figcaption></figure><p>I know my handwriting is pretty bad, so I&apos;m not having much hope for the handwriting to text conversion, though I tried it just for this post. As expected, it&apos;s OK, but far from good enough for me to make use of it. Can&apos;t blame them completely though, my writing is bad. But if I have to care about my handwriting at all times or rewrite half my notes, it&apos;s just as good as nothing.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/10/Notebook---page-1.png" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="1404" height="1872" srcset="https://lengrand.fr/content/images/size/w600/2024/10/Notebook---page-1.png 600w, https://lengrand.fr/content/images/size/w1000/2024/10/Notebook---page-1.png 1000w, https://lengrand.fr/content/images/2024/10/Notebook---page-1.png 1404w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">My handwriting</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/10/Notebook---page-2.png" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="1404" height="1872" srcset="https://lengrand.fr/content/images/size/w600/2024/10/Notebook---page-2.png 600w, https://lengrand.fr/content/images/size/w1000/2024/10/Notebook---page-2.png 1000w, https://lengrand.fr/content/images/2024/10/Notebook---page-2.png 1404w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">And the automatic conversion</span></figcaption></figure><p>In any case, it&apos;s not much of an issue for me, since <a href="https://lengrand.fr/why-i-still-take-notes-on-paper/" rel="noreferrer">I don&apos;t take notes to remember later, but to remember now</a>.</p><h3 id="the-ui">The UI</h3><p>I don&apos;t dislike the interface of the Remarkable, but I can&apos;t say I love it either. It&apos;s barebones (which is a good thing in my opinion) but it feels clunky to me at times too.</p><p>(I don&apos;t pretend to be a pro , so please correct me if there&apos;s obvious things I missed)</p><p>I try to take one notes per meeting, and to create a new note I need to click at least at 3 different places in the UI, maybe in a folder too, AND pick the name of my notes. </p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="113" src="https://www.youtube.com/embed/-5L8o8nRl2E?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Creating a new note - Remarkable article"></iframe><figcaption><p><span style="white-space: pre-wrap;">Creating a new note... Not without friction</span></p></figcaption></figure><p>Any note application I&apos;ve used in the past 10 years let&apos;s you start taking notes first, and setup later.</p><figure class="kg-card kg-embed-card kg-card-hascaption"><iframe width="200" height="150" src="https://www.youtube.com/embed/Eirl6ijHWfM?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" referrerpolicy="strict-origin-when-cross-origin" allowfullscreen title="Apple Notes - Remarkable article"></iframe><figcaption><p><span style="white-space: pre-wrap;">With Apple Notes, I&apos;m directly productive</span></p></figcaption></figure><p>I use folders to take notes, and I constantly need to click in and out of the folders. As far as I can tell, drag and drop isn&apos;t supported. </p><p>As I was saying earlier, the UI is also useful, but you can hit it by accident when writing and that is super frustrating (and distracting) half way through a meeting.</p><p>That being said, I LOVE the (web and native) application two way sync. It&apos;s super nice to just be able to drop anything on the website and have it ready to be read in the train (though, obviously your tablet has to have access to the internet). In the same way, taking notes all day and seeing them in the web environment is pretty cool.</p><p>It&apos;s 2024, and I would love to have a folder &quot;&#xE0; la Dropbox&quot; (or even a <a href="https://myemail.constantcontact.com/What-s-New--Books-in-your-Dropbox--3-MEAPs--and-2-pBooks-.html?soid=1101335703814&amp;aid=vgLAE0q0nTs&amp;ref=lengrand.fr">Dropbox integration like Manning books supports</a> since 2015(!!)) that auto syncs local content from my computer with the tablet and back, but I guess that&apos;s a bridge too far &#x1F60A;. </p><h2 id="the-ecosystem">The ecosystem</h2><p>I have quite a few things to say about the ecosystem, in the generic sense of the word. </p><h3 id="remarkable-connect">Remarkable connect</h3><p>First, I find it quite annoying to have to pay for a monthly service when already buying a premium piece of hardware which sets you 500 euros back. </p><p>Sure, you don&apos;t <em>need</em> to pay for <a href="https://remarkable.com/store/connect?ref=lengrand.fr">connect</a>; but then you don&apos;t get your notes synced up... So you&apos;re basically back to having a glorified ebook. </p><p>I DO understand that the company needs to make a living, and I&apos;m totally fine with their &quot;50 days&quot; free tier limitation. But come on, at least add the notes to it...</p><p>That being said, it&apos;s only a few euros a month so at least we&apos;re not talking about yet the price of another Netflix subscription.</p><p>I don&apos;t use any of the other features of connect, so I can&apos;t talk much about them.</p><h3 id="hardware-anyone">Hardware, anyone?</h3><p>Another gripe of mine is that the only keyboard that Remarkable officially supports is their own <strong>220 euros</strong> <a href="https://remarkable.com/store/remarkable-2/type-folio?ref=lengrand.fr">type folio</a>.</p><p>What&apos;s more interesting to me is that the tablet is literally equipped with a USB-C connector and the pins at the back of the tablet are also the pins of a USB connector. Which means that folks are literally able to <a href="https://codeberg.org/veryapt/remarkable-2-pogo-to-usb-adapter?ref=lengrand.fr">3D print and reverse engineer support for a keyboard themselves</a>. </p><p>Looks to me like it is a conscious decision to sustain lockin, which is a shame really. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://codeberg.org/veryapt/remarkable-2-pogo-to-usb-adapter/media/branch/main/images/back.jpg" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="864" height="1296"><figcaption><span style="white-space: pre-wrap;">Source </span><a href="https://codeberg.org/veryapt/remarkable-2-pogo-to-usb-adapter?ref=lengrand.fr"><span style="white-space: pre-wrap;">https://codeberg.org/veryapt/remarkable-2-pogo-to-usb-adapter</span></a></figcaption></figure><p>In the same way, the tablet currently doesn&apos;t support bluetooth (which could have been another way to support input devices). It&apos;s not a large issue, but would have been appreciated.</p><h3 id="integrations">Integrations</h3><p>Another thing I find surprisingly lacking is the relative lack of integrations with the tablet. </p><p>I&apos;m an avid <a href="https://getpocket.com/home?ref=lengrand.fr">Pocket</a> user for example, and synchronising pocket articles on the Remarkable requires setting up <a href="https://github.com/nov1n/RemarkablePocket?ref=lengrand.fr" rel="noreferrer">your own sync server</a> (Thanks Open-source once more!). This is something that <a href="https://help.kobo.com/hc/en-us/articles/360017763753-Use-the-Pocket-App-with-your-Kobo-eReader?ref=lengrand.fr">Kobo supports</a> since forever and I was surprised to not find it available. Same for Dropbox, agenda features, Google Drive, or anything really. </p><p>(EDIT : <a href="https://support.remarkable.com/s/article/Integrations?ref=lengrand.fr">Remarkable actually supports Google Drive and Dropbox</a>, but I just had no idea until someone pointed it out to me when reviewing the article. I don&apos;t think I received any information about it, and it seems only possible via the website so I never actually saw the option ^^. My other remarks stay valid)</p><p>(EDIT2: It&apos;s actually crazy, they have a &quot;save articles on your tablet&quot; extension as well, but none of those integrations are visible on the tablet or app themselves, only at the bottom of their website. Completely missed them... Installed and trying it out to see if it can replace pocket in the future).</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/10/image-1.png" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="2000" height="470" srcset="https://lengrand.fr/content/images/size/w600/2024/10/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2024/10/image-1.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/10/image-1.png 1600w, https://lengrand.fr/content/images/size/w2400/2024/10/image-1.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Turns out everything is possible, but they&apos;re quite literally all informal and <a href="https://github.com/reHackable/awesome-reMarkable?ref=lengrand.fr">made by the community</a>. I hear Remarkable is a small company and we can&apos;t ask them all; but I&apos;d like them at least supporting those efforts in some way or even better : centralize them.</p><h3 id="templates">Templates</h3><p>Another thing I didn&apos;t expect when I started using the tablet is the sheer amount of people using custom page templates. There is quite literally <a href="https://www.remarkabletemplates.com/?ref=lengrand.fr">a parallel industry</a> around these. Some of them <a href="https://hyperpaper.me/?ref=lengrand.fr">cost up to 40 euros</a>! </p><p>It&apos;s always fascinating to me to see how products can create new market opportunities, with value in them. One thing I wished, just like for integrations, is to see Remarkable recognise and support them (whatever that may mean). </p><p>A great place of inspiration for me is how Notion offers a gigantic list of <a href="https://www.notion.so/templates?ref=lengrand.fr">community templates</a> for example, but also a directory of <a href="https://www.notion.so/consulting-partners/create-a-project?ref=lengrand.fr">Notion partners</a> you can contact (and who make a living of it!).</p><h3 id="tiny-sloppy-things-that-accumulate">Tiny sloppy things that accumulate</h3><p>This is something that has no relation with the product, but I find a good illustration of my general feeling of the remarkable product. When logging in my account with Apple, I got an email telling me they detected the login with a link to my account to approve / refuse it.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/10/Screenshot-2024-10-24-at-23.26.34.png" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="1888" height="1462" srcset="https://lengrand.fr/content/images/size/w600/2024/10/Screenshot-2024-10-24-at-23.26.34.png 600w, https://lengrand.fr/content/images/size/w1000/2024/10/Screenshot-2024-10-24-at-23.26.34.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/10/Screenshot-2024-10-24-at-23.26.34.png 1600w, https://lengrand.fr/content/images/2024/10/Screenshot-2024-10-24-at-23.26.34.png 1888w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The remarkable email</span></figcaption></figure><p>The email looks great, and came in under a second. Except the myremarkable.com domain isn&apos;t active, and we&apos;re basically one step away from being scammed. The actual URL is <a href="https://my.remarkable.com/?ref=lengrand.fr">my.remarkable.com</a>.</p><p>When contacting support to mention it, I received no confirmation a ticket was created, only a direct answer that contained no information about my request.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/10/image.png" class="kg-image" alt="Impressions on the remarkable 2, one month in" loading="lazy" width="1882" height="368" srcset="https://lengrand.fr/content/images/size/w600/2024/10/image.png 600w, https://lengrand.fr/content/images/size/w1000/2024/10/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/10/image.png 1600w, https://lengrand.fr/content/images/2024/10/image.png 1882w" sizes="(min-width: 720px) 720px"></figure><p>It&apos;s well intended, but honestly a bit sloppy and a bad look (and if they don&apos;t own myremarkable.com; could actually be straight harmful).</p><h2 id="conclusion">Conclusion</h2><p>The article is longer than I expected and I feel like I spent a lot of time describing what I <em>didn&apos;t like</em> in the product. </p><p>Overall, I love my remarkable. It&apos;s really a GREAT tool to read articles and practise active reading. That being said, it also leaves a sour taste in my mouth because I feel like there&apos;s huge amount of potential to develop the ecosystem that isn&apos;t tapped into. And for the price of the product, I find it hard to accept it. I paid less than 100 euros for my Kobo; about 7 years ago. I paid about 2/3 of the price of my iPad for this tablet. But my iPad comes with a complete app store and all the Mac ecosystem. I hope I can say the same for Remarkable.</p><p>For now, I&apos;ll keep using it to read articles; and write notes on paper like before...</p><p></p><p></p>]]></content:encoded></item><item><title><![CDATA[Hosting Kotlin applications using Coolify]]></title><description><![CDATA[TL;DR : With Coolify you can host you Kotlin applications in seconds on your own server and benefit from auto deploys, custom domains, preview branches and more. You can see the code here, and access the sample here.]]></description><link>https://lengrand.fr/hosting-kotlin-applications-using-coolify/</link><guid isPermaLink="false">666817db6ff08319f98bac0c</guid><category><![CDATA[kotlin]]></category><category><![CDATA[ci]]></category><category><![CDATA[coolify]]></category><category><![CDATA[docker]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Tue, 11 Jun 2024 10:37:11 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2024/06/Screenshot-2024-06-11-at-12.36.26.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2024/06/Screenshot-2024-06-11-at-12.36.26.png" alt="Hosting Kotlin applications using Coolify"><p><em>TL;DR : With </em><a href="https://coolify.io/?ref=lengrand.fr" rel="noreferrer"><em>Coolify</em></a><em> you can host you Kotlin applications in seconds on your own server and benefit from auto deploys, custom domains, preview branches and more. You can see the code </em><a href="https://github.com/jlengrand/ktor-sample-coolify?ref=lengrand.fr"><em>here</em></a><em>, and access the sample </em><a href="http://p4o4s0g.138.201.122.228.sslip.io/?ref=lengrand.fr"><em>here</em></a><em>.</em></p><p>Lately, I&apos;ve been increasingly thinking about the fact that all of my applications / experiments are spread across providers (Supabase, AWS, Koyeb, Digital Ocean, ...) and I&apos;ve been toying with the idea of owning all of this back on my own servers. After discovering <a href="https://www.hetzner.com/sb/?ref=lengrand.fr" rel="noreferrer">Hetzner auction servers</a>, I realised that I could have a super beefy server for very cheap and decided to try it out.</p><p>Installing <a href="https://coolify.io/?ref=lengrand.fr" rel="noreferrer">Coolify</a> is as simple as running <code>$ curl -fsSL https://cdn.coollabs.io/coolify/install.sh | bash</code> on your server.</p><h2 id="a-sample-kotlin-application">A sample Kotlin application</h2><p></p><p>For this test, I&apos;ll go to the <a href="https://start.ktor.io/?ref=lengrand.fr" rel="noreferrer">Ktor Starter</a> website and create the simplest application I can think of.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1062" height="1542" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image.png 1000w, https://lengrand.fr/content/images/2024/06/image.png 1062w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">My minimal ktor application configuration</span></figcaption></figure><p>I&apos;ll then unzip the repo and create a GitHub repository from it (using the <a href="https://cli.github.com/?ref=lengrand.fr">GitHub CLI</a>, get it if you don&apos;t have it yet it&apos;s awesome &#x1F60A;).</p><pre><code>$ unzip ktor-sample-coolify.zip -d ktor-sample-coolify
$ cd ktor-sample-coolify
$ gh repo create . 
## Some setup, and final repository push to GitHub
</code></pre><p>Once that is done, I can access my repository <a href="https://github.com/jlengrand/ktor-sample-coolify/?ref=lengrand.fr">here</a>.</p><h2 id="creating-a-coolify-github-application">Creating a Coolify GitHub application</h2><p>We have to deploy this application to Coolify now. There are several ways to do it, but the most powerful one will be via a <a href="https://coolify.io/docs/knowledge-base/git/github/integration?ref=lengrand.fr">GitHub app</a>, we&apos;ll see why very soon.</p><p>To do this, we&apos;ll add a new GitHub app source to Coolify.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-1.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1834" height="1364" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-1.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-1.png 1600w, https://lengrand.fr/content/images/2024/06/image-1.png 1834w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The GitHub app dialog</span></figcaption></figure><p></p><p>It will then ask us which features we want to activate, and we&apos;ll be redirected to GitHub to approve the creation of the app, and then requested to select which repositories to apply this application to (I selected them all, but you can also choose to segregate better and only add the one repository we created earlier).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-2.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1950" height="456" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-2.png 1600w, https://lengrand.fr/content/images/2024/06/image-2.png 1950w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Registering a new GitHub app</span></figcaption></figure><p>Deploying our Ktor application</p><p>Now that the connection between Coolify and GitHub is setup, we want to deploy our Ktor application. To do this, we create a new resource and select the Private repository with GitHub option.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-3.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="2000" height="644" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-3.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-3.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-3.png 1600w, https://lengrand.fr/content/images/2024/06/image-3.png 2000w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Menu for creating a new resource</span></figcaption></figure><p>I&apos;m not going to show you all of the dialogs, but you&apos;ll need to select which server to deploy on, which GitHub app to use and then which repository to choose.</p><p>Once all of this is done, we&apos;ll have access to our deployment configuration. We&apos;ll select the <code>main</code> branch for the deployment, and the port 8080 which is the default Ktor port.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-4.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1952" height="788" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-4.png 1600w, https://lengrand.fr/content/images/2024/06/image-4.png 1952w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Basic configuration for our Ktor deployment</span></figcaption></figure><p>Once that is done, we can hit the <strong>deploy</strong> button. By the way, we can also very much appreciate the fact that Coolify will use <a href="https://sslip.io/?ref=lengrand.fr" rel="noreferrer">sslip.io</a> to generate a domain URL for your app without you having to setup anything (Granted, it&apos;s not the URL we want but it&apos;s so much better than an IP address and port combination).</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-6.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1944" height="994" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-6.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-6.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-6.png 1600w, https://lengrand.fr/content/images/2024/06/image-6.png 1944w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Sslip domain generated for us by Coolify</span></figcaption></figure><h3 id="first-roadblock-invalid-nixpack-start-command">First roadblock : Invalid Nixpack start command</h3><p>Once thing that I haven&apos;t mentioned yet here is that our Ktor sample application does not have any kind of DockerFile. Nixpacks will magically detect which kind of project it is yet, and start building it, running gradle tests, building the project and creating a Docker deployment based on its own inference. I didn&apos;t know about this yet, and honestly I was &#x1F92F;.</p><p>The issue though, is that our deployment fails :</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-7.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1818" height="482" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-7.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-7.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-7.png 1600w, https://lengrand.fr/content/images/2024/06/image-7.png 1818w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">No main manifest attribute found error</span></figcaption></figure><p>Now, that&apos;s a very well known error for any seasoned JVM developer I think &#x1F60A;. We can spot the issue rather quickly when investigating the logs. Here is the commands that Nixpack will use to build/deploy our project : </p><pre><code class="language-bash">&#x2554;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550; Nixpacks v1.24.1 &#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2557;
&#x2551; setup &#x2502; jdk17, gradle, curl, wget &#x2551;
&#x2551;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2551;
&#x2551; build &#x2502; ./gradlew clean build -x check -x test &#x2551;
&#x2551;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2500;&#x2551;
&#x2551; start &#x2502; java $JAVA_OPTS -jar $(ls -1 build/libs/*jar | grep -v plain) &#x2551;
&#x255A;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x2550;&#x255D;

</code></pre><p>The issue, however, is that we actually build 2 jars during our build step, and Nixpack runs the incorrect one in <a href="https://nixpacks.com/docs/providers/java?ref=lengrand.fr">its start phase</a>. This is not a Ktor only issue by the way, <a href="https://github.com/railwayapp/nixpacks/issues/979?ref=lengrand.fr">it seems to happen for Spring boot too</a>.</p><pre><code class="language-bash"> &#xF179; &#x2571; &#xF07C; ~/Dev/ktor-sample-coolify/b/libs &#x2571; &#xF113; &#xF126; feat/automat&#x2026;-branch-test &#xE0B0; ls -la                                                                                         &#xE0B2; &#x2714; &#x2571; 12:00:46 &#xF017;
total 30584
drwxr-xr-x   7 julienlengrand-lambert  staff       224 Jun 10 15:23 .
drwxr-xr-x  11 julienlengrand-lambert  staff       352 Jun 10 15:22 ..
-rw-r--r--@  1 julienlengrand-lambert  staff      6148 Jun 10 15:23 .DS_Store
drwx------   7 julienlengrand-lambert  staff       224 Jun 10 15:23 quest.lengrand.ktor-sample-coolify-0.0.1
-rw-r--r--@  1 julienlengrand-lambert  staff      5193 Jun 10 15:21 quest.lengrand.ktor-sample-coolify-0.0.1.jar
drwx------  18 julienlengrand-lambert  staff       576 Jun 10 15:23 quest.lengrand.ktor-sample-coolify-all
-rw-r--r--@  1 julienlengrand-lambert  staff  15638896 Jun 10 15:22 quest.lengrand.ktor-sample-coolify-all.jar</code></pre><p>We have 2 ways to fix this : </p><ul><li>Create a <code>nixpacks.toml</code> to customize the start command</li><li>Change the Coolify configuration and set the start command to <code>./gradlew start</code></li></ul><p>I&apos;ve chosen the latter for simplicity this time.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-8.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1406" height="160" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-8.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-8.png 1000w, https://lengrand.fr/content/images/2024/06/image-8.png 1406w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Custom start command in Coolify</span></figcaption></figure><p>We change, save and press <strong>deploy</strong> again.</p><h3 id="second-roadblock-issue-with-healthchecks">Second roadblock : Issue with healthchecks</h3><p>Deployment somehow fails again. This time, it seems to be due to the automated healthchecks from Coolify to indicate that the application is unhealthy. And the default behaviour for Coolify <a href="https://coolify.io/docs/knowledge-base/traefik/healthcheck?ref=lengrand.fr">is to 404 any traffic to unheathly applications</a>.</p><pre><code class="language-bash">[COMMAND] docker inspect --format=&apos;{{json .State.Health.Status}}&apos; xc8g0s0-100357313316
[OUTPUT]
&quot;unhealthy&quot;

[2024-Jun-11 10:06:05.645616]

[COMMAND] docker inspect --format=&apos;{{json .State.Health.Log}}&apos; xc8g0s0-100357313316
[OUTPUT]
[{&quot;Start&quot;:&quot;2024-06-11T12:05:43.755413473+02:00&quot;,&quot;End&quot;:&quot;2024-06-11T12:05:43.808713519+02:00&quot;,&quot;ExitCode&quot;:1,&quot;Output&quot;:&quot;&quot;},{&quot;Start&quot;:&quot;2024-06-11T12:05:48.809795855+02:00&quot;,&quot;End&quot;:&quot;2024-06-11T12:05:48.8473437+02:00&quot;,&quot;ExitCode&quot;:1,&quot;Output&quot;:&quot;&quot;},{&quot;Start&quot;:&quot;2024-06-11T12:05:53.848150166+02:00&quot;,&quot;End&quot;:&quot;2024-06-11T12:05:53.985646744+02:00&quot;,&quot;ExitCode&quot;:1,&quot;Output&quot;:&quot;&quot;},{&quot;Start&quot;:&quot;2024-06-11T12:05:58.986804475+02:00&quot;,&quot;End&quot;:&quot;2024-06-11T12:05:59.036324085+02:00&quot;,&quot;ExitCode&quot;:1,&quot;Output&quot;:&quot;&quot;},{&quot;Start&quot;:&quot;2024-06-11T12:06:04.037004721+02:00&quot;,&quot;End&quot;:&quot;2024-06-11T12:06:04.07944818+02:00&quot;,&quot;ExitCode&quot;:1,&quot;Output&quot;:&quot;&quot;}]

[2024-Jun-11 10:06:05.648560] Attempt 10 of 10 | Healthcheck status: &quot;unhealthy&quot;
[2024-Jun-11 10:06:05.651092] Healthcheck logs: (no logs) | Return code: 1
[2024-Jun-11 10:06:05.653988] ----------------------------------------
[2024-Jun-11 10:06:05.656408] Container logs:
[2024-Jun-11 10:06:05.745223] Downloading https://services.gradle.org/distributions/gradle-8.4-bin.zip
............10%............20%.............30%............40%.............50%............60%.............70%............80%.............90%............100%

Welcome to Gradle 8.4!

Here are the highlights of this release:
- Compiling and testing with Java 21
- Faster Java compilation on Windows
- Role focused dependency configurations creation

For more details see https://docs.gradle.org/8.4/release-notes.html

Starting a Gradle Daemon (subsequent builds will be faster)
[2024-Jun-11 10:06:05.748707] ----------------------------------------
[2024-Jun-11 10:06:05.751778] Removing old containers.
[2024-Jun-11 10:06:05.754548] New container is not healthy, rolling back to the old container.
[2024-Jun-11 10:06:06.335355] Rolling update completed.</code></pre><p>I haven&apos;t found the solution for this just yet, so I&apos;ve decided to disable healthchecks for now. We press <strong>deploy</strong> again.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-9.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="970" height="862" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-9.png 600w, https://lengrand.fr/content/images/2024/06/image-9.png 970w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Disabling healthchecks</span></figcaption></figure><p>That&apos;s it, this time we&apos;re in business! If we go to the generated URL, our application answers as expected</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-10.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1376" height="626" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-10.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-10.png 1000w, https://lengrand.fr/content/images/2024/06/image-10.png 1376w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Hello world!</span></figcaption></figure><h2 id="where-the-magic-begins">Where the magic begins! </h2><p>Now that our configuration is valid, we can benefit from all the magic that a <a href="https://fly.io/?ref=lengrand.fr">Fly.io</a>, <a href="https://www.koyeb.com/?ref=lengrand.fr">Koyeb</a> or any other cloud provider can offer us, but on our own terms!</p><h3 id="preview-pull-requests">Preview Pull requests</h3><p>One of the features I love the most is preview pull requests. We create a new branch with a custom endpoint:</p><pre><code class="language-kotlin">fun Application.configureRouting() {
    routing {
      ...
        get(&quot;/mood/{mood}&quot;){
            call.respondText(&quot;Are you feeling ${call.parameters[&quot;mood&quot;]}?&quot;)
        }
    }
}
</code></pre><p>We commit, push the new branch and create a Pull Request. Coolify will automatically detect this, start a new deployment and generate a new SSlip URL, all of this while our main deployment is still running! You can see this happen <a href="https://github.com/jlengrand/ktor-sample-coolify/pull/2?ref=lengrand.fr">here</a>.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-11.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1772" height="612" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-11.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-11.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/06/image-11.png 1600w, https://lengrand.fr/content/images/2024/06/image-11.png 1772w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Deployment after a new Pull Request is created</span></figcaption></figure><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-12.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1388" height="402" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-12.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-12.png 1000w, https://lengrand.fr/content/images/2024/06/image-12.png 1388w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Dedicated URL for the Pull Request, in parallel to the main deployment</span></figcaption></figure><p>And just like expected, you will also have access to information about the deployment directly in your Pull Request: </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/06/image-13.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1426" height="678" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-13.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-13.png 1000w, https://lengrand.fr/content/images/2024/06/image-13.png 1426w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Coolify keeping me updated about the deployment straight from my PR</span></figcaption></figure><p>Similarly, any merge / commit to main trigger a new deployment, so you can basically have CI/CD with a great Developer Experience, all of that on your own premises.</p><p>There you go, <a href="https://ktor.sample.lengrand.quest/mood/happy?ref=lengrand.fr"><strong>I&apos;m feeling happy</strong></a>.  &#x1F60A;</p><h3 id="custom-secure-domains">Custom secure domains </h3><p>This is not the object of today&apos;s article, but adding <a href="https://coolify.io/docs/knowledge-base/domains?ref=lengrand.fr">custom domains</a> to Coolify is also very simple and the tool will take care of all the SSL certificates setup / renewal for you automagically so it takes seconds to create shareable domains, including multiple wildcards. For example, you can access my app <a href="https://ktorsample.lengrand.quest/?ref=lengrand.fr">here</a>, <a href="https://ktor.sample.lengrand.quest/?ref=lengrand.fr">here</a> and <a href="http://p4o4s0g.138.201.122.228.sslip.io/?ref=lengrand.fr">here</a> too and all I had to do was change the &quot;Domains&quot; input and restart.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/06/image-14.png" class="kg-image" alt="Hosting Kotlin applications using Coolify" loading="lazy" width="1402" height="806" srcset="https://lengrand.fr/content/images/size/w600/2024/06/image-14.png 600w, https://lengrand.fr/content/images/size/w1000/2024/06/image-14.png 1000w, https://lengrand.fr/content/images/2024/06/image-14.png 1402w" sizes="(min-width: 720px) 720px"></figure><h2 id="a-word-of-conclusion">A word of conclusion</h2><p>You know me by now, I&apos;m a sucker for good DevEx. And I usually love to share my excitement for new tooling, which is why I&apos;m a big fan of tools like <a href="https://supabase.com/?ref=lengrand.fr">Supabase</a>, <a href="https://www.tinybird.co/?ref=lengrand.fr">TinyBird</a>, <a href="https://www.koyeb.com/?ref=lengrand.fr">Koyeb</a> or <a href="https://www.digitalocean.com/?ref=lengrand.fr">Digital Ocean</a>.</p><p>What impresses me A LOT here though, is that all of this is available for free locally as well, and is mainly developed by <a href="https://x.com/heyandras?ref=lengrand.fr"><strong>a single person</strong></a><strong>. </strong>I honestly wish the very best to Andras and will definitely be supporting his work further. </p><p>I could deploy a complete application within an hour using lots of tooling I have no experience about, and without having to read any documentation. That allows me to just focus on writing my application, and I just love this.</p><p>Now, there&apos;s still a lot I want to explore. Obviously, we need to fix those healthchecks. I also want to create a more &quot;production like&quot; application, with a database, observability setup and more, but we&apos;ll see this soon, I have total confidence I can figure it out! &#x1F60A;</p><p>Try out Coolify, it&apos;s worth it!</p>]]></content:encoded></item><item><title><![CDATA[🎉Celebrating Kotlin 2.0🎉]]></title><description><![CDATA[Upgrading a simple Kotlin and PicoCLI project to Kotlin 2.0 in under 5 minutes to celebrate the new version coming up!]]></description><link>https://lengrand.fr/celebrating-kotlin-2-0/</link><guid isPermaLink="false">664e67b702fb1c43eb7ff206</guid><category><![CDATA[kotlin]]></category><category><![CDATA[jvm]]></category><category><![CDATA[java]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Wed, 22 May 2024 22:22:53 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1530103862676-de8c9debad1d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHBhcnR5fGVufDB8fHx8MTcxNjQxNDM5NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1530103862676-de8c9debad1d?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDF8fHBhcnR5fGVufDB8fHx8MTcxNjQxNDM5NHww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;"><p>Upgrading a simple Kotlin and PicoCLI project to Kotlin 2.0 in under 5 minutes. <em>TL;DR: You can see the diff </em><a href="https://github.com/jlengrand/swacli/pull/8/files?ref=lengrand.fr#diff-49a96e7eea8a94af862798a45174e6ac43eb4f8b4bd40759b5da63ba31ec3ef7"><em>here</em></a><em>.</em></p><h2 id="introduction">Introduction</h2><p>As you may or may not know, KotlinConf will be kicking off tomorrow and Kotlin 2.0 will be announced during the Keynote. I can&apos;t be there this year, but I&apos;m celebrating in my own way by upgrading one of my projects tonight &#x1F60A;. I&apos;ll be upgrading <a href="https://github.com/jlengrand/swacli/pull/8?ref=lengrand.fr">SwaCLI</a>, a StarWars CLI demo app I&apos;ve used in talks to demo the amazing PicoCLI project.</p><p>I have been following the whole 2.0 project from very far away, having mostly done Java for the past months, so I really don&apos;t know what to expect. Hopefully a smooth experience!</p><p>First, this is what SwaCLI does : It lists Star Wars planets or characters, in many different ways. This is the paginated version</p><p>When running <code>$ swacli planets</code> for example, it gives us a sorted list of planets.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/05/image-1.png" class="kg-image" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;" loading="lazy" width="704" height="586" srcset="https://lengrand.fr/content/images/size/w600/2024/05/image-1.png 600w, https://lengrand.fr/content/images/2024/05/image-1.png 704w"><figcaption><span style="white-space: pre-wrap;">A paginated list of Star Wars planets</span></figcaption></figure><p>As any typical Kotlin project, it can be compiled using <code>$./gradlew build</code>.</p><p>The <code>gradle.build</code> file looks like this:</p><pre><code class="language-groovy">plugins {
    id &apos;java&apos;
    id &apos;org.jetbrains.kotlin.jvm&apos; version &apos;1.4.10&apos;
    id &apos;org.jetbrains.kotlin.plugin.serialization&apos; version &apos;1.4.10&apos;
}

apply plugin: &apos;kotlin-kapt&apos;


group &apos;nl.lengrand&apos;
version &apos;1.0-SNAPSHOT&apos;

repositories {
    mavenCentral()
}

dependencies {
    implementation &quot;org.jetbrains.kotlin:kotlin-stdlib&quot;

    implementation &apos;info.picocli:picocli:4.7.6&apos;
    implementation &apos;org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3&apos;

    implementation &apos;com.github.kittinunf.fuel:fuel:2.3.1&apos;
    implementation &apos;com.github.kittinunf.fuel:fuel-kotlinx-serialization:2.3.1&apos;

    testImplementation &quot;org.junit.jupiter:junit-jupiter-api:5.10.2&quot;
    testRuntimeOnly &quot;org.junit.jupiter:junit-jupiter-engine:5.10.2&quot;

    kapt &apos;info.picocli:picocli-codegen:4.7.6&apos;
}

compileKotlin {
    kotlinOptions.jvmTarget = &quot;21&quot;
}
compileTestKotlin {
    kotlinOptions.jvmTarget = &quot;21&quot;
}

kapt {
    arguments {
        arg(&quot;project&quot;, &quot;${project.group}/${project.name}&quot;)
    }
}

tasks.register(&apos;customFatJar&apos;, Jar) {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes &apos;Main-Class&apos;: &apos;nl.lengrand.swacli.SwaCLIPaginate&apos;
    }
    archiveBaseName = &apos;all-in-one-jar&apos;
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}</code></pre><p>Yup, I haven&apos;t touched that project in a looooong time, we&apos;re still rocking kotlin 1.4.10 &#x1F605;.</p><p>Rambo style, without reading anything I&apos;ll just update my plugins version to 2.0.0, let&apos;s see what happens. I saw it pop up yesterday on <a href="https://x.com/jlengrand/status/1792875249719009548?ref=lengrand.fr">Twitter</a>, so I&apos;m curious.</p><pre><code class="language-groovy">plugins {
    id &apos;java&apos;
    id &apos;org.jetbrains.kotlin.jvm&apos; version &apos;2.0.0&apos;
    id &apos;org.jetbrains.kotlin.plugin.serialization&apos; version &apos;2.0.0&apos;
}</code></pre><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/05/Screenshot-2024-05-22-at-23.35.36.png" class="kg-image" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;" loading="lazy" width="2000" height="673" srcset="https://lengrand.fr/content/images/size/w600/2024/05/Screenshot-2024-05-22-at-23.35.36.png 600w, https://lengrand.fr/content/images/size/w1000/2024/05/Screenshot-2024-05-22-at-23.35.36.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/05/Screenshot-2024-05-22-at-23.35.36.png 1600w, https://lengrand.fr/content/images/size/w2400/2024/05/Screenshot-2024-05-22-at-23.35.36.png 2400w" sizes="(min-width: 720px) 720px"></figure><p>Downloading, and migrations; that&apos;s a good sign!</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/05/Screenshot-2024-05-22-at-23.35.46.png" class="kg-image" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;" loading="lazy" width="788" height="168" srcset="https://lengrand.fr/content/images/size/w600/2024/05/Screenshot-2024-05-22-at-23.35.46.png 600w, https://lengrand.fr/content/images/2024/05/Screenshot-2024-05-22-at-23.35.46.png 788w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">No migration detected</span></figcaption></figure><p>Ok, First thing I&apos;m seeing is a deprecation on the <code>kotlinOptions</code>. Let&apos;s change that. Without checking the docs, it looks like we want the <code>kotlin.compilerOptions</code> now.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/05/image-2.png" class="kg-image" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;" loading="lazy" width="1238" height="202" srcset="https://lengrand.fr/content/images/size/w600/2024/05/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2024/05/image-2.png 1000w, https://lengrand.fr/content/images/2024/05/image-2.png 1238w" sizes="(min-width: 720px) 720px"></figure><p>Uh, error.</p><pre><code>Build file &apos;/Users/julienlengrand-lambert/Developer/swacli/build.gradle&apos; line: 38

A problem occurred evaluating root project &apos;swacli&apos;.
&gt; Cannot set the value of property &apos;jvmTarget&apos; of type org.jetbrains.kotlin.gradle.dsl.JvmTarget using an instance of type java.lang.String.
</code></pre><p>Ohhhhh, looks like our targets have their own enum now! Let&apos;s change that!</p><pre><code>compileKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}
compileTestKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}</code></pre><p>Now, that was easy! Haven&apos;t touched the doc yet &#x1F62C;. Let&apos;s keep going. We run <code>./gradlew. build</code> again : </p><pre><code> $ ./gradlew build       

&gt; Task :kaptGenerateStubsKotlin
w: Kapt currently doesn&apos;t support language version 2.0+. Falling back to 1.9.

&gt; Task :kaptGenerateStubsTestKotlin
w: Kapt currently doesn&apos;t support language version 2.0+. Falling back to 1.9.

&gt; Task :kaptTestKotlin
warning: The following options were not recognized by any processor: &apos;[project, kapt.kotlin.generated]&apos;

Deprecated Gradle features were used in this build, making it incompatible with Gradle 9.0.

You can use &apos;--warning-mode all&apos; to show the individual deprecation warnings and determine if they come from your own scripts or plugins.

For more on this, please refer to https://docs.gradle.org/8.7/userguide/command_line_interface.html#sec:command_line_warnings in the Gradle documentation.

BUILD SUCCESSFUL in 6s
8 actionable tasks: 8 executed

</code></pre><p>Ok, build successful,  amazing. However, I see that the <a href="https://kotlinlang.org/docs/kapt.html?ref=lengrand.fr">kapt</a> (Kotlin Annotation Processing Tool) plugin automagically detects Kotlin 2.0 and knows it doesn&apos;t support it and reverts back to 1.9. <strong>Amazing developer experience if you ask me</strong> (really, kudos for the gentle fallback!), but it also means we&apos;re not running 2.0 just yet &#x1F631;.</p><p>Fortunately, picoCLI only uses kapt to generate the docs and CLI options in our tool. Very useful, but I can leave without to try the new shiny version. Let&apos;s comment kapt out.</p><pre><code>import org.jetbrains.kotlin.gradle.dsl.JvmTarget

plugins {
    id &apos;java&apos;
    id &apos;org.jetbrains.kotlin.jvm&apos; version &apos;2.0.0&apos;
    id &apos;org.jetbrains.kotlin.plugin.serialization&apos; version &apos;2.0.0&apos;
}

//apply plugin: &apos;kotlin-kapt&apos;

group &apos;nl.lengrand&apos;
version &apos;1.0-SNAPSHOT&apos;

repositories {
    mavenCentral()
}

dependencies {
    implementation &quot;org.jetbrains.kotlin:kotlin-stdlib&quot;

    implementation &apos;info.picocli:picocli:4.7.6&apos;
    implementation &apos;org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3&apos;

    implementation &apos;com.github.kittinunf.fuel:fuel:2.3.1&apos;
    implementation &apos;com.github.kittinunf.fuel:fuel-kotlinx-serialization:2.3.1&apos;

    testImplementation &quot;org.junit.jupiter:junit-jupiter-api:5.10.2&quot;
    testRuntimeOnly &quot;org.junit.jupiter:junit-jupiter-engine:5.10.2&quot;

//    kapt &apos;info.picocli:picocli-codegen:4.7.6&apos;
}

test {
    useJUnitPlatform()
}

compileKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}
compileTestKotlin {
    kotlin.compilerOptions.jvmTarget = JvmTarget.JVM_21
}

//kapt {
//    arguments {
//        arg(&quot;project&quot;, &quot;${project.group}/${project.name}&quot;)
//    }
//}

tasks.register(&apos;customFatJar&apos;, Jar) {
    duplicatesStrategy = DuplicatesStrategy.EXCLUDE

    manifest {
        attributes &apos;Main-Class&apos;: &apos;nl.lengrand.swacli.SwaCLIPaginate&apos;
    }
    archiveBaseName = &apos;all-in-one-jar&apos;
    from { configurations.runtimeClasspath.collect { it.isDirectory() ? it : zipTree(it) } }
    with jar
}</code></pre><p>And when we build again: </p><pre><code>$ ./gradlew build                                                         

BUILD SUCCESSFUL in 2s
4 actionable tasks: 4 executed
</code></pre><p>Let&apos;s run the tool : </p><figure class="kg-card kg-code-card"><pre><code>$ java -cp build/libs/all-in-one-jar-1.0-SNAPSHOT.jar nl.lengrand.swacli.SwaCLIPaginate planets tat</code></pre><figcaption><p><span style="white-space: pre-wrap;">$</span></p></figcaption></figure><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/05/image-3.png" class="kg-image" alt="&#x1F389;Celebrating Kotlin 2.0&#x1F389;" loading="lazy" width="728" height="372" srcset="https://lengrand.fr/content/images/size/w600/2024/05/image-3.png 600w, https://lengrand.fr/content/images/2024/05/image-3.png 728w" sizes="(min-width: 720px) 720px"></figure><p>And just like that, our project is running Kotlin 2.0. <strong>Haven&apos;t even opened the docs yet, that&apos;s a smooth upgrade if I&apos;ve seen one! </strong>You can check the complete PR <a href="https://github.com/jlengrand/swacli/pull/8/files?ref=lengrand.fr">here</a>.</p><p>Of course, I do realise that my project is ultra simple, isn&apos;t containing anything fancy or multiplatform; but still it gives me enough confidence to move forward and keep upgrading my other projects. Remember, I was running 1.4 a couple minutes ago.</p><p>Once more, &#x1F389;congrats to the whole team at JetBrains for the release&#x1F389;, and have a lot of fun at KotlinConf tomorrow all of you! I&apos;ll be looking into the new stuff over the coming days and probably come back with some nice nuggets!</p>]]></content:encoded></item><item><title><![CDATA[A retrospective on getting solar panels installed]]></title><description><![CDATA[A retrospective on getting solar panels installed. The installation cost around 5k€ (4k€ after government subsidies), and we were able to generate over 80% of our yearly consumption and save just short of 1k€ in year 1 as well as shave off our CO2 footprint by over a ton]]></description><link>https://lengrand.fr/a-retrospective-on-getting-solar-panels-installed/</link><guid isPermaLink="false">65a6465f02fb1c43eb7ff04d</guid><category><![CDATA[electric]]></category><category><![CDATA[side-project]]></category><category><![CDATA[sustainability]]></category><category><![CDATA[solar]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Tue, 16 Jan 2024 11:28:03 GMT</pubDate><media:content url="https://images.unsplash.com/photo-1624397640148-949b1732bb0a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNvbGFyfGVufDB8fHx8MTcwNTQwNDMwOXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" medium="image"/><content:encoded><![CDATA[<img src="https://images.unsplash.com/photo-1624397640148-949b1732bb0a?crop=entropy&amp;cs=tinysrgb&amp;fit=max&amp;fm=jpg&amp;ixid=M3wxMTc3M3wwfDF8c2VhcmNofDJ8fHNvbGFyfGVufDB8fHx8MTcwNTQwNDMwOXww&amp;ixlib=rb-4.0.3&amp;q=80&amp;w=2000" alt="A retrospective on getting solar panels installed"><p>As you may know, we&apos;re trying many ways to reduce our impact on the environment as a family. For example, we didn&apos;t fly for several years, focussing instead on vacations that are easier to reach. We&apos;re also growing part of our own food organically, making a lot of our own products and almost exclusively using public transport.</p><p>A year ago now, we got solar panels installed on our roof and it&apos;s time for a small retrospective. </p><p><strong>TL;DR : The installation cost around 5&#x20AC; (4k&#x20AC; after government subsidies), and we were able to generate over 80% of our yearly consumption (used 30% straight up) and save just short of 1k&#x20AC; in year 1.</strong></p><h2 id="first-our-consumption">First, our consumption</h2><p></p><p>In this post, we&apos;ll only be talking about electricity. Our house heating (and warm water) is done using hot water pipes from the city, so it won&apos;t be part of the equation here.</p><p>As you may know already, the first thing to do if you are trying to lower your impact on the environment is not to recycle, but to start by reducing your consumption. Yup, it all starts by <em>reducing</em>.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image-4.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="1920" height="1080" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2024/01/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/01/image-4.png 1600w, https://lengrand.fr/content/images/2024/01/image-4.png 1920w" sizes="(min-width: 720px) 720px"></figure><p>We leave in a 100m2 house that is connected on both sides. We do not own an electric car, and we have two kids. <strong>In 2022, our yearly consumption was just above 3000kWh</strong>. If I believe the data from our energy contractor, our monthly consumption is about 45% smaller than similar families, which is kind of nice. (If you&apos;re interested btw, <a href="https://www.agwayenergy.com/blog/average-kwh-per-day/?ref=lengrand.fr#:~:text=According%20to%20the%20United%20States,kWh%20per%20day%20was%2029.">the average American household consumed 10,632-kilowatt hours (kWh) of electricity last year</a> &#x1F613;&#x1F631;) . </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2024/01/image-2.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="1122" height="884" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2024/01/image-2.png 1000w, https://lengrand.fr/content/images/2024/01/image-2.png 1122w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Our energy usage compared to average families in the Netherlands.</span></figcaption></figure><p>This is all done without many sacrifices btw, we&apos;re just using mostly &quot;dumb&quot; appliances and avoiding to run the dryer, for example. </p><h2 id="the-experience-and-the-setup">The experience, and the setup</h2><p></p><p>Interested to further see how to reduce our house&apos;s footprint, we invited <a href="https://energie-n.nl/?ref=lengrand.fr">Energie N</a> at home, a group of volunteers who comes and tells you based on your house installation which optimizations are the most impactful. (If you&apos;re searching for help by the way, I recommend checking for similar groups in your area. They have lots of experience visiting many houses and it&apos;s been extremely helpful). </p><p>For example, <a href="https://energyforums.net/hvac/how-humidity-affects-heating-and-cooling/?ref=lengrand.fr#:~:text=Water%20has%20more%20thermal%20mass,it%20can%20store%20more%20heat.&amp;text=This%20same%20concept%20applies%20to,humid%20air%20than%20dry%20air.">I learnt that dry air is cheaper to heat that humid air</a>. Which is very logical if you think about it, but I never had before &#x1F913;. </p><p>Based on their recommendations, we decided to look into installing solar panels on our house. Solar panels are extremely common in the Netherlands (<a href="https://en.wikipedia.org/wiki/Solar_power_in_the_Netherlands?ref=lengrand.fr#:~:text=By%202018%20residential%20Solar%20PV,had%20solar%20rooftop%20systems%20installed.">more than 25% of households have them now!!</a>) so there&apos;s a plethora of companies to choose from. They do everything online, using cadaster and aerial imagery to plan your future installation. Here is our part of the contract looked for us, without them even having to come</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image-5.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="626" height="878" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image-5.png 600w, https://lengrand.fr/content/images/2024/01/image-5.png 626w"></figure><p>Now, I have to say that not everything is flawless with those methods. As you can see, the space we have to install those panels is limited because of the roof extension we have in our bedroom. There&apos;s also legal requirements for margins on each side of your roof to avoid taking space off of your neighbor&apos;s roof. The first contractor who came over realized that there was a 5cm discrepancy between his plans and our actual roof and had to cancel the installation...</p><p>We contracted a second company, <strong>who back then had a 16 months waiting time</strong> (due to the war in Ukraine and the energy prices exploding)! Luckily, a few weeks later another customer cancelled their installation date in our street and we could get them installed very fast. </p><p>The whole installation took less than 3 hours, and was flawless. Super happy with our contractor.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/4E01796B-185A-44B2-9551-C46E4B3BD179_1_102.jpeg" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="2000" height="1500" srcset="https://lengrand.fr/content/images/size/w600/2024/01/4E01796B-185A-44B2-9551-C46E4B3BD179_1_102.jpeg 600w, https://lengrand.fr/content/images/size/w1000/2024/01/4E01796B-185A-44B2-9551-C46E4B3BD179_1_102.jpeg 1000w, https://lengrand.fr/content/images/size/w1600/2024/01/4E01796B-185A-44B2-9551-C46E4B3BD179_1_102.jpeg 1600w, https://lengrand.fr/content/images/2024/01/4E01796B-185A-44B2-9551-C46E4B3BD179_1_102.jpeg 2048w" sizes="(min-width: 720px) 720px"></figure><h2 id="a-year-later-the-results">A year later, the results</h2><p>We&apos;re now a year later. Let&apos;s have a look at the results!</p><p>We do not have batteries in our installation. The way it works in the Netherlands at the moment, is that the energy you generate is either used direction by your house, or sent back to the grid. The energy you send back to the grid is removed from your bill on a 1/1 ratio. </p><p>Last year, <strong>our 6 panels generated a total of 2393kWh</strong>. The cool thing is that you really can see we had a very sunny spring, but a crap summer &#x1F605;. I&apos;m curious how it&apos;ll compare to next year.</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image-6.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="591" height="656"></figure><p>Out of this energy, we sent almost 1600kWh back to the grid. </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="1816" height="994" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image.png 600w, https://lengrand.fr/content/images/size/w1000/2024/01/image.png 1000w, https://lengrand.fr/content/images/size/w1600/2024/01/image.png 1600w, https://lengrand.fr/content/images/2024/01/image.png 1816w" sizes="(min-width: 720px) 720px"></figure><p><strong>This means we used directly just over 30%, or 800kWh</strong>. If the conditions of energy delivery were to change, it might actually be worth installing batteries, because it seems that we sent back over two thirds of what we generated. </p><p><strong>After all this, our leftover usage is 2226 -&#xA0; 1591 = 635kWh, which means we generated almost 80% of our total consumption!</strong> In terms of financials, we saved just shy of 1k<strong>&#x20AC;</strong> this year, meaning that <strong>I&apos;d expect our installation to be profitable within the next 4 years if all goes well.</strong></p><p>However, if we </p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image-1.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="1524" height="766" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image-1.png 600w, https://lengrand.fr/content/images/size/w1000/2024/01/image-1.png 1000w, https://lengrand.fr/content/images/2024/01/image-1.png 1524w" sizes="(min-width: 720px) 720px"></figure><p></p><h2 id="and-in-terms-of-co2-emissions">And in terms of CO2 emissions?</h2><p>This will be a very rough calculation here, because I&apos;m not including the cost of the solar panels manufacturing, and the values I will be giving change all the time, but overall <a href="https://www.nowtricity.com/country/netherlands/?ref=lengrand.fr">if we look at the numbers</a> : </p><p>On a normal day, 1kWh generates roughly 500g of CO2 (In 2023 the average emissions of Netherlands were 421 g CO2eq/kWh.). Meaning that us generating 2393kWh this year leads to a reduction of 1150kg of our CO2 footprint, 400kg of which we didn&apos;t even send back to the grid. Not bad, that&apos;s almost 10% of our combined footprint!</p><figure class="kg-card kg-image-card"><img src="https://lengrand.fr/content/images/2024/01/image-7.png" class="kg-image" alt="A retrospective on getting solar panels installed" loading="lazy" width="1150" height="808" srcset="https://lengrand.fr/content/images/size/w600/2024/01/image-7.png 600w, https://lengrand.fr/content/images/size/w1000/2024/01/image-7.png 1000w, https://lengrand.fr/content/images/2024/01/image-7.png 1150w" sizes="(min-width: 720px) 720px"></figure><h2 id="next-steps-and-conclusion">Next steps and conclusion</h2><p>Overall, I&apos;m super happy about the whole setup. We&apos;re reducing our footprint, while saving money, raising the value of the house and for absolutely no trouble. Hard to do better.</p><p>I think we&apos;ve about done what we could for energy production in the household. I think the next big thing for us will revolve around insulation, looking at getting better windows and more. I&apos;ll post about this another time!</p><p>Hope you liked this post, maybe it can motivate you too!</p>]]></content:encoded></item><item><title><![CDATA[Visualizing your AAARRP priorities as a way to manage up in your DevRel team]]></title><description><![CDATA[In this article, I'm describing how we used the AAARRRP framework to visually describe our DevRel team's strategy and clarify the reason for  our day to day activities to our internal stakeholders, and more focus on impact instead. ]]></description><link>https://lengrand.fr/aaarrp-metrics-as-a-way-to-manage-expectations-up/</link><guid isPermaLink="false">6523b75f02fb1c43eb7fea1b</guid><category><![CDATA[devrel]]></category><category><![CDATA[strategy]]></category><category><![CDATA[development]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Tue, 12 Dec 2023 09:46:10 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2023/12/image-8-1.png" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2023/12/image-8-1.png" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team"><p><em>TL;DR : we have been using </em><a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr"><em>the AAARRRP framework</em></a><em> as a visual and easy way to align with stakeholders which types of activities are the most relevant for our customers. It can look like this</em></p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/image-8.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="1190" height="736" srcset="https://lengrand.fr/content/images/size/w600/2023/12/image-8.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/image-8.png 1000w, https://lengrand.fr/content/images/2023/12/image-8.png 1190w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Possible DevRel priorities focussing on activation, retention and product</span></figcaption></figure><p>Let&apos;s dive into it!</p><h3 id="some-context">Some context</h3><p>Even though I have been doing Developer Relations for a few years unofficially or semi-officially, it was only two years ago that I got my first official role as a Developer Advocate. And after just a few months, I was offered to lead the team. We&apos;re a small team of three (me included) and our team falls under engineering.</p><p>It was never really an issue for us to create a strategy on what to achieve, or how to achieve it. We focussed on our 3 pillars: <a href="https://www.whatisdevrel.com/?ref=lengrand.fr">Code, Content and Community</a> and selected the most relevant activities.</p><p>For example, being a B2B company we knew that running the conference circuit wasn&apos;t the most efficient way to create value, for the simple reason that people wouldn&apos;t be able to sign up for our services. We quickly decided instead to embed ourselves close to documentation and provide many samples for the companies who were using us, bringing a lot of internal feedback at the same time.</p><p>But even though we had no doubt about our priorities, it wasn&apos;t always as clear for higher management, nor other stakeholders. After all, Developer Relations was a relatively new thing inside the company and only a few people had direct experience with the domain in the past. And we all know that even for companies seasoned with DevRel, measuring impact and making sure activities align are a difficult topic. </p><p>We received a lot of challenging questions over time regarding our priorities :</p><ul><li>We see that the <em>insert competitor</em> DevRel team is very present on Youtube. How is it that your are not doing the same? </li><li>How is it that your Twitter account gets very few likes compared to <em>insert other company</em>.</li><li>Why do we not see many community contributions on your GitHub account, compared to <em>insert large company</em>? </li><li>Would you please join us at the booths for <em>insert conference, </em>we want to increase hiring.</li></ul><p>All those questions are very fair, and they deserve clear answers. Thing is, they were quite obvious for us given the context we had and it was a struggle for me to communicate those answers at scale and in a strategic way (understand : Find a way that people understand those answers, and hopefully give enough context that they come to the same decisions by themselves). </p><p>It&apos;s after reading about the AAARRRP framework that a potential solution came to mind &#x1F60A;.</p><h3 id="quick-sideline-about-priorities-activities">Quick sideline about priorities / activities</h3><p>My point with this method is to focus on how to communicate in a scalable way <strong>the type of activity</strong> we plan on focussing on, <strong>not the exact activity performed</strong>. </p><p>For example, we might want people to understand that writing code samples is the most impactful activity we can do. </p><p>However, we will not use it to communicate <strong>which</strong> code sample to write. For this, we might instead use a combination of data sources like documentation statistics, or support tickets for example. I may write more about this later in another article.</p><h3 id="a-quick-intro-about-the-aaarrrp-framework"> A quick intro about the AAARRRP framework</h3><p>I&apos;m not going to expand myself much about this, because I would be ripping off the original article. I recommend you to <a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr">go read it</a> instead if you have never heard of it before.</p><p>Here is the very minimal version. As Developer Relations team, we can operate on several key moments of the customer journey : </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/image-4.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="2000" height="555" srcset="https://lengrand.fr/content/images/size/w600/2023/12/image-4.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/image-4.png 1000w, https://lengrand.fr/content/images/size/w1600/2023/12/image-4.png 1600w, https://lengrand.fr/content/images/2023/12/image-4.png 2284w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">The AAARRRP framework funnel</span></figcaption></figure><p>Depending on the type of company we&apos;re working on and the strategic priorities of that company, some of those parts will be more crucial than others. </p><p>Early on, many SAAS products want to focus on <strong>awareness</strong> and <strong>acquisition</strong> to drive as many new customers in as possible, and grow fast (The famous <a href="https://www.swyx.io/measuring-devrel?ref=lengrand.fr">MAD</a>) .</p><p>As your product grows in maturity, you may want to increase the &quot;stickiness&quot; of your product and increase <strong>retention</strong>, or work on <strong>activation</strong> of additional features for your existing customer base.</p><p>Typical DevRel activities that we carry have impact of different parts of that funnel (usually several at the same time). </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/image-2.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="2000" height="1393" srcset="https://lengrand.fr/content/images/size/w600/2023/12/image-2.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/image-2.png 1000w, https://lengrand.fr/content/images/size/w1600/2023/12/image-2.png 1600w, https://lengrand.fr/content/images/2023/12/image-2.png 2042w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Activities and funnel mapping. Original from https://www.leggetter.co.uk/aaarrrp/</span></figcaption></figure><p><em>Note: The original post also adds weight to the equation in order to select activities. We will not here, for simplicity&apos;s sake. The whole idea stays valid here</em>. </p><h3 id="from-aaarrrp-to-strategic-communication"> From AAARRRP to strategic communication</h3><p><em>Note: All those charts have been altered and do not contain internal information </em>&#x1F60A; </p><p>So far, nothing new under the sun. That&apos;s where we&apos;ll go just one step further and use the content above for strategic communication. </p><p>Let&apos;s say that based on the current context, <strong>we decide to focus on activation, retention and product</strong>. </p><ul><li>We can draw this as a chart to help communication with stakeholders. </li><li>We can also draw (rough, given we do not have internal knowledge) equivalent charts for other companies in the space for comparison. </li><li>We can even draw our own chart year on year, to show how our activities vary as priorities shift, or impact decreased (<a href="https://en.wikipedia.org/wiki/Diminishing_returns?ref=lengrand.fr">law of diminishing returns</a>).</li><li><strong>Now for the key part :</strong> We use these charts to validate assumptions with  stakeholders, <strong>who will be able to derive the corresponding activities themselves.</strong></li></ul><p>For example, given the decision above we can draw this: </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/image-8.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="1190" height="736" srcset="https://lengrand.fr/content/images/size/w600/2023/12/image-8.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/image-8.png 1000w, https://lengrand.fr/content/images/2023/12/image-8.png 1190w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">Possible DevRel priorities focussing on activation, retention and product</span></figcaption></figure><p>With those priorities agreed, the common understanding is that we should focus on activities like Code Samples, Guides, Tutorials, and answering Stack Overflow questions.</p><p>Again, we can more easily explain why we have a lesser focus on social media and the conference circuit that others by comparing main focuses between entities. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/data-src-image-6dd98e93-7b30-4f89-acd5-424eb4c60376.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="1200" height="742" srcset="https://lengrand.fr/content/images/size/w600/2023/12/data-src-image-6dd98e93-7b30-4f89-acd5-424eb4c60376.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/data-src-image-6dd98e93-7b30-4f89-acd5-424eb4c60376.png 1000w, https://lengrand.fr/content/images/2023/12/data-src-image-6dd98e93-7b30-4f89-acd5-424eb4c60376.png 1200w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">possible DevRel team profile from another company focussing more on MAD</span></figcaption></figure><p>Once the shape of our strategy is agreed to, the type of activities expected become clearer for everyone and we can focus on the impact of those activities. And if the priorities change, it&apos;s always time to come back to the drawing board &#x1F389;.</p><p>Let&apos;s imagine now that our company&apos;s internal focus heavily shifts towards &quot;closing deals&quot; because we need to extend our runway. It might be time for the DevRel team to <strong>start acting on the Revenue </strong>part of the funnel, reduce Product efforts <strong>and do Pre-Sales activities</strong>. We can update the plan, propose a new shape and double check with stakeholders that the change fits the new landscape :  </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lengrand.fr/content/images/2023/12/image-7.png" class="kg-image" alt="Visualizing your AAARRP priorities as a way to manage up in your DevRel team" loading="lazy" width="2000" height="614" srcset="https://lengrand.fr/content/images/size/w600/2023/12/image-7.png 600w, https://lengrand.fr/content/images/size/w1000/2023/12/image-7.png 1000w, https://lengrand.fr/content/images/size/w1600/2023/12/image-7.png 1600w, https://lengrand.fr/content/images/2023/12/image-7.png 2398w" sizes="(min-width: 720px) 720px"><figcaption><span style="white-space: pre-wrap;">DevRel team proposing to shift priorities following a company strategic change</span></figcaption></figure><p>Of course, whether the type of activities to be carried on fits DevRel well, is sustainable long term and more is another discussion altogether. The main point here is to make your priorities clear and communicate them accordingly.</p><h3 id="about-the-scale-of-priorities">About the scale of priorities </h3><p>One question you might wonder is : How do you calculate the scale of each priority in your bar chart?</p><p> Well, so far our method isn&apos;t very scientific &#x1F60A;. We mostly want to show relative priorities of activities and as such we&apos;re using a set number of points and sharing them across the different axes of the chart. For example, pick 70 points (10 per angle) and subdivide. The chart then helps us make decisions quarter to quarter or day to day. Of course, when drawing other team&apos;s chart, all numbers are best effort and based on impressions. </p><p>As we use these graphs more, we&apos;re experimenting with other ways to measure that could be more relevant. For example incorporating alignment and relevant weights that <a href="https://www.leggetter.co.uk/aaarrrp/?ref=lengrand.fr">the original AAARRRP article</a> mentions.</p><h3 id="a-word-of-conclusion">A word of conclusion</h3><p>Overall, the only thing we&apos;ve been doing is taking the AAARRRP framework as an inspiration to clearly visualise what our team focusses on and bring some clarity internally. We&apos;ve been using it on all of our presentations and internal pages focussing on our team&apos;s strategy. </p><p>It has helped to focus discussions with stakeholders more on how much impact we can bring, rather than what activities to carry and why and it&apos;s really been useful for us.</p><p>Having joined DevRelCon this year I know that internal communication is always a strong topic in DevRel teams. Maybe this can help bring some clarity on the topic! </p><p>Cheers, and talk soon. </p><p>Julien</p>]]></content:encoded></item><item><title><![CDATA[Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client]]></title><description><![CDATA[In this article, I'll be implementing an openapi generator from scratch so you can too! We'll be creating a very simple generator for the Jetbrains HTTP Client]]></description><link>https://lengrand.fr/creating-an-openapi-generator-from-scratch-from-yaml-to-jetbrains-http-client/</link><guid isPermaLink="false">6544f72902fb1c43eb7fec8f</guid><category><![CDATA[openapi]]></category><category><![CDATA[jetbrains]]></category><category><![CDATA[http]]></category><category><![CDATA[development]]></category><category><![CDATA[cli]]></category><dc:creator><![CDATA[Julien]]></dc:creator><pubDate>Sat, 04 Nov 2023 17:40:33 GMT</pubDate><media:content url="https://lengrand.fr/content/images/2023/11/F9_7HZZW4AEvcHF.jpeg" medium="image"/><content:encoded><![CDATA[<img src="https://lengrand.fr/content/images/2023/11/F9_7HZZW4AEvcHF.jpeg" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client"><p>This is the online version of the article with the same name I wrote for the Dutch Java Magazine &#x1F60A;.</p><figure class="kg-card kg-embed-card"><blockquote class="twitter-tweet"><p lang="en" dir="ltr">Creating your own code generator for the <a href="https://twitter.com/jetbrains?ref_src=twsrc%5Etfw&amp;ref=lengrand.fr">@jetbrains</a> http client from an <a href="https://twitter.com/hashtag/openapi?src=hash&amp;ref_src=twsrc%5Etfw&amp;ref=lengrand.fr">#openapi</a> specification? It&#x2019;s possible and you can read how in the latest edition of the Dutch <a href="https://twitter.com/hashtag/java?src=hash&amp;ref_src=twsrc%5Etfw&amp;ref=lengrand.fr">#java</a> magazine!<br>And yes, it&#x2019;s Jetbrains, not yet brains &#x1F602; <a href="https://t.co/zLGkpibLtN?ref=lengrand.fr">pic.twitter.com/zLGkpibLtN</a></p>&#x2014; Julien Lengrand-Lambert &#x1F951;&#x1F44B; (@jlengrand) <a href="https://twitter.com/jlengrand/status/1720369690105590201?ref_src=twsrc%5Etfw&amp;ref=lengrand.fr">November 3, 2023</a></blockquote>
<script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script></figure><p>In the previous edition of the magazine, we discussed how the <a href="https://www.jetbrains.com/help/idea/http-client-reference.html?ref=lengrand.fr">JetBrains HTTP Client</a> could be used to run HTTP Queries, automate them and even use them in your CI/CD pipelines. Just like <a href="https://www.postman.com/?ref=lengrand.fr">Postman</a>, but text based and can be part of your source code. Pretty cool.</p><p>For reference, it could look like this. </p><pre><code class="language-http">### Github API - Traffic per day

GET https://api.github.com/repos/{{owner}}/{{repo}}/traffic/views?per=day
Accept: application/vnd.github+json
X-GitHub-Api-Version: 2022-11-28
Authorization: Bearer {{github_key}}</code></pre><p>With an environment file that looks like this : </p><pre><code class="language-json">{
  &quot;dev&quot;: {
    &quot;github_key&quot;: &quot;not_that_easy&quot;,
    &quot;owner&quot;: &quot;jlengrand&quot;,
    &quot;repo&quot;: &quot;elm-firebase&quot;
  }}</code></pre><p>Now, that is very nice, but it requires a lot of manual work.<strong> Wouldn&#x2019;t it be nice to be able to automate this?</strong> Fortunately, most of us developing APIs also generate <a href="https://www.openapis.org/?ref=lengrand.fr">OpenAPI</a> Specifications for them. When I looked however, there was no OpenAPI generator yet available for the Jetbrains HTTP Client. This is the story of how I&apos;ve implemented it from scratch, and how you could too if you find yourself in the same situation! We&apos;ll use the JetBrains HTTP Client as a practical example, but the knowledge is transferable &#x1F642;.</p><p>The OpenAPI generator project contains a core engine, as well as many packages with each a specific generator (Java, Ada, &#x2026;). The Jetbrains HTTP Client generator is actually published, and you can find <a href="https://github.com/OpenAPITools/openapi-generator/pull/14477/files?ref=lengrand.fr">the merge request</a> as well as <a href="https://openapi-generator.tech/docs/generators/jetbrains-http-client?ref=lengrand.fr">the documentation</a> on GitHub. If you have installed the last available OpenAPI generator release, you can actually try it out in a terminal as such : </p><pre><code class="language-bash">$ openapi-generator generate -i https://api.opendota.com/api  -g jetbrains-http-client -o dotaClient</code></pre><p>At its core, the idea of the OpenAPI generator is quite simple : It takes a specification file (JSON or YAML), transforms it into a set of objects in memory, and uses those objects to generate code / files using <a href="https://mustache.github.io/?ref=lengrand.fr">mustache</a> template files. </p><p>You can actually find most of that logic in the <code>DefaultGenerator</code> source file of the library. There, you can see that actions are separated into 3 groups : </p><ul><li>models (basically data types)</li><li>operations (actual operations)</li><li>supporting files (environments, READMEs, &#x2026;). </li></ul><p>Each of those is illustrated by a method, and takes separate objects as inputs :</p><pre><code class="language-java"> &#x2026;
  void generateModels(List&lt;File&gt; files, List&lt;ModelMap&gt; allModels, List&lt;String&gt; unusedModels) {&#x2026;}
&#x2026;
    void generateApis(List&lt;File&gt; files, List&lt;OperationsMap&gt; allOperations, List&lt;ModelMap&gt; allModels) {...}
&#x2026;
    private void generateSupportingFiles(List&lt;File&gt; files, Map&lt;String, Object&gt; bundle) {...}
&#x2026;</code></pre><p>You can find <a href="https://github.com/OpenAPITools/openapi-generator/blob/78f3b19b58df699ef883b89a7a44531407377719/modules/openapi-generator/src/main/java/org/openapitools/codegen/DefaultGenerator.java?ref=lengrand.fr#L433">the actual source file on GitHub</a>. The objects for each of those methods are large <code>Map</code> classes that contain the necessary data in a semi-structured format. Here is an example of how <code>allModels</code> looks like: </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh7-us.googleusercontent.com/NVgYf1bDqjbDEuvOkdmDReLlE0pVj2M3A64JpNQncZmFvosFOlA4tGzb1idJWD8cWexTV-tlzd18VJwJ0lgkqHYi80GZFqmj3S-oJtXwa9Y2LrRG1mU-gdlKfBIV0hZsIBm2arP9QlVTQlfIvuwS_Tc" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="624" height="360"><figcaption><span style="white-space: pre-wrap;">a debug view of the allModels object</span></figcaption></figure><p>As you can see, the object is essentially a lot of key/value pairs that are quite recognisable and directly come from the OpenAPI specification file. </p><p>To create our own client, we will take advantage of this nice work. Let&apos;s dive into it. We first clone the repository</p><pre><code class="language-bash">$ git@github.com:OpenAPITools/openapi-generator.git; cd openapi-generator</code></pre><p>We can then use the <code>/new.sh</code> script to generate a few placeholder files for us. We&apos;ll be generating a client, and since we&apos;re not creating any bugs we won&apos;t be generating test files.</p><pre><code class="language-bash">$  ./new.sh -n java-magazine-client -c

Creating modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/JavaMagazineClientClientCodegen.java
Creating modules/openapi-generator/src/main/resources/java-magazine-client/README.mustache
Creating modules/openapi-generator/src/main/resources/java-magazine-client/model.mustache
Creating modules/openapi-generator/src/main/resources/java-magazine-client/api.mustache
Creating bin/configs/java-magazine-client-petstore-new.yaml
Finished.</code></pre><p>The library nicely generates a client generator for us, as well as some template files and even a config so we can test it easily! The config uses the well known <a href="https://spring-framework-petclinic-qctjpkmzuq-od.a.run.app/?ref=lengrand.fr">petstore</a> by default.</p><p>This is how the config file looks like : </p><pre><code class="language-yaml">generatorName: java-magazine-client
outputDir: samples/client/petstore/java/magazine/client
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/java-magazine-client
additionalProperties:
  hideGenerationTimestamp: &quot;true&quot;</code></pre><p>It nicely mentions to the OpenAPI generator library which generator to use, which sample OpenAPI file to use as input, where the mustache template files are located and where to store the output</p><p><strong>Let&apos;s run it! </strong></p><pre><code class="language-bash">$ ./mvnw clean package # package once to have the generator inside the generated jar
$ ./bin/generate-samples.sh bin/configs/java-magazine-client-petstore-new.yaml</code></pre><p>Let&apos;s see what the generated output looks like : </p><figure class="kg-card kg-image-card kg-width-wide kg-card-hascaption"><img src="https://lh7-us.googleusercontent.com/QdjBEGu-dpqVoX_EMfZ5DZPG9ND4g3KjY1HR6Yo0LvChQmnRAPQBpBV-MAp6HNteXRbAb4ZOjQkQs9LhxBkj9KiZ7iRFdVKNTwNyNzNjCAQUWoc4RnipISl2kJcsKpauctW-D-Atkq7J5jEOyXcsA98" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="624" height="197"><figcaption><span style="white-space: pre-wrap;">a tree view of the generated client</span></figcaption></figure><p>We haven&apos;t done any work yet, and our generator is already spitting out things! Unfortunately, as we can see, all those files are empty. That&apos;s because our mustache template files also are empty. Let&apos;s fix that now!</p><p>We&apos;ll start by customizing the <code>JavaMagazineClientClientCodegen</code> to fit our needs. We want a very minimal implementation that fits in this article, so we&apos;ll actually decide to NOT implement any supporting files (the README), nor Models and instead focus solely on the API. The way to do this in a custom generator is to extend the <code>postProcessOperationsWithModels</code> from the <code>CodeGenConfig</code> interface. We change the <code>.zz</code> extension into <code>.http</code> files that will be recognised by IntelliJ. And because in this specific (simplistic) case, we will not need any alterations to the <code>OperationsMap</code> object we can actually only call the super method. Our final class looks like this : </p><pre><code class="language-java">package org.openapitools.codegen.languages;
import org.openapitools.codegen.*;
import java.io.File;
import java.util.*;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationsMap;

public class JavaMagazineClientClientCodegen extends DefaultCodegen implements CodegenConfig {

    public CodegenType getTag() { return CodegenType.CLIENT; }

    public String getName() { return &quot;java-magazine-client&quot;; }

    public String getHelp() { return &quot;Generates a java-magazine-client client.&quot;; }

    public JavaMagazineClientClientCodegen() {
        super();

        outputFolder = &quot;generated-code&quot; + File.separator + &quot;java-magazine-client&quot;;
        apiTemplateFiles.put(&quot;api.mustache&quot;, &quot;.http&quot;);
        embeddedTemplateDir = templateDir = &quot;java-magazine-client&quot;;
        apiPackage = &quot;Apis&quot;;
    }

    @Override
    public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List&lt;ModelMap&gt; allModels) {
        return super.postProcessOperationsWithModels(objs, allModels);
    }
}</code></pre><p><em>Note: At first glance, the method and variable names may look a bit like magic. It is because most of the logic comes from DefaultGenerator, and CodeGenConfig. If you feel lost, those two classes are where it&apos;s at.</em></p><p>Now that we have our baseline, what we want to do is work on our mustache files. Those files are basically templates that will be fed into the processing pipeline to generate our <code>.http</code> files.</p><p>We know we want one file per main API endpoint, with some documentation. We also want the <code>@name</code> unique identifier from the Jetbrains HTTP Client to be able <a href="https://www.jetbrains.com/help/idea/exploring-http-syntax.html?ref=lengrand.fr#http_request_names">to reference our code</a>. Finally, we want to add the supported content type for the calls.</p><p>If we look at the data object available for operations, we end up with this, where each <code>{{item}}</code> notation is the value of the item key inside the object.</p><pre><code class="language-mustache">## {{classname}}
{{#operations}}
{{#operation}}

### {{#summary}}{{summary}}{{/summary}}
# @name {{operationId}}
{{httpMethod}} {{basePath}}{{path}}
{{#consumes}}Content-Type: {{{mediaType}}}
{{/consumes}}
{{/operation}}
{{/operations}}</code></pre><p>We can see it clearly if we look at the object during processing</p><figure class="kg-card kg-image-card"><img src="https://lh7-us.googleusercontent.com/LW_g2JLAtaj1yq2LJeg0LBb0yTOy4ufSyurPNWW6XjKcdsv5GhVSASr6yWS7vYv3vmEuS9xmbZqzBa4Mqt76dg_Bv47gMoifUjxInC0-z1WkJYJRU3grz4RBApXJAZl4ZCx1irLQ69axWx5CQAe1fM0" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="624" height="152"></figure><figure class="kg-card kg-image-card"><img src="https://lh7-us.googleusercontent.com/HL2UN4ZvPrLgENa7ergdKqrLfcLFEp_N8I46e973y1QsKWO7nzaiqSyjypk-YAdA7_fA_HdjIJR2PwI9zWAym9tVMtSLksXMMZYVmWj6oJbr84V4A90PfMWjqQZ468lQmc9eN8CJEY8h3L4apRAGcgw" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="255" height="210"></figure><p><em>Note: Unfortunately, to my knowledge the best way to dive into the data model is still to go pause at runtime, I haven&apos;t yet found a complete data model documentation online. If you do, let me know!</em></p><p>Let&apos;s rerun the generation and see what we get now : </p><pre><code class="language-bash">$ ./mvnw package 
$ ./bin/generate-samples.sh bin/configs/java-magazine-client-petstore-new.yaml</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh7-us.googleusercontent.com/hJ-53Q53ibGC0DuCaqu7yuoWpnLJB3d6g9SYUQ26-XeAVLW5JgavLfljBo08hnuyMwUsQo4Hgz5aBthp8L8jqFCpq1RBFWCz-PWFvpdofXDgR4o7QI_iyFKYMz4Afbet38-rEnzAuCXL4aCaL7ZUnZE" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="624" height="111"><figcaption><span style="white-space: pre-wrap;">the result of the generation of our client</span></figcaption></figure><p>Great! Only API files, and one per API, as wanted. Let&apos;s see what they contain! </p><pre><code class="language-http">## PetApi

### Add a new pet to the store
# @name addPet
POST http://petstore.swagger.io/v2/pet
Content-Type: application/json
Content-Type: application/xml

### Deletes a pet
# @name deletePet
DELETE http://petstore.swagger.io/v2/pet/{petId}</code></pre><p>Looks great to me! Let&apos;s try to run one of the calls</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://lh7-us.googleusercontent.com/LrHrgXoLj4y9_lyYCzAbtLGle6eDCqv-eyDWOVQLkCkdsb2LskNOrdhBEO0c0wDMuRb9EHbh3i21TpLEcntMyd_qhHqeYgIsQDzDOD7FCrf6VNsaxe3RY1OzrzB21uqo1SxwzlF7lXZ7oqyno3FAGSo" class="kg-image" alt="Creating an OpenAPI generator from scratch : From YAML to JetBrains HTTP Client" loading="lazy" width="624" height="272"><figcaption><span style="white-space: pre-wrap;">Running one of the calls that&apos;s just been generated</span></figcaption></figure><p>It works just fine, and we get a 200 response as well. Success! </p><p>Now, there&apos;s only one little issue. The variables!  In the Jetbrains HTTP Client format, <a href="https://www.jetbrains.com/help/idea/exploring-http-syntax.html?ref=lengrand.fr#using_request_vars">variables are written as</a> <code>{{variable}}</code>. OpenAPI implementations only have single braces. We need to fix that!</p><p>What we&apos;ll be doing here is implement <a href="https://mustache.github.io/mustache.5.html?ref=lengrand.fr">a custom mustache lambda</a> that doubles up the braces when it finds them. The lambda is essentially a string replacement</p><pre><code class="language-java">public static class DoubleMustacheLambda implements Mustache.Lambda {
        @Override
        public void execute(Template.Fragment fragment, Writer writer) throws IOException {
            String text = fragment.execute();
            writer.write(text
                    .replaceAll(&quot;\\{&quot;, &quot;{{&quot;)
                    .replaceAll(&quot;}&quot;, &quot;}}&quot;)
            );
        }
    }</code></pre><p>In order to make it available in our Generator, the openapi generator library offers the same mechanism as for the rest : We have to override a ready-made method.</p><pre><code class="language-java">    @Override
    protected ImmutableMap.Builder&lt;String, Mustache.Lambda&gt; addMustacheLambdas() {

        return super.addMustacheLambdas()
                .put(&quot;doubleMustache&quot;, new JavaMagazineClientClientCodegen.DoubleMustacheLambda());
    }</code></pre><p>The code above is added to our <code>JavaMagazineClientClientCodegen</code> class. </p><p>Next, we also need to modify our mustache template to add that lambda at the right location (around the <code>path</code> parameter). If that path is a variable, the braces will then be doubled </p><pre><code class="language-mustache">## {{classname}}
{{#operations}}
{{#operation}}

### {{#summary}}{{summary}}{{/summary}}
# @name {{operationId}}
{{httpMethod}} {{basePath}}{{#lambda.doubleMustache}}{{path}}{{/lambda.doubleMustache}}
{{#consumes}}Content-Type: {{{mediaType}}}
{{/consumes}}
{{/operation}}
{{/operations}}</code></pre><p>Et voil&#xE0;! Running the sample again, we can now use variables as they are meant to be inside IntelliJ!</p><p>In the sample below, I&apos;m using the following local environment file : </p><pre><code class="language-json">{
  &quot;dev&quot;: {
    &quot;petId&quot;: 3
  }
}</code></pre><p>There is still a lot more to do with this generator. READMEs, payload, auth, headers, &#x2026; But now it&apos;s a matter of updating the mustache files as we want. </p><p>I&apos;d love to have a more fleshed out generator, because it&apos;d be an amazing and cheap way together with <a href="https://www.jetbrains.com/help/idea/http-client-cli.html?ref=lengrand.fr">the client CLI</a> to have a great automated integration tests pipeline and get people running in second with your API.</p><p>I hope this article made you feel like trying to create your own OpenAPI generator. The only limit is your imagination! And as you can see, the merging process is actually relatively pleasant, because the volunteers of the project LOVE to see people bringing out new ideas to life.</p><p>Happy to hear your thoughts, as always!</p>]]></content:encoded></item></channel></rss>