Exploring Java Records In A Jakarta EE Context

Jakarta EE

Java Records, one of the major highlights of the Java 16 release, provides a concise and immutable way to define classes for modelling data. This conciseness lends itself useful in a typical Jakarta EE application that can have a number of layers that need to share data. For example the data layer might want to return a subset of a given data set to a calling client through a data projection object. The REST layer might want to have separate entities for server and client side among others. This blog post explores the adoption of Java Records in a Jakarta EE application as a data transfer and projection object.

Java Records is an excellent construct for achieving these kinds of data flow in an application because of their consciousness. Among the benefits of adopting Java Records in a Jakarta EE application are

  1. Conciseness: Java Records allow you to define classes with less ceremonial and boiler plate code than typical Java classes. Because they come with built-in methods for equality, hash code, toString and accessor methods, they are easier to read and write, and reduce the chances of introducing subtle, and sometimes not so subtle bugs.
  2. Immutability: By default Java Records are immutable. This means their values cannot be changed once they are created. This makes them ideal for use as data transfer objects, as they can be safely passed around between different layers of your application without the risk of their values being changed.
  3. Interoperability: Java Records can be used with existing Java constructs such as streams, lambdas and JSON processing. This makes them a powerful tool for building modern Java applications.

Using Java Records in a Jakarta EE Application

To show the use of Java Records as data transfer and projection objects, let us consider the following non-trivial Order Jakarta Persistence entity.


@Entity
@Table(name = "OrderTable")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;

private LocalDate orderDate;
private String orderId;
@Enumerated(EnumType.STRING)
private OrderStatus orderStatus;
@Embedded
private Address shippingAddress;
@Embedded
private Address billingAddress;
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(mappedBy = "order", cascade = CascadeType.ALL)
private Payment payment;
private BigDecimal subtotal;
private BigDecimal tax;
private BigDecimal shippingFee;
private BigDecimal discount;
private BigDecimal total;
private String notes;
}

For a given case, we want to return just a tiny subset of this entity. For a given date, we want to return all orders that were created before or after the given date, depending on which endpoint is called. Using Java Records, let us create an OrderSummary record with just the fields we need.

public record OrderSummary(String orderId, OrderStatus orderStatus, BigDecimal total, LocalDate orderDate) {

}

The OrderSummary has just the orderId, OrderStatus, order total and order date. This is the order summary we wish to return, based on which the client can call for details of each order using the returned orderId. With the record in place, let’s create a query using the Jakarta Criteria API to return a list of OrderSummary for all orders based on a given OrderStatus.

  public List<OrderSummary> getOrderSummariesByStatus(final OrderStatus orderStatus) {


 CriteriaBuilder cb = em.getCriteriaBuilder();
 CriteriaQuery<OrderSummary> cq
 = cb.createQuery(OrderSummary.class);


 Root<Order> rootEntity = cq.from(Order.class);


 cq.select(cb.construct(
 OrderSummary.class,
 rootEntity.get("orderId"),
 rootEntity.get("orderStatus"),
 rootEntity.get("total"),
 rootEntity.get("orderDate")))
                .where(cb.equal(rootEntity.get("orderStatus"), orderStatus));


return em.createQuery(cq).getResultList();
 }

The above method shows the use of the typesafe Criteria API to construct a projection of order summaries based on the returned set of queried data. With the above in place, the following sample REST resource uses it to directly return the summaries to the calling client.

@Path("/order")
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class OrderResource {
    @Inject
    private PersistenceService persistenceService;
    @GET
    @Path("{orderStatus}")
  public List<OrderSummary> getOrdersByStatus(@PathParam("orderStatus") @NotNull OrderStatus orderStatus) {
        return persistenceService.getOrderSummariesByStatus(orderStatus);
    }
}

The REST resource method calls the getOrderSummariesByStatus method on the PersistenceService. The implementation of this method was shown earlier, using the Criteria API to return projected order summaries. As you can see, the REST resource directly uses the OrderSummary record as the return type, without the need for an extra DTO or any other abstraction.

Conclusion

Java Records are a powerful feature introduced in Java 16 that can simplify the creation of data transfer and projection objects, and help reduce much of the boilerplate code needed for mapping data in a Jakarta EE application. By using this construct, you can create more concise and immutable classes that are easy to read and write in your Jakarta EE application. And as a developer, code clarity, readability and reasonable conciseness is a gift you give to your future self.

Read more about Jakarta EE:

Comments (0)

Post a comment

Your email address will not be published. Required fields are marked *

Payara needs the contact information you provide to us to contact you about our products and services. You may unsubscribe from these communications at any time. For information on how to unsubscribe, as well as our privacy practices and commitment to protecting your privacy, please review our Legal & Privacy Policy.

This site uses Akismet to reduce spam. Learn how your comment data is processed.

Related Posts

Docker logo 4 minutes
Community

Level Up Your Jakarta EE Apps with Payara 7 and New Docker Images

We’re excited to share major updates around the Docker image story for the Payara Platform Community, aligned with our […]

Timeline showing Payara Platform Enterprise 4, 5, and 6 support phases (Full, Extended, Lifetime) from 2023–2033, along with JDK 8, 11, 17, and 21 support periods and end-of-life markers. 4 minutes
Thought Leadership

Understanding the Payara Platform Enterprise Software Lifecycle: How We Support Long-Term Stability 

Keeping an application server running smoothly isn’t so much about new features, but more about predictability and consistency. Software […]

Patrik Dudits presenting at Devoxx Belgium 2025 5 minutes
Cloud & Microservices

Devoxx BE 2025: It Only Starts with a Container & How Abstraction Becomes Reality 

At Devoxx Belgium 2025, I was able to talk about what happens after you build your container. In theory, […]