Ujorm3: A New Lightweight ORM for JavaBeans and Records

Published: (March 23, 2026 at 12:27 PM EDT)
4 min read
Source: Dev.to

Source: Dev.to

Why a New ORM?

The original JDBC API was never meant to be a full‑featured ORM, and over the years a plethora of libraries and frameworks have sprung up, each with its own trade‑offs in complexity, performance, and usability.

Ujorm 3 aims to be a reliable, safe, efficient, and easy‑to‑understand tool for relational‑database access:

  • Thin layer over JDBC – no hidden magic.
  • Stateless objects and native SQL – you stay in full control of what is sent to the database.
  • No SQL‑dialect handling – write plain SQL; the library only adds type‑safe mapping helpers.
  • No query‑result caching – only a small amount of metadata is retained for speed.

The first release candidate (RC1) is available on Maven Central under the Apache License 2.0.

Core Concepts

ConceptDescription
SqlQuery (formerly SqlParamBuilder)Facade over PreparedStatement. Supports named parameters, eliminates checked exceptions, and returns a Stream for SELECTs.
ResultSetMapperMaps a ResultSet to domain objects. The mapping model is built once (on first use) and reused, dramatically reducing per‑query overhead.
AnnotationsUses standard jakarta.persistence annotations (@Table, @Column, @Id). The library can also infer mappings when annotations are absent.
Supported POJOsBoth mutable JavaBeans and immutable records are fully supported.
RelationsOnly M:1 (many‑to‑one) relations are supported. 1:M collections are deliberately omitted to avoid hidden queries and N+1 problems.
Column LabelsColumns can be referenced by a dot‑chained label (e.g. "city.name"). For safety, a generated type‑safe metamodel (Meta* classes) is preferred.
EntityManagerProvides CRUD operations (including batch commands) via a Crud object. Supports partial updates by enumerating columns or by diffing the original object.

Example: Selecting Employees

// Static mapper – thread‑safe and reusable
static final ResultSetMapper EMPLOYEE_MAPPER =
        ResultSetMapper.of(Employee.class);

void select() {
    var sql = """
            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 > :employeeId
            """;

    var employees = SqlQuery.run(connection(), query -> query
            .sql(sql)
            // Map columns – the ${COLUMNS} placeholder is replaced automatically
            .column("e.id",   MetaEmployee.id)
            .column("e.name", MetaEmployee.name)
            .column("c.name", MetaEmployee.city, MetaCity.name)
            .column("c.country_code", MetaEmployee.city, MetaCity.countryCode)
            .column("b.name", MetaEmployee.boss, MetaEmployee.name)
            .bind("employeeId", 0L)
            // Convert the ResultSet stream to a List
            .streamMap(EMPLOYEE_MAPPER.mapper())
            .toList());
}

The domain class does not need prior registration.
If you prefer to keep the SQL text untouched, use label() instead of column(). **column() and label() cannot be mixed in the same query.

Benchmark Highlights

  • Runtime‑generated mapping classes (instead of reflection) → lower memory usage, less GC pressure.
  • Zero external dependencies → the compiled benchmark + Ujorm 3 JAR is:
org.ujorm
ujorm-orm
3.0.0-RC1

Generating the Metamodel (optional)

If you want the type‑safe Meta* classes, enable the annotation processor in the maven-compiler-plugin:

<plugin>
    <groupId>org.apache.maven.plugins</groupId>
    <artifactId>maven-compiler-plugin</artifactId>
    <version>3.11.0</version>
    <configuration>
        <annotationProcessorPaths>
            <path>
                <groupId>org.ujorm</groupId>
                <artifactId>ujorm-processor</artifactId>
                <version>3.0.0-RC1</version>
            </path>
        </annotationProcessorPaths>
    </configuration>
</plugin>

Summary

Ujorm 3 delivers a minimal‑overhead, transparent, and type‑safe way to work with relational databases in Java. By staying close to JDBC while providing a clean, modern API, it lets developers write clear, performant code without the hidden costs of heavyweight ORMs.

Give it a try in your next Java 17+ project and experience the simplicity of “the simplest thing that could possibly work.”

<dependency>
    <groupId>org.ujorm</groupId>
    <artifactId>ujorm-meta-processor</artifactId>
    <version>3.0.0-RC1</version>
</dependency>

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.

Note:
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 Ujorm 3 library is definitely worth a try.

Project Homepage

(link to the project’s homepage can be inserted here)

More Examples as a JUnit Test

(link or description of additional JUnit test examples can be inserted here)

Benchmark Tests

(link or description of benchmark tests can be inserted here)

0 views
Back to Blog

Related posts

Read more »

bridge99

java private String extractValueObject value { if value == null return 'null'; // 1. Unmask Spring's TypedStringValue immediately if value instanceof TypedStrin...

SJF4J: A Structured JSON Facade for Java

Introduction Working with JSON in Java usually means choosing between two approaches: - Data binding POJO – strong typing, but rigid - Tree model JsonNode / Ma...