<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Ryan Harrison</title>
    <description>My blog, portfolio and technology related ramblings</description>
    <link>https://ryanharrison.co.uk/</link>
    <atom:link href="https://ryanharrison.co.uk/feed.xml" rel="self" type="application/rss+xml"/>
    <pubDate>Wed, 02 Sep 2026 16:02:31 +0000</pubDate>
    <lastBuildDate>Wed, 02 Sep 2026 16:02:31 +0000</lastBuildDate>
    <generator>Jekyll v4.4.1</generator>
    
      <item>
        <title>Mocking Time in Java</title>
        <description>&lt;p&gt;If your code calls &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instant.now()&lt;/code&gt; directly, you’re stuck with whatever the system clock says. This makes
it nearly impossible to write deterministic tests for time-sensitive logic like expiration checks, scheduling, or time-based
calculations.&lt;/p&gt;

&lt;p&gt;The solution is to inject time as a dependency rather than calling static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now()&lt;/code&gt; methods directly. Java’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; abstraction
provides a clean way to do this, and there are several approaches depending on your needs.&lt;/p&gt;

&lt;h2 id=&quot;the-problem-with-static-now-calls&quot;&gt;The Problem with Static now() Calls&lt;/h2&gt;

&lt;p&gt;Consider a service that checks if a subscription has expired:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isExpired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Subscription&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpiryDate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;());&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This code is difficult to test because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt; always returns the actual current time. You can’t easily test the
boundary conditions around expiration without changing your system clock or waiting for time to pass.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;using-clock-for-dependency-injection&quot;&gt;Using Clock for Dependency Injection&lt;/h2&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java.time.Clock&lt;/code&gt; class was designed to solve this problem. Instead of calling static &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;now()&lt;/code&gt; methods, you pass a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt;
instance to the time API:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;ZonedDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Refactor your code to accept a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; parameter:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;kd&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kd&quot;&gt;final&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;SubscriptionService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;this&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;kd&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;boolean&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;isExpired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Subscription&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;getExpiryDate&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;().&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isBefore&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;In production, inject &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock.systemDefaultZone()&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock.systemUTC()&lt;/code&gt;. In tests, use a fixed clock.&lt;/p&gt;

&lt;h2 id=&quot;built-in-clock-implementations&quot;&gt;Built-in Clock Implementations&lt;/h2&gt;

&lt;p&gt;Java provides several &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; implementations out of the box:&lt;/p&gt;

&lt;h3 id=&quot;clockfixed&quot;&gt;Clock.fixed()&lt;/h3&gt;

&lt;p&gt;Creates a clock that always returns the same instant. Good for most unit tests:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testExpiredSubscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2026-01-20T10:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ZoneId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTC&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;Subscription&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2026-01-20T09:00:00&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isExpired&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;clockoffset&quot;&gt;Clock.offset()&lt;/h3&gt;

&lt;p&gt;Creates a clock that’s offset from another clock by a duration. Useful when you need to test time progression:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testRenewalWindow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;baseClock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;fixed&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2026-01-20T10:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ZoneId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTC&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Create a clock 30 days in the future&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;futureClock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;offset&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;baseClock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ofDays&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;30&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;RenewalService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;RenewalService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;futureClock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isInRenewalWindow&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;subscription&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;clocksystem&quot;&gt;Clock.system()&lt;/h3&gt;

&lt;p&gt;The production clocks that return the actual system time:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// Uses system default timezone&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;systemDefaultZone&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Uses UTC timezone&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;systemUTC&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;

&lt;span class=&quot;c1&quot;&gt;// Uses specific timezone&lt;/span&gt;
&lt;span class=&quot;nc&quot;&gt;Clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;system&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;ZoneId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;America/New_York&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h2 id=&quot;mutableclock-from-threeten-extra&quot;&gt;MutableClock from ThreeTen-Extra&lt;/h2&gt;

&lt;p&gt;For tests that need to simulate time progression, the &lt;a href=&quot;https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/MutableClock.html&quot;&gt;MutableClock&lt;/a&gt; from the threeten-extra library is useful. 
It allows you to advance time during a test.&lt;/p&gt;

&lt;p&gt;Add the dependency:&lt;/p&gt;

&lt;div class=&quot;language-xml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.threeten&lt;span class=&quot;nt&quot;&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;threeten-extra&lt;span class=&quot;nt&quot;&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;version&amp;gt;&lt;/span&gt;1.8.0&lt;span class=&quot;nt&quot;&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;nt&quot;&gt;&amp;lt;scope&amp;gt;&lt;/span&gt;test&lt;span class=&quot;nt&quot;&gt;&amp;lt;/scope&amp;gt;&lt;/span&gt;
&lt;span class=&quot;nt&quot;&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Use it to control time in your tests:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testTokenExpiration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;MutableClock&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;MutableClock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;Instant&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2026-01-20T10:00:00Z&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;),&lt;/span&gt;
            &lt;span class=&quot;nc&quot;&gt;ZoneId&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;of&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;UTC&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;nc&quot;&gt;TokenService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;TokenService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
    &lt;span class=&quot;nc&quot;&gt;String&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;token&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;createToken&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;user123&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

    &lt;span class=&quot;n&quot;&gt;assertTrue&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Advance time by 2 hours&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;clock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;add&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;Duration&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;ofHours&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;mi&quot;&gt;2&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Token should now be expired&lt;/span&gt;
    &lt;span class=&quot;n&quot;&gt;assertFalse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;service&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;isValid&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;token&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;));&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This is cleaner than creating multiple fixed clocks or using offset clocks for complex time-based scenarios.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;using-mockito-mockedstatic-antipattern&quot;&gt;Using Mockito MockedStatic (Antipattern)&lt;/h2&gt;

&lt;p&gt;Mockito 3.4+ supports mocking static methods with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MockedStatic&lt;/code&gt;. You might be tempted to use this for mocking
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LocalDateTime.now()&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-java highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;
&lt;span class=&quot;nd&quot;&gt;@Test&lt;/span&gt;
&lt;span class=&quot;kt&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;testWithMockedStatic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;try&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;MockedStatic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;mockStatic&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;class&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;))&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;fixedTime&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;LocalDateTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;parse&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;2026-01-20T10:00:00&quot;&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;mock&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;when&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;nl&quot;&gt;LocalDateTime:&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;:&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;now&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;).&lt;/span&gt;&lt;span class=&quot;na&quot;&gt;thenReturn&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;fixedTime&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;);&lt;/span&gt;

        &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;service&lt;/span&gt; &lt;span class=&quot;o&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;SubscriptionService&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;();&lt;/span&gt;
        &lt;span class=&quot;c1&quot;&gt;// Test code here&lt;/span&gt;
    &lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;o&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;This is generally an antipattern and should be avoided:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Thread safety issues&lt;/strong&gt;: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MockedStatic&lt;/code&gt; affects all threads, which can cause flaky tests in parallel test suites&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Design smell&lt;/strong&gt;: If you need to mock static methods, it usually indicates your code isn’t designed for testability&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Performance&lt;/strong&gt;: Static mocking is slower than dependency injection&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Scope&lt;/strong&gt;: Easy to accidentally leak mocks between tests if not careful with cleanup&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Clock&lt;/code&gt; abstraction exists specifically to avoid needing static mocking. Use it instead.&lt;/p&gt;

&lt;h2 id=&quot;more-reading&quot;&gt;More Reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/javase/8/docs/api/java/time/Clock.html&quot;&gt;Java Clock Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.threeten.org/threeten-extra/apidocs/org.threeten.extra/org/threeten/extra/MutableClock.html&quot;&gt;ThreeTen-Extra MutableClock&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://javadoc.io/doc/org.mockito/mockito-core/latest/org/mockito/Mockito.html#static_mocks&quot;&gt;Mockito MockedStatic Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Wed, 02 Sep 2026 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2026/09/02/mocking-time-in-java.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2026/09/02/mocking-time-in-java.html</guid>
        
        <category>java</category>
        
        <category>testing</category>
        
        <category>junit</category>
        
        <category>mockito</category>
        
        <category>time</category>
        
        <category>clock</category>
        
        
      </item>
    
      <item>
        <title>Git - Disable autocrlf</title>
        <description>&lt;p&gt;If you’re on Windows, Git has almost certainly been silently messing with your line endings since the day you installed it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;TLDR:&lt;/strong&gt; The fix is this one command:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; core.autocrlf &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;It’s on by default. It’s a terrible default. Mainly due to historical reasons that no longer exist.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;what-coreautocrlf-does&quot;&gt;What &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.autocrlf&lt;/code&gt; Does&lt;/h2&gt;

&lt;p&gt;In short, Windows has historically used &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r\n&lt;/code&gt;) as its line ending character, while Linux and macOS use plain &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\n&lt;/code&gt;). 
At some point, someone decided that Git on Windows should helpfully bridge this gap automatically. 
So by default, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.autocrlf&lt;/code&gt; is set to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;true&lt;/code&gt; on Windows, which means:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;On &lt;strong&gt;checkout&lt;/strong&gt; - Git converts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; line endings in the repository to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt; in your working directory&lt;/li&gt;
  &lt;li&gt;On &lt;strong&gt;commit&lt;/strong&gt; - Git converts &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt; back to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; before storing the file&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In theory, this means your files always have Windows-style line endings locally, and the repository stays clean with Unix-style endings. 
Sounds reasonable enough. In practice though, it can be really quite annoying.&lt;/p&gt;

&lt;h2 id=&quot;why-its-a-bad-default&quot;&gt;Why It’s a Bad Default&lt;/h2&gt;

&lt;h3 id=&quot;wsl-makes-it-worse&quot;&gt;WSL Makes It Worse&lt;/h3&gt;

