<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="3.10.0">Jekyll</generator><link href="https://sue.lamzi.com/feed.xml" rel="self" type="application/atom+xml" /><link href="https://sue.lamzi.com/" rel="alternate" type="text/html" /><updated>2026-07-09T08:02:55+00:00</updated><id>https://sue.lamzi.com/feed.xml</id><title type="html">Sue’s Notebook</title><subtitle>On software architecture, living documentation and sustainable software design.</subtitle><entry><title type="html">On Exceptions and Expected Business Failures</title><link href="https://sue.lamzi.com/general/2026/07/03/exception-handling/" rel="alternate" type="text/html" title="On Exceptions and Expected Business Failures" /><published>2026-07-03T00:00:00+00:00</published><updated>2026-07-03T00:00:00+00:00</updated><id>https://sue.lamzi.com/general/2026/07/03/exception-handling</id><content type="html" xml:base="https://sue.lamzi.com/general/2026/07/03/exception-handling/"><![CDATA[<p>Most of the applications I’ve been working on are fairly typical three-tier application. In those, each layer throws exceptions, they are either caught and converted by the next layer or - most of the time - are just runtime exceptions converted into HTTP response by an exception handler at presentation level:</p>

<pre><code class="language-mermaid">sequenceDiagram
    actor C as Caller
    participant P as Presentation
    participant A as Application
    participant D as Data
    A --&gt;&gt; P: BusinessException
    P -&gt;&gt; C: 400 
    D --&gt;&gt; P: TechnicalException  
    P -&gt;&gt; C: 500
</code></pre>

<p>When needed, some projects added an Error code as an enum in order to perform more complex handling. For most applications, it’s more than enough.</p>

<p>I’m currently working on an application with a richer business and decided to formalise it into an additional <strong>domain</strong> layer.</p>

<p>This ended up being a  DDD inspired application with an hexagonal architecture:</p>

<p><img src="/assets/images/hexagon.svg" alt="High-level dependency graph" /></p>

<p>The typical runtime exceptions handled by the presentation layer still worked well for interrupting the flow in the application and infrastructure layers, but they felt less appropriate at the boundary with the new domain layer. That made me wonder whether expected business failures should really follow the same pattern.</p>

<h2 id="requirements">Requirements</h2>

<p>Going back to the drawing board for something as fundamental as failure handling is an interesting exercise. It’s not something I’d ever really questioned before.</p>

<p>I had already worked on modelling exceptions so they could carry additional information useful to the front-end, but the underlying pattern was still the same: something fails, an exception interrupts the flow, and the presentation layer eventually turns it into a response.</p>

<p>This time felt different. A responsibility that traditionally belongs to a single layer in a three-tier application was now split across two.  And neither had the full picture: the domain knew <em>what</em> failed, while its caller knew <em>in which context</em> that failure occurred.</p>

<blockquote>
  <p>The domain should report what failed, but not decide what that failure means</p>
</blockquote>

<p>The domain cannot know if a failure is a critical exception or a simple business rejection. those are the consequences of the failure, they are not the failure itself. As an example, failing to build a Category in an application service is most likely due to an invalid input that needs to be reported back to the user while the same failure from the infrastructure layer means data corruption. the domain doesn’t know.</p>

<p>Since the service handling the domain should be able to handle failures appropriately, it needs to be clearly apparent in the contract</p>

<blockquote>
  <p>The caller must be aware that the operation it performed might result in failure</p>
</blockquote>

<p>And more generally, failures as much as success should be part of the normal domain behaviour.</p>

<blockquote>
  <p>Any operation impacting the domain should result either in a success or a failure.</p>
</blockquote>

<p>Expected business failures are part of the normal behaviour of the domain, not exceptional situations.</p>

<p>An additional property that I  wanted to have is to allow  for multiple business failures to be reported for a single operation.</p>

<blockquote>
  <p>The model must be able to represent all meaningful business failures produced by an operation.</p>
</blockquote>

<p>Some domain operations may fail for more than one reason. The model should not force you to pick only one violation if several are meaningful.</p>

<h2 id="representation">Representation</h2>

<p>So what should we use as the carrier for those failures?</p>

<p>The obvious first contender was using exceptions.  It’s familiar and it works.  I’m not for reinventing the wheel just for the sake of it.</p>

<p><strong>Runtime exceptions</strong> were not an option as they are not visible in the contract.  <strong>Checked exceptions</strong> on the other hand checked (pun intended) nearly all the boxes. Representing multiple failures feels a bit awkward but aside from that, it does the job.</p>

<p>It is worth asking though:  are business failures really exceptional and if not, should they be treated as exceptions?</p>

<p>Another contender, one that I like a lot, is to explicitly model domain operations as returning a <strong>Result</strong>.</p>

<p>It aligns well with the idea that  <em>Any operation impacting the domain should result either in a success or a failure.</em> and it explicitly tells the caller that the operation may fail by making failure part of the return contract. It also supports all of  my requirements.</p>

