<?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: ThembaTman0</title>
    <description>The latest articles on DEV Community by ThembaTman0 (@themba).</description>
    <link>https://dev.to/themba</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%2F906713%2Ffbd99458-7931-4aa7-b28f-0aae7db26ad7.jpeg</url>
      <title>DEV Community: ThembaTman0</title>
      <link>https://dev.to/themba</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/themba"/>
    <language>en</language>
    <item>
      <title>Faker Doesn't Know Your Entities Are Related, So I Built Something That Does</title>
      <dc:creator>ThembaTman0</dc:creator>
      <pubDate>Mon, 07 Sep 2026 21:48:44 +0000</pubDate>
      <link>https://dev.to/themba/faker-doesnt-know-your-entities-are-related-so-i-built-something-that-does-2jgn</link>
      <guid>https://dev.to/themba/faker-doesnt-know-your-entities-are-related-so-i-built-something-that-does-2jgn</guid>
      <description>&lt;h1&gt;
  
  
  Faker Doesn't Know Your Entities Are Related, So I Built Something That Does
&lt;/h1&gt;

&lt;p&gt;You've added a second entity to the schema, wired up a &lt;code&gt;@ManyToOne&lt;/code&gt;, and gone back to your seed script to generate fifty more rows. Ninety seconds later, the app refuses to start: unique constraint violation, somewhere inside a loop you wrote three weeks ago at 11pm. You fix it. You restart. A different field breaks a different constraint.&lt;/p&gt;

&lt;p&gt;This is the exact moment every Spring Boot developer eventually meets the real limit of tools like Faker. They're brilliant at generating a name, an email, an address. They have no idea the &lt;code&gt;Payment&lt;/code&gt; sitting in front of them needs a &lt;code&gt;Counterparty&lt;/code&gt; to already exist.&lt;/p&gt;

&lt;p&gt;So you do what everyone does: hand-write the wiring. Create parents first. Hold onto their generated IDs. Wire them into children. Hope you didn't just violate a &lt;code&gt;@NotNull&lt;/code&gt; somewhere in the process. It works, for a while. Then the schema changes, and the script quietly stops matching reality until the next 3am debugging session finds out the hard way.&lt;/p&gt;

&lt;p&gt;I hit this enough times that I stopped patching the script and looked at the actual problem: the information needed to seed this correctly already exists. It's sitting right there in the entity, in the annotations you already wrote. &lt;code&gt;@ManyToOne&lt;/code&gt;, &lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@Column(unique = true)&lt;/code&gt;, JPA already knows the shape of your data. Nothing should need to be told that twice.&lt;/p&gt;

&lt;p&gt;That became &lt;strong&gt;SynthForge&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core idea
&lt;/h2&gt;

&lt;p&gt;Instead of writing a script that generates data, you annotate the entity:&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;@Entity&lt;/span&gt; &lt;span class="nd"&gt;@Seed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;50&lt;/span&gt;&lt;span class="o"&gt;)&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;Counterparty&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="cm"&gt;/* fields only */&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="nd"&gt;@Entity&lt;/span&gt; &lt;span class="nd"&gt;@Seed&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;count&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="kd"&gt;public&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;Payment&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="nd"&gt;@ManyToOne&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;optional&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&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;Counterparty&lt;/span&gt; &lt;span class="n"&gt;counterparty&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;p&gt;Start the app in a dev profile. Both tables populate, correctly ordered, on every restart. No seed method. No calling code, anywhere. The entity is the seed script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's actually happening underneath
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Entity scanning.&lt;/strong&gt; SynthForge reads JPA-managed attributes through the &lt;code&gt;jakarta.persistence.metamodel.Metamodel&lt;/code&gt; API, not raw reflection. That distinction matters more than it sounds: only real persistent fields ever get touched, nothing that merely resembles one.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Relationship ordering.&lt;/strong&gt; From the owning-side &lt;code&gt;@ManyToOne&lt;/code&gt;/&lt;code&gt;@OneToOne&lt;/code&gt; relationships, it builds a dependency graph and topologically sorts it. Parent rows exist before a child is ever generated to reference them. This is precisely where hand-written scripts fail first: the moment a second level of nesting shows up, manual ID-tracking becomes its own source of bugs, the thing you're debugging instead of your actual feature.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Constraint-aware generation.&lt;/strong&gt; &lt;code&gt;@NotNull&lt;/code&gt;, &lt;code&gt;@Size&lt;/code&gt;, &lt;code&gt;@Email&lt;/code&gt;, and a set of field-name heuristics (&lt;code&gt;email&lt;/code&gt;, &lt;code&gt;iban&lt;/code&gt;, &lt;code&gt;amount&lt;/code&gt;, &lt;code&gt;country&lt;/code&gt;) drive realistic values through Datafaker. Hit a &lt;code&gt;@Column(unique = true)&lt;/code&gt; field, and instead of a constraint violation forty rows in, you get a bounded retry loop.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Idempotent restarts.&lt;/strong&gt; A table that already has rows gets skipped. Restart against a persistent database as many times as you want; nothing duplicates.&lt;/p&gt;

&lt;h2&gt;
  
  
  "Isn't this just Instancio?"
&lt;/h2&gt;

&lt;p&gt;Fair question, and worth answering directly rather than dodging it. Instancio, and its &lt;code&gt;instancio-jpa&lt;/code&gt; extension, gives you an API to call from inside a test: build a graph, persist it, for that one test. It's genuinely good at that job.&lt;/p&gt;

&lt;p&gt;SynthForge isn't called from anywhere. Annotate the entity, start the app in an enabled profile, and the database is already populated before your code runs a single line. Different tool, because it solves a different moment: application startup, not a single test method.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where it stands
&lt;/h2&gt;

&lt;p&gt;Live on Maven Central:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;dependency&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;io.github.thembatman0&lt;span class="nt"&gt;&amp;lt;/groupId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;artifactId&amp;gt;&lt;/span&gt;synthforge-spring&lt;span class="nt"&gt;&amp;lt;/artifactId&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;version&amp;gt;&lt;/span&gt;0.1.0&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/dependency&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Repo: &lt;a href="https://github.com/ThembaTman0/synthforge" rel="noopener noreferrer"&gt;github.com/ThembaTman0/synthforge&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;It won't fix your architecture and it won't make decisions for you. But the next time you add a foreign key at 11pm, there's a decent chance it means you get to go to bed instead.&lt;/p&gt;

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