DEV Community

Pavel Ponec
Pavel Ponec

Posted on

PetStore: Pure Java for UI and Safe SQL Mapping

I would like to introduce the PetStore 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.

Screenshot


🛠️ Two Pillars of Ujorm3

1. UI Creation without Templates (ujo-web)

We have replaced traditional engines like Thymeleaf or JSP with pure Java code.

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

2. Modern Database Handling (ujo-orm)

Forget about complex XML mapping or runtime errors in SQL queries.

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

📁 Code Sample (PetServlet)

The project is designed with an emphasis on straightforwardness.
The following example from a stateless servlet demonstrates how elegantly logic, parameters, and HTML generation can be connected:

@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) {
    var ctx = HttpContext.ofServlet(req, resp);
    var contextPath = req.getContextPath();
    var action = ctx.parameter(ACTION, Action::paramValueOf);
    var petId = ctx.parameter(PET_ID, Long::parseLong);

    var pets = services.getPets();
    var categories = services.getCategories();
    var petToEdit = Action.EDIT.equals(action)
            ? services.getPetById(petId).orElse(null) : null;

    try (var html = HtmlElement.of(ctx, BOOTSTRAP_CSS)) {
        try (var body = html.addBody(Css.container, Css.mt5)) {
            renderHeader(body, contextPath);
            renderTable(body, pets);
            renderForm(body, petToEdit, categories);
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Here is what a native SQL query looks like in pure Java:

static final EntityManager<Pet, Long> PET_EM = 
             EntityManager.of(Pet.class);

public List<Pet> findAll() {
    var sql = """
        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 >= :id
        ORDER BY p.id
        """;

    return SqlQuery.run(connection.get(), query -> query
        .sql(sql)
        .label("p.id", MetaPet.id)
        .label("p.name", MetaPet.name)
        .label("p.status", MetaPet.status)
        .label("c.id", MetaPet.category, MetaCategory.id)
        .label("c.name", MetaPet.category, MetaCategory.name)
        .bind("id", 1L)
        .streamMap(PET_EM.mapper())
        .toList());
}
Enter fullscreen mode Exit fullscreen mode

💡 Why Choose This Approach?

This architecture represents an interesting alternative for developers who are tired of heavy JPA frameworks or bloated frontend technologies.

Where Ujorm PetStore shines most:

  • B2B and administrative applications: Where development speed and long-term maintainability are important.
  • Microservices: Thanks to minimal overhead and fast startup.
  • Projects with HTMX: It perfectly complements modern trends of returning to server-side rendering.

The "Java-First" philosophy drastically reduces context switching between Java, SQL, XML, and various templating languages.
Everything you need is under the protection of the compiler.


🚀 Try It Locally

The application utilizes the best of the current ecosystem:

  • Java 25
  • Spring Boot 3.5.0
  • H2 Database (In-memory)

All you need is JDK 25 and Maven installed, then just run:

mvn spring-boot:run
Enter fullscreen mode Exit fullscreen mode

The application will start at http://localhost:8080.

Resources and Links:

  • PetServlet.java – A stateless Servlet acting as both Controller and View. It handles HTTP communication and builds the HTML.
  • Dao.java – Data access layer integrating Spring JDBC with Ujorm EntityManager.
  • Ujorm 3 Library on GitHub – Official library repository.
  • ORM Benchmarks – How this approach compares to the competition.

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!

Top comments (0)