<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luke Tong</title>
    <description>The latest articles on DEV Community by Luke Tong (@luke_tong_d4f228249f32d86).</description>
    <link>https://dev.to/luke_tong_d4f228249f32d86</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3139634%2Fc08d2890-f0d4-47b7-81fe-a9e21f131b0b.png</url>
      <title>DEV Community: Luke Tong</title>
      <link>https://dev.to/luke_tong_d4f228249f32d86</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luke_tong_d4f228249f32d86"/>
    <language>en</language>
    <item>
      <title>Can I Let AI Work While I Drink Coffee? A Hard-Learned Lesson</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Tue, 26 Aug 2025 23:09:39 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/can-i-let-ai-work-while-i-drink-coffee-a-hard-learned-lesson-57ej</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/can-i-let-ai-work-while-i-drink-coffee-a-hard-learned-lesson-57ej</guid>
      <description>&lt;h1&gt;
  
  
  Can I Let AI Work While I Drink Coffee? A Hard-Learned Lesson
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;"Can I let AI work while I drink coffee?"&lt;/em&gt;&lt;br&gt;&lt;br&gt;
That was the question I asked myself recently.  &lt;/p&gt;

&lt;p&gt;The dream is simple: AI takes care of the heavy lifting, while I sit back and enjoy a latte. The reality, as I found out, is far more complicated.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Story: Debugging a SOAP Microservice
&lt;/h2&gt;

&lt;p&gt;I was working on a microservice that had to call downstream &lt;strong&gt;legacy systems&lt;/strong&gt;.  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Downstream system &lt;strong&gt;A&lt;/strong&gt; only supported &lt;strong&gt;SOAP 1.1&lt;/strong&gt;.
&lt;/li&gt;
&lt;li&gt;Downstream system &lt;strong&gt;B&lt;/strong&gt; only supported &lt;strong&gt;SOAP 1.2&lt;/strong&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When I ran the service, I got a nasty &lt;code&gt;500 Internal Server Error&lt;/code&gt;.&lt;br&gt;&lt;br&gt;
So I turned to AI for help.  &lt;/p&gt;

&lt;p&gt;AI quickly generated &lt;strong&gt;three suggestions&lt;/strong&gt; and was about to try them one by one:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Change the payload value from &lt;code&gt;20000&lt;/code&gt; to &lt;code&gt;20000.00&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;Add a non-mandatory field into the payload.
&lt;/li&gt;
&lt;li&gt;Switch the client to use the correct &lt;strong&gt;SOAP 1.2&lt;/strong&gt; class instead of the wrong &lt;strong&gt;SOAP 1.1&lt;/strong&gt; one.
&lt;/li&gt;
&lt;/ol&gt;




&lt;h2&gt;
  
  
  My Reaction
&lt;/h2&gt;

&lt;p&gt;Looking at the list, I immediately thought:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Option 1 and 2&lt;/strong&gt; would at most cause a &lt;em&gt;400 Bad Request&lt;/em&gt; (schema or validation issue).
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Option 3&lt;/strong&gt; was the real killer — a protocol mismatch that could definitely lead to a &lt;em&gt;500 Internal Server Error&lt;/em&gt;.
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So I stopped AI right there and told it to skip straight to option 3.&lt;br&gt;&lt;br&gt;
And yes, that solved the problem.  &lt;/p&gt;




&lt;h2&gt;
  
  
  The Lesson Learned
&lt;/h2&gt;

&lt;p&gt;This experience taught me a few things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI is fast, but lacks sense of severity.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
It treats cosmetic payload tweaks and fatal protocol mismatches as equals.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Human expertise is still crucial.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
An experienced engineer can immediately judge which error source is most plausible.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Supervising AI can be exhausting.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If I have to stare at logs all the time, I don’t feel like I’ve delegated anything.  &lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In short, AI felt less like an autopilot and more like a very fast but inexperienced intern.  &lt;/p&gt;




&lt;h2&gt;
  
  
  How to Work Smarter With AI
&lt;/h2&gt;

&lt;p&gt;So, what can we do to make AI genuinely useful without draining our attention?&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Ask for explanations and prioritization first.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Don’t let AI execute fixes blindly. Have it rank possible causes with reasoning.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Restrict the action scope.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Let AI propose fixes, but you decide which one to run.  &lt;/p&gt;&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;Provide a knowledge base.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Feed AI some rules like:  &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;500 + SOAP call → likely protocol/endpoint mismatch&lt;/code&gt;.
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;400 → payload/schema issue&lt;/code&gt;.
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This way, AI can learn from &lt;em&gt;your&lt;/em&gt; experience instead of brute-forcing random fixes.  &lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping Up
&lt;/h2&gt;

&lt;p&gt;So, can I let AI work while I drink coffee?&lt;br&gt;&lt;br&gt;
&lt;strong&gt;Not yet.&lt;/strong&gt;  &lt;/p&gt;

&lt;p&gt;But if I set up the right guardrails and workflows, maybe one day I really can.  &lt;/p&gt;

&lt;p&gt;Until then, I’ll keep one hand on my coffee and the other on the AI logs. ☕️👀  &lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you had a similar experience supervising AI? Share your story — I’d love to hear how others balance trust and control.&lt;/em&gt;  &lt;/p&gt;

</description>
      <category>ai</category>
      <category>vibingcode</category>
    </item>
    <item>
      <title>Am I Getting Lazy with AI? A Software Engineer’s Honest Reflection</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Wed, 06 Aug 2025 04:11:27 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/am-i-getting-lazy-with-ai-a-software-engineers-honest-reflection-4c4f</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/am-i-getting-lazy-with-ai-a-software-engineers-honest-reflection-4c4f</guid>
      <description>&lt;h1&gt;
  
  
  Am I Getting Lazy with AI? A Software Engineer's Honest Reflection
&lt;/h1&gt;

&lt;h2&gt;
  
  
  ✍️ Introduction
&lt;/h2&gt;

&lt;p&gt;As a software engineer, I know sorting algorithms like the back of my hand. I’ve studied their time complexities, edge cases, and when to use one over another.&lt;/p&gt;

&lt;p&gt;But recently, something happened:&lt;br&gt;&lt;br&gt;
Faced with a simple task — sort a list of numbers — I asked AI to write the code. It did. Fast and clean.&lt;br&gt;&lt;br&gt;
There was a small bug; I fixed it. No big deal. But then I paused.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“If I keep doing this… will I forget how to sort at all?”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That question led to a deeper concern:&lt;br&gt;&lt;br&gt;
Am I becoming too dependent on AI? Am I just outsourcing my thinking?&lt;/p&gt;




&lt;h2&gt;
  
  
  🧪 Two Scenarios That Made Me Think
&lt;/h2&gt;

&lt;p&gt;Let me break this down with two real-world examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  ✅ Scenario 1: Simple Task, AI-Generated Code
&lt;/h3&gt;

&lt;p&gt;I ask AI to write a sorting function. It returns it in seconds.&lt;br&gt;&lt;br&gt;
I tweak something minor, and move on.&lt;/p&gt;

&lt;p&gt;Over time, I realize:&lt;br&gt;&lt;br&gt;
I haven't &lt;em&gt;manually&lt;/em&gt; written a sorting algorithm in months.&lt;/p&gt;

&lt;p&gt;Sure, I still &lt;em&gt;understand&lt;/em&gt; it. I can discuss quicksort vs. mergesort, in-place vs. stable, O(n log n) vs. O(n²). But my hands… have forgotten the feel of writing it.&lt;/p&gt;

&lt;h3&gt;
  
  
  ⚙️ Scenario 2: Complex Workflow with AI as a Helper
&lt;/h3&gt;

&lt;p&gt;A real business problem: first do A, then sort, then trigger B and C.&lt;br&gt;&lt;br&gt;
I ask AI to help implement this sequence.&lt;br&gt;&lt;br&gt;
It gives me blocks of code. I connect them. It works. I test and deploy.&lt;/p&gt;

&lt;p&gt;Here, AI didn't &lt;em&gt;solve&lt;/em&gt; the whole problem. It helped with pieces. I still designed the sequence, validated the flow, and ensured it aligned with the business goal.&lt;/p&gt;