<p>The only downside I found is that we don’t have control over the return type of a constructor. It’s not a major drawback though, it’s easy to turn the constructors private and hide them behind a factory method.</p>

<p>Both can do the job, but each comes with trade-offs.</p>

<p>One advantage checked exceptions have is that the compiler forces the caller to acknowledge them. A <code class="language-plaintext highlighter-rouge">Result</code> can simply be ignored. 
In practice I found that discipline, reviews and static analysis were enough, but it’s a trade-off worth acknowledging.</p>

<p>On the other side, catching an exception only to convert it into another one is heavy.  It sure works, but it made me wonder: am I modelling the failure, or merely re-purposing exceptions to represent ordinary business outcomes?</p>

<p>If a business operation can normally end in either success or rejection, <code class="language-plaintext highlighter-rouge">Result</code> represents that reality better than an exception.</p>

<p>Overall, I went with the <code class="language-plaintext highlighter-rouge">Result</code> approach because I felt it aligned better with the spirit of my requirements but checked exceptions  would have been a perfectly valid solution as well.</p>

<p>All things considered, the right responsibility split is much more important than the mechanism used to implement it.</p>

<h2 id="interpretation">Interpretation</h2>

<p>Similarly to the domain not being able to select the right presentation-facing exception by itself, the application cannot infer it from the context alone.</p>

<p>Consider a  <code class="language-plaintext highlighter-rouge">productCreator.create</code> operation :</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="n">productCreator</span><span class="o">.</span><span class="na">create</span><span class="o">(</span><span class="n">name</span><span class="o">,</span> <span class="n">barcode</span><span class="o">,</span> <span class="n">actor</span><span class="o">)</span>
</code></pre></div></div>

<p>it could fail because the product barcode is invalid, because the actor isn’t authorised to create a product, or because the barcode is already in use. Although they are all business failures, they lead to different outcomes: BadRequest, Forbidden or Conflict.</p>

<p>The application service must be able to determine the appropriate action based on both the context and the failure it received.</p>

<p>One option is to base the application’s decision on individual business failures. This provides the maximum amount of semantic information, but it also tightly couples the application to the domain’s business rules.</p>

<p>Another option is to expose a broader classification of business failures instead of the individual failures themselves. This can be achieved by introducing <strong>failure categories</strong> that group together business failures sharing the same broad semantics.</p>

<p>Most of the time, the application doesn’t need to know whether the barcode or the product name is invalid, only that it is dealing with a <code class="language-plaintext highlighter-rouge">VALIDATION</code> failure. Broadly speaking, it only needs to know <strong>what kind of failure</strong> it received in order to make an informed decision, not the specific business rule that was violated.</p>

<p>And what if the application needs to react to a specific business failure? Working with typed failures still allows it to base its decision on more detailed information when, and only when,  it needs it.</p>

<p>The two approaches can be summarised as follows:</p>

<table>
  <thead>
    <tr>
      <th>Individual failures</th>
      <th>Failure categories</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <td>Explain exactly <strong>what</strong> failed</td>
      <td>Explain <strong>what kind</strong> of failure occurred</td>
    </tr>
    <tr>
      <td>Large and grows with the business</td>
      <td>Small and relatively stable</td>
    </tr>
    <tr>
      <td>Fine-grained</td>
      <td>Coarse-grained</td>
    </tr>
    <tr>
      <td>Used when detail matters</td>
      <td>Used when broad semantics are enough</td>
    </tr>
  </tbody>
</table>

<p>The application’s decision space is intentionally much smaller than the domain’s failure space: the domain should be free to grow new business rules without forcing the application to grow new decision logic.</p>

<p>Individual failures explain <strong>what</strong> happened; categories provide just enough information for the application to decide <strong>what to do</strong>.</p>

<p>But that alone is is not enough to determine the application’s response. The same business failure may have different meanings depending on <strong>how the situation arose</strong> and <strong>who is responsible for it</strong>..</p>

<p>During product creation, a <code class="language-plaintext highlighter-rouge">VALIDATION</code> failure is interpreted as a <code class="language-plaintext highlighter-rouge">BadRequest</code> because the invalid data originates from the caller.</p>

<pre><code class="language-mermaid">sequenceDiagram
    participant C as Controller
    participant A as Application
    participant D as Domain

    C-&gt;&gt;A: createProduct(request)

    A-&gt;&gt;D: createProduct.create(request.barcode,...)

    alt Success
        D--&gt;&gt;A: Result.success()
    else Invalid barcode
        D--&gt;&gt;A: Result.failure: VALIDATION
		A--&gt;&gt;A: Wrong caller input
        A--&gt;&gt;C: BadRequestException
    end
</code></pre>

<p>By contrast, when validating an identity that was produced internally, the same <code class="language-plaintext highlighter-rouge">VALIDATION</code> failure indicates an application invariant has been violated and therefore results in an <code class="language-plaintext highlighter-rouge">ApplicationException</code></p>

