<?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: Mohamed Essam</title>
    <description>The latest articles on DEV Community by Mohamed Essam (@mohamedessamessmat).</description>
    <link>https://dev.to/mohamedessamessmat</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.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4127574%2F4f5f3786-e1e1-4ca5-8f7e-318abfb7f467.jpeg</url>
      <title>DEV Community: Mohamed Essam</title>
      <link>https://dev.to/mohamedessamessmat</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/mohamedessamessmat"/>
    <language>en</language>
    <item>
      <title>Every real bug I found came from running the code, never from reading it</title>
      <dc:creator>Mohamed Essam</dc:creator>
      <pubDate>Wed, 16 Sep 2026 09:08:02 +0000</pubDate>
      <link>https://dev.to/mohamedessamessmat/every-real-bug-i-found-came-from-running-the-code-never-from-reading-it-4oe1</link>
      <guid>https://dev.to/mohamedessamessmat/every-real-bug-i-found-came-from-running-the-code-never-from-reading-it-4oe1</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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjor8fbvx0lxll2nxza7w.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.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fjor8fbvx0lxll2nxza7w.png" alt="adfmig: Oracle ADF to Spring Boot. Know what a migration takes, before you commit to it."&gt;&lt;/a&gt;&lt;br&gt;
I spent months building a tool that converts Oracle ADF applications into Spring Boot projects. By the time I thought it was solid, it had a test suite I was proud of:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;192 unit tests&lt;/li&gt;
&lt;li&gt;263 real applications that each generated a project which compiled&lt;/li&gt;
&lt;li&gt;a sample of those projects started against a live Oracle database with &lt;strong&gt;every JPA mapping validated&lt;/strong&gt; against the real schema&lt;/li&gt;
&lt;li&gt;3,311 of 3,312 ADF attributes either mapped or explained by a diagnostic&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last one matters more than it sounds. Hibernate's &lt;code&gt;ddl-auto: validate&lt;/code&gt; refuses to start if a single mapping disagrees with the database. Every column, every type, every key — checked against the real thing, at startup, or the application doesn't come up.&lt;/p&gt;

&lt;p&gt;So: it compiles, it boots, and the database agrees with every mapping. What could still be wrong?&lt;/p&gt;

&lt;p&gt;Then someone asked me a question I didn't have a good answer to.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;"Did you actually call the endpoints?"&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I hadn't. I had been treating &lt;em&gt;"the application started"&lt;/em&gt; as &lt;em&gt;"the application works."&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;I wrote a script to call every endpoint of one real application.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Roughly half of them returned HTTP 500.&lt;/strong&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Bug one: the queries that were never given their parameters
&lt;/h2&gt;

&lt;p&gt;ADF view objects can carry SQL with named bind variables:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;select&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="n"&gt;orders&lt;/span&gt; &lt;span class="k"&gt;where&lt;/span&gt; &lt;span class="n"&gt;region_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;regionId&lt;/span&gt; &lt;span class="k"&gt;and&lt;/span&gt; &lt;span class="n"&gt;status&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="n"&gt;status&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The generator carried that SQL across faithfully into a native query. It just never passed the parameters.&lt;/p&gt;

&lt;p&gt;Every one of those endpoints threw &lt;code&gt;QueryParameterException&lt;/code&gt; the moment it was called. Roughly half the API of a real application, dead on arrival.&lt;/p&gt;

&lt;p&gt;Here is why nothing caught it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Compilation passed.&lt;/strong&gt; The query is a string. Strings compile.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Startup passed.&lt;/strong&gt; Hibernate validates &lt;em&gt;mappings&lt;/em&gt; against the schema. A native query isn't a mapping.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;The unit tests passed.&lt;/strong&gt; They asserted the generated code contained the right SQL — which it did.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every check I had was looking at the shape of the thing. None of them looked at what happened when you used it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug two: two entities, one name, wrong answers
&lt;/h2&gt;

&lt;p&gt;This one is worse, because it didn't throw anything.&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;// wrong: two entities answer to "Customer"&lt;/span&gt;
&lt;span class="n"&gt;entitiesBySimpleName&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="s"&gt;"Customer"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="c1"&gt;// right&lt;/span&gt;
&lt;span class="n"&gt;entitiesByFullyQualifiedName&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="s"&gt;"com.example.billing.Customer"&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One application had several pairs of entities that shared a simple class name across different packages — &lt;code&gt;com.example.billing.Customer&lt;/code&gt; and &lt;code&gt;com.example.crm.Customer&lt;/code&gt;. My code resolved entities by simple name.&lt;/p&gt;

&lt;p&gt;So a query written for one table silently ran against the other.&lt;/p&gt;