&lt;p&gt;But again, I wonder:  &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Am I letting AI think for me? Am I cutting corners?&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;




&lt;h2&gt;
  
  
  🤖 Am I Getting Lazy?
&lt;/h2&gt;

&lt;p&gt;Let’s get honest.&lt;/p&gt;

&lt;p&gt;AI is changing how we work. It’s tempting to offload everything — code, test cases, even architecture sketches. But where do we draw the line?&lt;/p&gt;

&lt;p&gt;Here’s the key distinction I’ve come to:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It’s not about whether you use AI. It’s about whether you stop thinking.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Let’s break it down:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Situation&lt;/th&gt;
&lt;th&gt;Lazy?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Copying code without understanding it&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Skipping testing and assuming AI is right&lt;/td&gt;
&lt;td&gt;✅ Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Using AI code but auditing it line-by-line&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Leveraging AI to go faster and solve deeper problems&lt;/td&gt;
&lt;td&gt;❌ No&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Losing some "hand feel" but keeping conceptual clarity&lt;/td&gt;
&lt;td&gt;⚠️ Maybe — monitor it&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;




&lt;h2&gt;
  
  
  🚗 The Best Analogy I Can Think Of
&lt;/h2&gt;

&lt;p&gt;Using AI as a developer is a lot like &lt;strong&gt;driving an automatic transmission car&lt;/strong&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;You don’t shift gears manually anymore.&lt;/li&gt;
&lt;li&gt;You &lt;em&gt;might&lt;/em&gt; forget how a clutch works.&lt;/li&gt;
&lt;li&gt;But you drive smoother, faster, and farther.&lt;/li&gt;
&lt;li&gt;And if you're paying attention to the road, that’s what matters.&lt;/li&gt;
&lt;/ul&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;You’ve lost the “gear-shifting hand feel”, but gained speed and freedom to look further ahead.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;However, if you stop looking at the road just because the car is doing more — that’s when you crash.&lt;/p&gt;

&lt;p&gt;AI helps me move faster. But &lt;strong&gt;I still steer&lt;/strong&gt;. I still decide where to go, when to stop, what to avoid.&lt;/p&gt;

&lt;p&gt;And I’ve realized — that’s not lazy. That’s &lt;strong&gt;evolution&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 What Really Matters
&lt;/h2&gt;

&lt;p&gt;Here’s what I ask myself now, before labeling anything “lazy”:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Am I still evaluating the problem with clarity?&lt;/li&gt;
&lt;li&gt;Do I understand the code AI gave me?&lt;/li&gt;
&lt;li&gt;Am I using saved time to work on more meaningful, complex problems?&lt;/li&gt;
&lt;li&gt;Am I growing as an engineer — even if in a different direction?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answer is yes, then I’m not outsourcing my intelligence. I’m amplifying it.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Conclusion: Use the Tool. Stay the Driver.
&lt;/h2&gt;

&lt;p&gt;I’ve accepted it: I won’t write sorting functions by hand very often anymore. And that’s okay.&lt;/p&gt;

&lt;p&gt;Because now, I’m solving more impactful problems — designing systems, exploring trade-offs, mentoring others, delivering value.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;AI is my automatic transmission. But I’m still in control of the wheel.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;And as long as I keep steering with intent, I’m not getting lazy — I’m getting smarter.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Have you ever felt like AI is making you a bit “softer” as a dev? Let me know in the comments — I’d love to hear how you're navigating this shift.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
    </item>
    <item>
      <title>Why I Canceled My AI Copilot — And Solved the Bug Myself</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Mon, 21 Jul 2025 06:27:35 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/why-i-canceled-my-ai-copilot-and-solved-the-bug-myself-51pe</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/why-i-canceled-my-ai-copilot-and-solved-the-bug-myself-51pe</guid>
      <description>&lt;p&gt;&lt;strong&gt;Meta Description:&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI is fast, powerful, and relentless — but in the real world, experience and human reasoning still matter. Here's what happened when I let my instincts override the AI.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;As a senior software engineer, I still debug faster than AI.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;One day, after updating a YAML configuration file, my test cases started failing.&lt;br&gt;&lt;br&gt;
As usual, I pasted the exception logs into my AI copilot and let it begin its diagnosis.&lt;/p&gt;

&lt;p&gt;It started scanning — meticulously, systematically, line by line.&lt;br&gt;&lt;br&gt;
Meanwhile, I casually reviewed what I had just changed.&lt;/p&gt;

&lt;p&gt;Then I realized it: I had updated the API endpoint in the YAML file, but forgot to update the corresponding URL in the test cases.&lt;/p&gt;

&lt;p&gt;That was the issue.&lt;br&gt;&lt;br&gt;
The AI was still busy scanning. I canceled the session and fixed the problem myself.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 AI Scans, I Reason
&lt;/h2&gt;

&lt;p&gt;This experience reminded me of a fundamental difference between human engineers and AI copilots.&lt;/p&gt;

&lt;p&gt;AI is powerful — it can parse and analyze thousands of lines of code in seconds.&lt;br&gt;&lt;br&gt;
But it lacks the one thing that experienced engineers bring to the table: &lt;strong&gt;contextual intuition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I didn’t need to read every line of code. I knew where the high-risk change was.&lt;br&gt;&lt;br&gt;
I remembered what I just modified.&lt;br&gt;&lt;br&gt;
And based on years of experience, I formed a hypothesis and validated it — all within seconds.&lt;/p&gt;




&lt;h2&gt;
  
  
  ⚙️ AI’s “Full Scan” vs. Human “Hypothesis”
&lt;/h2&gt;

&lt;p&gt;AI approaches each bug like a blank slate. It doesn't know which areas are more likely to be the cause. So it starts with a brute-force scan of the entire codebase.&lt;/p&gt;

&lt;p&gt;But as a senior engineer, I don't need to start from zero.&lt;br&gt;&lt;br&gt;
I can zoom in on the most probable cause using:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Knowledge of recent code changes
&lt;/li&gt;
&lt;li&gt;Familiarity with system architecture
&lt;/li&gt;
&lt;li&gt;Pattern recognition from past issues
&lt;/li&gt;
&lt;li&gt;And above all, the ability to &lt;strong&gt;form and revise hypotheses&lt;/strong&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Debugging isn’t about reading every line — it’s about asking: &lt;em&gt;What’s the most likely root cause?&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  ❌ AI Doesn’t Know When It’s Wrong
&lt;/h2&gt;

&lt;p&gt;AI copilots also tend to commit to a direction and keep going — even when it’s wrong.&lt;/p&gt;

&lt;p&gt;In this case, it assumed the problem was inside the code logic.&lt;br&gt;&lt;br&gt;
It didn’t stop to consider: &lt;em&gt;Maybe it’s not the code, but the configuration.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
But I did — immediately.&lt;/p&gt;

&lt;p&gt;This is what I call the "direction sense" that experienced engineers develop.&lt;br&gt;&lt;br&gt;
It’s not magic. It’s the result of seeing dozens, if not hundreds, of similar bugs.&lt;/p&gt;

&lt;p&gt;I knew the AI was digging in the wrong place. And I didn’t waste time letting it continue.&lt;/p&gt;




&lt;h2&gt;
  
  
  🎯 Experience Is Compressed Intelligence
&lt;/h2&gt;

&lt;p&gt;There’s something deeply human about remembering, &lt;em&gt;“Wait, didn’t I just change that YAML file?”&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;That memory isn’t in the AI.&lt;br&gt;&lt;br&gt;
It doesn’t have temporal awareness. It doesn’t know what just happened in your IDE five minutes ago.&lt;/p&gt;

&lt;p&gt;This kind of “mental diff” — comparing current state with recent changes — is a uniquely human advantage.&lt;br&gt;&lt;br&gt;
It allows us to shortcut the entire diagnosis process, while AI is still loading the full context.&lt;/p&gt;




&lt;h2&gt;
  
  
  🔄 When I’m Stuck, AI’s Brute Force Becomes an Asset
&lt;/h2&gt;