<pre><code class="language-mermaid">sequenceDiagram
    participant P as Presentation
    participant A as Application
    participant D as Domain

    A-&gt;&gt;D: Identity.of(actor.identity())

    alt Success
        D--&gt;&gt;A: Result.success()
    else Invalid identity
        D--&gt;&gt;A: Result.failure: VALIDATION
        A--&gt;&gt;A: Internal invariant violated
        A--&gt;&gt;P: ApplicationException
    end
</code></pre>

<p>Armed with both the context and the kind of failure it received, the application has all the information it needs to interpret the failure correctly.</p>

<h2 id="conclusion">Conclusion</h2>

<p>I think I ended up in a comfortable place.  The responsibility split between the different layers is coherent</p>

<ul>
  <li><strong>presentation</strong> produce caller facing HTTP Responses</li>
  <li><strong>application</strong> determine what kind of exception to throw based on the orchestration context and the type of failures it encountered</li>
  <li><strong>domain</strong> enforces business rules and inform on failures</li>
  <li><strong>infrastructure</strong>  throws only technical exceptions</li>
</ul>

<p>The domain knows why something is invalid. The application knows whether that invalidity is expected but it needed  <em>some</em> semantic information to make a decision. Introducing the failure categories neatly solved the tension between the application having to know too much (being aware of every expected failures in the context) or too little ( just getting a failure without knowing how to interpret it).</p>

<p>The value of categories is not only that they reduce coupling. It is that they let the application handle an <strong>exhaustive closed set of interpretations</strong> without knowing the <strong>open-ended set of business failures</strong>.</p>

<p>Regarding the failure carrier, once you’ve ruled out the objectively poor fit of run time exceptions, I don’t think the choice has much importance. At this point,  the team’s experience and conventions become a perfectly valid tie-breaker. It’s somewhat ironic that I ended up comparing two approaches that are both less common today. Runtime exceptions have become the default in many Java applications, but they were the first option I ruled out because they didn’t satisfy my main requirement: making expected business failures part of the contract.</p>]]></content><author><name></name></author><category term="general" /><category term="java" /><category term="oop" /><category term="exception" /><category term="ddd" /><category term="architecture" /><summary type="html"><![CDATA[Introducing a domain layer made me reconsider how business failures and exceptions are handled, and what responsibility each architectural layer should have in that process.]]></summary></entry><entry><title type="html">DRY…at last</title><link href="https://sue.lamzi.com/general/2026/07/02/mermaid-java-dsl/" rel="alternate" type="text/html" title="DRY…at last" /><published>2026-07-02T00:00:00+00:00</published><updated>2026-07-02T00:00:00+00:00</updated><id>https://sue.lamzi.com/general/2026/07/02/mermaid-java-dsl</id><content type="html" xml:base="https://sue.lamzi.com/general/2026/07/02/mermaid-java-dsl/"><![CDATA[<p>One of the things I’m notoriously bad at is visualising. Some people have vivid pictures in their minds; I’m met with a dark void.</p>

<p>That’s probably why I’m so reliant on documentation to organise my thoughts: I need the visual support.</p>

<p>But, like most technically inclined people, I’m also (selectively!) extremely lazy.  Maintaining documentation by hand is time-consuming, unsatisfactory and, most of the time, a lost battle.</p>

<p>So quite naturally, I developed an interest in living documentation. After all, it’s time spent doing what I like - playing with code - to obtain what I need: an easy-to-consume representation of knowledge that would otherwise remain scattered throughout the project.</p>

<p>I find the emergence of  knowledge from raw data extremely satisfying.</p>

<p>In need of visual representations, I’ve looked at different options for diagram generation and eventually settled on Mermaid.
It supports most of the diagram types I need, lets me focus on content instead of layout and is widely supported (Confluence, GitHub, GitLab, even this Jekyll site…).</p>

<p>It is also remarkably easy to generate. All you need is a StringBuilder and you are good to go.</p>

<p>That StringBuilder… Having to go back to the Mermaid documentation every time I needed to check the syntax for a diagram was a no-go for me.</p>

<p>I was in Java; I wanted a Java DSL. I prefer working with an abstraction that lets my IDE help me with completion and is less error-prone than handling raw strings everywhere.</p>

<p>Surprisingly, and really frustratingly, I couldn’t find one. Be it because living documentation isn’t widespread or because I’m lazier than most, apparently the DIY approach was the way to go.</p>

<p>So that’s what I did. I wrapped it in a thin DSL. Then I copied it, then I copied it, then… you get the idea.</p>

<p>Eventually, when I got fed up with the duplication, I extracted it into a small reusable library. At first, just for myself.</p>

