<?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: Pavel Ponec</title>
    <description>The latest articles on DEV Community by Pavel Ponec (@pponec).</description>
    <link>https://dev.to/pponec</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%2F3837101%2Fffef3f8f-fb34-4a7a-9a80-77e9c29670e6.jpg</url>
      <title>DEV Community: Pavel Ponec</title>
      <link>https://dev.to/pponec</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/pponec"/>
    <language>en</language>
    <item>
      <title>PetStore: Pure Java for UI and Safe SQL Mapping</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Thu, 02 Apr 2026 07:41:35 +0000</pubDate>
      <link>https://dev.to/pponec/petstore-pure-java-for-ui-and-safe-sql-mapping-hh9</link>
      <guid>https://dev.to/pponec/petstore-pure-java-for-ui-and-safe-sql-mapping-hh9</guid>
      <description>&lt;p&gt;I would like to introduce the &lt;strong&gt;PetStore&lt;/strong&gt; sample application, which demonstrates a pure "Java-first" approach to web interface development. The entire project is built on the idea of maximum type safety and clarity, achieved through two modules of the Ujorm3 library. These effectively eliminate common abstraction layers that often complicate development and debugging.&lt;/p&gt;

&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%2Fy73qzcl760lnp9dijr71.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%2Fy73qzcl760lnp9dijr71.png" alt="Screenshot" width="711" height="606"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🛠️ Two Pillars of Ujorm3
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. UI Creation without Templates (ujo-web)
&lt;/h3&gt;

&lt;p&gt;We have replaced traditional engines like Thymeleaf or JSP with pure Java code.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Type-safe rendering:&lt;/strong&gt; HTML is generated using the &lt;code&gt;HtmlElement&lt;/code&gt; builder and &lt;code&gt;try-with-resources&lt;/code&gt; blocks.
This approach allows writing Java code in a natural tree structure that faithfully mirrors the HTML structure.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Refactoring with full IDE support:&lt;/strong&gt; Since the UI is defined in Java, everything you are used to works – autocomplete (IntelliSense), instant refactoring (e.g., extracting a table into a &lt;code&gt;renderTable()&lt;/code&gt; method), and correctness checking while writing.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;No more parameter errors:&lt;/strong&gt; The &lt;code&gt;HttpParameter&lt;/code&gt; interface uses enums to define web parameters.
This practically eliminates typos in form field names, which in standard solutions only manifest at runtime.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Modern Database Handling (ujo-orm)
&lt;/h3&gt;

&lt;p&gt;Forget about complex XML mapping or runtime errors in SQL queries.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Using Java Records:&lt;/strong&gt; Standard Java records serve as domain objects (&lt;code&gt;Pet&lt;/code&gt;, &lt;code&gt;Category&lt;/code&gt;).
They are naturally immutable, clean, and fully compatible with &lt;code&gt;@Table&lt;/code&gt; and &lt;code&gt;@Column&lt;/code&gt; annotations.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Type-Safe SQL Builder:&lt;/strong&gt; An annotation processor generates metamodels (e.g., &lt;code&gt;MetaPet&lt;/code&gt;) during compilation.
The compiler catches an error in a column name, not an application crash in production.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL under control:&lt;/strong&gt; No unexpected &lt;code&gt;LazyInitializationException&lt;/code&gt; or hidden N+1 problems.
You have absolute control over every &lt;code&gt;SqlQuery&lt;/code&gt;.
Moreover, you can easily map results from native SQL back to records using the &lt;code&gt;label()&lt;/code&gt; method.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  📁 Code Sample (PetServlet)
&lt;/h2&gt;