&lt;p&gt;Of course, this doesn’t mean I always outsmart AI.&lt;br&gt;&lt;br&gt;
There are moments when I hit a wall — when nothing obvious stands out, and my intuition fails to spark.&lt;/p&gt;

&lt;p&gt;In those cases, AI’s brute-force approach becomes not just helpful, but &lt;strong&gt;necessary&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It scans without fatigue.&lt;br&gt;&lt;br&gt;
It doesn’t get discouraged.&lt;br&gt;&lt;br&gt;
It doesn't care if the problem is obscure, tedious, or buried in legacy code.&lt;br&gt;&lt;br&gt;
And sometimes, in that exhaustive analysis, it highlights things I would’ve completely overlooked.&lt;/p&gt;

&lt;p&gt;So yes — while experience can beat AI in many scenarios, &lt;strong&gt;AI shines when there’s no shortcut, no hunch, and no recent change to trace back.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;It’s not a competition.&lt;br&gt;&lt;br&gt;
It’s a collaboration — and knowing when to rely on which partner is the real skill.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Be a context engineering</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Thu, 10 Jul 2025 07:01:38 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/be-a-context-engineering-1omo</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/be-a-context-engineering-1omo</guid>
      <description>&lt;p&gt;A few months ago, I came across the term &lt;em&gt;prompt engineer&lt;/em&gt; — someone who crafts precise instructions for AI tools like ChatGPT or Copilot, tuning their inputs to get better outputs.&lt;/p&gt;

&lt;p&gt;Back then, I thought, &lt;em&gt;Interesting. That’s a skill.&lt;/em&gt;&lt;br&gt;&lt;br&gt;
But today, I believe that role has already evolved.&lt;br&gt;&lt;br&gt;
We're not just engineering prompts anymore — &lt;strong&gt;we're engineering context&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠 What Does a Context Engineer Do?
&lt;/h2&gt;

&lt;p&gt;To me, a &lt;strong&gt;context engineer&lt;/strong&gt; is someone who takes full ownership of the &lt;em&gt;whole system&lt;/em&gt;. It’s no longer about getting the syntax of a prompt right. It’s about understanding the architecture, the business constraints, the flow, and then guiding the AI through that terrain like a well-briefed junior developer.&lt;/p&gt;

&lt;p&gt;We design the mission.&lt;br&gt;&lt;br&gt;
We define the environment.&lt;br&gt;&lt;br&gt;
We decide what AI does — and more importantly, what it doesn’t do.&lt;/p&gt;

&lt;p&gt;Let me show you what I mean.&lt;/p&gt;




&lt;h2&gt;
  
  
  📦 Real-Life Scenario: AI as My Coding Assistant
&lt;/h2&gt;

&lt;p&gt;Suppose I need to create a new REST endpoint called &lt;code&gt;createCustomer&lt;/code&gt; using Java and Spring Boot. Like any real-world situation, there are legal and business constraints defined by the company, and there are existing configurations and components I must respect.&lt;/p&gt;

&lt;p&gt;Here’s the kind of prompt I give to the AI:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;As a senior software engineer, your task is to create an endpoint &lt;code&gt;createCustomer&lt;/code&gt;. You must use full JDK21 features and best practices. To achieve this, you need to:&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Understand the endpoint by scanning the YAML file at &lt;code&gt;/aaa/bbb/ccc/myproject.yaml&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Create the &lt;code&gt;createCustomer&lt;/code&gt; endpoint in the controller.&lt;/li&gt;
&lt;li&gt;Create a service class to handle this logic.&lt;/li&gt;
&lt;li&gt;Apply our business logic in the service layer:
    – Map &lt;code&gt;field1&lt;/code&gt; from &lt;code&gt;MySource&lt;/code&gt; to &lt;code&gt;fieldA&lt;/code&gt; in &lt;code&gt;MyDest&lt;/code&gt;.
    – Map &lt;code&gt;field2&lt;/code&gt; from &lt;code&gt;MySource&lt;/code&gt; to &lt;code&gt;fieldB&lt;/code&gt; in &lt;code&gt;MyDest&lt;/code&gt;.
    – Use a dedicated mapper class. Check out &lt;code&gt;YourMapper.java&lt;/code&gt; for reference.&lt;/li&gt;
&lt;li&gt;Call &lt;code&gt;MyRestClient&lt;/code&gt;:
    – Autowire it (already defined in &lt;code&gt;MyConfig&lt;/code&gt;).
    – Make a POST call to the destination (see &lt;code&gt;application-local.yml&lt;/code&gt;).
    – Ensure it returns a &lt;code&gt;YourDest&lt;/code&gt; object as defined in the YAML.&lt;/li&gt;
&lt;li&gt;Create another mapper class to reverse-map &lt;code&gt;YourDest&lt;/code&gt; to &lt;code&gt;YourSource&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;p&gt;I don’t write a single line of code in this process — but I &lt;em&gt;design every step&lt;/em&gt; of how the code should be written.&lt;/p&gt;

&lt;p&gt;Why? Because I know the system. I know the constraints. I know how to delegate effectively — even to an AI.&lt;/p&gt;

&lt;p&gt;And because I know all that, &lt;strong&gt;I can be fully responsible for the result.&lt;/strong&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  👨‍💻 I Still Review the Code — Because I’m Responsible for It
&lt;/h2&gt;

&lt;p&gt;Let’s be clear: I don’t just blindly accept whatever the AI gives me.&lt;/p&gt;

&lt;p&gt;Yes, I use AI to avoid repetition.&lt;br&gt;&lt;br&gt;
Yes, I let it scaffold classes, write boilerplate, and even suggest elegant solutions.&lt;br&gt;&lt;br&gt;
But ultimately, &lt;strong&gt;I own the code&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;I read every single line.&lt;br&gt;&lt;br&gt;
If the AI comes up with something surprising — great! But I’ll ask it to explain it clearly. And if the explanation sounds too clever, I’ll say:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Explain this to me like I’m a high school student."&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;If it can’t teach it clearly, it probably didn’t understand it either.&lt;/p&gt;

&lt;p&gt;This isn't about ego. It’s about &lt;strong&gt;ownership&lt;/strong&gt;.&lt;/p&gt;




&lt;h2&gt;
  
  
  🤖 AI Is Not Magic — It’s Your Junior Developer
&lt;/h2&gt;

&lt;p&gt;Here’s what I’ve realized over time: the most productive way to work with AI is to treat it like a &lt;strong&gt;very fast, context-hungry, slightly naive junior dev&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It doesn't know what your business cares about.&lt;br&gt;&lt;br&gt;
It doesn't know your company's weird YAML structure.&lt;br&gt;&lt;br&gt;
It doesn't know what happened in the last project that blew up in production.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But you do.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
So your job is to set the scene, break down the tasks, refer to existing assets, and guide the AI as if it were a teammate.&lt;/p&gt;

&lt;p&gt;That’s what being a context engineer is all about.&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 Final Thoughts
&lt;/h2&gt;

&lt;p&gt;We're entering a new phase of software engineering — one where AI isn’t just a Copilot, but a &lt;strong&gt;collaborator&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;To thrive in this new era, we don’t need to become AI whisperers.&lt;br&gt;&lt;br&gt;
We need to become &lt;strong&gt;context architects&lt;/strong&gt;.&lt;br&gt;&lt;br&gt;
People who understand the system, the goals, and how to lead a machine through the forest of complexity — without losing sight of the destination.&lt;/p&gt;

&lt;p&gt;So no, I don’t write most of the code anymore.&lt;/p&gt;

&lt;p&gt;But I still build.&lt;br&gt;&lt;br&gt;
And I still take full responsibility.&lt;br&gt;&lt;br&gt;
Because &lt;strong&gt;I’m not just prompting the AI — I’m engineering the context in which it thinks&lt;/strong&gt;.&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Do not use AI wrongly</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Thu, 26 Jun 2025 06:56:02 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/do-not-use-ai-wrongly-232</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/do-not-use-ai-wrongly-232</guid>
      <description>&lt;p&gt;In recent years, AI has become the centerpiece of conversation in many engineering teams—and rightfully so. Tools like GitHub Copilot, Copilot Chat, and other AI pair programmers have reshaped how we write code, document systems, and reason about complexity.&lt;/p&gt;