<p>I intentionally kept the DSL close to Mermaid’s terminology. The goal wasn’t to invent another abstraction, only to avoid writing Mermaid by hand.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">new</span> <span class="nc">FlowchartDiagram</span><span class="o">()</span>
	<span class="o">.</span><span class="na">direction</span><span class="o">(</span><span class="no">LR</span><span class="o">)</span>
    <span class="o">.</span><span class="na">addNode</span><span class="o">(</span><span class="n">node</span><span class="o">(</span><span class="s">"P1"</span><span class="o">).</span><span class="na">shape</span><span class="o">(</span><span class="n">classicNodeShape</span><span class="o">(</span><span class="s">"Project 1"</span><span class="o">,</span> <span class="no">SQUARE_EDGES</span><span class="o">)))</span>
    <span class="o">.</span><span class="na">addNode</span><span class="o">(</span><span class="n">node</span><span class="o">(</span><span class="s">"P2"</span><span class="o">).</span><span class="na">shape</span><span class="o">(</span><span class="n">classicNodeShape</span><span class="o">(</span><span class="s">"Project 2"</span><span class="o">,</span> <span class="no">SQUARE_EDGES</span><span class="o">)))</span>
    <span class="o">.</span><span class="na">addNode</span><span class="o">(</span><span class="n">node</span><span class="o">(</span><span class="s">"P3"</span><span class="o">).</span><span class="na">shape</span><span class="o">(</span><span class="n">classicNodeShape</span><span class="o">(</span><span class="s">"Project 3"</span><span class="o">,</span> <span class="no">SQUARE_EDGES</span><span class="o">)))</span>
    <span class="o">.</span><span class="na">addNode</span><span class="o">(</span><span class="n">node</span><span class="o">(</span><span class="s">"DSL"</span><span class="o">).</span><span class="na">shape</span><span class="o">(</span><span class="n">classicNodeShape</span><span class="o">(</span><span class="s">"mermaid-java-dsl"</span><span class="o">,</span> <span class="no">SQUARE_EDGES</span><span class="o">)))</span>
    
    <span class="o">.</span><span class="na">addLink</span><span class="o">(</span><span class="s">"P1"</span><span class="o">,</span> <span class="s">"DSL"</span><span class="o">)</span>
    <span class="o">.</span><span class="na">addLink</span><span class="o">(</span><span class="s">"P2"</span><span class="o">,</span> <span class="s">"DSL"</span><span class="o">)</span>
    <span class="o">.</span><span class="na">addLink</span><span class="o">(</span><span class="s">"P3"</span><span class="o">,</span> <span class="s">"DSL"</span><span class="o">);</span>
</code></pre></div></div>

<pre><code class="language-mermaid">flowchart LR
    P1[Project 1]
    P2[Project 2]
    P3[Project 3]
    DSL[mermaid-java-dsl]
    P1 --&gt; DSL
    P2 --&gt; DSL
    P3 --&gt; DSL
</code></pre>

<p>But selective laziness struck again, this time in the opposite direction: Reusable code needs to be tested.
So I turned the examples from the Mermaid documentation into tests. If the DSL could reproduce the documented diagrams, I could be reasonably confident it generated valid Mermaid. And of course, I had to cover <em>all</em> the examples… because.</p>

<p>That could have been the end of the story. My problem was solved,  and the little library had been living its best life on my personal GitLab for quite a while. What a nice Happy Ending.</p>

<p>But I remembered my own frustration when I was looking for something similar and couldn’t find anything. So I figured I might as well open-source it.</p>

<p>It doesn’t implement every Mermaid diagram type, only the ones I actively use (Flowcharts and Class Diagrams for now). That said, those implementations cover all the examples from the Mermaid documentation, so they should be fairly complete.</p>

<p>It solved my problem.</p>

<p>If it happens to solve yours too, even better.</p>

<p>The code is on GitHub: https://github.com/lamzi-com/mermaid-java-dsl  and if you’d like to give it a try, it’s available on Maven Central:</p>

<div class="language-xml highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">&lt;dependency&gt;</span>
    <span class="nt">&lt;groupId&gt;</span>com.lamzi.doc<span class="nt">&lt;/groupId&gt;</span>
    <span class="nt">&lt;artifactId&gt;</span>mermaid-java-dsl<span class="nt">&lt;/artifactId&gt;</span>
<span class="nt">&lt;/dependency&gt;</span>
</code></pre></div></div>

<p>It’s still undocumented, but the test cases are full of examples.</p>

<ul>
  <li>
    <p><a href="https://github.com/lamzi-com/mermaid-java-dsl/blob/main/src/test/java/com/lamzi/doc/mermaid/diagram/classdiagram/ClassDiagramTest.java">classDiagram</a></p>
  </li>
  <li>
    <p><a href="https://github.com/lamzi-com/mermaid-java-dsl/blob/main/src/test/java/com/lamzi/doc/mermaid/diagram/flowchart/FlowchartDiagramTest.java">flowchartDiagram</a></p>
  </li>