&lt;p&gt;If you use &lt;a href=&quot;https://learn.microsoft.com/en-us/windows/wsl/about&quot;&gt;WSL&lt;/a&gt; (and you definitely should if you use Windows), this setting can cause issues. 
Your Linux environment expects &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; line endings. 
Your Windows Git has helpfully checked out everything with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt;. 
The result is that your scripts and config files have a stray &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;\r&lt;/code&gt; at the end of every line, which causes failures. 
WSL and Git will also start warning you about line ending inconsistencies whenever you touch a repo:&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;warning: LF will be replaced by CRLF in some-file.sh.
The file will have its original line endings in your working directory
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;You’ve probably seen this warning before, or rather enough times that you have started ignoring it.&lt;/p&gt;

&lt;h3 id=&quot;modern-editors-handle-lf-just-fine&quot;&gt;Modern Editors Handle LF Just Fine&lt;/h3&gt;

&lt;p&gt;The argument for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;CRLF&lt;/code&gt; on Windows was that Notepad couldn’t handle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt;-only files - which was true in 2003. 
Notepad has supported &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; line endings since Windows 10 (2018).
Every other editor - VS Code, Sublime, Vim, IntelliJ, Notepad++ etc - has handled it just fine for even longer. 
There is no editor you are realistically using today that will break on &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; line endings.&lt;/p&gt;

&lt;h3 id=&quot;the-conversion-is-lossy-in-practice&quot;&gt;The Conversion Is Lossy in Practice&lt;/h3&gt;

&lt;p&gt;Even when it works as intended, the automatic conversion can cause issues. Binary files can get corrupted if Git misidentifies them as text. 
Files can show up as changed in &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git diff&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;git status&lt;/code&gt; purely because of line ending differences, with no actual content change.&lt;/p&gt;

&lt;p&gt;If you work in a team where some people have &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;autocrlf true&lt;/code&gt; and others have it &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt;, you’ll end up with commits that are just line ending changes.&lt;/p&gt;

&lt;h2 id=&quot;disable-it&quot;&gt;Disable it&lt;/h2&gt;

&lt;p&gt;Set it to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;false&lt;/code&gt; like it should be by default:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;git config &lt;span class=&quot;nt&quot;&gt;--global&lt;/span&gt; core.autocrlf &lt;span class=&quot;nb&quot;&gt;false&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;From then on, Git leaves your line endings exactly as they are - no silent conversions on checkout, no conversions on commit. 
If you want to be explicit about line endings across a team, a &lt;a href=&quot;https://git-scm.com/docs/gitattributes&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitattributes&lt;/code&gt;&lt;/a&gt; file in the repository is a much better approach since it applies the same rules for everyone regardless of their local config.&lt;/p&gt;

&lt;p&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; everywhere, configure your editor to save with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;LF&lt;/code&gt; by default. Don’t think about it again (until your move machine).&lt;/p&gt;

&lt;h2 id=&quot;further-reading&quot;&gt;Further Reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/git-config#Documentation/git-config.txt-coreautocrlf&quot;&gt;Git Documentation - &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;core.autocrlf&lt;/code&gt;&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://git-scm.com/docs/gitattributes&quot;&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.gitattributes&lt;/code&gt; reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Tue, 21 Apr 2026 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2026/04/21/git-disable-autocrlf.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2026/04/21/git-disable-autocrlf.html</guid>
        
        <category>git</category>
        
        <category>windows</category>
        
        <category>line endings</category>
        
        <category>crlf</category>
        
        <category>lf</category>
        
        <category>wsl</category>
        
        <category>config</category>
        
        
      </item>
    
      <item>
        <title>Engineering Guidelines Site</title>
        <description>&lt;p&gt;I’ve put together a reference site for engineering best practices, available at &lt;a href=&quot;https://guidelines.ryanharrison.co.uk&quot;&gt;guidelines.ryanharrison.co.uk&lt;/a&gt;. The goal is a single place to find guidelines and conventions covering the full stack - from core principles through to deployment.&lt;/p&gt;

&lt;p&gt;Rather than being prescriptive about specific tools, most sections aim to explain the reasoning behind a recommendation so you can apply it to your own context.&lt;/p&gt;

&lt;h2 id=&quot;whats-covered&quot;&gt;What’s Covered&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Principles and practices&lt;/strong&gt; - &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/principles&quot;&gt;Core engineering principles&lt;/a&gt; covers the fundamentals: SOLID, DRY, KISS, YAGNI, clean code, and when to apply them. There’s also a &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/sdlc/code-review&quot;&gt;code review guide&lt;/a&gt; and sections on &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/sdlc/technical-debt&quot;&gt;technical debt&lt;/a&gt;, &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/sdlc/pull-requests&quot;&gt;pull requests&lt;/a&gt;, and &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/sdlc/git/git-workflow&quot;&gt;git workflow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Architecture&lt;/strong&gt; - Patterns for &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/architecture/microservices&quot;&gt;microservices&lt;/a&gt;, &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/architecture/event-driven-architecture&quot;&gt;event-driven systems&lt;/a&gt;, and &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/architecture/multi-tenancy&quot;&gt;multi-tenancy&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;API design&lt;/strong&gt; - &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/api/rest/rest-fundamentals&quot;&gt;REST fundamentals&lt;/a&gt; and patterns, &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/api/graphql&quot;&gt;GraphQL&lt;/a&gt;, and &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/api/contracts/openapi-specifications&quot;&gt;OpenAPI contract-first development&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Testing&lt;/strong&gt; - The &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/testing/testing-strategy&quot;&gt;testing strategy&lt;/a&gt; page covers the overall approach. From there, individual pages go into unit, integration, contract, end-to-end, mutation, and chaos testing.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security and observability&lt;/strong&gt; - &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/security/security-overview&quot;&gt;Security overview&lt;/a&gt; covering authentication, authorisation, input validation, and data protection. &lt;a href=&quot;https://guidelines.ryanharrison.co.uk/observability/observability-overview&quot;&gt;Observability&lt;/a&gt; covers structured logging, metrics, tracing, and alerting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Languages and frameworks&lt;/strong&gt; - Guidelines for Java, Kotlin, TypeScript, and Swift, with framework-specific sections for Spring Boot, React, Angular, React Native, Android, and iOS.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Infrastructure&lt;/strong&gt; - Docker, Kubernetes, Terraform, and a fairly detailed AWS section covering compute, networking, storage, EKS, and more.&lt;/p&gt;
</description>
        <pubDate>Sun, 08 Mar 2026 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2026/03/08/engineering-guidelines-site.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2026/03/08/engineering-guidelines-site.html</guid>
        
        <category>engineering</category>
        
        <category>best-practices</category>
        
        <category>architecture</category>
        
        <category>testing</category>
        
        <category>devops</category>
        
        <category>sdlc</category>
        
        
      </item>
    
      <item>
        <title>Building Jekyll Sites with Docker</title>
        <description>&lt;p&gt;This site is built with Jekyll. That unfortunately often means wrestling with Ruby versions, gem dependencies, and environment configuration.
Different machines require different setups, and what works on your machine might not work in CI or on your VPS. Docker solves this
for many other areas by providing a standalone and reproducible build environment, so why not here as well?&lt;/p&gt;

&lt;p&gt;The challenge is that the official Jekyll Docker images are no longer being actively maintained. Thankfully, there’s a decent
community alternative that handles modern Jekyll sites without the maintenance burden.&lt;/p&gt;

&lt;p&gt;The &lt;a href=&quot;https://github.com/BretFisher/jekyll-serve&quot;&gt;bretfisher/jekyll-serve&lt;/a&gt; image is well-maintained (for now) and works as a
drop-in replacement for the official Jekyll images. By default, it serves your site locally via &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll serve&lt;/code&gt;, but you can
override the command to run any Jekyll operation you need.&lt;/p&gt;

&lt;p&gt;The image handles all the Ruby and gem setup for you, so you don’t need to worry about version conflicts or system dependencies.&lt;/p&gt;

&lt;h2 id=&quot;local-development-with-docker-compose&quot;&gt;Local Development with Docker Compose&lt;/h2&gt;

&lt;p&gt;For local development, the fastest approach is using Docker Compose. Create a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;compose.yml&lt;/code&gt; file in your Jekyll project
root:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jekyll&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bretfisher/jekyll-serve&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.:/site&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;4000:4000&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Start your development server with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker compose up
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Your site will be available at &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;http://localhost:4000&lt;/code&gt; with live reload enabled. The key benefit here is that Docker Compose
reuses the same container across runs, which caches your gems. This means subsequent starts are very quick - typically just a few
seconds once the gems are installed.&lt;/p&gt;

&lt;p&gt;Without compose, you can achieve the same result with:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 4000:4000 &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/site bretfisher/jekyll-serve
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;However, this creates a new container each time, which means reinstalling gems on every run (unless you mess around with more 
volume mounts, see below).&lt;/p&gt;

&lt;h2 id=&quot;building-for-cicd&quot;&gt;Building for CI/CD&lt;/h2&gt;

&lt;p&gt;For continuous integration or deployment builds, you want to build the static site without running the server. Override the
default command to run the Jekyll build process:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/site bretfisher/jekyll-serve bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This generates your static site into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_site&lt;/code&gt; directory. Here’s an example GitHub Actions workflow that builds on every push (also the one used to build this site):&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build Jekyll Site&lt;/span&gt;
&lt;span class=&quot;na&quot;&gt;on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;push&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;branches&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;[&lt;/span&gt; &lt;span class=&quot;nv&quot;&gt;master&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;]&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;jobs&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;build&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;runs-on&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;ubuntu-latest&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;steps&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;uses&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;actions/checkout@v2&lt;/span&gt;

      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;na&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;Build with Jekyll&lt;/span&gt;
        &lt;span class=&quot;na&quot;&gt;run&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;pi&quot;&gt;|&lt;/span&gt;
          &lt;span class=&quot;s&quot;&gt;docker run -v $:/site \&lt;/span&gt;
            &lt;span class=&quot;s&quot;&gt;bretfisher/jekyll-serve bundle exec jekyll build&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The build is completely reproducible because the Docker image contains a known-good version of Ruby and all the necessary build
tools.&lt;/p&gt;