&lt;p&gt;The project is designed with an emphasis on straightforwardness.&lt;br&gt;
The following example from a stateless servlet demonstrates how elegantly logic, parameters, and HTML generation can be connected:&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;@Override&lt;/span&gt;
&lt;span class="kd"&gt;protected&lt;/span&gt; &lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;doGet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;HttpServletRequest&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;HttpServletResponse&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HttpContext&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;ofServlet&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;contextPath&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;req&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getContextPath&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;ACTION&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Action:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;paramValueOf&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;petId&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;parameter&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PET_ID&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nl"&gt;Long:&lt;/span&gt;&lt;span class="o"&gt;:&lt;/span&gt;&lt;span class="n"&gt;parseLong&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPets&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getCategories&lt;/span&gt;&lt;span class="o"&gt;();&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;petToEdit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Action&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;EDIT&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;equals&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;?&lt;/span&gt; &lt;span class="n"&gt;services&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;getPetById&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;petId&lt;/span&gt;&lt;span class="o"&gt;).&lt;/span&gt;&lt;span class="na"&gt;orElse&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt; &lt;span class="o"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;HtmlElement&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="no"&gt;BOOTSTRAP_CSS&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;body&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;addBody&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Css&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;container&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Css&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mt5&lt;/span&gt;&lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;renderHeader&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contextPath&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;renderTable&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pets&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
            &lt;span class="n"&gt;renderForm&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;petToEdit&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="n"&gt;categories&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;
        &lt;span class="o"&gt;}&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;Here is what a native SQL query looks like in pure Java:&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="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;Long&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;PET_EM&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; 
             &lt;span class="nc"&gt;EntityManager&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;public&lt;/span&gt; &lt;span class="nc"&gt;List&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Pet&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;findAll&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
        SELECT p.id AS ${p.id}
        , p.name    AS ${p.name}
        , p.status  AS ${p.status}
        , c.id      AS ${c.id}
        , c.name    AS ${c.name}
        FROM pet p
        LEFT JOIN category c ON c.id = p.category_id
        WHERE p.id &amp;gt;= :id
        ORDER BY p.id
        """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&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="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"p.status"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;label&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaPet&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;category&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCategory&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;1L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;streamMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;PET_EM&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
        &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&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;
  
  
  💡 Why Choose This Approach?
&lt;/h2&gt;

&lt;p&gt;This architecture represents an interesting alternative for developers who are tired of heavy JPA frameworks or bloated frontend technologies.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Where Ujorm PetStore shines most:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;B2B and administrative applications:&lt;/strong&gt; Where development speed and long-term maintainability are important.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Microservices:&lt;/strong&gt; Thanks to minimal overhead and fast startup.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Projects with HTMX:&lt;/strong&gt; It perfectly complements modern trends of returning to server-side rendering.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;"Java-First"&lt;/strong&gt; philosophy drastically reduces context switching between Java, SQL, XML, and various templating languages.&lt;br&gt;
Everything you need is under the protection of the compiler.&lt;/p&gt;


&lt;h2&gt;
  
  
  🚀 Try It Locally
&lt;/h2&gt;

&lt;p&gt;The application utilizes the best of the current ecosystem:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Java 25&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Spring Boot 3.5.0&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;H2 Database&lt;/strong&gt; (In-memory)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All you need is JDK 25 and Maven installed, then just run:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;mvn spring-boot:run
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The application will start at &lt;code&gt;http://localhost:8080&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resources and Links:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm-petstore/blob/main/src/main/java/org/ujorm/petstore/PetServlet.java" rel="noopener noreferrer"&gt;PetServlet.java&lt;/a&gt; – A stateless Servlet acting as both Controller and View. It handles HTTP communication and builds the HTML.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm-petstore/blob/main/src/main/java/org/ujorm/petstore/Dao.java" rel="noopener noreferrer"&gt;Dao.java&lt;/a&gt; – Data access layer integrating Spring JDBC with Ujorm &lt;code&gt;EntityManager&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/ujorm/tree/ujorm3" rel="noopener noreferrer"&gt;Ujorm 3 Library on GitHub&lt;/a&gt; – Official library repository.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/pponec/orm-benchmarks" rel="noopener noreferrer"&gt;ORM Benchmarks&lt;/a&gt; – How this approach compares to the competition.&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;Does it make sense to you to have the UI and DB layers so tightly coupled with the compiler? I will be glad for any technical feedback!&lt;/p&gt;

</description>
      <category>java</category>
      <category>showdev</category>
      <category>sql</category>
      <category>ui</category>
    </item>
    <item>
      <title>Ujorm3: A New Lightweight ORM for JavaBeans and Records</title>
      <dc:creator>Pavel Ponec</dc:creator>
      <pubDate>Mon, 23 Mar 2026 16:27:15 +0000</pubDate>
      <link>https://dev.to/pponec/ujorm3-a-new-lightweight-orm-for-javabeans-and-records-3pph</link>
      <guid>https://dev.to/pponec/ujorm3-a-new-lightweight-orm-for-javabeans-and-records-3pph</guid>
      <description>&lt;blockquote&gt;
&lt;p&gt;"Do the simplest thing that could possibly work."&lt;br&gt;
— Kent Beck, creator of Extreme Programming and pioneer of Test-Driven Development.&lt;/p&gt;
&lt;/blockquote&gt;

&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%2Fnyrs8uccbasly3369ay8.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%2Fnyrs8uccbasly3369ay8.png" height="300" width="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I believe the Java language architects didn't exactly hit the mark when designing the API for the original JDBC library for database operations. As a result, a significant number of various libraries and frameworks have emerged in the Java ecosystem, differing in their approach, level of complexity, and quality. I would like to introduce you to a brand new lightweight ORM library, &lt;strong&gt;Ujorm3&lt;/strong&gt;, which I believe beats its competitors with its simplicity, transparent behavior, and low overhead. The goal of this project is to offer a reliable, safe, efficient, and easy-to-understand tool for working with relational databases without hidden magic and complex abstractions that often complicate both debugging and performance. The &lt;strong&gt;first&lt;/strong&gt; release candidate (RC1) is now available in the Maven Central Repository, released under the free Apache License 2.0.&lt;/p&gt;

&lt;p&gt;The library builds on the familiar principles of JDBC but adds a thin layer of a user-friendly API on top of them. It works with clean, stateless objects and native SQL, so the developer has full control over what is actually executed in the database. Ujorm3 deliberately avoids implementing SQL dialects and instead uses native SQL complemented by type-safe tools for mapping database results to Java objects. It does not cache the results of any user queries. To achieve maximum speed, however, Ujorm3 retains certain metadata.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application API Classes
&lt;/h2&gt;

&lt;p&gt;The core class for database operations is &lt;code&gt;SqlQuery&lt;/code&gt; (originally named &lt;a href="https://blog.root.cz/ponec/170-radku-java-kodu-pro-sql/" rel="noopener noreferrer"&gt;SqlParamBuilder&lt;/a&gt;), which acts as a facade over &lt;code&gt;PreparedStatement&lt;/code&gt;. The object supports named parameters for SQL statements, eliminates checked exceptions, and provides the result of a &lt;code&gt;SELECT&lt;/code&gt; operation as an efficient &lt;code&gt;Stream&amp;lt;ResultSet&amp;gt;&lt;/code&gt;. The mapping of data from a &lt;code&gt;ResultSet&lt;/code&gt; to domain objects is then handled by a separate class called &lt;code&gt;ResultSetMapper&amp;lt;DOMAIN&amp;gt;&lt;/code&gt;. Its instance prepares the mapping model upon first use and subsequently reuses it, which significantly reduces the overhead when processing a large volume of queries.&lt;/p&gt;

&lt;p&gt;Mapping class attributes to database columns can be specified using annotations from the &lt;code&gt;jakarta.persistence&lt;/code&gt; package (&lt;code&gt;@Table&lt;/code&gt;, &lt;code&gt;@Column&lt;/code&gt;, &lt;code&gt;@Id&lt;/code&gt;), but the library can infer some properties even without them. Both mutable JavaBeans and immutable Records are fully supported. Ujorm3 only works with M:1 relations—1:M collections are intentionally omitted to prevent the generation of hidden queries and N+1 problems. Relational columns of a &lt;code&gt;SELECT&lt;/code&gt; statement can be mapped using their labels in the format &lt;code&gt;"city.name"&lt;/code&gt;, which contain a dot-chained list of Java attributes of the domain objects. However, it is better to use a type-safe metamodel.&lt;/p&gt;

&lt;p&gt;Automatically generated &lt;code&gt;Meta*&lt;/code&gt; classes enable safe column mapping without the use of typo-prone text strings. The use of a &lt;code&gt;SELECT&lt;/code&gt; statement can then look like this, for example:&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="kd"&gt;static&lt;/span&gt; &lt;span class="kd"&gt;final&lt;/span&gt; &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;
        &lt;span class="nc"&gt;ResultSetMapper&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;of&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Employee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;class&lt;/span&gt;&lt;span class="o"&gt;);&lt;/span&gt;

&lt;span class="kt"&gt;void&lt;/span&gt; &lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;sql&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"""
            SELECT ${COLUMNS}
            FROM employee e
            JOIN city c ON c.id = e.city_id
            LEFT JOIN employee b ON b.id = e.boss_id
            WHERE e.id &amp;gt; :employeeId
            """&lt;/span&gt;&lt;span class="o"&gt;;&lt;/span&gt;

    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;employees&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;SqlQuery&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;run&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;connection&lt;/span&gt;&lt;span class="o"&gt;(),&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sql&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.id"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"e.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"c.country_code"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;city&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaCity&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;countryCode&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;column&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"b.name"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;boss&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="nc"&gt;MetaEmployee&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;bind&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"employeeId"&lt;/span&gt;&lt;span class="o"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0L&lt;/span&gt;&lt;span class="o"&gt;)&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;streamMap&lt;/span&gt;&lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="no"&gt;EMPLOYEE_MAPPER&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;mapper&lt;/span&gt;&lt;span class="o"&gt;())&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="na"&gt;toList&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;Please note that the domain class does not need to be registered anywhere in advance. For efficient work, however, I recommend creating a static mapper, whose implementation is prepared for multithreaded access. The &lt;code&gt;column()&lt;/code&gt; method adds a database column with a label to the SQL template at the position of the &lt;code&gt;${COLUMNS}&lt;/code&gt; placeholder. An alternative &lt;code&gt;label()&lt;/code&gt; method is also supported, allowing you to explicitly declare only column labels, thereby keeping the SQL query in the Java code closer to its native notation. However, these two approaches cannot be combined in a single query.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;EntityManager&lt;/code&gt; is used for working with entities, providing simple CRUD operations—including batch commands—through a &lt;code&gt;Crud&lt;/code&gt; object. An interesting feature is the possibility of partial updates—the developer can specify an enumeration of columns to be updated, or pass the original object to the library, from which it will infer the changes itself. The mentioned classes are illustrated in a simplified class diagram. All listed methods are public:&lt;/p&gt;

&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%2F19wyj19wahv9ykdegsx1.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%2F19wyj19wahv9ykdegsx1.png" alt="Class diagram" width="800" height="433"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Performance
&lt;/h2&gt;

&lt;p&gt;Ujorm3 achieves very good &lt;a href="https://github.com/pponec/orm-benchmarks/tree/development?tab=readme-ov-file#orm-benchmark" rel="noopener noreferrer"&gt;results&lt;/a&gt; in benchmark tests, where it is compared with some popular ORM libraries. The mechanism of writing values to domain objects also contributes to the good score. Instead of the traditional approach using Java reflection, the library generates and compiles its own classes at runtime. Such an approach generally reduces memory requirements, minimizes overhead, and saves work for the Garbage Collector. The library has no dependencies on external libraries, and the compiled benchmark module (including the Ujorm3 library itself) is less than 3 MB, which is advantageous for microservices and embedded environments. However, it is good to keep in mind that in a production environment, in conjunction with slower databases, the differences in performance may partially blur.&lt;/p&gt;

&lt;h2&gt;
  
  
  Getting Started
&lt;/h2&gt;

&lt;p&gt;To try the library in your Java 17+ project, simply add the dependency to your Maven configuration:&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;org.ujorm&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;ujo-core&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;3.0.0-RC1&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;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;org.ujorm&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;ujorm-orm&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;3.0.0-RC1&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;To automatically generate metamodel classes, add the optional APT configuration to the &lt;code&gt;build&lt;/code&gt; element:&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;plugins&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;plugin&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.apache.maven.plugins&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;maven-compiler-plugin&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;3.14.1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;configuration&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;annotationProcessorPaths&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;path&amp;gt;&lt;/span&gt;
                    &lt;span class="nt"&gt;&amp;lt;groupId&amp;gt;&lt;/span&gt;org.ujorm&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;ujorm-meta-processor&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;3.0.0-RC1&lt;span class="nt"&gt;&amp;lt;/version&amp;gt;&lt;/span&gt;
                &lt;span class="nt"&gt;&amp;lt;/path&amp;gt;&lt;/span&gt;
            &lt;span class="nt"&gt;&amp;lt;/annotationProcessorPaths&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;/configuration&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/plugin&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/plugins&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Ujorm module from the Benchmark project can be used as a template for a sample implementation. The library's codebase is currently covered by JUnit tests that utilize an in-memory H2 database (in addition to mocked objects). Before releasing the final version, I plan to add integration tests for PostgreSQL, MySQL, Oracle, and MS SQL Server databases.&lt;/p&gt;

&lt;h2&gt;
  
  
  When to Choose the Ujorm3 Library?
&lt;/h2&gt;

&lt;p&gt;If you are working for a corporate client expecting standards or portability of abstractions between databases, use JPA/Hibernate instead. If you have already found an ORM framework that meets your expectations and needs, stick with it. However, if you are looking for a fast and transparent alternative without hidden mechanisms for your new project, the Ujorm3 library is definitely worth a try.&lt;/p&gt;

&lt;h2&gt;
  
  
  Useful Links:
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm/tree/ujorm3?tab=readme-ov-file#-ujorm3-library" rel="noopener noreferrer"&gt;Project Homepage&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm-petstore?tab=readme-ov-file#ujorm-petstore" rel="noopener noreferrer"&gt;PetStore Demo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/orm-benchmarks/tree/development?tab=readme-ov-file#orm-benchmark" rel="noopener noreferrer"&gt;Benchmark Tests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/pponec/ujorm/blob/ujorm3/project-m2/ujo-orm/src/test/java/org/ujorm/orm/tutorial/TutorialTest.java" rel="noopener noreferrer"&gt;More Examples as a JUnit Test&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

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