</ul>]]></content><author><name></name></author><category term="general" /><category term="java" /><category term="mermaid" /><category term="living-documentation" /><category term="documentation" /><category term="architecture" /><category term="open-source" /><summary type="html"><![CDATA[Extracting a reusable Java DSL for generating Mermaid diagrams into an open-source library.]]></summary></entry><entry><title type="html">The illusion of Encapsulation</title><link href="https://sue.lamzi.com/general/2026/06/26/on-getters-and-setters/" rel="alternate" type="text/html" title="The illusion of Encapsulation" /><published>2026-06-26T00:00:00+00:00</published><updated>2026-06-26T00:00:00+00:00</updated><id>https://sue.lamzi.com/general/2026/06/26/on-getters-and-setters</id><content type="html" xml:base="https://sue.lamzi.com/general/2026/06/26/on-getters-and-setters/"><![CDATA[<h2 id="the-beginners-paradox">The Beginner’s Paradox</h2>

<p>One of the first principles we learn when studying OOP is: <em>encapsulation is important.</em>
Nice one, I can agree with that.</p>

<p>I also learned:  <em>Java is an OOP language.</em> Okay!</p>

<p>Then comes the reality of your first job: let’s write a DTO.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">User</span><span class="o">{</span>
	<span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span> 
	
	<span class="kd">public</span> <span class="nc">String</span> <span class="nf">getName</span><span class="o">(){</span>
		<span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">name</span><span class="o">;</span> 
	<span class="o">}</span> 
	
	<span class="kd">public</span> <span class="kt">void</span> <span class="nf">setName</span><span class="o">(</span><span class="nc">String</span> <span class="n">name</span><span class="o">){</span>
		<span class="k">this</span><span class="o">.</span><span class="na">name</span> <span class="o">=</span> <span class="n">name</span><span class="o">;</span> 
	<span class="o">}</span>
<span class="o">}</span>  
</code></pre></div></div>

<ul>
  <li><strong>me:</strong> but with the getter and setter, the field is public. why not just make it public?</li>
  <li><strong>senior:</strong> No, Encapsulation is important! The field must remain private</li>
  <li><strong>me:</strong> but…</li>
  <li><strong>senior:</strong> That’s the JavaBeans convention. That’s  how it’s done in Java.</li>
</ul>

<p>My first reaction was incomprehension: Why does the <code class="language-plaintext highlighter-rouge">public</code> modifier even exist for fields if it so strongly shouldn’t be used? And aren’t objects supposed to enforce their own state’s validity? How can they do it in this situation?</p>

<h2 id="why-and-how-the-convention-that-became-law">Why and How: The Convention That Became Law</h2>

<p>Objects are supposed to encapsulate state and behaviour; but the reality of DTOs is that they have a different purpose: representing data.</p>

<p>Conceptually, they are a collection of properties; they are closer to a C <code class="language-plaintext highlighter-rouge">struct</code> than to a rich domain object.</p>

<div class="language-c highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">struct</span> <span class="n">user</span> <span class="p">{</span>
    <span class="kt">char</span> <span class="o">*</span><span class="n">name</span><span class="p">;</span>
<span class="p">};</span>
</code></pre></div></div>

<p>The problem is that, historically, Java had no way to express this concept. The language had primitives, enums and objects but nothing that explicitly meant <em>“this type is just data.”</em></p>

<p>So how do you represent a complex data structure in a language whose primary abstraction is <strong>the object</strong>? As an object, of course!</p>

<p>As the Java ecosystem grew,  frameworks and tools increasingly needed a standard way to discover and manipulate object properties. GUI builders, serializers, and later frameworks all faced the same challenge.</p>

<p>The JavaBeans specification answered that need by introducing a standardised property model based on getters and setters.</p>

<p>At the time, it was a pragmatic solution to a real problem.</p>

<p>The convention worked remarkably well… for the problem it was designed to solve:  IDEs started generating getters and setters; frameworks understood them;  teams stopped documenting or testing them because they were considered trivial boilerplate.</p>

<p>Over time, the convention escaped its original purpose. Eventually, writing a stateful object without getters and setters started looking wrong. The convention had become the norm.</p>

<p>It no longer felt like a convention, it simply became how Java is written.</p>

<h2 id="the-social-contract">The Social Contract</h2>

<p>And because it became how Java is written, developers started building expectations.  Eventually, those expectations formed  a social contract, leading developers to infer things that the type system never promises.</p>

<p>Here are a few examples of situations I encountered with my  colleagues when I started  my Java journey, before I understood how deeply those conventions had shaped developers’ expectations.</p>

<p>In a new application, I prevented a list  from being modified via the getter so we had to use a designated  method for adding items. 
My reasoning was that a getter was meant to access the information, not to modify it, so it felt cleaner that way:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Item</span><span class="o">&gt;</span> <span class="nf">getItems</span><span class="o">()</span> <span class="o">{</span>
    <span class="k">return</span> <span class="nc">Collections</span><span class="o">.</span><span class="na">unmodifiableList</span><span class="o">(</span><span class="n">items</span><span class="o">);</span>
<span class="o">}</span>