&lt;p&gt;But there’s a line between &lt;strong&gt;leveraging AI&lt;/strong&gt; and &lt;strong&gt;abdication of engineering responsibility&lt;/strong&gt;. And when that line is crossed, the result isn’t acceleration—it’s dysfunction.&lt;/p&gt;

&lt;p&gt;Here’s a real story from within our company.&lt;/p&gt;




&lt;h2&gt;
  
  
  🧓 The Veteran Engineer Who Gave Up on Coding
&lt;/h2&gt;

&lt;p&gt;We have a senior developer on our team. He's respected, experienced, and not far from retirement. Over the last few years, he’s become fixated on AI—convinced that within two years, large language models will replace most of our roles. As a sort of “legacy project” before he retires, he’s set out to train and integrate an AI model to automate our internal development pipeline.&lt;/p&gt;

&lt;p&gt;At first glance, this seems aligned with the company’s goals. We’ve been early adopters of GitHub Copilot, and we’ve successfully integrated other AI tooling into our workflows. We encourage experimentation.&lt;/p&gt;

&lt;p&gt;But what’s happening here goes far beyond “reasonable usage.”&lt;/p&gt;




&lt;h2&gt;
  
  
  ❌ From Developer to AI Prompt Engineer—Literally
&lt;/h2&gt;

&lt;p&gt;This engineer has &lt;strong&gt;stopped writing code himself&lt;/strong&gt;. Completely. He refuses to hand-craft PRs or write patches manually. Instead, his workflow looks like this:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;He copies the contents of Jira tickets into Copilot Chat or a similar AI.&lt;/li&gt;
&lt;li&gt;He waits for a pull request to be generated.&lt;/li&gt;
&lt;li&gt;He does not review or edit the code.&lt;/li&gt;
&lt;li&gt;If the generated code is broken, he refuses to debug it directly. Instead, he tries to "teach" the AI to fix it by adding new comments or prompts.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result? A simple task estimated to take one day ends up dragging on for five—full of bugs, blocked reviewers, and broken functionality.&lt;/p&gt;

&lt;p&gt;His justification? &lt;em&gt;“It’s fine if we’re slower now. Once the AI matures, we’ll recover the time tenfold.”&lt;/em&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🧠 But Here's the Reality
&lt;/h2&gt;

&lt;p&gt;This approach is not innovation. It’s negligence. And it reveals a deeper misunderstanding about what AI &lt;strong&gt;can&lt;/strong&gt; and &lt;strong&gt;should&lt;/strong&gt; do in a professional engineering context.&lt;/p&gt;

&lt;p&gt;Let’s break it down:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;strong&gt;AI is a tool—not a replacement for ownership&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Copilot is meant to assist, not to take full responsibility. A developer who cannot explain, debug, or adapt generated code is not working &lt;em&gt;with&lt;/em&gt; AI—they’re being replaced by it in a very literal sense.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. &lt;strong&gt;Code quality suffers when understanding vanishes&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;AI-generated code is only as good as the person reviewing and integrating it. When bugs arise—and they always do—the developer must be able to dive in, reason through the logic, and make decisions. Otherwise, debugging becomes a game of prompt roulette.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. &lt;strong&gt;“Training” Copilot with comments is a fantasy&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;Copilot is not a fine-tunable LLM in your IDE. Adding comments in hopes that it will “learn” better behaviors across sessions is fundamentally flawed. This isn’t Reinforcement Learning. It’s wishful thinking.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. &lt;strong&gt;Your team pays the price&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;The engineer may believe they're leading innovation, but in practice, they’re slowing the team down. Others must compensate, fix broken PRs, and decipher logic that even the original author doesn’t understand.&lt;/p&gt;




&lt;h2&gt;
  
  
  ✅ Healthy AI Usage Patterns
&lt;/h2&gt;

&lt;p&gt;To avoid this kind of misuse, teams should define clear guidelines. Here are a few that have worked for us:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;You own the code you commit. Period.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI can draft, suggest, and autocomplete—but responsibility doesn’t shift.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;AI involvement should be transparent.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If 80% of a PR was generated by AI, state it. Let reviewers calibrate expectations.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;PRs must be understandable and maintainable by humans.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
No black-box code. If you can’t explain it, don’t ship it.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;Review and debugging are not optional.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
AI may generate code, but it won’t rescue you from runtime bugs, broken APIs, or business logic mismatches.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  🧭 Final Thoughts: Use AI Responsibly
&lt;/h2&gt;

&lt;p&gt;AI is here to stay. It will change how we develop software—but not by removing responsibility from engineers. Instead, it &lt;strong&gt;amplifies both good and bad engineering practices&lt;/strong&gt;. If you cut corners, AI cuts them faster. If you build clean, modular, well-documented systems, AI can help accelerate that process.&lt;/p&gt;

&lt;p&gt;But the moment we stop thinking, stop understanding, and stop caring—that’s when AI stops being a tool and becomes a crutch.&lt;/p&gt;

&lt;p&gt;Let’s not mistake &lt;strong&gt;delegation&lt;/strong&gt; for &lt;strong&gt;abdication&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Let’s use AI as professionals.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; Over-relying on AI isn't innovation—it's negligence. Use AI to assist, not replace your understanding, ownership, and responsibility.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>programming</category>
      <category>softwareengineering</category>
    </item>
    <item>
      <title>Rethinking the software engineer's role in the era of LLMs, agents, and intelligent automation</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Mon, 16 Jun 2025 04:29:23 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/rethinking-the-software-engineers-role-in-the-era-of-llms-agents-and-intelligent-automation-1l1p</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/rethinking-the-software-engineers-role-in-the-era-of-llms-agents-and-intelligent-automation-1l1p</guid>
      <description>&lt;p&gt;As AI tools like Copilot and agent-based frameworks evolve, I’ve noticed something quietly shifting in my day-to-day workflow. I no longer see myself as someone who &lt;em&gt;writes code&lt;/em&gt; line-by-line. Instead, I break down my objectives into well-defined tasks, hand them off to an agent, and then—critically—I review the outcome.&lt;/p&gt;

&lt;p&gt;In other words, my role is transforming: from engineer-as-implementer to engineer-as-orchestrator.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Core Skills Are Still Essential
&lt;/h2&gt;

&lt;p&gt;Despite AI’s ability to generate high-quality code (sometimes better than mine), I’ve learned this doesn’t absolve me from needing &lt;em&gt;strong fundamentals&lt;/em&gt;. On the contrary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I still need to perform micro-decisions.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Even the best-generated code often requires judgment calls on architecture, trade-offs, and edge cases.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I need to spot AI hallucinations.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
LLMs can confidently produce subtle but critical logical errors. Recognizing them is a senior engineer’s job.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;strong&gt;I need to deeply review what I didn't write.&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
If I don't understand the code, I can't guarantee its correctness—or maintainability.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The Paradox: Great Code That’s Incomprehensible
&lt;/h2&gt;

&lt;p&gt;Sometimes the code produced by my AI assistant is &lt;em&gt;too good&lt;/em&gt;. It’s elegant. Modular. Efficient. But so abstract or deeply nested that it becomes difficult for a human engineer—even an experienced one—to fully grasp in a short time.&lt;/p&gt;

&lt;p&gt;Now here’s the problem: if such “perfect-looking” code contains a &lt;em&gt;tiny mistake&lt;/em&gt;, the resulting bug becomes both unpredictable and hard to trace. Worse still, most debugging tools aren’t designed to help you untangle code you didn’t write.&lt;/p&gt;

&lt;p&gt;That’s not just a problem. That’s a production risk.&lt;/p&gt;




&lt;h2&gt;
  
  
  Rethinking Interviews: Prompt Engineering &amp;gt; Syntax
&lt;/h2&gt;

&lt;p&gt;If I were hiring today, I wouldn’t focus on whether a candidate knows every language feature. Instead, I’d ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Can you break down an ambiguous, multi-part requirement into manageable components?&lt;/li&gt;
&lt;li&gt;Can you design high-leverage prompts to get useful results from an agent?&lt;/li&gt;
&lt;li&gt;Can you architect systems where &lt;em&gt;humans and agents&lt;/em&gt; collaborate meaningfully?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;In that sense, &lt;em&gt;prompt literacy&lt;/em&gt; is becoming just as important as code literacy.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Road Ahead: Human-in-the-Loop Programming
&lt;/h2&gt;

