Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Jakarta Data

In the previous lessons we wrote PostRepository by hand: every method is a JPQL query and an EntityManager call. Jakarta Data 1.0 is a new Jakarta EE 11 specification that removes this repetitive code. You declare only a repository interface (the object the application uses to read and save entities), and the server creates the implementation itself at startup. Spring Data has similar repositories; now they are a standard.

The server feature

In Open Liberty the specification is enabled by the data-1.0 feature. It is already part of webProfile-11.0 from our server.xml, so there is nothing to add. You can see it in the log at startup:

[AUDIT   ] CWWKF0012I: The server installed the following features: [appAuthentication-3.1, appSecurity-6.0, cdi-4.1, concurrent-3.1, data-1.0, ...]

If your project lists individual features instead of a profile, add the line <feature>data-1.0</feature> to featureManager. The API is already there too: the jakarta.data package is part of the jakarta.jakartaee-api dependency, version 11.

The repository interface

Create the file src/main/java/com/example/blog/Posts.java:

// src/main/java/com/example/blog/Posts.java
package com.example.blog;
 
import jakarta.data.repository.CrudRepository;
import jakarta.data.repository.Repository;
 
@Repository(dataStore = "java:app/env/blogUnit")
public interface Posts extends CrudRepository<Post, Long> {
}

@Repository tells the server: create an implementation and make it a CDI bean. In CrudRepository<Post, Long> the first type is the entity from the JPA lesson, the second is the type of its @Id. The interface has no body, yet it already has methods: the parent interfaces provide them.

The dataStore parameter says how the repository reaches the database. We use our blog persistence unit from persistence.xml — then the repository and PostRepository see the same table. To do that, the persistence unit gets a JNDI name. Add an annotation to RestApplication:

// src/main/java/com/example/blog/RestApplication.java
package com.example.blog;
 
import jakarta.persistence.PersistenceUnit;
import jakarta.ws.rs.ApplicationPath;
import jakarta.ws.rs.core.Application;
 
@ApplicationPath("/api")
@PersistenceUnit(name = "java:app/env/blogUnit", unitName = "blog")
public class RestApplication extends Application {
}

@PersistenceUnit on a class declares the reference java:app/env/blogUnit to the blog unit. The java:app namespace is shared by the whole application, so declaring the reference once is enough.

Built-in methods

MethodFromWhat it does
findById(id)BasicRepositoryFinds by key, returns an Optional
findAll()BasicRepositoryAll rows as a Stream
save(post)BasicRepositoryInserts a new one or updates an existing one
deleteById(id), delete(post)BasicRepositoryDeletes
insert(post)CrudRepositoryInsert only, an error for an existing one
update(post)CrudRepositoryUpdate only

CrudRepository extends BasicRepository. If you do not need separate insert and update, extend BasicRepository.

Injecting it into a resource

To keep the existing API intact, we add a second version next to it — /api/v2/posts. Create src/main/java/com/example/blog/PostV2Resource.java:

// src/main/java/com/example/blog/PostV2Resource.java
package com.example.blog;
 
import jakarta.inject.Inject;
import jakarta.validation.Valid;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import java.time.LocalDate;
import java.util.List;
 
@Path("/v2/posts")
@Produces(MediaType.APPLICATION_JSON)
public class PostV2Resource {
 
    @Inject
    Posts posts;
 
    @GET
    public List<Post> list() {
        return posts.findAll().toList();
    }
 
    @GET
    @Path("/{id}")
    public Post show(@PathParam("id") long id) {
        return posts.findById(id).orElseThrow(() -> new PostNotFoundException(id));
    }
 
    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Post create(@Valid NewPost data) {
        return posts.insert(new Post(data.title(), data.body(), LocalDate.now()));
    }
}

The repository is injected with @Inject, like any bean. Writing methods run in a transaction on their own, and inside a @Transactional method they join the one already open.

Trying it out

curl http://localhost:8080/blog/api/v2/posts
curl -i -H "Content-Type: application/json" -d '{"title":"Jakarta Data","body":"Repositories without boilerplate."}' http://localhost:8080/blog/api/v2/posts
curl http://localhost:8080/blog/api/posts/3
[{"body":"The first post of the blog.","createdAt":"2026-10-01","id":1,"title":"Hello, Jakarta EE"},{"body":"How a request reaches our code.","createdAt":"2026-10-02","id":2,"title":"Servlets and filters"}]
HTTP/1.1 200 OK
X-Powered-By: Blog on Jakarta EE
Content-Type: application/json
Content-Length: 99

{"body":"Repositories without boilerplate.","createdAt":"2026-10-04","id":3,"title":"Jakarta Data"}
{"body":"Repositories without boilerplate.","createdAt":"2026-10-04","id":3,"title":"Jakarta Data"}

A post created through the repository is also visible in the old /api/posts API: both work with the same table. A request to /api/v2/posts/99 returns 404 with {"error":"Post 99 not found","status":404} — the PostNotFoundException mapper from the REST chapter handles it.

Common mistake

Writing @Repository without dataStore. Then the repository looks for the default data source java:comp/DefaultDataSource, which our server.xml does not have, and the very first request fails with a 500 error:

CWWKD1077E: The blog#blog.war application artifact has one or more repository interfaces (com.example.blog.Posts) that use or default to the java:comp/DefaultDataSource data store. This data store must be configured in the server configuration by defining a dataSource element that has an identifier of DefaultDataSource.

Set dataStore to the persistence unit reference as shown above, or define a data source with id="DefaultDataSource" in server.xml.

Thanks for reading!

Donate to the site — we work hard for you.