<span class="kd">public</span> <span class="kt">void</span> <span class="nf">addItem</span><span class="o">(</span><span class="nc">Item</span> <span class="n">item</span><span class="o">){</span>
    <span class="k">this</span><span class="o">.</span><span class="na">items</span><span class="o">.</span><span class="na">add</span><span class="o">(</span><span class="n">item</span><span class="o">);</span>
<span class="o">}</span>
</code></pre></div></div>

<p>My colleague was outraged! Whether the design itself was better or not was almost beside the point. The issue was that I had broken his expectations.</p>

<p>To him, <code class="language-plaintext highlighter-rouge">getItems()</code> meant gaining access to the <code class="language-plaintext highlighter-rouge">items</code> field.</p>

<p>To me, it meant asking the object for its items.</p>

<p>Those are not necessarily the same thing.</p>

<p>The social contract demands that a getter exposes a backing field. Diverging from that behaviour creates confusion.</p>

<p>Looking back, we might have been able to compromise by skipping the getter naming convention like we did with another colleague where I proposed returning an <code class="language-plaintext highlighter-rouge">Optional</code>  from a getter-like method as the field was known to sometimes (often) be null:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">public</span> <span class="kd">class</span> <span class="nc">Person</span><span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Address</span> <span class="n">address</span><span class="o">;</span> 
	<span class="o">...</span> 
	<span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Address</span><span class="o">&gt;</span> <span class="nf">getAddress</span><span class="o">(){...}</span>
<span class="o">}</span>
</code></pre></div></div>

<p>The colleague rejected it, really uneasy, because “<em>the return type is not correct</em>”. The expectation is that <code class="language-plaintext highlighter-rouge">getAddress</code> should return an <code class="language-plaintext highlighter-rouge">Address</code>.</p>

<p>In this case, he agreed for the need and we settled on an alternative name:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nc">Optional</span><span class="o">&lt;</span><span class="nc">Address</span><span class="o">&gt;</span> <span class="nf">findAddress</span><span class="o">(){...}</span>
</code></pre></div></div>

<p>But compromising was only possible because he didn’t feel that the method was a getter anymore.</p>

<p>Trying to write an accessor that doesn’t follow the get/set convention was also often poorly received.<br />
For example,  <code class="language-plaintext highlighter-rouge">person.name()</code> instead of <code class="language-plaintext highlighter-rouge">person.getName()</code> was a no go in my team because, “<em>That’s not how it’s done in java</em>”.</p>

<p>Good conventions reduce cognitive load but they also shape our mental model: at some point, methods stopped being methods.</p>

<p>When people see <code class="language-plaintext highlighter-rouge">person.getName();</code> they don’t see a method to retrieve a person’s name, they see the <code class="language-plaintext highlighter-rouge">name</code> property.</p>

<p>Which implies a set of expectations:</p>

<p>For getters,  that they return a  backing field in O(1) time, that they won’t perform any kind of transformation, validation nor side effects and often, that they are paired with a setter.</p>

<p>For setters, that they would perform simple assignment with no side effect, computation, normalisation nor any validation.  <code class="language-plaintext highlighter-rouge">Objects.requireNonNull(...)</code> is sometimes tolerated but more often than not, any kind of validation is perceived as a betrayal.</p>

<p>Ironically, most of those expectations were never part of Java or even the JavaBeans specification. They emerged organically over decades of shared practice.</p>

<p>And that wouldn’t have been an issue if it had remained confined to data structure use cases only.</p>

<p>But it didn’t.</p>

<blockquote>
  <p><em>When you have a hammer, everything is a nail.</em></p>
</blockquote>

<p>Habits are powerful. Once a solution becomes familiar, we naturally start applying it in places where it wasn’t originally intended.</p>

<h2 id="the-hidden-costs">The hidden costs</h2>

<p>Those expectations solved a real problem, but once they became pervasive, they also introduced some subtle costs.</p>

<h3 id="the-illusion-of-encapsulation">The illusion of encapsulation</h3>

<p>First and foremost, what does <strong>encapsulation</strong> mean to you? While it is one of the fundamental principles of object-oriented programming, I’m not sure it means the same thing to everyone.</p>

<p>My understanding - and the one I’ll be using in this section - is the following:</p>

<ul>
  <li>allowing an object to keep control of its own state and enforce its invariants</li>
  <li>hiding an object’s internal representation behind its public interface</li>
</ul>

<p>While using methods to access an object’s state technically preserves the encapsulation, in practice, following the expectations surrounding the JavaBeans conventions can undermine many of its benefits.</p>

<h4 id="benefit-1-maintaining-invariants">Benefit #1: Maintaining invariants</h4>

<p>Because the field is private, we think the object is in control. Yet the very expectations encourage us to treat the getter and setter as direct field access.</p>

