<?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: Gilles Bernard</title>
    <description>The latest articles on DEV Community by Gilles Bernard (@tipikae).</description>
    <link>https://dev.to/tipikae</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%2F4074379%2F1cfba9dd-63dc-41a3-8da2-d3c28bacdc52.jpg</url>
      <title>DEV Community: Gilles Bernard</title>
      <link>https://dev.to/tipikae</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/tipikae"/>
    <language>en</language>
    <item>
      <title>I read my competitor's generated code. It was better than mine in six places.</title>
      <dc:creator>Gilles Bernard</dc:creator>
      <pubDate>Wed, 12 Aug 2026 12:51:51 +0000</pubDate>
      <link>https://dev.to/tipikae/i-read-my-competitors-generated-code-it-was-better-than-mine-in-six-places-2e0h</link>
      <guid>https://dev.to/tipikae/i-read-my-competitors-generated-code-it-was-better-than-mine-in-six-places-2e0h</guid>
      <description>&lt;p&gt;I build a browser-based Spring Boot generator. A competitor, Bootify, has been around longer and is listed in awesome-java. I had never actually read their output.&lt;/p&gt;

&lt;p&gt;So I generated the same domain model in both tools, unzipped the two projects side by side, and went through them file by file. This post is what I found, including the parts where their output is better than mine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Disclosure up front:&lt;/strong&gt; I am the author of SpringBoot Generator, one of the two tools compared here. Everything below is checkable — generate the same model in both, both have a free tier, and diff the ZIPs yourself.&lt;/p&gt;

&lt;h2&gt;
  
  
  The method
&lt;/h2&gt;

&lt;p&gt;Same domain on both sides, deliberately trivial:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Customer&lt;/code&gt; — &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;name&lt;/code&gt; (String, 255, required)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Order&lt;/code&gt; — &lt;code&gt;id&lt;/code&gt;, &lt;code&gt;label&lt;/code&gt; (String, 255, required), a required many-to-one to &lt;code&gt;Customer&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;code&gt;Order&lt;/code&gt; is a reserved SQL keyword. That was on purpose: escaping reserved table names is a real correctness question and a cheap way to see whether a generator thinks about SQL at all.&lt;/p&gt;

&lt;p&gt;I ticked every option each tool offered on the tier I used, downloaded both ZIPs, and compared.&lt;/p&gt;

&lt;h3&gt;
  
  
  Where the comparison is unfair, stated up front
&lt;/h3&gt;

&lt;p&gt;The two inputs are not identical, and pretending otherwise would invalidate everything after it. Four differences:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;their model declares the foreign keys &lt;strong&gt;in the opposite direction&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;theirs is on &lt;strong&gt;PostgreSQL&lt;/strong&gt;, mine on &lt;strong&gt;MySQL&lt;/strong&gt;;&lt;/li&gt;
&lt;li&gt;theirs is a &lt;strong&gt;Gradle&lt;/strong&gt; build, mine &lt;strong&gt;Maven&lt;/strong&gt; (both tools support both);&lt;/li&gt;
&lt;li&gt;their fields were &lt;strong&gt;not marked required&lt;/strong&gt;, so their validation annotations are thinner than mine for reasons that have nothing to do with the generator.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;strong&gt;validation depth and SQL column types are not comparable here.&lt;/strong&gt; Topology, layering, tests, tooling and documentation are.&lt;/p&gt;

&lt;h2&gt;
  
  
  The headline numbers
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;SpringBoot Generator&lt;/th&gt;
&lt;th&gt;Bootify&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;ZIP size&lt;/td&gt;
&lt;td&gt;68 KB&lt;/td&gt;
&lt;td&gt;196 KB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Files&lt;/td&gt;
&lt;td&gt;71&lt;/td&gt;
&lt;td&gt;95&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;README lines&lt;/td&gt;
&lt;td&gt;200, across 3 files&lt;/td&gt;
&lt;td&gt;57, one file&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The size difference is mostly their &lt;code&gt;gradle-wrapper.jar&lt;/code&gt;. It means nothing.&lt;/p&gt;

&lt;h2&gt;
  
  
  The one structural difference
&lt;/h2&gt;

&lt;p&gt;This is the thing that actually distinguishes the two, and it is not a checkbox.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bootify emits a single deployable artifact.&lt;/strong&gt; The generated Angular app lives in &lt;code&gt;src/main/webapp/&lt;/code&gt; and is served by Spring through an &lt;code&gt;AngularForwardController&lt;/code&gt;. One build, one JAR, one deployment.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Mine emits two projects&lt;/strong&gt;, &lt;code&gt;backend/&lt;/code&gt; and &lt;code&gt;frontend/&lt;/code&gt;, each with its own README, its own build, and no knowledge of where the other one is deployed beyond a CORS config and a base URL.&lt;/p&gt;