&lt;p&gt;We’re not moving toward a future where developers become obsolete.&lt;br&gt;&lt;br&gt;
We’re moving toward one where developers become &lt;em&gt;more human&lt;/em&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Less typing, more thinking
&lt;/li&gt;
&lt;li&gt;Less repetition, more orchestration
&lt;/li&gt;
&lt;li&gt;Less syntax, more semantics&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I don’t know where AI will take us next. A year ago, the tools I use today didn’t even exist. But I do know this:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;I no longer measure myself by how much code I can write. I measure myself by how well I can direct, verify, and refine what AI writes.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That’s the new senior engineer mindset.&lt;/p&gt;




&lt;h2&gt;
  
  
  Final Thought
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;“It’s not just about writing code anymore. It’s about managing meaning across agents, tools, and human intent. That’s engineering at the next level.”&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
    </item>
    <item>
      <title>Clean Code: The Art of Self-Documenting Code</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Wed, 04 Jun 2025 06:08:43 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/clean-code-the-art-of-self-documenting-code-509o</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/clean-code-the-art-of-self-documenting-code-509o</guid>
      <description>&lt;p&gt;As a senior software engineer with over a decade of experience, I’ve seen how comments in code can evolve from helpful documentation to misleading traps. Today, I want to share some insights about writing cleaner, self-documenting code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Problem with Comments
&lt;/h3&gt;

&lt;p&gt;Why Im so down on comments? Because they lie. Not always, not intentionally but too often. The older the comment is the more likely it is to be wrong. The reason is software engineers maintain the code but not the comments. Code will not lie to you but comment will; Code evolves but comments not. Thus comments sometimes will be misleading.&lt;/p&gt;

&lt;p&gt;Therefore although comments are sometimes necessary, we need to try to minumise them.&lt;/p&gt;

&lt;p&gt;One of the common motivation for comments is bad code. We wrote a piece of code and we know it is complex, confusing and hard to understand. So we say to ourselves, “It is good for readers, I need to write down my thoughts here”. But we have another choice, we can spend time in cleaning it.&lt;/p&gt;

&lt;p&gt;Have a look at the below piece of code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;//does the module from the list depend on the subsystem?
if(module.getDependSubsystems().contain(othermodule.getSubSystem()))
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;At first glance, the comment seems helpful. But let’s break down why this isn’t ideal:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;- The comment is explaining what the code does (which should be obvious from the code itself)&lt;/li&gt;
&lt;li&gt;- There’s a typo in the method name (contain vs. contains)&lt;/li&gt;
&lt;li&gt;- The variable names aren’t as descriptive as they could be
###The Better Approach
Here’s how we can refactor this code to be self-documenting:
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;List&amp;lt;String&amp;gt; dependentSubsystems = module.getDependentSubsystems();
String targetSubSystem = otherModule.getSubSystem();
boolean isModuleDependentOnSubSystem = dependentSubsystems.contains(targetSubSystem);