<p>See the following setter example.</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="cm">/**
     * Replace the current age with a new valid one
     *
     * @param age the new age of the person (does it even make sense to externally change the age? hmm... )
     * @throws NullPointerException if age is null
     * @throws BusinessException    if not 0 &lt;= age &lt;= MAXIMUM_AGE
     */</span>
    <span class="kd">public</span> <span class="kt">void</span> <span class="nf">setAge</span><span class="o">(</span><span class="nc">Integer</span> <span class="n">age</span><span class="o">)</span> <span class="o">{</span>
        <span class="nc">Objects</span><span class="o">.</span><span class="na">requireNonNull</span><span class="o">(</span><span class="n">age</span><span class="o">);</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">age</span> <span class="o">&lt;</span> <span class="mi">0</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">BusinessException</span><span class="o">(</span><span class="s">"Age must be strictly positive"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">if</span> <span class="o">(</span><span class="n">age</span> <span class="o">&gt;</span> <span class="no">MAXIMUM_AGE</span><span class="o">)</span> <span class="o">{</span>
            <span class="k">throw</span> <span class="k">new</span> <span class="nf">BusinessException</span><span class="o">(</span><span class="s">"Nobody can be that old"</span><span class="o">);</span>
        <span class="o">}</span>
        <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">=</span> <span class="n">age</span><span class="o">;</span>
    <span class="o">}</span>
</code></pre></div></div>

<p>Have you seen many setters like this?  The reality of this method is that most  Java developers - including myself - would call it something like  <code class="language-plaintext highlighter-rouge">changeAge(...)</code> and not consider it a setter.</p>

<p>As soon as we start using the control that encapsulation gives us, the method stops feeling like a setter.
It now has to be documented and tested because callers can no longer infer its behaviour from the naming convention alone.</p>

<p>But uncontrolled mutation jeopardises every other method on the object.</p>

<p>Once the object can no longer assume that <code class="language-plaintext highlighter-rouge">age</code> is valid, every method operating on it becomes more defensive… or more fragile</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code>    <span class="kd">public</span> <span class="kt">boolean</span> <span class="nf">canDrinkAlcohol</span><span class="o">(){</span>
        <span class="k">return</span> <span class="k">this</span><span class="o">.</span><span class="na">age</span> <span class="o">&gt;=</span> <span class="no">AGE_LIMIT</span><span class="o">;</span> 
    <span class="o">}</span>
</code></pre></div></div>

<h4 id="benefit-2-information-hiding">Benefit #2: Information hiding</h4>

<p>Here again, one of the benefits of encapsulation is that it allows an object to hide its internal structure.</p>

<p>In theory, this would let us rename - or remove - a field without impacting the public API.</p>

<p>In practice, however, <code class="language-plaintext highlighter-rouge">getAge()</code> doesn’t just expose a value. It also creates the expectation that a private field named <code class="language-plaintext highlighter-rouge">age</code> exists behind it.</p>

<p>Renaming that field now either changes the public API or creates a discrepancy between the implementation and what future developers expect to find.</p>

<p>Information hiding isn’t only about what the language exposes. It’s also about what developers infer.</p>

<p>So what information are we actually hiding?</p>

<h3 id="null-creep">Null creep</h3>

<p>Once objects are no longer expected to enforce invariants, there is little preventing them from existing in partially populated states. 
At that point, reusing the same representation across several use cases becomes an attractive way to reduce duplication.</p>

<p>See the <code class="language-plaintext highlighter-rouge">Product</code> DTO here:</p>

<div class="language-java highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nd">@Data</span>
<span class="kd">public</span> <span class="kd">class</span> <span class="nc">Product</span><span class="o">{</span>
    <span class="kd">private</span> <span class="nc">Long</span> <span class="n">id</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">name</span><span class="o">;</span>
    <span class="kd">private</span> <span class="nc">String</span> <span class="n">description</span><span class="o">;</span> 
    <span class="kd">private</span> <span class="nc">List</span><span class="o">&lt;</span><span class="nc">Category</span><span class="o">&gt;</span> <span class="n">categories</span><span class="o">;</span> 
<span class="o">}</span>
</code></pre></div></div>

<ul>
  <li><code class="language-plaintext highlighter-rouge">id</code> is null when used as a request body in <code class="language-plaintext highlighter-rouge">createProduct</code></li>
  <li><code class="language-plaintext highlighter-rouge">description</code> and <code class="language-plaintext highlighter-rouge">categories</code> are null in the return  of <code class="language-plaintext highlighter-rouge">findProducts</code> as they are not needed in the listing.</li>
  <li>All fields are populated in <code class="language-plaintext highlighter-rouge">getProduct</code>.</li>
</ul>

<p>Three use cases, three implicit contracts.</p>

<p>The problem isn’t the <code class="language-plaintext highlighter-rouge">null</code> values themselves, the problem is that nothing in the type tells us which fields are expected to be populated.  That knowledge now lives outside of the type system.</p>

<p>Once again, explicit contracts are replaced by shared expectations.</p>

<h3 id="boilerplate-explosion">Boilerplate explosion</h3>

<p>The last cost I would like to emphasise is the amount of ceremony involved.</p>