&lt;p&gt;The endpoint returned &lt;code&gt;200 OK&lt;/code&gt;. It returned well-formed JSON. It returned rows. &lt;strong&gt;They were rows from the wrong table.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Nothing anywhere in my test suite could have caught that, because every check I had would have been perfectly satisfied. The status code was right. The shape was right. The content was wrong, and only a person who knew the data would have known.&lt;/p&gt;

&lt;p&gt;I found it by calling an endpoint and looking at what came back.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bug three: the filter that quietly disappeared
&lt;/h2&gt;

&lt;p&gt;ADF view objects can narrow their own query — a &lt;code&gt;WHERE&lt;/code&gt; clause attached to the view, separate from the entity behind it.&lt;/p&gt;

&lt;p&gt;In one case that narrowing wasn't carried over. The generated endpoint returned every row in the table. The original returned a filtered subset.&lt;/p&gt;

&lt;p&gt;Again: 200 OK. Again: valid JSON. Again: more rows than the application it replaced was ever willing to show, which in a system with row-level access rules is not a bug, it's a &lt;strong&gt;data leak&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The pattern
&lt;/h2&gt;

&lt;p&gt;Three bugs. All three passed:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;check&lt;/th&gt;
&lt;th&gt;result&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Compilation&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Hibernate schema validation at startup&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;192 unit tests&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;45 acceptance tests against a real database&lt;/td&gt;
&lt;td&gt;✅&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Every one of my checks was testing &lt;em&gt;structure&lt;/em&gt;. Does it compile, do the types line up, does the schema match, does the generated file contain the expected text.&lt;/p&gt;

&lt;p&gt;None of them tested &lt;em&gt;meaning&lt;/em&gt;. Does this endpoint return the rows it is supposed to return.&lt;/p&gt;

&lt;p&gt;And structure is the easy half. Structure is what a compiler and a schema validator are for — they're very good at it, which is exactly why passing them feels like more assurance than it is.&lt;/p&gt;

&lt;h2&gt;
  
  
  What "tested" actually has to mean
&lt;/h2&gt;

&lt;p&gt;I added a command that starts the generated application and calls every single endpoint it publishes, then reports three numbers: what served, what was correctly denied, and what failed.&lt;/p&gt;

&lt;p&gt;The first honest run reported a large number of failures, a large number of endpoints correctly denied, and a minority actually serving data.&lt;/p&gt;

&lt;p&gt;That was uncomfortable to read. It was also the first number I had produced that meant anything.&lt;/p&gt;

&lt;p&gt;The "correctly denied" column matters too. In ADF, a REST resource with no grant in &lt;code&gt;jazn-data.xml&lt;/code&gt; is unreachable. So the generated application denies it as well — because publishing it open would turn a closed door into an open one, and that is a security change disguised as a migration. A 403 there isn't a failure. But you only learn which 403s are correct by calling them and checking against the original's policy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why I'm writing this down
&lt;/h2&gt;

&lt;p&gt;There's a comfortable kind of testing that verifies your code is &lt;em&gt;shaped&lt;/em&gt; the way you intended. It's fast, it runs in CI, it goes green, and it tells you almost nothing about whether the software does the right thing.&lt;/p&gt;

&lt;p&gt;Then there's the slow kind: run it, call it, and compare the output against the truth. For me that meant seeding a database, starting a generated application, calling an endpoint, and reading the rows that came back next to the rows the table actually held.&lt;/p&gt;

&lt;p&gt;Every real defect this project has ever had came from the second kind. Not one came from reading code, or from a type error, or from a test I wrote in advance.&lt;/p&gt;

&lt;p&gt;I now assume that anything which has only passed structural checks is probably wrong in some way I haven't looked for yet.&lt;/p&gt;

&lt;p&gt;One more thing worth saying: when a result looks wrong, suspect your test data before your code. Twice I chased an empty response that turned out to be a failed &lt;code&gt;INSERT&lt;/code&gt; in the seed script, not a broken query. The tool was right and the fixture was lying.&lt;/p&gt;




&lt;p&gt;The tool is &lt;strong&gt;&lt;a href="https://github.com/trippysolutions/adfmig" rel="noopener noreferrer"&gt;adfmig&lt;/a&gt;&lt;/strong&gt;. The assessment half is MIT licensed and complete — point it at an ADF application and it tells you what a migration to Spring Boot would actually involve: what carries across, what has to be rewritten by hand, and where the source doesn't say enough to migrate safely. It runs entirely on your machine; nothing is uploaded.&lt;/p&gt;

&lt;p&gt;If it reads your application wrong, there is an issue template called exactly that. Those reports are worth more to me than any number in this article.&lt;/p&gt;

</description>
      <category>java</category>
      <category>testing</category>
      <category>programming</category>
      <category>webdev</category>
    </item>
  </channel>
</rss>