&lt;h2 id=&quot;building-on-low-memory-machines&quot;&gt;Building on Low Memory Machines&lt;/h2&gt;

&lt;p&gt;If you’re like me and building Jekyll sites on machines with limited memory (1GB or less), you might run into issues. Gem installation often
requires building native extensions, which can be memory-intensive. The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bretfisher/jekyll-serve&lt;/code&gt; image runs
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;bundle install --retry 5 --jobs 20&lt;/code&gt; by default, which parallelizes gem installation, but uses more memory. This caused issues for me on resource-restricted boxes.&lt;/p&gt;

&lt;p&gt;You can override the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;entrypoint&lt;/code&gt; to reduce the number of parallel jobs:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/site &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;--entrypoint&lt;/span&gt; /bin/bash &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bretfisher/jekyll-serve &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-c&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;bundle install --jobs 2 &amp;amp;&amp;amp; bundle exec jekyll build&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Reducing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--jobs&lt;/code&gt; from 20 to 2 significantly reduces memory usage during the gem installation phase. The build will take a bit
longer, but it won’t crash on memory-constrained systems.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;caching-gems-for-faster-builds&quot;&gt;Caching Gems for Faster Builds&lt;/h2&gt;

&lt;p&gt;If you’re building on a persistent machine like a VPS, you can use a volume mount to cache gems across builds:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; &lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;pwd&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;:/site &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  &lt;span class=&quot;nt&quot;&gt;-v&lt;/span&gt; jekyll-gems:/usr/local/bundle &lt;span class=&quot;se&quot;&gt;\&lt;/span&gt;
  bretfisher/jekyll-serve bundle &lt;span class=&quot;nb&quot;&gt;exec &lt;/span&gt;jekyll build
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The named volume &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;jekyll-gems&lt;/code&gt; persists the installed gems. The first build installs all gems, but subsequent builds skip gem
installation entirely and jump straight to building your site. This is much faster and significantly less resource-intensive.&lt;/p&gt;

&lt;p&gt;You can also apply this technique in docker-compose:&lt;/p&gt;

&lt;div class=&quot;language-yaml highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;na&quot;&gt;services&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jekyll&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;image&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;bretfisher/jekyll-serve&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;.:/site&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s&quot;&gt;jekyll-gems:/usr/local/bundle&lt;/span&gt;
    &lt;span class=&quot;na&quot;&gt;ports&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
      &lt;span class=&quot;pi&quot;&gt;-&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;4000:4000&quot;&lt;/span&gt;

&lt;span class=&quot;na&quot;&gt;volumes&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
  &lt;span class=&quot;na&quot;&gt;jekyll-gems&lt;/span&gt;&lt;span class=&quot;pi&quot;&gt;:&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s pretty much it. Easy enough Jekyll builds without messing around with Ruby/gem packages/versions.&lt;/p&gt;

&lt;h2 id=&quot;more-reading&quot;&gt;More Reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://github.com/BretFisher/jekyll-serve&quot;&gt;bretfisher/jekyll-serve on GitHub&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://jekyllrb.com/docs/&quot;&gt;Jekyll Documentation&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.docker.com/compose/&quot;&gt;Docker Compose Documentation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sun, 22 Feb 2026 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2026/02/22/building-jekyll-sites-with-docker.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2026/02/22/building-jekyll-sites-with-docker.html</guid>
        
        <category>jekyll</category>
        
        <category>docker</category>
        
        <category>ci</category>
        
        <category>devops</category>
        
        <category>static-site</category>
        
        <category>docker-compose</category>
        
        
      </item>
    
      <item>
        <title>Remote Debugging Java Apps with IntelliJ</title>
        <description>&lt;p&gt;A junior developer recently came to me for help with one of those classic issues which appears on a remote dev environment,
but for whatever reason can’t (at least easily) be replicated locally. Their immediate thought was to add more log statements and
redeploy the app to see what’s going on. Not unreasonable, but this is a dev environment, so I connected IntelliJ to one of the
running containers and began stepping through the code and inspecting variables. They looked at me thinking I was performing some
kind of black magic, so here’s an intro or a quick reminder of something which goes very underappreciated.&lt;/p&gt;

&lt;h2 id=&quot;why-remote-debug&quot;&gt;Why Remote Debug?&lt;/h2&gt;

&lt;p&gt;There are a few general scenarios when you might reach for remote debugging:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Environment-specific bugs&lt;/strong&gt; - Issues that only appear in staging, testing, or production-like environments with specific
configurations, data, or network conditions&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Container debugging&lt;/strong&gt; - When your application runs inside Docker containers or Kubernetes pods and you need to debug without
rebuilding images&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Shared development environments&lt;/strong&gt; - Debugging applications running on shared development servers or VMs&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Integration testing&lt;/strong&gt; - Troubleshooting complex integration scenarios with external systems that can’t be replicated locally&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Rather than relying solely on log statements or trying to recreate production conditions locally, remote debugging lets you step
through the actual running code in the target environment - a lot better than adding log statements!&lt;/p&gt;

&lt;h2 id=&quot;how-it-works&quot;&gt;How It Works&lt;/h2&gt;

&lt;p&gt;Java remote debugging uses the Java Debug Wire Protocol (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JDWP&lt;/code&gt;), which is a communication protocol between a debugger and a Java
Virtual Machine. The JVM opens a socket that a debugger can connect to, allowing it to control execution, set breakpoints, inspect
variables, and evaluate expressions.&lt;/p&gt;

&lt;p&gt;The JVM can act as either a server (listening for debugger connections) or a client (connecting to a debugger). In most cases,
you’ll configure the JVM as a server and then have your IDE connect to it.&lt;/p&gt;

&lt;h2 id=&quot;enabling-remote-debugging&quot;&gt;Enabling Remote Debugging&lt;/h2&gt;

&lt;p&gt;To enable remote debugging, you need to pass specific JVM arguments when starting your Java application. The modern syntax (Java
9+) looks like this:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java &lt;span class=&quot;nt&quot;&gt;-agentlib&lt;/span&gt;:jdwp&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;transport&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dt_socket,server&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;y,suspend&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n,address&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;*&lt;/span&gt;:5005 &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; myapp.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here’s a high-level breakdown of what each parameter does:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;-agentlib:jdwp&lt;/code&gt;&lt;/strong&gt; - Loads the JDWP agent library for debugging&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;transport=dt_socket&lt;/code&gt;&lt;/strong&gt; - Uses socket transport for the debug connection (the standard approach)&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;server=y&lt;/code&gt;&lt;/strong&gt; - Configures the JVM to listen for debugger connections rather than connecting out to a debugger&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspend=n&lt;/code&gt;&lt;/strong&gt; - Starts the application immediately without waiting for a debugger to attach. Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspend=y&lt;/code&gt; if you need to
debug startup code&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address=*:5005&lt;/code&gt;&lt;/strong&gt; - Binds to all network interfaces on port 5005. You can specify a specific IP address or hostname instead
of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For older Java versions (Java 8 and earlier, so I hope you won’t see this), you might see the older syntax:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;java &lt;span class=&quot;nt&quot;&gt;-Xdebug&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-Xrunjdwp&lt;/span&gt;:transport&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;dt_socket,server&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;y,suspend&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;n,address&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;5005 &lt;span class=&quot;nt&quot;&gt;-jar&lt;/span&gt; myapp.jar
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This accomplishes the same thing but uses deprecated flags. Note that in Java 8, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address&lt;/code&gt; parameter only accepts a port
number, not the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;*:port&lt;/code&gt; format.&lt;/p&gt;

&lt;!--more--&gt;

&lt;h2 id=&quot;common-config-scenarios&quot;&gt;Common Config Scenarios&lt;/h2&gt;

&lt;h3 id=&quot;docker-containers&quot;&gt;Docker Containers&lt;/h3&gt;

&lt;p&gt;When running Java applications in Docker, expose the debug port and configure the JVM arguments:&lt;/p&gt;