<p>Beyond the number of  lines of code required to write those accessors, every field  exposed via methods becomes part of the public API.  And some qualities are expected from public API methods which are not necessarily expected nor beneficial for  getters and setters.</p>

<p>Testing and documentation have costs attached to them. And when getters and setters are expected to be just dumb accessors, are those costs justified?</p>

<p>In practice we either acknowledge them as a special kind of methods by exempting them from test/documentation or we decide to prioritise consistency and live with the costs.</p>

<p>In any case, the sheer amount of boilerplate is a clue that something is off.</p>

<h2 id="light-at-the-end-of-the-tunnel">Light at the end of the tunnel?</h2>

<p>Unsurprisingly, the ecosystem started looking for ways to remove it.</p>

<h3 id="ide-code-generation">IDE code generation</h3>

<p>The first step was IDE code generation. It reduced the effort required to write those methods, but they were still part of the codebase. The boilerplate remained, along with the maintenance burden.</p>

<p>Then came automated code generation from dedicated libraries.</p>

<h3 id="lomboks-annotations">Lombok’s annotations</h3>

<p>Lombok is an initiative aiming at reducing boilerplate (not only in getters and setters) by the use of annotations and code generation.</p>

<p>The idea is that trivial code could be generated on the fly instead of painstakingly being written and maintained by hand.</p>

<p>It’s nice for boilerplate reduction, and it has the added value of increasing the code readability.</p>

<p>The presence of annotations in the code base communicates intent much more clearly than by simply inferring it  from a combination of fields and accessor names: rather than merely generating boilerplate, annotations such as <code class="language-plaintext highlighter-rouge">@Getter</code>, <code class="language-plaintext highlighter-rouge">@Setter</code> or <code class="language-plaintext highlighter-rouge">@Data</code>  explicitly tell the  reader that the class is meant to be a data holder.</p>

<p>But what it does not solve is the problem of differentiating data from behaviour in  the Java language; it only codified the convention (which is already a nice improvement in itself).</p>

<h3 id="records-clarify-intent">Records Clarify Intent</h3>

<p>The introduction of records into the Java language is the first time the language explicitly recognises data carriers as a distinct construct.</p>

<p>The fact that records use a noun as a name instead of an action is also a nice move in my opinion. It more clearly indicates that those methods are not acting on anything. They provide access to the underlying information.</p>

<p>Records are not an all-purpose solution but I believe it is a move in the right direction.</p>

<h2 id="conclusion">Conclusion</h2>

<p>Over time, we’ve started conflating three different things.</p>

<ul>
  <li>what <strong>Java</strong> allows,</li>
  <li>what <strong>JavaBeans</strong> standardised,</li>
  <li>what <strong>Java developers</strong> have come to expect.</li>
</ul>

<p>Those three are related, but they’re not the same.</p>

<p>As the convention became ubiquitous, it also shaped our mental model. More and more knowledge became implicit, relying on developers’ expectations rather than on what the language or the type system explicitly expressed.</p>

<p>When someone says, <em>“That’s not how Java is written,”</em> they’re often not talking about the language at all. They’re talking about the expectations inherited from a convention that solved a very real problem thirty years ago.</p>

<p>Getters and setters were meant to preserve the possibility of encapsulation. But the irony is that decades of convention have often discouraged us from exercising that possibility. In many codebases, getters and setters are no longer treated as methods that form an abstraction boundary; they’re treated as public fields written with extra ceremony and stronger cultural expectations.</p>

<p>Fortunately, both the ecosystem and the language itself have continued to evolve. IDEs, Lombok and, more recently, records all acknowledge that data carriers deserve better support. Whether that evolution will eventually blur the line between data and behaviour less than JavaBeans did remains to be seen, but I think it’s a step in the right direction.</p>]]></content><author><name></name></author><category term="general" /><category term="java" /><category term="oop" /><category term="api-design" /><category term="architecture" /><summary type="html"><![CDATA[Getters and setters were introduced as a practical way to represent data in Java. Decades later, the convention has become so pervasive that it shapes how many developers interpret objects, often in ways the language itself never specifies.]]></summary></entry><entry><title type="html">Hello World</title><link href="https://sue.lamzi.com/general/2026/06/16/hello-world/" rel="alternate" type="text/html" title="Hello World" /><published>2026-06-16T00:00:00+00:00</published><updated>2026-06-16T00:00:00+00:00</updated><id>https://sue.lamzi.com/general/2026/06/16/hello-world</id><content type="html" xml:base="https://sue.lamzi.com/general/2026/06/16/hello-world/"><![CDATA[<p>Hello to you, who stumbled upon this page.</p>

<p>This is the first post, because there has to be a first.</p>

<p>I’m opening this site to share and organize my thoughts on software engineering.</p>

<p>I hope you’ll find them useful. If not, I apologize in advance. :D</p>]]></content><author><name></name></author><category term="general" /><summary type="html"><![CDATA[Hello to you, who stumbled upon this page.]]></summary></entry></feed>