&lt;p&gt;Neither is correct in the abstract. A single artifact is genuinely simpler to ship — one pipeline, no CORS, no origin config. Two projects is what you want when the frontend goes on a CDN, when a second client appears, or when the two sides have different release&lt;br&gt;
cadences.&lt;/p&gt;

&lt;p&gt;I bet on the second. It is a bet, not a feature.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where Bootify's output is better than mine
&lt;/h2&gt;

&lt;p&gt;Six of them. This is the part I did not expect to write.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Audit columns.&lt;/strong&gt; Their entities carry &lt;code&gt;dateCreated&lt;/code&gt; and &lt;code&gt;lastUpdated&lt;/code&gt;, maintained automatically, with &lt;code&gt;updatable = false&lt;/code&gt; where it matters. Mine have nothing. On a real project this is one of the first things you add by hand.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Referential integrity on delete.&lt;/strong&gt; They generate &lt;code&gt;events/BeforeDelete*&lt;/code&gt; handlers and a &lt;code&gt;ReferencedException&lt;/code&gt;, so deleting a parent that still has children fails cleanly. Mine does not address it, so you get whatever the database decides.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A real frontend shell.&lt;/strong&gt; Theirs ships a header, a home page, an error page, a global &lt;code&gt;error-handler&lt;/code&gt;, a &lt;code&gt;title-strategy&lt;/code&gt;, an &lt;code&gt;input-errors&lt;/code&gt; component, a favicon, a logo and SCSS. Mine generates CRUD screens and nothing around them — no header, no home, no error&lt;br&gt;
page.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Separate templates.&lt;/strong&gt; Their Angular components use &lt;code&gt;.html&lt;/code&gt; + &lt;code&gt;.scss&lt;/code&gt; files. Mine inlines templates in the &lt;code&gt;.ts&lt;/code&gt;. Theirs is the idiomatic choice.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;i18n attributes&lt;/strong&gt; on their form components, factored through a reusable &lt;code&gt;app-input-row&lt;/code&gt;. Mine writes each form field out longhand, in English only.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;open-in-view: false&lt;/code&gt;&lt;/strong&gt; in their config. Minor, but it is the correct default and I had not set it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where my output is ahead
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Generated tests.&lt;/strong&gt; My ZIP contains 8 written &lt;code&gt;@Test&lt;/code&gt; methods per service with real assertions, plus a Testcontainers integration test of 136 lines. Their free ZIP contained no &lt;code&gt;src/test/&lt;/code&gt; directory at all.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;This is the number that needs the asterisk&lt;/strong&gt;, so here it is: Bootify sells test generation on their paid plan. I compared a free ZIP against my own output with the paid options enabled. The honest claim is not "they don't generate tests" — it is that I have not verified what their paid tier produces, and neither should you take my word for it.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Deployment artifacts.&lt;/strong&gt; A &lt;code&gt;Dockerfile&lt;/code&gt;, a &lt;code&gt;docker-compose.yml&lt;/code&gt; that starts the application &lt;em&gt;and&lt;/em&gt; the database, and a &lt;code&gt;.dockerignore&lt;/code&gt;. Theirs has no Dockerfile; its compose file starts the database only, wired through &lt;code&gt;spring-boot-docker-compose&lt;/code&gt; in &lt;code&gt;start-only&lt;/code&gt; mode. Theirs is better for &lt;code&gt;./gradlew bootRun&lt;/code&gt; on a laptop. Mine is better for putting the thing on a server.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Environment profiles.&lt;/strong&gt; &lt;code&gt;application-dev.properties&lt;/code&gt; and &lt;code&gt;application-prod.properties&lt;/code&gt;, with &lt;code&gt;ddl-auto&lt;/code&gt; set to &lt;code&gt;create-drop&lt;/code&gt; and &lt;code&gt;validate&lt;/code&gt; respectively. Theirs has one &lt;code&gt;application.yml&lt;/code&gt; with &lt;code&gt;ddl-auto: update&lt;/code&gt; — which is the setting you least want pointed at a production database.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A SQL schema file.&lt;/strong&gt; &lt;code&gt;resources/sql/script.sql&lt;/code&gt; ships with the project. Theirs relies on Hibernate to create the schema.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Documentation.&lt;/strong&gt; 200 lines across three READMEs against 57 in one.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it's a tie
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;The reserved keyword.&lt;/strong&gt; Both handle &lt;code&gt;Order&lt;/code&gt;, differently. They escape it at the annotation level: &lt;code&gt;@Table(name = "\"order\"")&lt;/code&gt;. I set &lt;code&gt;hibernate.globally_quoted_identifiers=true&lt;/code&gt; in the properties. Both work; theirs is more surgical, mine is harder to accidentally undo.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ID generation&lt;/strong&gt; — &lt;code&gt;IDENTITY&lt;/code&gt; on my side, &lt;code&gt;@SequenceGenerator&lt;/code&gt; on theirs. Both fine.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;DTO mapping&lt;/strong&gt; — I generate explicit mappers behind an &lt;code&gt;IEntityMapper&lt;/code&gt; interface, they map inside the service. More files on my side, fewer indirections on theirs.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Lazy loading.&lt;/strong&gt; Their &lt;code&gt;@ManyToOne&lt;/code&gt; associations are explicitly &lt;code&gt;fetch = FetchType.LAZY&lt;/code&gt;. Mine were bare — which means &lt;code&gt;EAGER&lt;/code&gt;, the JPA default, and an N+1 waiting to happen. That was the single worst finding of this comparison against me, and it was one annotation. I fixed it, and it is live.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I found in my own output
&lt;/h2&gt;