if (isModuleDependentOnSubSystem) {
    // handle dependency case
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Let’s analyze the improvements:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Meaningful Variable Names: dependentSubsystems and targetSubSystem clearly describe their purpose&lt;/li&gt;
&lt;li&gt;Extracted Boolean Logic: The condition is now stored in a well-named boolean variable&lt;/li&gt;
&lt;li&gt;Proper Method Names: Fixed the typo in contains&lt;/li&gt;
&lt;li&gt;No Comments Needed: The code is now self-explanatory&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  When Are Comments Appropriate?
&lt;/h3&gt;

&lt;p&gt;Comments aren’t always bad. They’re useful for:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Legal requirements&lt;/li&gt;
&lt;li&gt;API documentation&lt;/li&gt;
&lt;li&gt;Complex algorithms that can’t be simplified further&lt;/li&gt;
&lt;li&gt;Explaining why something is done, not what is being done&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Conclusion
&lt;/h3&gt;

&lt;p&gt;Remember: The best code is code that tells its own story. Before adding a comment, ask yourself: “Could I make this clearer through better code structure?”&lt;/p&gt;

</description>
      <category>programming</category>
      <category>cleancode</category>
      <category>java</category>
      <category>softwaredevelopment</category>
    </item>
    <item>
      <title>Beyond double: Essential BigDecimal Practices for Accurate Financial Calculations</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Wed, 28 May 2025 05:25:19 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/beyond-double-essential-bigdecimal-practices-for-accurate-financial-calculations-52m1</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/beyond-double-essential-bigdecimal-practices-for-accurate-financial-calculations-52m1</guid>
      <description>&lt;p&gt;If you’ve been around software development for a bit, especially anywhere near financial applications, you’ve probably heard the golden rule: don’t use double (or float) for money. It's common knowledge, and for good reason – floating-point arithmetic can lead to precision errors that are a nightmare in finance. The go-to solution is java.math.BigDecimal.&lt;/p&gt;

&lt;p&gt;However, simply switching to BigDecimal isn't a magic bullet. There are nuances to using it correctly that can trip up even experienced developers. I've seen these issues pop up time and again, so let's dive into some crucial points that often get overlooked.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initialize with Strings: The Precision &amp;amp; Scale Matter
&lt;/h3&gt;

&lt;p&gt;You might be tempted to create a BigDecimal like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Don't do this!
BigDecimal amount = new BigDecimal(0.1);
System.out.println(amount); // Might print something like 0.1000000000000000055511151231257827021181583404541015625
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;“Wait, what?” Yeah, that’s not exactly 0.1. The problem is that the double literal 0.1 itself cannot be perfectly represented in binary floating-point. When you pass it to the BigDecimal(double) constructor, you're essentially creating a BigDecimal from an already imprecise representation.&lt;/p&gt;

&lt;p&gt;The correct way to initialize BigDecimal with a specific decimal value is to use the string constructor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BigDecimal correctAmount = new BigDecimal("0.1");
System.out.println(correctAmount); // Prints 0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why? Because the string “0.1” is an exact representation. BigDecimal uses concepts of precision (the total number of digits) and scale (the number of digits to the right of the decimal point). The string constructor allows BigDecimal to parse this representation accurately, preserving the intended precision and scale.&lt;/p&gt;

&lt;h3&gt;
  
  
  Got a Double? Use BigDecimal.valueOf()
&lt;/h3&gt;

&lt;p&gt;Sometimes, you might receive a double value from a library or an external system, and you need to convert it to BigDecimal. If you're stuck with a double variable, avoid the new BigDecimal(double) constructor for the reasons mentioned above.&lt;/p&gt;

&lt;p&gt;Instead, use the static factory method BigDecimal.valueOf():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double priceDouble = 0.1;
// BigDecimal stillNotIdeal = new BigDecimal(priceDouble); // Avoid if possible
BigDecimal betterPrice = BigDecimal.valueOf(priceDouble);
System.out.println(betterPrice); // Prints 0.1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;BigDecimal.valueOf(double) is generally preferred over new BigDecimal(double) because it often gives a more predictable result. It uses the canonical string representation of the double (e.g., Double.toString(doubleVal)), which can help avoid some of the more egregious precision issues you see with the double constructor directly. However, remember the best practice is to start with strings or integers if you have control over the input.&lt;/p&gt;

&lt;h3&gt;
  
  
  Formatting Floats? BigDecimal is Your Friend, Not String.format()
&lt;/h3&gt;

&lt;p&gt;When it comes to displaying floating-point numbers, especially currency, formatting is key. You might instinctively reach for String.format():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;double value = 123.456;
// String formatted = String.format("%.2f", value); 
// Potential for rounding surprises
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;While String.format() works for basic cases, when dealing with the precision that BigDecimal offers, it's better to let BigDecimal handle its own formatting, often in conjunction with NumberFormat for locale-specific currency symbols and conventions.&lt;/p&gt;

&lt;p&gt;For simple scale setting:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BigDecimal preciseValue = new BigDecimal("123.456789");
BigDecimal roundedValue = preciseValue.setScale(2, RoundingMode.HALF_UP); // Explicit rounding
System.out.println(roundedValue); // Prints 123.46

// For currency formatting:
NumberFormat currencyFormatter = NumberFormat.getCurrencyInstance(Locale.US);
System.out.println(currencyFormatter.format(roundedValue)); // Prints $123.46
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;By using BigDecimal's setScale() method with an explicit RoundingMode, you have full control over how rounding occurs, which is crucial in financial contexts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Comparing BigDecimal: equals() vs. compareTo() – The Scale Trap!
&lt;/h3&gt;

&lt;p&gt;This one bites a lot of people. You have two BigDecimal objects that represent the same numerical value, say $10.00 and $10.0.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;BigDecimal val1 = new BigDecimal("10.0");  // scale = 1
BigDecimal val2 = new BigDecimal("10.00"); // scale = 2

System.out.println(val1.equals(val2)); // Prints false!
System.out.println(val1.hashCode() == val2.hashCode()); // Likely false!
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Why false? Because BigDecimal.equals() considers both the value and the scale. Since "10.0" (scale 1) and "10.00" (scale 2) have different scales, equals() returns false. Similarly, their hashCode() values will likely differ.&lt;/p&gt;

&lt;p&gt;If you want to check if two BigDecimal objects represent the same numerical value, regardless of their scale, you must use compareTo():&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;System.out.println(val1.compareTo(val2) == 0); // Prints true!

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;compareTo() returns:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;0 if the values are numerically equal.&lt;/li&gt;
&lt;li&gt;A negative integer if val1 is numerically less than val2.&lt;/li&gt;
&lt;li&gt;A positive integer if val1 is numerically greater than val2.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;So, for logical equality of monetary amounts, always use compareTo() == 0.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Silent Killer: Numerical Overflow with Primitives
&lt;/h3&gt;

&lt;p&gt;Beyond BigDecimal, let's touch on a general numerical gremlin: overflow. All primitive numeric types in Java (byte, short, int, long, and even char when used numerically) have a fixed range of values they can hold.&lt;/p&gt;

&lt;p&gt;What happens when you exceed this range?&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;int maxIntValue = Integer.MAX_VALUE; // 2147483647
int result = maxIntValue + 1;
System.out.println(result); // Prints -2147483648 (Integer.MIN_VALUE)

long largeNumber = Long.MAX_VALUE;
long overflowed = largeNumber + 100; // Wraps around to a negative number
System.out.println(overflowed);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice something scary? No error! No exception! The calculation silently wraps around. This can lead to incredibly subtle and dangerous bugs, especially in calculations involving quantities, counts, or IDs.&lt;/p&gt;

&lt;p&gt;The solution? Since Java 8, the Math class provides "exact" arithmetic methods that will throw an ArithmeticException on overflow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;try {
    int sum = Math.addExact(Integer.MAX_VALUE, 1);
    System.out.println(sum);
} catch (ArithmeticException e) {
    System.err.println("Overflow detected! " + e.getMessage());
}

try {
    long product = Math.multiplyExact(Long.MAX_VALUE / 2, 3); // This would overflow
    System.out.println(product);
} catch (ArithmeticException e) {
    System.err.println("Overflow detected during multiplication! " + e.getMessage());
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Other useful methods include subtractExact(), multiplyExact(), incrementExact(), decrementExact(), and toIntExact() (for converting long to int safely). Using these methods makes your code more robust by failing fast when an overflow occurs.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrapping Up
&lt;/h3&gt;

&lt;p&gt;Working with numbers in software, especially when money or critical quantities are involved, requires diligence. BigDecimal is a powerful tool, but like any tool, you need to understand its intricacies. And always be mindful of the limits of primitive types!&lt;/p&gt;

&lt;p&gt;By keeping these points in mind:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Use string constructors for BigDecimal (new BigDecimal("0.1")).&lt;/li&gt;
&lt;li&gt;Prefer BigDecimal.valueOf(double) if you have a double.&lt;/li&gt;
&lt;li&gt;Format using BigDecimal's methods (setScale()) and NumberFormat.&lt;/li&gt;
&lt;li&gt;Compare values with compareTo(), not equals().&lt;/li&gt;
&lt;li&gt;Guard against primitive overflow with Math.xxxExact() methods.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You’ll write more accurate, reliable, and maintainable code. Happy coding!&lt;/p&gt;

</description>
      <category>programming</category>
      <category>java</category>
      <category>java21</category>
      <category>problemresolving</category>
    </item>
    <item>
      <title>The Subtle Pitfall of @RequiredArgsConstructor: A Lesson from Integration Testing2</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Thu, 15 May 2025 23:43:09 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/the-subtle-pitfall-of-requiredargsconstructor-a-lesson-from-integration-testing2-3oil</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/the-subtle-pitfall-of-requiredargsconstructor-a-lesson-from-integration-testing2-3oil</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Recently, I encountered a frustrating issue in our Spring Boot application test suite. One specific test kept failing with a 500 Internal Server Error while similar tests were succeeding. After hours of troubleshooting, the culprit turned out to be a single missing keyword - &lt;code&gt;final&lt;/code&gt;. Let me walk you through the debugging journey and explain why this tiny oversight had such a significant impact.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I had a test case &lt;code&gt;shouldSuccessfullyCallMyService()&lt;/code&gt; that consistently failed with a &lt;code&gt;SERVER_ERROR&lt;/code&gt;, while similar tests like &lt;code&gt;shouldSuccessfullyCallCreateStopAccount()&lt;/code&gt; worked perfectly fine. Both tests used MockWebServer to simulate backend service responses, but only one was failing.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@Test&lt;/span&gt;
&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;shouldSuccessfullyCallMyService&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;String&lt;/span&gt; &lt;span class="n"&gt;myServiceUrl&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="no"&gt;MY_SERVICE&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;replace&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"{resourceId}"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;RESOURCE_ID&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;MyServiceResponse&lt;/span&gt; &lt;span class="n"&gt;expectedMyServiceResponse&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;getStatus200MyServiceResponse&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;

    &lt;span class="n"&gt;mockMyServiceServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;enqueue&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;
            &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nf"&gt;MockResponse&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setResponseCode&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;setBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;convertObjectToJsonString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedMyServiceResponse&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt;
                    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;CONTENT_TYPE&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;APPLICATION_JSON_VALUE&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;

    &lt;span class="n"&gt;webTestClient&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;get&lt;/span&gt;&lt;span class="o"&gt;().&lt;/span&gt;&lt;span class="na"&gt;uri&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myServiceUrl&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="c1"&gt;// Headers and assertions omitted for brevity&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;expectStatus&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;is2xxSuccessful&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Investigation
&lt;/h2&gt;

&lt;p&gt;My first instinct was to check the differences between the passing and failing tests:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;HTTP Method&lt;/strong&gt;: The failing test used &lt;code&gt;GET&lt;/code&gt; while successful tests used &lt;code&gt;POST&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Response Structure&lt;/strong&gt;: Perhaps issues in the response model?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Mock Server Configuration&lt;/strong&gt;: Different configurations between tests?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I added debug logs to see what was happening:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Mock server URL: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;mockSapOrchServiceServer&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;url&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;myServiceUrl&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;span class="nc"&gt;System&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;out&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;println&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Response body: "&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="n"&gt;convertObjectToJsonString&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;expectedMyServiceResponse&lt;/span&gt;&lt;span class="o"&gt;));&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The logs showed the mock server was correctly configured, and the response body looked good. The most puzzling aspect was that the test was failing with a 500 error, suggesting an exception in our controller or service layer.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Breakthrough
&lt;/h2&gt;

&lt;p&gt;After ruling out issues with the test configuration, I shifted focus to comparing the controllers handling these endpoints. The &lt;code&gt;YourController&lt;/code&gt; handled the successful endpoints, while &lt;code&gt;MyController&lt;/code&gt; handled the failing endpoint.&lt;/p&gt;

&lt;p&gt;Looking at both controllers side by side revealed the subtle but critical difference:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="c1"&gt;// YourController.java - working correctly&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;YourController&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;YourApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;YourService&lt;/span&gt; &lt;span class="n"&gt;yourService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;
    &lt;span class="c1"&gt;// Other services...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// MyController.java - failing&lt;/span&gt;
&lt;span class="nd"&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MyApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="nc"&gt;MyServiceService&lt;/span&gt; &lt;span class="n"&gt;myServiceService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Missing 'final' keyword!&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;final&lt;/code&gt; keyword was missing from the service field declaration in &lt;code&gt;MtController&lt;/code&gt;!&lt;/p&gt;

&lt;h2&gt;
  
  
  Why This Matters: Lombok and Spring Dependency Injection
&lt;/h2&gt;

&lt;p&gt;This tiny omission had massive consequences because of how Lombok's &lt;code&gt;@RequiredArgsConstructor&lt;/code&gt; works:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;@RequiredArgsConstructor&lt;/code&gt; generates a constructor &lt;strong&gt;only for fields marked as &lt;code&gt;final&lt;/code&gt;&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Spring uses this constructor for dependency injection&lt;/li&gt;
&lt;li&gt;Without &lt;code&gt;final&lt;/code&gt;, no constructor parameter is generated for the service&lt;/li&gt;
&lt;li&gt;Spring can't inject the dependency, leaving the service as &lt;code&gt;null&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;When the controller tries to use the service, it throws a &lt;code&gt;NullPointerException&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The exception bubbles up as a 500 Internal Server Error&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Fix
&lt;/h2&gt;

&lt;p&gt;The solution was elegantly simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight java"&gt;&lt;code&gt;&lt;span class="nd"&gt;@RequiredArgsConstructor&lt;/span&gt;
&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;MyController&lt;/span&gt; &lt;span class="kd"&gt;implements&lt;/span&gt; &lt;span class="nc"&gt;MyApi&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;MyServiceService&lt;/span&gt; &lt;span class="n"&gt;myServiceService&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// Added 'final'&lt;/span&gt;
    &lt;span class="c1"&gt;// ...&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After adding the &lt;code&gt;final&lt;/code&gt; keyword, Spring could properly inject the dependency, and the test passed successfully.&lt;/p&gt;

&lt;h2&gt;
  
  
  Lessons Learned
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Be consistent with field declarations&lt;/strong&gt;: When using Lombok's &lt;code&gt;@RequiredArgsConstructor&lt;/code&gt;, always mark dependencies as &lt;code&gt;final&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Understand your annotations&lt;/strong&gt;: Know exactly how annotations like &lt;code&gt;@RequiredArgsConstructor&lt;/code&gt; behave.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Compare working vs. non-working code&lt;/strong&gt;: Sometimes the most subtle differences cause the biggest problems.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Look beyond test configuration&lt;/strong&gt;: When tests fail with server errors, examine your application code carefully.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This debugging journey highlights how a single keyword can make or break a Spring application. It also demonstrates why understanding the frameworks and libraries we use is crucial - what seemed like a complex issue with mock servers or HTTP methods was actually a fundamental misunderstanding of how Lombok and Spring work together.&lt;/p&gt;

&lt;p&gt;Next time you encounter a 500 error in your Spring Boot tests, remember to check if your dependencies are properly declared and configured for injection. Sometimes the smallest details make all the difference!&lt;/p&gt;

</description>
    </item>
    <item>
      <title>Annoying NullPointerException part1</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Sun, 11 May 2025 00:56:10 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/annoying-nullpointerexception-part1-3gh9</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/annoying-nullpointerexception-part1-3gh9</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd662zl38e7zvse2yiri.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fsd662zl38e7zvse2yiri.png" alt="Image description" width="800" height="530"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;One day, I received a text message that said, “Dear null, hello, XXX.” I laughed at that moment because it’s a joke that programmers can easily get. The program didn’t retrieve my name and instead formatted the empty space as null. Clearly, null was not handled properly. Even if it was replaced with “guest” or “customer,” it wouldn’t have caused such a joke.&lt;/p&gt;

&lt;p&gt;When a variable in a program is null, it means that it has no reference or pointer. Any operation performed on this variable will inevitably result in a null pointer exception, which is called a NullPointerException in Java. So, when and how does a null pointer exception occur, and how can it be fixed?&lt;/p&gt;

&lt;p&gt;Although null pointer exceptions can be frustrating, they are relatively easy to identify. What’s more troublesome is understanding the meaning of null. For example, when a client sends null data to a server, what is their intention? Are they providing an empty value or not providing any value at all? Another example is the NULL value in database fields. Does it have any special meaning? When dealing with NULL values in a database, what should be taken into special consideration when writing SQL?&lt;/p&gt;

&lt;p&gt;Today, let’s embark on a journey to explore the pitfalls of null with these questions in mind.&lt;/p&gt;

&lt;p&gt;NullPointerException is the most common exception in Java code, and I categorize its most likely scenarios into the following five:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Parameter value is an Integer or other wrapper type, and a NullPointerException occurs due to auto-unboxing when used.&lt;/li&gt;
&lt;li&gt;NullPointerException occurs during string comparison.&lt;/li&gt;
&lt;li&gt;Containers like ConcurrentHashMap do not support null keys or values. Attempting to put a null key or value forcefully will result in a NullPointerException.&lt;/li&gt;
&lt;li&gt;Object A contains object B. After obtaining B through a field of object A, a NullPointerException occurs when invoking a method on B without checking the field for null.&lt;/li&gt;
&lt;li&gt;A List returned by a method or remote service is not empty but null. Invoking a method on the List without checking for null will result in a NullPointerException.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;To demonstrate these five scenarios, I have written a method called wrongMethod and another method called wrong to invoke it. The wrong method takes an input parameter test, which is a string of length 4 consisting of 0s and 1s. Each position represents whether the corresponding parameter in wrongMethod is null (1) or not null (0), allowing us to simulate various null pointer situations.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private List&amp;lt;String&amp;gt; wrongMethod(FooService fooService, Integer i, String s, String t) {
    log.info("result {} {} {} {}", i + 1, s.equals("OK"), s.equals(t),
            new ConcurrentHashMap&amp;lt;String, String&amp;gt;().put(null, null));
    if (fooService.getBarService().bar().equals("OK"))
        log.info("OK");
    return null;
}
@GetMapping("wrong")
    public int wrong(@RequestParam(value = "test", defaultValue = "1111") String test) {
        return wrongMethod(test.charAt(0) == '1' ? null : new FooService(),
                test.charAt(1) == '1' ? null : 1,
                test.charAt(2) == '1' ? null : "OK",
                test.charAt(3) == '1' ? null : "OK").size();
    }
class FooService {
        @Getter
        private BarService barService;

    }

    class BarService {
        String bar() {
            return "OK";
        }
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It’s evident that this case encounters a NullPointerException because the variable is a null pointer. Trying to retrieve the value of the variable or access its members will result in a NullPointerException. However, pinpointing the exact location of this exception can be tricky.&lt;/p&gt;

&lt;p&gt;In the wrongMethod test method, we simulate four instances of a NullPointerException in a single line of code, which we log for tracking purposes. These instances are as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Performing the operation +1 on the input parameter Integer i.&lt;/li&gt;
&lt;li&gt;Comparing the input parameter String s to check if its content is equal to “OK.”&lt;/li&gt;
&lt;li&gt;Comparing the input parameters String s and String t to check if they are equal.&lt;/li&gt;
&lt;li&gt;Performing a put operation on a newly created ConcurrentHashMap, setting both the Key and Value as null.
The output of the exception information is as follows:
&lt;code&gt;java.lang.NullPointerException: null
at com.foo.myblog.nullvalue.controller.NullValueController.wrongMethod(NullValueController.java:41) 
at com.foo.myblog.nullvalue.controller.NullValueController.wrong(NullValueController.java:23)&lt;/code&gt;
Next, let’s take a look at how to fix the five null pointer exceptions mentioned earlier.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In fact, the most straightforward way to handle any null pointer exception is to check for null before performing any operations. However, this approach only prevents the exception from occurring again. We still need to determine whether the null pointer in the program logic originates from the input parameters or a bug:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If it originates from the input parameters, further analysis is needed to determine if the input parameters are reasonable, among other considerations.&lt;/li&gt;
&lt;li&gt;If it originates from a bug, the null pointer may not be solely a programming bug but could also involve business attributes and interface invocation standards.
In this case, since it’s a demo, we will focus on the pure null pointer check and fix approach. When considering checking for null before processing, many people may think of using if-else blocks. However, this approach increases code complexity and reduces readability. Instead, we can try using Java 8’s Optional class to eliminate such if-else logic, enabling null checks and processing in a single line of code.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the approach to fixing these issues:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;For checking null with Integer, you can use Optional.ofNullable to construct an Optional and then use orElse(0) to replace null with a default value before performing the +1 operation.&lt;/li&gt;
&lt;li&gt;For comparing String with a literal value, you can place the literal value first, for example, "OK".equals(s). This way, even if s is null, it will not throw a NullPointerException. For comparing two nullable String variables, you can use Objects.equals, which handles null checks.&lt;/li&gt;
&lt;li&gt;Regarding ConcurrentHashMap, since neither its Key nor Value supports null, the fix is to avoid storing null values in it. While HashMap allows null as Key and Value, ConcurrentHashMap, despite being a thread-safe version of HashMap, does not support null values for Key and Value. This can be a common misconception.&lt;/li&gt;
&lt;li&gt;In the case of cascading calls like fooService.getBarService().bar().equals("OK"), there are multiple places where null checks are required, including fooService, the return value of getBarService(), and the String returned by the bar method. Using if-else statements for null checks may require several lines of code, but using Optional allows you to handle it in a single line.&lt;/li&gt;
&lt;li&gt;For the List returned by rightMethod, since it’s uncertain whether it is null or not, you can also use Optional.ofNullable to wrap the return value and then use .orElse(Collections.emptyList()) to get an empty List if the original List is null. Finally, you can call the size method.
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private List&amp;lt;String&amp;gt; rightMethod(FooService fooService, Integer i, String s, String t) {
        log.info("result {} {} {} {}", Optional.ofNullable(i).orElse(0) + 1, "OK".equals(s), Objects.equals(s, t), new HashMap&amp;lt;String, String&amp;gt;().put(null, null));
        Optional.ofNullable(fooService)
                .map(FooService::getBarService)
                .filter(barService -&amp;gt; "OK".equals(barService.bar()))
                .ifPresent(result -&amp;gt; log.info("OK"));
        return new ArrayList&amp;lt;&amp;gt;();
    }
@GetMapping("right")
    public int right(@RequestParam(value = "test", defaultValue = "1111") String test) {
        return Optional.ofNullable(rightMethod(test.charAt(0) == '1' ? null : new FooService(),
                test.charAt(1) == '1' ? null : 1,
                test.charAt(2) == '1' ? null : "OK",
                test.charAt(3) == '1' ? null : "OK"))
                .orElse(Collections.emptyList()).size();
    }
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These approaches can help you fix the null pointer exceptions and enhance the robustness of your code.&lt;/p&gt;

&lt;p&gt;This raises another question: using null checks or Optional to avoid null pointer exceptions may not always be the best solution, as the absence of null pointer exceptions may hide deeper bugs. Therefore, to effectively address null pointer exceptions, it’s crucial to analyze each case individually, identify the root cause, and then implement appropriate null checks. Handling null values is not just about checking for non-null values and proceeding with normal business flow. It also involves considering whether an exception should be thrown, setting default values, or logging relevant information when encountering null values.&lt;/p&gt;

&lt;p&gt;Each scenario requires careful consideration of the specific requirements and behavior of your application. This case-by-case approach allows you to address the underlying issues and handle null values in a way that aligns with your application’s design and requirements.&lt;/p&gt;

&lt;p&gt;source code can be found in &lt;a href="https://github.com/mrtong/myblog/tree/main/src/main/java/com/foo/myblog/nullvalue" rel="noopener noreferrer"&gt;here&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>troubleshooting</category>
      <category>java</category>
      <category>nullpointerexception</category>
    </item>
    <item>
      <title>The Subtle Pitfall of @RequiredArgsConstructor: A Lesson from Integration Testing</title>
      <dc:creator>Luke Tong</dc:creator>
      <pubDate>Fri, 09 May 2025 01:00:58 +0000</pubDate>
      <link>https://dev.to/luke_tong_d4f228249f32d86/the-subtle-pitfall-of-requiredargsconstructor-a-lesson-from-integration-testing-145i</link>
      <guid>https://dev.to/luke_tong_d4f228249f32d86/the-subtle-pitfall-of-requiredargsconstructor-a-lesson-from-integration-testing-145i</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa31nu93tu4n15d4rhcy.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffa31nu93tu4n15d4rhcy.png" alt="Image description" width="800" height="243"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As a senior software engineer, I’m constantly navigating the balance between code readability, framework conventions, and real-world maintainability. Recently, a minor code review comment turned into a subtle debugging session that reminded me once again: annotations are powerful, but their magic must be understood deeply to avoid surprises.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Setup: Constructor Injection in a Spring Controller
&lt;/h4&gt;

&lt;p&gt;In a typical Spring Boot project, I had the following straightforward controller implementation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
public class MyController {

    private MyService myService;

    public MyController(MyService myService) {
        this.myService = myService;
    }

    public MyResponse createAccount() {
        myService.myMethod();
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is clean, explicit constructor injection — something I’ve used and trusted for years. I had also written a full integration test using WebTestClient to verify the entire workflow, and everything passed smoothly.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Code Review: “Use@RequiredArgsConstructor”
&lt;/h4&gt;

&lt;p&gt;During a code review, a team member pointed out that the company’s coding guideline encourages the use of Lombok’s @RequiredArgsConstructor to reduce boilerplate. I’m personally not a big fan of @RequiredArgsConstructor, mostly because its behavior isn’t always obvious at a glance. That said, I didn’t see any immediate harm and refactored the controller:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@RestController
@RequiredArgsConstructor
public class MyController {

    private MyService myService;

    public MyResponse createAccount() {
        myService.myMethod();
    }

    // ...
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No constructor. Just Lombok magic.&lt;/p&gt;

&lt;h4&gt;
  
  
  The Surprise: NullPointerException in Integration Test
&lt;/h4&gt;

&lt;p&gt;To my surprise, my integration test suddenly failed with a NullPointerException at myService.myMethod(). It was clear: myService was null. But why?&lt;/p&gt;

&lt;p&gt;How@RequiredArgsConstructorReally Works&lt;br&gt;
Here’s the catch: @RequiredArgsConstructor only generates a constructor for fields that are either: final, or annotated with @NonNull.&lt;/p&gt;

&lt;p&gt;In my refactored code, myService was neither.&lt;/p&gt;

&lt;p&gt;So although the annotation was present, no constructor was actually generated by Lombok. Spring Boot silently used the default no-args constructor — and since myService was never initialized, the result was a null pointer.&lt;/p&gt;
&lt;h4&gt;
  
  
  The Fix
&lt;/h4&gt;

&lt;p&gt;After reviewing the Lombok documentation, I updated my field to be:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;private final MyService myService;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now @RequiredArgsConstructor kicked in, generated the proper constructor, and Spring injected myService as expected. My integration test passed again.&lt;/p&gt;

&lt;h4&gt;
  
  
  Key Takeaways
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;Annotations like @RequiredArgsConstructor can be deceptive if not used carefully. Understanding how and when they generate code is crucial — especially in dependency injection scenarios.&lt;/li&gt;
&lt;li&gt;Constructor injection remains the most explicit and reliable form of wiring dependencies, particularly in Spring-based applications.&lt;/li&gt;
&lt;li&gt;Final fields signal immutability and are necessary for Lombok to do its job with @RequiredArgsConstructor.
Final Thoughts
Lombok can reduce boilerplate, but it comes with trade-offs. In teams, especially larger ones, annotations like @RequiredArgsConstructor can improve consistency — but only if everyone understands the magic behind the scenes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So next time you get a null pointer where you least expect it, don’t forget to check whether your “constructor” actually exists.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>springboot</category>
      <category>troubleshooting</category>
      <category>discuss</category>
    </item>
  </channel>
</rss>