&lt;div class=&quot;language-dockerfile highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Dockerfile&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;FROM&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; eclipse-temurin:25&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;COPY&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; target/myapp.jar /app/myapp.jar&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;EXPOSE&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; 8080 5005&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;ENTRYPOINT&lt;/span&gt;&lt;span class=&quot;s&quot;&gt; [&quot;java&quot;, &quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005&quot;, &quot;-jar&quot;, &quot;/app/myapp.jar&quot;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Then map the port when running:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;docker run &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 8080:8080 &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; 5005:5005 myapp
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;spring-boot&quot;&gt;Spring Boot&lt;/h3&gt;

&lt;p&gt;For Spring Boot apps, you can pass the debug arguments through Maven or Gradle:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Maven&lt;/span&gt;
mvn spring-boot:run &lt;span class=&quot;nt&quot;&gt;-Dspring-boot&lt;/span&gt;.run.jvmArguments&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Gradle&lt;/span&gt;
gradle bootRun &lt;span class=&quot;nt&quot;&gt;--debug-jvm&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The Gradle &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;--debug-jvm&lt;/code&gt; flag automatically configures remote debugging on port 5005 with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;suspend=y&lt;/code&gt;, so the application will
wait for a debugger before starting.&lt;/p&gt;

&lt;h3 id=&quot;environment-variables&quot;&gt;Environment Variables&lt;/h3&gt;

&lt;p&gt;Many application servers and deployment platforms let you set JVM options through environment variables:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# Generic Java applications&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JAVA_OPTS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005&quot;&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# Spring Boot with environment variable&lt;/span&gt;
&lt;span class=&quot;nb&quot;&gt;export &lt;/span&gt;&lt;span class=&quot;nv&quot;&gt;JAVA_TOOL_OPTIONS&lt;/span&gt;&lt;span class=&quot;o&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;-agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=*:5005&quot;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JAVA_TOOL_OPTIONS&lt;/code&gt; var is automatically picked up by the JVM without needing to explicitly pass it to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;java&lt;/code&gt; command.&lt;/p&gt;

&lt;h2 id=&quot;connecting-from-intellij-idea&quot;&gt;Connecting from IntelliJ IDEA&lt;/h2&gt;

&lt;p&gt;Once your Java application is running with remote debugging enabled, you want to actually step through and inspect things. This 
basically means you need to attach IntelliJ to it:&lt;/p&gt;

&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;Create a Remote JVM Debug Configuration&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Go to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Run&lt;/code&gt; → &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Edit Configurations&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Click the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;+&lt;/code&gt; button and select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Remote JVM Debug&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Give it a descriptive name like “Remote Debug - Staging”&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Configure the Connection&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;&lt;strong&gt;Debugger mode&lt;/strong&gt;: Select &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Attach to remote JVM&lt;/code&gt; (the default)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Host&lt;/strong&gt;: Enter the hostname or IP address of the machine running your Java application (use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost&lt;/code&gt; if debugging a
local Docker container)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Port&lt;/strong&gt;: Enter the debug port (typically &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;5005&lt;/code&gt;)&lt;/li&gt;
      &lt;li&gt;&lt;strong&gt;Command line arguments&lt;/strong&gt;: IntelliJ shows you the JVM arguments needed - useful for reference but not required here&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Set Module Classpath&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Under “Use module classpath”, select the appropriate module&lt;/li&gt;
      &lt;li&gt;This ensures IntelliJ can map the remote code to your local source files&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Start Debugging&lt;/strong&gt;
    &lt;ul&gt;
      &lt;li&gt;Click &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Debug&lt;/code&gt; or press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Shift + F9&lt;/code&gt;&lt;/li&gt;
      &lt;li&gt;Set breakpoints in your code&lt;/li&gt;
      &lt;li&gt;Trigger the code path in the remote application (make an HTTP request, send a message, etc.)&lt;/li&gt;
      &lt;li&gt;IntelliJ will pause execution when breakpoints are hit&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ol&gt;

&lt;!-- Image: Screenshot showing IntelliJ Remote JVM Debug configuration dialog --&gt;
&lt;p&gt;&lt;img src=&quot;/images/2026/intellij-remote-debug-config.png&quot; alt=&quot;IntelliJ Remote Debug Configuration&quot; /&gt;&lt;/p&gt;

&lt;p&gt;When the debugger attaches successfully, you’ll see a message in the Debug console: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;&quot;Connected to the target VM, address: &apos;
hostname:5005&apos;, transport: &apos;socket&apos;&quot;&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;some-considerations&quot;&gt;Some Considerations&lt;/h2&gt;

&lt;h3 id=&quot;source-code-synchronization&quot;&gt;Source Code Synchronization&lt;/h3&gt;

&lt;p&gt;Remote debugging requires that your local source code matches the bytecode running remotely. If the versions don’t match, you’ll
see incorrect line numbers, unexpected behavior, or the debugger won’t stop at breakpoints.&lt;/p&gt;

&lt;h3 id=&quot;performance-impact&quot;&gt;Performance Impact&lt;/h3&gt;

&lt;p&gt;Remote debugging does have a performance overhead, particularly when:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;Stepping through code line by line&lt;/li&gt;
  &lt;li&gt;Evaluating complex expressions&lt;/li&gt;
  &lt;li&gt;Inspecting large objects or collections&lt;/li&gt;
  &lt;li&gt;Using conditional breakpoints&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AKA don’t do it in production.&lt;/p&gt;

&lt;h3 id=&quot;security&quot;&gt;Security&lt;/h3&gt;

&lt;p&gt;Opening a debug port exposes your application to potential security risks. Anyone who can connect to the debug port has full
control over the JVM, including the ability to execute arbitrary code and access sensitive data.&lt;/p&gt;

&lt;p&gt;Best practices:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;Never expose debug ports directly to the internet&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;Use &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address=localhost:5005&lt;/code&gt; instead of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;address=*:5005&lt;/code&gt; when possible, then use SSH tunneling to connect&lt;/li&gt;
  &lt;li&gt;Only enable remote debugging in non-production environments, or very temporarily in production with strict access controls&lt;/li&gt;
  &lt;li&gt;Use firewalls and security groups to restrict access to the debug port&lt;/li&gt;
  &lt;li&gt;Disable debugging after troubleshooting is complete&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AKA don’t do it in production.&lt;/p&gt;

&lt;h3 id=&quot;ssh-tunneling&quot;&gt;SSH Tunneling&lt;/h3&gt;

&lt;p&gt;For remote servers, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH&lt;/code&gt; tunneling can provide a secure way to access the debug port:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ssh &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; 5005:localhost:5005 user@remote-server
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;This forwards your local port 5005 to the remote server’s &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:5005&lt;/code&gt;. Configure your IntelliJ debug connection to use
&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;localhost:5005&lt;/code&gt;, and the connection will be tunneled securely through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH&lt;/code&gt;.&lt;/p&gt;

&lt;h2 id=&quot;more-reading&quot;&gt;More Reading&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://docs.oracle.com/en/java/javase/17/docs/specs/jpda/jpda.html&quot;&gt;Java Platform Debugger Architecture (JPDA)&lt;/a&gt; - Official Oracle documentation&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.jetbrains.com/help/idea/tutorial-remote-debug.html&quot;&gt;IntelliJ IDEA Remote Debugging Tutorial&lt;/a&gt; - JetBrains official guide&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://blog.docker.com/2016/09/java-development-using-docker/&quot;&gt;Docker Java Remote Debugging&lt;/a&gt; - Docker blog post on Java debugging&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Mon, 19 Jan 2026 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2026/01/19/remote-debugging-java-apps-intellij.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2026/01/19/remote-debugging-java-apps-intellij.html</guid>
        
        <category>java</category>
        
        <category>debugging</category>
        
        <category>intellij</category>
        
        <category>jvm</category>
        
        <category>development</category>
        
        <category>jdwp</category>
        
        
      </item>
    
      <item>
        <title>Oracle Cloud - Setting up a Server on the &quot;Always Free&quot; Tier</title>
        <description>&lt;p&gt;So it turns out that &lt;a href=&quot;https://www.oracle.com/uk/cloud/&quot;&gt;Oracle Cloud&lt;/a&gt; (OCI) offers an extremely generous free tier, and not many people seem to know about it. Beyond the 30 day free trial, which gives you $300 worth of free credits to use (not bad at all), they also offer a pretty substantial amount of infrastructure in their &lt;a href=&quot;https://www.oracle.com/uk/cloud/free/#always-free&quot;&gt;Always Free&lt;/a&gt; tier as well. In terms of server (VPS) infrastructure, this includes (at the time of writing at least):&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;2 AMD based Compute VMs&lt;/strong&gt; with 1 vCPU (x86) and 1 GB memory each + 0.48Gbps max network bandwidth&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;4 Arm-based Ampere A1 cores and 24 GB of memory&lt;/strong&gt; usable as up to 4 VMs + &lt;strong&gt;4GBps max&lt;/strong&gt; network bandwidth&lt;/li&gt;
  &lt;li&gt;2 Block Volumes Storage - &lt;strong&gt;200 GB total&lt;/strong&gt;&lt;/li&gt;
  &lt;li&gt;10 GB Object Storage – Standard + 10GB Object Storage Infrequent Acccess + 10GB Archive Storage&lt;/li&gt;
  &lt;li&gt;Outbound Data Transfer -&lt;strong&gt;10 TB per month&lt;/strong&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Now, whatever you may think of Oracle in general, you can’t deny that this is a good deal. You can in theory set up a max of 6 VPS instances, all for free, on a commerical cloud environment. Even if the setup process might be a bit awkward compared to other providers, you can’t really complain too much. You would be spending a fairly significant amount on the equivalent amount of infrastruture from elsewhere like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AWS&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;DigitalOcean&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;In the rest of this post I will quickly run through the steps to setup a small VPS server running Nginx on the free Oracle Cloud tier. This is a standard VPS, just like you would find anywhere else, running &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ubuntu Server 22.04&lt;/code&gt;. A &lt;a href=&quot;https://www.terraform.io/&quot;&gt;Terraform&lt;/a&gt; provider is also available if you wanted, but for simplicity I will go through the web console.&lt;/p&gt;

&lt;h3 id=&quot;create-an-always-free-oracle-cloud-account&quot;&gt;Create an “Always Free” Oracle Cloud Account&lt;/h3&gt;

&lt;p&gt;Go to &lt;a href=&quot;https://www.oracle.com/uk/cloud/free/#always-free&quot;&gt;https://www.oracle.com/uk/cloud/free/#always-free&lt;/a&gt; and create an account as usual. You will have to provide credit card information (I presume to prevent misuse), but won’t be charged. For the first 30 days you will be able to play with $300 of credit if you want to, but after that time is up your account will revert automatically to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Always Free&lt;/code&gt; tier.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_free_tier_main_page.png&quot; alt=&quot;Oracle Free Tier Main Page&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;create-an-instance&quot;&gt;Create an Instance&lt;/h3&gt;

&lt;p&gt;Once signed in, select the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Instances&lt;/code&gt; option on the main dashboard (yes the console isn’t the best for navigation). You should be presented with the following screen which will show all active instances. You can see that I’ve created a couple already:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_instances_dashboard.png&quot; alt=&quot;Oracle Cloud Instances Dashboard&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Click on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create Instance&lt;/code&gt; button to create a new VM. This is the standard VPS configuration/settings page:&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Placement&lt;/code&gt; - controls which &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AZ&lt;/code&gt; the server is deployed to. Can be kept at default to let OCI choose the best. It should be noted that I’m also creating instances directly in the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;UK (London)&lt;/code&gt; region which is great for latency&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Security&lt;/code&gt; - keep as default&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Image and Shape&lt;/code&gt; - select the Image and type of server (amount of resources) you wish to deploy:&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this example we will go with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ubuntu Server 22.04&lt;/code&gt; minimal as our OS. It works great on the limited amounts of CPU and RAM on these nodes as it unbundles a lot of of the default packages that you probably don’t need anyway (you can get them back if you need).&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_os_select.png&quot; alt=&quot;Oracle Cloud OS Selection&quot; /&gt;&lt;/p&gt;

&lt;p&gt;For &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;shape&lt;/code&gt;, you can choose between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AMD (2.0 GHz AMD EPY 7551 x86)&lt;/code&gt; vs &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARM based (Ampere)&lt;/code&gt; CPU’s. You are limited to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;1GB RAM&lt;/code&gt; max on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;AMD&lt;/code&gt; shapes (makes sense since they are more expensive), but up to &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;24GB&lt;/code&gt; on the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARM&lt;/code&gt; cores. You get a max of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;4Gbps&lt;/code&gt; of bandwidth on those &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ARM&lt;/code&gt; boxes well, which is very impressive for a free offering (though I haven’t benchmarked what you actually get). Here we will go with an&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ampere&lt;/code&gt; based VM with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;2 vCores&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;6GB&lt;/code&gt; of memory (did I mention already that all this is free?):&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_shape_select.png&quot; alt=&quot;Oracle Cloud Shape Selection&quot; /&gt;&lt;/p&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Networking&lt;/code&gt; - can keep these as default to use your default root &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Virtual cloud network&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;subnet&lt;/code&gt;. Also make sure the option is checked to assign a public &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;IPv4&lt;/code&gt; address to your instance&lt;/li&gt;
  &lt;li&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Boot Volume&lt;/code&gt; - by default you will get a 50GB volume. You can increase this if you want to, up the max of 200GB allowed in the free tier&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;create-an-ssh-key&quot;&gt;Create an SSH Key&lt;/h3&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Add SSH Keys&lt;/code&gt; section you can choose to automatically generate a keypair, but I prefer to create my own. There are plenty of tools for this, for now I will use &lt;a href=&quot;https://www.puttygen.com/&quot;&gt;PuttyGen&lt;/a&gt; to create a new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ed25519&lt;/code&gt; keypair. Save both the private and public key locally for use later as usual. &lt;strong&gt;Don’t worry I’m not using this key&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/puttygen_create.png&quot; alt=&quot;PuttyGen Key Creation&quot; /&gt;&lt;/p&gt;

&lt;p&gt;Then paste the public key into the corresponding box in the console. Oracle Cloud (OCI) will inject this public key into the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;.ssh/authorized_keys&lt;/code&gt; file for the main &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubuntu&lt;/code&gt; user on the new VPS instance, thus allowing you to login through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_add_ssh_keys.png&quot; alt=&quot;Oracle Cloud Add SSH Keys&quot; /&gt;&lt;/p&gt;

&lt;p&gt;You can look through the other configuration options as needed, but that should be good enough for now. Press &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Create&lt;/code&gt; and wait a couple minutes for the instance to be created. Startup times seem pretty reasonable on Oracle Cloud.&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_instance_status_page.png&quot; alt=&quot;Oracle Cloud Instance Status Page&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;update-the-default-security-group&quot;&gt;Update the default Security Group&lt;/h3&gt;

&lt;p&gt;As you might expect, by default the security group (or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;security list&lt;/code&gt; in Oracle Cloud) will block all traffic coming into your server by default apart from port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;22&lt;/code&gt; for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;SSH&lt;/code&gt;. This is good, but as we want to setup an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Nginx&lt;/code&gt; web server on ours, we need to add a couple new ingress rules.&lt;/p&gt;

&lt;p&gt;In the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Virtual Cloud Networks&lt;/code&gt; section, navigate into your default &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VCN&lt;/code&gt; which was created by default. On the left, select the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Security Lists&lt;/code&gt; option. There should be a single default entry for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;VCN&lt;/code&gt;. Here we will add two new &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ingress rules&lt;/code&gt; for port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; and port &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;443&lt;/code&gt;. It should look something like the following after the changes:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/oracle_cloud_ingress_rules.png&quot; alt=&quot;Oracle Cloud Ingress Rules&quot; /&gt;&lt;/p&gt;

&lt;h3 id=&quot;login-to-the-instance&quot;&gt;Login to the Instance&lt;/h3&gt;

&lt;p&gt;Now all that needs to be done is to login to the instance using your private key saved from earlier. The default user is called &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubuntu&lt;/code&gt;  so if using standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh&lt;/code&gt; commands then something like &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ssh -i /path/to/private/key ubuntu@ipaddresss&lt;/code&gt; should get you in.&lt;/p&gt;

&lt;p&gt;The &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ubuntu&lt;/code&gt; user has &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo&lt;/code&gt; access by default, so you can now start installing packages and using the instance for whatever you need.&lt;/p&gt;

&lt;p&gt;Follow my &lt;a href=&quot;https://ryanharrison.co.uk/guides.html&quot;&gt;other guide posts&lt;/a&gt; on how to setup an &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Ubuntu Server&lt;/code&gt; instance from scratch. For now we can just install &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Nginx&lt;/code&gt; to see our server running: &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo apt install nginx&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;sudo systemctl status nginx&lt;/code&gt; to check that it’s running.&lt;/p&gt;

&lt;h3 id=&quot;configuring-iptables&quot;&gt;Configuring iptables&lt;/h3&gt;

&lt;p&gt;One thing that might cause issues is the fact that the Oracle Ubuntu image sets up an iptables rule to block all traffic by default (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;ufw&lt;/code&gt; is not installed). That means if you had an Nginx server running, you won’t be able to ping it using the public IPV4 address unless you open up access on the ports. This seems a strange choice considering this is also controlled by the security group, but extra layers can’t hurt I guess.&lt;/p&gt;

&lt;p&gt;To allow access on ports &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;80&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;443&lt;/code&gt; for a standard web server with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;HTTPS&lt;/code&gt; enabled, run the following commands:&lt;/p&gt;

&lt;div class=&quot;language-shell highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;iptables &lt;span class=&quot;nt&quot;&gt;-I&lt;/span&gt; INPUT 6 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; NEW &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 80 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;netfilter-persistent save
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;iptables &lt;span class=&quot;nt&quot;&gt;-I&lt;/span&gt; INPUT 6 &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt; state &lt;span class=&quot;nt&quot;&gt;--state&lt;/span&gt; NEW &lt;span class=&quot;nt&quot;&gt;-p&lt;/span&gt; tcp &lt;span class=&quot;nt&quot;&gt;--dport&lt;/span&gt; 443 &lt;span class=&quot;nt&quot;&gt;-j&lt;/span&gt; ACCEPT
&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;netfilter-persistent save
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If you now navigate to the public IPv4 address in a browser, you should see the standard Nginx welcome page:&lt;/p&gt;

&lt;p&gt;&lt;img src=&quot;/images/2023/nginx_welcome_page.png&quot; alt=&quot;Nginx Welcome Page&quot; /&gt;&lt;/p&gt;

&lt;p&gt;That about wraps up the setup process for this post. As I said, the Oracle Cloud free tier is extremely generous in terms of the sheer amount of infastructure you can provision. Plus, it operates just like the others big players as a proper commerical cloud offering, so uptime, network performance and integration with the general infra tooling should work out of the box.&lt;/p&gt;
</description>
        <pubDate>Sat, 28 Jan 2023 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2023/01/28/oracle-cloud-free-server.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2023/01/28/oracle-cloud-free-server.html</guid>
        
        <category>oracle</category>
        
        <category>cloud</category>
        
        <category>server</category>
        
        <category>free</category>
        
        <category>always</category>
        
        <category>vps</category>
        
        <category>instance</category>
        
        <category>oci</category>
        
        
      </item>
    
      <item>
        <title>Kafka vs MQ</title>
        <description>&lt;p&gt;Some quick high levels notes on Kafka vs MQ. This is a question that often gets asked by folks already who are familiar with traditional queues (IBM MQ/RabbitMQ etc) when they are introduced to the world of Kafka:&lt;/p&gt;

&lt;h3 id=&quot;kafka-high-level-uses&quot;&gt;Kafka High Level Uses&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Event input buffer for data analytics/ML&lt;/li&gt;
  &lt;li&gt;Event driven microservices&lt;/li&gt;
  &lt;li&gt;Bridge to cloud-native apps&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;activemq&quot;&gt;ActiveMQ&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Consumer gets pushed certain number of message by broker depending on prefetch&lt;/li&gt;
  &lt;li&gt;Consumer chunks through them, on each ack, broker deletes from data store&lt;/li&gt;
  &lt;li&gt;Produce pushes single message, consumer acks, deletes, gone 1to1&lt;/li&gt;
  &lt;li&gt;Conforms to standard &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;JMS&lt;/code&gt; based messaging&lt;/li&gt;
&lt;/ul&gt;

&lt;h4 id=&quot;topics-in-mq&quot;&gt;Topics in MQ&lt;/h4&gt;

&lt;ul&gt;
  &lt;li&gt;Subscribers only receive messages published while it is connected&lt;/li&gt;
  &lt;li&gt;Or durable where client can disconnect and still receive messages after&lt;/li&gt;
  &lt;li&gt;In MQ can block brokers, fill data stores&lt;/li&gt;
  &lt;li&gt;Each consumer gets copy of the message unless composite destinations/message groups&lt;/li&gt;
  &lt;li&gt;Hard to create dynamic consumers or change the topology&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;kafka&quot;&gt;Kafka&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;each group gets message, but in group only one consumer&lt;/li&gt;
  &lt;li&gt;consumers defines the interaction (pull)
    &lt;ul&gt;
      &lt;li&gt;partitions assignment, offset resets, consumer group&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;
    &lt;p&gt;consumer can apply backpressure or rebalance&lt;/p&gt;
  &lt;/li&gt;
  &lt;li&gt;Can’t go back through the log&lt;/li&gt;
  &lt;li&gt;Difficult to load balance effectively&lt;/li&gt;
  &lt;li&gt;Completing consumers vs one partition still processing whilst other is blocked&lt;/li&gt;
  &lt;li&gt;Hard to change topology or increase number of queues&lt;/li&gt;
  &lt;li&gt;Hard to handle slow/failing consumers&lt;/li&gt;
  &lt;li&gt;not predefining functionality to behave like a queue or topic, defined by consumers
    &lt;ul&gt;
      &lt;li&gt;introduce new consumer groups adhoc to change how destination functions&lt;/li&gt;
      &lt;li&gt;single consumer group = queue&lt;/li&gt;
      &lt;li&gt;multi consumer groups = topic&lt;/li&gt;
      &lt;li&gt;what offset to start from&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;one consumer group can fail and replay whilst another succeeds&lt;/li&gt;
  &lt;li&gt;MQ always queue one out at a time - not depending on consumers, Kafka behaviour changes on number of partitions/consumers&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;useful-video-links&quot;&gt;Useful Video Links&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6yG2myKcMQE&quot;&gt;https://www.youtube.com/watch?v=6yG2myKcMQE&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=sjDnqrnnYNM&quot;&gt;https://www.youtube.com/watch?v=sjDnqrnnYNM&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=Sa3Csx6zFcs&quot;&gt;https://www.youtube.com/watch?v=Sa3Csx6zFcs&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=7Faly8jORIw&quot;&gt;https://www.youtube.com/watch?v=7Faly8jORIw&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
</description>
        <pubDate>Sat, 08 Oct 2022 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2022/10/08/kafka-vs-mq.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2022/10/08/kafka-vs-mq.html</guid>
        
        <category>kafka</category>
        
        <category>mq</category>
        
        <category>rabbitmq</category>
        
        <category>differences</category>
        
        <category>usages</category>
        
        
      </item>
    
      <item>
        <title>Kafka Recommendations &amp; Best Practices</title>
        <description>&lt;p&gt;Most Kafka design and configuration choices are use case dependent and come with trade-offs, so it’s hard to define any best usage rules. However, below are some points of interest and general recommendations based on previous experience that you may want to consider or look more into:&lt;/p&gt;

&lt;h2 id=&quot;topics&quot;&gt;Topics&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure all topics have a consistent naming scheme. If using a shared cluster, prefix with the system name. Kafka can have issues when there are both underscores _ and periods . in the topic name, so choose one or the other as a common separator&lt;/li&gt;
  &lt;li&gt;Determine the number of partitions the new topic should have, taking into account:
    &lt;ul&gt;
      &lt;li&gt;volume of data - more partitions increases the upper-bound on the number of concurrent consumers processing messages&lt;/li&gt;
      &lt;li&gt;it is easier to add new partitions to an existing topic in the future rather than remove partitions if there are too many already&lt;/li&gt;
      &lt;li&gt;during a rebalance, all consumers will stop and be reassigned new partitions. If there are too many partitions/consumers then this can be slower process to coordinate and redistribute and may cause additional stress on the cluster. Don’t set the partition count too high as there are side effects&lt;/li&gt;
      &lt;li&gt;as a general rule of thumb, keep the number of partitions an easily and evenly divisible by your expected number of consumers e.g having 12 partitions will evenly spread load across 3 or 4 consumers subscribed to it and processing messages in parallel&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;How long data on the topics should be kept in the cluster (retention) - as small as possible. For shared clusters this will be a max of 7 days in order to support the cluster being completely rebuilt over the weekend. Retention can be set on a per topic basis as needed&lt;/li&gt;
  &lt;li&gt;The topic replication factor will be likely be determined by the existing cluster configuration to ensure message durability. Likly will be set to 3 or 4 to survive broker failure or complete data centre failover&lt;/li&gt;
  &lt;li&gt;Don’t set compression at the topic level, prefer at the producer level. Look into other topic level configuration properties such as compaction as needed depending on specific use case&lt;/li&gt;
  &lt;li&gt;What the key of the topic will be depending on partitioning and source data - remember all messages with the same key will be sent to the same partition&lt;/li&gt;
  &lt;li&gt;What the value POJO should be - see below for more details&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;messages&quot;&gt;Messages&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;When creating a key, take into account data skew on the broker and partitions - ideally there will be even distribution between them all. Otherwise you may see issues if one consumer is processing all the messages with the others sitting idle
    &lt;ul&gt;
      &lt;li&gt;if not key is provided, the cluster will use a round-robin approach to distribute messages - this is better than having a bad custom key&lt;/li&gt;
      &lt;li&gt;the key should contain data elements with high cardinality to increase the probability of good data distribution (based on hashes)&lt;/li&gt;
      &lt;li&gt;Do not include variable data elements in keys (e.g version numbers). The overall id is a good key property for example as it means all versions of that event will be sent to the same partition. There is no ordering guarantee across partitions so if you need one message to be processed after another, they must have the same key&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Use strongly typed keys and values on topic - don’t use Strings as its harder to manage deserialization and versioning&lt;/li&gt;
  &lt;li&gt;Consider using a message wrapper to add metadata fields to all messages (or use headers)
    &lt;ul&gt;
      &lt;li&gt;adds a number of fields to better support tracking across services source instance, cause, origin, run/batch UUID and unique message id’s&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;If the topic is to consume very high volumes of data, then try to avoid unnecessary duplication or large objects which are not needed to improve throughput and network traffic&lt;/li&gt;
  &lt;li&gt;Consider changes to the format of the messages over time e.g adding or removing fields
    &lt;ul&gt;
      &lt;li&gt;this has to be synced between producers/consumers to ensure the messages can still be deserialized properly&lt;/li&gt;
      &lt;li&gt;it is also a reason to use short retention periods and consumers will not need to process very old messages&lt;/li&gt;
      &lt;li&gt;consider looking into Avro and schema registry to better manage this aspect&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;producers&quot;&gt;Producers&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;If your project is Spring based, use the provided KafkaTemplate classs which wraps the Apache producer and makes config/sending messages much easier&lt;/li&gt;
  &lt;li&gt;Specify acks=all in the producer configuration to force the partition leader to replicate messages to a certain number of followers (depending on min-insync replias) before ack’ing the messages and received
    &lt;ul&gt;
      &lt;li&gt;this will increase latency between sending and receiving ack, but otherwise you may lose messages if a broker goes down and the message has not been replicated yet&lt;/li&gt;
      &lt;li&gt;KafkaTemplate operates in an sync manner by default, so call .get() on returned future to block until ack is complete&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Enable the idempotence property on the producer to esnure that the same message doesn’t get sent twice under some error conditions (adds tracking ids to the message to prevent duplicates). Note that depending on use case and consumer behaviour this may not be needed. Be aware that this also overrides other producer configs such as retries (infinite) and max inflight requests&lt;/li&gt;
  &lt;li&gt;Performance at the producer level is mainly driven by efficient micro-batching of messages
    &lt;ul&gt;
      &lt;li&gt;batch size and linger.ms can set to delay sending until the producer has more messages to include in the batch&lt;/li&gt;
      &lt;li&gt;this is a balancing act as increasing these will add higher latency to message delivery, but give more overall throughput&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Consider setting compression on the producer to improve throughout and reduce load on storage, but might not be suitable for very low latency apps as introduces a decompression cost on the consumer side. It is not recommended to add this to the topic level which will cause the cluster to perform compression on the broker instead of the producer&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;consumers&quot;&gt;Consumers&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;If your project is Spring based, use the KafkaListener and KafkaListenerContainerFactory classes to greatly simplify the otherwise complicated setup of the core Apache consumer classes and adds inegrations with other aspects of Spring Boot
    &lt;ul&gt;
      &lt;li&gt;makes setting deserializers, concurrency, error handling etc easier&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Kafka only provides ordering guarantees for messages in the same partition, so if your consumer needs to see one message after another, they must have the same key
    &lt;ul&gt;
      &lt;li&gt;even then rebalancing &lt;em&gt;may&lt;/em&gt; cause disturbances in this behaviour&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;To scale consumption, place consumers into he same consumer group to evenly spread partitions between instances&lt;/li&gt;
  &lt;li&gt;If the same listener is subscribed to multiple topics, it is recommended to place them in different consumer groups, otherwise rebalancing will impact processing in all when not necessary&lt;/li&gt;
  &lt;li&gt;Improve throughput by configuring min bytes and max wait times for batching - again this is a balancing act of throughput vs latency&lt;/li&gt;
  &lt;li&gt;Ensure all consumers are idempotent (able to consume the same message multiple times without any negative impact)
    &lt;ul&gt;
      &lt;li&gt;Kafka has a number of ways to try and combat duplication, but it is strongly recommended to place checks in all consumers as this is still never guaranteed from Kafka&lt;/li&gt;
      &lt;li&gt;Sending offset acknowledgements are a balancing act between at-least-once and at-most-once processing - the former being preferable in most cases&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ensure that consumer lag (difference in rate of production vs consumption) is properly traced via monitoring tools to detect general issues or slow consumers&lt;/li&gt;
  &lt;li&gt;By default Kafka will auto-commit offsets as a specific interval, but this alone introduces risk of data loss (commits ofsset before fully processed vs processed before committing offset and then crashing)
    &lt;ul&gt;
      &lt;li&gt;Is is strongly recommended to disable auto commit and manage offset commits at the application level&lt;/li&gt;
      &lt;li&gt;You can process on message at a time and then sync commit the offset after processing is complete -safest approach but more network traffic as less offset batching&lt;/li&gt;
      &lt;li&gt;Process the messages as a batch in your service code and then sync commit all the offsets at the end - more throughput but more chance of duplicated effort&lt;/li&gt;
      &lt;li&gt;By making all consumers idempotent, these concerns are removed entirely as long as the offsets are committed after processing is completed&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;error-handling&quot;&gt;Error Handling&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Ensure that all exception scenarios are thought about and handled (deserialization, delivery failure, listener failure, broker down, timeouts, transient issues, error topic publish etc) and alerts are raised when necessary (but ideally the system should be able to recover itself from many of these problems)&lt;/li&gt;
  &lt;li&gt;Avoid creating configuration/consumers/producers manually, use factory methods in a core lib which should come setup already for error handling in a a consistent manner&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;kafka-spring-consumer&quot;&gt;Kafka Spring Consumer&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;There are a number of techniques to handle errors when consuming topics depending on use case - pausing the consumer, raise alerts and continue, error topics etc&lt;/li&gt;
  &lt;li&gt;A common approach is for each topic to have associated error topics (suffix .error app exceptions and .deserr for deserialization exceptions). Messages are automatically forwarded to these error topics when corresponding exceptions are thrown
    &lt;ul&gt;
      &lt;li&gt;Error topics always have just one topic to maintain message order over time&lt;/li&gt;
      &lt;li&gt;If the deserializer fails to convert your JSON to the corresponding POJO, the message will be placed immediately on the .deserr error topic and an alert raised (no point in retries)&lt;/li&gt;
      &lt;li&gt;Key and value will be String versions of the original record to cover format issues&lt;/li&gt;
      &lt;li&gt;Headers of the message include specific exception details including the origin offset and partition and a stacktrace - very useful for debugging&lt;/li&gt;
      &lt;li&gt;If error topic publication fails, the record is not acknowledged and the container paused + an alert is generated - signals the cluster is likely having issues&lt;/li&gt;
      &lt;li&gt;Take into account the multiple consumer groups may be processing messages from the same topic - include the consumer group name in the error topic name&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;If message fails during processing within a listener (general app exception)
    &lt;ul&gt;
      &lt;li&gt;Can retry processing multiple times to cover transient issues - be careful about duplicating work&lt;/li&gt;
      &lt;li&gt;If an exception is still thrown when processing the same message (app level issue such as db connection) then the message(s) are placed on the .error topic and the offsets are committed to continue consuming the next messages&lt;/li&gt;
      &lt;li&gt;Headers of the message include exception details and stacktrace just like deserialization errors&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ensure monitoring is in place to detach any depth on error topics
    &lt;ul&gt;
      &lt;li&gt;Likely resolution will be to reflow the messages after the underlying issue resolved - making consumer idempotent is very important here&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ensure transactionality is setup to rollback other changes (db) if the consumer fails&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;kafka-spring-producer&quot;&gt;Kafka Spring Producer&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;All producers require ack from all broker replicas before committing. By default this is set to ‘all’ meaning that before proceeding a number of brokers need to ack all the messages - as defined by the min-insync-replicas config property at the broker level&lt;/li&gt;
  &lt;li&gt;If using KafkaTemplate make sure to call .get() on the future to block until the ack is received (by default it will continue immediately and you may not know about the issue). With this behaviour an exception will be thrown as you would expect if the production fails&lt;/li&gt;
  &lt;li&gt;Ensure transactionality is setup to rollback changes if errors occur - pullback messages sent to other topics etc&lt;/li&gt;
&lt;/ul&gt;

&lt;h3 id=&quot;kafka-streams&quot;&gt;Kafka Streams&lt;/h3&gt;

&lt;ul&gt;
  &lt;li&gt;Similarly to general consumer error handling, but as Kafka streams spolits work into a number of parallel stream tasks, is a lot harder to manage centrally&lt;/li&gt;
  &lt;li&gt;Specify a deserialization exception handler - similar to Spring consumers, to publish messages onto error topic automatically if a record cannot be deserialized. Stream will move onto the next record
    &lt;ul&gt;
      &lt;li&gt;Create all Serde’s using builders to setup deserializers consistently and ensure handlers are included by default&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;All potential exceptions must be handled within each stream task (map/filter etc) - if an exception is not caught it will crash the streams thread and force a rebalancing
    &lt;ul&gt;
      &lt;li&gt;default exception handlers will just log the error and continue&lt;/li&gt;
      &lt;li&gt;could wrap all operations in try/catch and handle accordingly&lt;/li&gt;
      &lt;li&gt;or could wrap the base KStream/KTable etc to give consistent error handling and avoid duplicated code - ‘protected’ versions&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;If streams thread does do for some reason, an uncaught exception handler should be provided to generate a critiical alert. If all the streams threads die (potentially by all trying to process the same message), nothing will be consumed at all and the app will be stalled&lt;/li&gt;
  &lt;li&gt;Ensure monitoring is in place to ensure that the streams app as a whole is live and healthy&lt;/li&gt;
&lt;/ul&gt;

&lt;h2 id=&quot;misc&quot;&gt;Misc&lt;/h2&gt;

&lt;ul&gt;
  &lt;li&gt;Although during local development you could directly connect to the main dev cluster, it is recommended for devs to instead create and use their own local clusters
    &lt;ul&gt;
      &lt;li&gt;this gives a lot more control during dev for debugging/modifying properties without impacting others&lt;/li&gt;
      &lt;li&gt;any new topic can be created or invalid messages sent without also being seent/consumed by the main services and causing unnecessary exceptions and alerts&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Maintain clear documentation behind the reasons behind each topic and the config behind them - volume expectations etc&lt;/li&gt;
  &lt;li&gt;Create a Kafka topology diagram showing clearly the path(s) taken by messages between components - this is especially important the more services and topics are created&lt;/li&gt;
  &lt;li&gt;Create documentation showing exactly what keys/values are put onto each topic - useful for debugging and new joiners&lt;/li&gt;
  &lt;li&gt;Ensure all Kafka configuration for new producers/consumers is done in a single central place (likely a shared lib) to avoid duplication and chance of issues if placed everywhere
    &lt;ul&gt;
      &lt;li&gt;list of broker hosts per environment, properties for Kerberos and auth etc&lt;/li&gt;
      &lt;li&gt;maintain consistency in configuration for all producers/consumers regardless of where they are used - serialization, error handling, transactions etc&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
  &lt;li&gt;Ensure you have the appropriate disaster recovery plans in place to either recover from the cluster being down or failover
    &lt;ul&gt;
      &lt;li&gt;Kafka is not meant as a data store, although unlikely plan for all the data to be lost at any point&lt;/li&gt;
      &lt;li&gt;Determine how the application should react if the cluster is not available - brokers being down or transient network issues&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/li&gt;
&lt;/ul&gt;

</description>
        <pubDate>Sat, 18 Jun 2022 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2022/06/18/kafka-recommendations-best-practices.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2022/06/18/kafka-recommendations-best-practices.html</guid>
        
        <category>kafka</category>
        
        <category>command</category>
        
        <category>cli</category>
        
        <category>topic</category>
        
        <category>producer</category>
        
        <category>consumer</category>
        
        <category>consumer group</category>
        
        <category>zookeeper</category>
        
        
      </item>
    
      <item>
        <title>Kafka Command Cheat Sheet</title>
        <description>&lt;h2 id=&quot;environment-variables&quot;&gt;Environment Variables&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Set path to binaries&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export JAVA_HOME=/path/to/jdk&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export PATH=$PATH:/path/to/kafka/common/bin&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Add Kerberos / SASL config options (if required)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export KAFKA_OPTS=&quot;-Djava.security.krb5.conf/etc/krb5.conf -Dzookeeper.sasl.client.username=myuser -Djava.security.auth.login.config=/var/tmp/jaas.conf&quot;&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export KCONFIG=/var/tmp/kafka-client.properties&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Where properties contains:&lt;/p&gt;

&lt;div class=&quot;language-properties highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;py&quot;&gt;security.protocol&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;SASL_PLAINTEXT&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;sasl.mechanism&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;GSSAPI&lt;/span&gt;
&lt;span class=&quot;py&quot;&gt;sasl.kerberos.service.name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;=&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;myuser&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Set common broker lists&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export KBROKER=host:9092&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;export ZBROKER=host:2181&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;topics&quot;&gt;Topics&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Create a new topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-topics.sh --zookeeper $ZBROKER --create --replication-factor 3 --partitions 4 --topic topic1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Describe an existing topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-topics.sh --zookeeper $ZBROKER --describe --topic topic1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;List all topics in the cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-topics.sh --zookeeper $ZBROKER --list&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete a topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-topics.sh --zookeeper $ZBROKER --delete --topic topic1.*&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Alter topic configuration&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-configs.sh --zookeeper $ZBROKER --alter --entity-type topics --entity-name topic1 --add-config retention.ms=2592000000&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;consumers&quot;&gt;Consumers&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run a console consumer starting from the beginning of a topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-console-consumer.sh --bootstrap-server $KBROKER --topic topic1 --from-beginning --consumer-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Consumer which prints key and value for each message&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-console-consumer.sh --bootstrap-server $KBROKER --topic topic1 --from-beginning --property print.key=true --property key.separator=&quot;|&quot; --consumer-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Create a consumer inside a specific group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-console-consumer.sh --bootstrap-server $KBROKER --topic topic1 --group group1 --consumer-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;producers&quot;&gt;Producers&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Run a console producer pushing to a specific topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-console-producer.sh --broker-list $KBROKER --topic topic1 --producer-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Include a specific key in each published message&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-console-producer.sh --broker-list $KBROKER --topic topic1 --property &quot;parse.key=true&quot; --property &quot;key.separator=|&quot; --producer-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;consumer-groups&quot;&gt;Consumer Groups&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;List all consumer groups in the cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --list --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Describe offsets and status of a consumer group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --describe --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete a consumer group&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --delete --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reset offsets to the beginning for all topics&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --reset-offsets --to-earliest --all-topics --execute --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reset offsets to beginning for a specific topic&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --reset-offsets --to-earliest --topic topic1 --execute --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reset offsets to latest&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --reset-offsets --to-latest --all-topics --execute --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reset offsets to specific value&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --reset-offsets --to-offset 10 --topic topic1:10 --execute --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reset offsets by a specific shift amount&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-consumer-groups.sh --bootstrap-server $KBROKER --group group1 --reset-offsets --shift-by -10 --topic topic1 --execute --command-config $KCONFIG&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;acls&quot;&gt;ACL’s&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Grant superuser access to all topics and groups&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;kafka-acls.sh --authorizer-properties zookeeper.connect=$ZBROKER --add --allow-principal User:myuser --operation ALL --topic &apos;*&apos; --group &apos;*&apos; --cluster&lt;/code&gt;&lt;/p&gt;

&lt;hr /&gt;

&lt;h2 id=&quot;zookeeper&quot;&gt;Zookeeper&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Connect to the Zookeeper cluster&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;zookeeper-shell.sh $ZHOST:$ZPORT&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Set Zookeeper ACL for superuser&lt;/strong&gt;&lt;/p&gt;

&lt;div class=&quot;language-plaintext highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;ls /
setAcl / sasl:myuser:cdrqa,world:anyone:r
getAcl /
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;&lt;a href=&quot;https://medium.com/@TimvanBaarsen/apache-kafka-cli-commands-cheat-sheet-a6f06eac01b&quot;&gt;https://medium.com/@TimvanBaarsen/apache-kafka-cli-commands-cheat-sheet-a6f06eac01b&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href=&quot;https://docs.cloudera.com/documentation/enterprise/6/6.3/topics/kafka_admin_cli.html&quot;&gt;https://docs.cloudera.com/documentation/enterprise/6/6.3/topics/kafka_admin_cli.html&lt;/a&gt;&lt;/p&gt;

</description>
        <pubDate>Tue, 05 Apr 2022 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2022/04/05/kafka-command-cheat-sheet.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2022/04/05/kafka-command-cheat-sheet.html</guid>
        
        <category>kafka</category>
        
        <category>command</category>
        
        <category>cheat sheet</category>
        
        <category>cli</category>
        
        <category>topic</category>
        
        <category>producer</category>
        
        <category>consumer</category>
        
        <category>consumer group</category>
        
        <category>zookeeper</category>
        
        
      </item>
    
      <item>
        <title>Ubuntu Server Setup Part 10 - Install Docker and Docker Compose</title>
        <description>&lt;ul&gt;
  &lt;li&gt;&lt;a href=&quot;/2016/03/29/ubuntu-server-setup-part-1-logging-in.html&quot;&gt;Part 1 - Logging In&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/03/11/ubuntu-server-setup-part-2-securing-login.html&quot;&gt;Part 2 - Securing Login&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/07/31/ubuntu-server-setup-part-3-setup-firewall.html&quot;&gt;Part 3 - Installing a Firewall&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/08/08/ubuntu-server-setup-part-4-setup-nginx-server.html&quot;&gt;Part 4 - Setup Nginx Web Server&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/08/27/ubuntu-server-setup-part-5-git-ruby-jekyll.html&quot;&gt;Part 5 - Install Git, Ruby and Jekyll&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/09/12/ubuntu-server-setup-part-6-https-with-lets-encrypt.html&quot;&gt;Part 6 - HTTPS With Let’s Encrypt&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/10/10/ubuntu-server-setup-part-7-forward-email-postfix.html&quot;&gt;Part 7 - Email Forwarding with Postfix&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2018/10/30/ubuntu-server-setup-part-8-sending-email-through-gmail.html&quot;&gt;Part 8 - Sending Email Through Gmail&lt;/a&gt;&lt;/li&gt;
  &lt;li&gt;&lt;a href=&quot;/2019/06/16/ubuntu-server-setup-part-9-reverse-proxy-nginx.html&quot;&gt;Part 9 - Setup a Reverse Proxy with Nginx&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It’s likely that you will want to run containers of some kind on your server, for that we’ll be installing and using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Docker&lt;/code&gt;. This consists of a couple different parts - the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Docker Engine&lt;/code&gt; itself which runs in the background as a daemon process, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; CLI commands which allow you to interact with the Engine, and finally &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; which is another tool for easily managing multiple containers. On Ubuntu based machines the recommended way of installation is to use the official Docker repository.&lt;/p&gt;

&lt;h3 id=&quot;install-prerequisites-and-set-up-the-repository&quot;&gt;Install Prerequisites and Set Up the Repository&lt;/h3&gt;

&lt;p&gt;Before we do anything more, update your local &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt; repositories and install a few prerequisite packages which are required in later steps:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;ca-certificates curl gnupg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Next, we need to add the official &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPG&lt;/code&gt; key provided by Docker for their repository:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-fsSL&lt;/span&gt; https://download.docker.com/linux/ubuntu/gpg | &lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;gpg &lt;span class=&quot;nt&quot;&gt;--dearmor&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/share/keyrings/docker-archive-keyring.gpg
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Finally, we can add the official Docker repository, specifying that the releases must be signed by the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GPG&lt;/code&gt; key we downloaded in the previous step. The command below will point to the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;stable&lt;/code&gt; repository, but you can use the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;nightly&lt;/code&gt; or &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;test&lt;/code&gt; channels if you prefer:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;echo&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;deb [arch=&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;dpkg &lt;span class=&quot;nt&quot;&gt;--print-architecture&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; signed-by=/usr/share/keyrings/docker-archive-keyring.gpg] https://download.docker.com/linux/ubuntu &lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;lsb_release &lt;span class=&quot;nt&quot;&gt;-cs&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt; stable&quot;&lt;/span&gt; | &lt;span class=&quot;nb&quot;&gt;sudo tee&lt;/span&gt; /etc/apt/sources.list.d/docker.list &lt;span class=&quot;o&quot;&gt;&amp;gt;&lt;/span&gt; /dev/null
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;h3 id=&quot;install-docker&quot;&gt;Install Docker&lt;/h3&gt;

&lt;p&gt;Now we have the Docker repository and keys setup, we can pull down the latest packages again and directly install Docker directly through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;apt&lt;/code&gt;:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get update

&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;apt-get &lt;span class=&quot;nb&quot;&gt;install &lt;/span&gt;docker-ce docker-ce-cli containerd.io
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;If everything completed successfully you should now have Docker Engine running in the background and the ability to run the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker&lt;/code&gt; CLI commands to run and manage images/containers. To verify things are working correctly, start an instance of the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;hello-world&lt;/code&gt; image:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More details at &lt;a href=&quot;https://docs.docker.com/engine/install/ubuntu/&quot;&gt;https://docs.docker.com/engine/install/ubuntu/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;configuration-tips&quot;&gt;Configuration Tips&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Run Docker CLI as a non-root user&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;You may have noticed in the verify step above that we had to invoke the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker run&lt;/code&gt; command as &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; in order to successfully run a new container. This is required since the Docker daemon always runs as the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt; user and it binds to a Unix socket which is also owned by &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;root&lt;/code&gt;. This is quite painful when interacting with the CLI however, so there is a workaround using groups:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# add a new group called &apos;docker&apos; (it might already exist)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;groupadd docker

&lt;span class=&quot;c&quot;&gt;# add the current user to the docker group&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;usermod &lt;span class=&quot;nt&quot;&gt;-aG&lt;/span&gt; docker &lt;span class=&quot;nv&quot;&gt;$USER&lt;/span&gt;

&lt;span class=&quot;c&quot;&gt;# after logging out and back in again you should be able to run docker without sudo&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker run hello-world
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Note that this is not the same as running the daemon itself as a non-root user, so all the same security implications still remain when running containers.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Run Docker on startup&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;By default the daemon processes required to interact with Docker are not configured to start when the system boots. To rectify this we can instruct &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;systemd&lt;/code&gt; to automatically start them for us:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;docker.service
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;systemctl &lt;span class=&quot;nb&quot;&gt;enable &lt;/span&gt;containerd.service
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More details at &lt;a href=&quot;https://docs.docker.com/engine/install/linux-postinstall/&quot;&gt;https://docs.docker.com/engine/install/linux-postinstall/&lt;/a&gt;&lt;/p&gt;

&lt;h3 id=&quot;install-docker-compose&quot;&gt;Install Docker Compose&lt;/h3&gt;

&lt;p&gt;For reasons I don’t fully understand &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;docker-compose&lt;/code&gt; doesn’t ship with the core Docker packages and so requires an extra installation step. Fortunately, it’s just a single binary so we can just directly download it into a location within the current &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;PATH&lt;/code&gt; and start using it:&lt;/p&gt;

&lt;div class=&quot;language-bash highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c&quot;&gt;# download the latest release binary (replace the version from https://github.com/docker/compose/releases)&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo &lt;/span&gt;curl &lt;span class=&quot;nt&quot;&gt;-L&lt;/span&gt; &lt;span class=&quot;s2&quot;&gt;&quot;https://github.com/docker/compose/releases/download/v2.2.3/docker-compose-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-s&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;-&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;$(&lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;uname&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-m&lt;/span&gt;&lt;span class=&quot;si&quot;&gt;)&lt;/span&gt;&lt;span class=&quot;s2&quot;&gt;&quot;&lt;/span&gt; &lt;span class=&quot;nt&quot;&gt;-o&lt;/span&gt; /usr/local/bin/docker-compose

&lt;span class=&quot;c&quot;&gt;# add execute permissions&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;&lt;span class=&quot;nb&quot;&gt;sudo chmod&lt;/span&gt; +x /usr/local/bin/docker-compose

&lt;span class=&quot;c&quot;&gt;# verify everything is working&lt;/span&gt;
&lt;span class=&quot;nv&quot;&gt;$ &lt;/span&gt;docker-compose &lt;span class=&quot;nt&quot;&gt;--version&lt;/span&gt;
Docker Compose version v2.2.3
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;More details at &lt;a href=&quot;https://github.com/docker/compose&quot;&gt;https://github.com/docker/compose&lt;/a&gt;&lt;/p&gt;
</description>
        <pubDate>Thu, 24 Feb 2022 00:00:00 +0000</pubDate>
        <link>https://ryanharrison.co.uk/2022/02/24/ubuntu-server-setup-part-10-install-docker.html</link>
        <guid isPermaLink="true">https://ryanharrison.co.uk/2022/02/24/ubuntu-server-setup-part-10-install-docker.html</guid>
        
        <category>ubuntu</category>
        
        <category>server</category>
        
        <category>docker</category>
        
        <category>container</category>
        
        <category>compose</category>
        
        <category>install</category>
        
        
      </item>
    
  </channel>
</rss>