&lt;p&gt;Comparing against someone else is mostly an excuse to read your own output as a stranger would. Two things fell out that had nothing to do with Bootify.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One-To-Many and Many-To-One were not producing the same product.&lt;/strong&gt; Same domain, same data, but declaring the relation one way generated a &lt;code&gt;&amp;lt;select&amp;gt;&lt;/code&gt; populated from the parent resource, and declaring it the other way generated a bare numeric input where the user was expected to type the parent's ID by hand — with no guard against &lt;code&gt;0&lt;/code&gt;. Both are legitimate JPA models and the frontend was faithfully following the backend in each case. It was still a trap: the wizard presents that choice as a matter of &lt;em&gt;direction&lt;/em&gt;, and the "natural" reading of "a customer has many orders" led to the degraded form. Fixed — every foreign key now generates a dropdown.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Integration tests skip any entity with a mandatory foreign key.&lt;/strong&gt; Still true as I write this. The reason is sound — POSTing an &lt;code&gt;Order&lt;/code&gt; requires a &lt;code&gt;Customer&lt;/code&gt; to already exist, and the fixture doesn't seed one — but the consequence is that on a realistic model, where most child entities have a required FK, integration test coverage lands on root entities only.&lt;br&gt;
And it is silent: nothing in the generated README explains why &lt;code&gt;OrderIntegrationTest&lt;/code&gt; isn't there. Seeding the parent is the fix. It is on the list.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this comparison does not establish
&lt;/h2&gt;

&lt;p&gt;It is one domain model, two entities, one relation. It says nothing about how either tool behaves on twenty entities, on inheritance, on many-to-many with payload, or on a schema that was designed by someone else five years ago. It does not measure generated code at runtime — I read it, I did not benchmark it.&lt;/p&gt;

&lt;p&gt;And one caveat that cost me two wrong conclusions, in both directions: &lt;strong&gt;I should not have read either vendor's pages as a statement of what the generator emits&lt;/strong&gt; — mine included. I recorded a competitor's capabilities from their documentation twice and was wrong both times, once against them and once in their favour. Everything stated as fact in this post came out of an unzipped project; anything I could only read on a page, I removed before publishing. Both generators emit Spring Boot 4.1.0 — that one I checked in the two &lt;code&gt;build.gradle&lt;/code&gt;/&lt;code&gt;pom.xml&lt;/code&gt; files, not on a website. If you are comparing generators, compare ZIPs.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I took away
&lt;/h2&gt;

&lt;p&gt;I expected to find that a competitor with a longer track record had a shallower output. The opposite was true on six counts, and one of them — the missing &lt;code&gt;LAZY&lt;/code&gt; — was a genuine defect in what I was shipping to paying users.&lt;/p&gt;

&lt;p&gt;What survived as a real difference is narrower than my marketing page implied, and I have since made the page say so: two separately deployable projects instead of one artifact, generated tests, and the deployment scaffolding around the code. That's it. That's the list.&lt;/p&gt;

&lt;p&gt;If you generate Spring Boot projects, go read the ZIP your generator gives you. Read it like you would read a pull request from someone you don't know. I found two defects in my own product in an afternoon, and I wrote it.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I build &lt;a href="https://www.springboot-generator.com" rel="noopener noreferrer"&gt;SpringBoot Generator&lt;/a&gt;. Generating a project is free and needs no account, so you can check any claim in this post against the actual output. If you do read the generated code, I'd rather hear what's wrong with it than get a star — here in the comments, or at &lt;a href="mailto:support@springboot-generator.com"&gt;support@springboot-generator.com&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>java</category>
      <category>springboot</category>
      <category>showdev</category>
      <category>codegen</category>
    </item>
  </channel>
</rss>
