{"id":3925,"date":"2025-11-20T10:00:00","date_gmt":"2025-11-20T15:00:00","guid":{"rendered":"https:\/\/www.mymiller.name\/wordpress\/?p=3925"},"modified":"2025-11-16T22:30:55","modified_gmt":"2025-11-17T03:30:55","slug":"spring-data-jpa-java-records-the-ultimate-duo-for-clean-fast-query-projections","status":"publish","type":"post","link":"https:\/\/www.mymiller.name\/wordpress\/spring_databases\/spring-data-jpa-java-records-the-ultimate-duo-for-clean-fast-query-projections\/","title":{"rendered":"Spring Data JPA &#038; Java Records: The Ultimate Duo for Clean, Fast Query Projections"},"content":{"rendered":"\n<h2 class=\"wp-block-heading\">Architecting Efficient Data Access with Immutability<\/h2>\n\n\n\n<p>As Spring developers, we spend a significant amount of time optimizing the path between the database and the client. One of the most common performance pitfalls is the <em>over-fetching<\/em> of data\u2014loading entire, complex JPA entities when all we need is a small subset of fields for a list view or a report.<\/p>\n\n\n\n<p>This practice leads to wasted memory, unnecessary I\/O, and slows down application performance. The solution lies in <strong>Query Projections<\/strong>, and with Java Records (available since Java 16), we can implement these projections in the cleanest, most modern way possible.<\/p>\n\n\n\n<p>In this post, we&#8217;ll dive into how to leverage Java Records to create immutable, self-describing Data Transfer Objects (DTOs) directly from your Spring Data JPA queries, avoiding proxies, entity loading, and maximizing efficiency.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">1. The Inefficiency of Over-Fetching<\/h2>\n\n\n\n<p>Imagine you have a detailed <code>Product<\/code> entity:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>public class Product {\n    private Long id;\n    private String name;\n    private String description; \/\/ Potentially very long text\n    private BigDecimal price;\n    private byte&#91;] imageBytes; \/\/ Large blob\n    private int stockQuantity;\n    private List&lt;Review&gt; reviews; \/\/ Lazy loaded collection\n    \/\/ ... many more fields\n}\n<\/code><\/pre>\n\n\n\n<p>If you only need a list of product names and prices for a catalog view, running a standard <code>repository.findAll()<\/code> loads every field, every time, including the potentially massive <code>description<\/code>, <code>imageBytes<\/code>, and collection proxies, just to ignore them later.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">2. Introducing Java Records for Projections<\/h2>\n\n\n\n<p>Before Java Records, creating a DTO projection meant writing a dedicated class with private final fields, a bulky constructor, and getters\u2014all boilerplate.<\/p>\n\n\n\n<p>Java Records simplify this boilerplate into a single line, making them the perfect candidates for projections:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Immutability:<\/strong> Records are inherently immutable, guaranteeing the data retrieved from the query remains consistent.<\/li>\n\n\n\n<li><strong>Conciseness:<\/strong> They eliminate boilerplate code, resulting in cleaner, more readable repositories.<\/li>\n\n\n\n<li><strong>Type Safety:<\/strong> They provide clear, strong typing for the projected data.<\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\">Defining the Record Projection<\/h3>\n\n\n\n<p>Let&#8217;s define a projection for our simple product catalog view.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main\/java\/com\/example\/dto\/ProductSummary.java\n\n\/**\n * An immutable DTO representing a summary view of a Product.\n * The component names (id, name, price) must match the query projection order.\n *\/\npublic record ProductSummary(\n    Long id,\n    String name,\n    BigDecimal price\n) {}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">3. Implementing the Projection in Spring Data JPA<\/h2>\n\n\n\n<p>To tell JPA to construct this record instead of fetching the entire <code>Product<\/code> entity, we use a <strong>JPA Constructor Expression<\/strong> within the <code>@Query<\/code> annotation.<\/p>\n\n\n\n<p>This is the most powerful technique because it allows JPA to select <em>only<\/em> the columns needed and use the database result set to directly instantiate the Java Record via its canonical constructor.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">The Repository Implementation<\/h3>\n\n\n\n<p>The key is the <code>SELECT new<\/code> clause, which must include the <strong>fully qualified name<\/strong> of the Java Record and pass the entity fields in the exact order defined by the record&#8217;s components.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main\/java\/com\/example\/repository\/ProductRepository.java\nimport com.example.dto.ProductSummary;\n\npublic interface ProductRepository extends JpaRepository&lt;Product, Long&gt; {\n\n    \/**\n     * Uses a JPA Constructor Expression to select only the necessary fields\n     * and constructs the immutable ProductSummary Record directly.\n     * This avoids loading the heavy Product entity into the Persistence Context.\n     *\/\n    @Query(\"\"\"\n        SELECT new com.example.dto.ProductSummary(p.id, p.name, p.price)\n        FROM Product p\n        WHERE p.active = true\n        ORDER BY p.name\n    \"\"\")\n    List&lt;ProductSummary&gt; findActiveProductSummaries();\n\n    \/\/ The component types must match exactly (e.g., String name -&gt; String name)\n}\n<\/code><\/pre>\n\n\n\n<h3 class=\"wp-block-heading\">Usage<\/h3>\n\n\n\n<p>Now, your service layer works exclusively with the lightweight, immutable <code>ProductSummary<\/code> objects, ensuring you only retrieve the data you need.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/\/ src\/main\/java\/com\/example\/service\/ProductService.java\n\n@Service\npublic class ProductService {\n    \n    private final ProductRepository productRepository;\n\n    @Autowired\n    public ProductService(ProductRepository productRepository) {\n        this.productRepository = productRepository;\n    }\n\n    public List&lt;ProductSummary&gt; getActiveCatalogView() {\n        \/\/ This call executes the optimized SQL query and returns records directly.\n        return productRepository.findActiveProductSummaries();\n    }\n}\n<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">4. Summary of Benefits<\/h2>\n\n\n\n<p>This approach offers substantial improvements for modern Spring applications:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><tbody><tr><td><strong>Benefit<\/strong><\/td><td><strong>Description<\/strong><\/td><\/tr><tr><td><strong>Performance<\/strong><\/td><td>The generated SQL query is highly efficient (<code>SELECT p.id, p.name, p.price FROM product...<\/code>). Fewer columns are transferred from the database, and the JPA engine skips entity management overhead (no dirty checking, no proxy generation).<\/td><\/tr><tr><td><strong>Immutability<\/strong><\/td><td>Java Records ensure that the projected data, once retrieved, cannot be modified, eliminating potential side effects and making code easier to reason about.<\/td><\/tr><tr><td><strong>Clean Code<\/strong><\/td><td>The Repository method signature clearly documents the returned structure, and the Record definition instantly tells you what data to expect, with no tedious boilerplate getters\/setters.<\/td><\/tr><tr><td><strong>Type Safety<\/strong><\/td><td>By referencing the Record&#8217;s canonical constructor in the <code>@Query<\/code>, the compiler helps ensure the field types and order are correct, unlike interface-based (Dynamic) projections.<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>By embracing Java Records, you are not just adopting a new syntax; you are significantly improving the efficiency, safety, and readability of your Spring Data JPA architecture. Start replacing those old boilerplate DTOs with sleek, modern Records today!<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Architecting Efficient Data Access with Immutability As Spring developers, we spend a significant amount of time optimizing the path between the database and the client. One of the most common performance pitfalls is the over-fetching of data\u2014loading entire, complex JPA entities when all we need is a small subset of fields for a list view [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":3926,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_coblocks_attr":"","_coblocks_dimensions":"","_coblocks_responsive_height":"","_coblocks_accordion_ie_support":"","jetpack_post_was_ever_published":false,"_jetpack_newsletter_access":"","_jetpack_dont_email_post_to_subs":false,"_jetpack_newsletter_tier_id":0,"_jetpack_memberships_contains_paywalled_content":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[435],"tags":[478,479,319],"series":[],"class_list":["post-3925","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-spring_databases","tag-jpa","tag-records","tag-spring"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2025\/11\/record-shop-9180482_1280.avif","jetpack-related-posts":[{"id":3502,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_databases\/spring-data-jpa-for-dummies-persisting-data-like-a-pro\/","url_meta":{"origin":3925,"position":0},"title":"Spring Data JPA for Dummies: Persisting Data Like a Pro","author":"Jeffery Miller","date":"December 24, 2025","format":false,"excerpt":"Ever feel bogged down by writing tons of code just to interact with your database? If you're a Java developer working with relational databases, Spring Data JPA is your new best friend. This blog post will give you a beginner-friendly introduction to Spring Data JPA, showing you how to save\u2026","rel":"","context":"In &quot;Spring Databases&quot;","block_context":{"text":"Spring Databases","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_databases\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/binary-2904980_1280.jpg?fit=1200%2C674&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/binary-2904980_1280.jpg?fit=1200%2C674&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/binary-2904980_1280.jpg?fit=1200%2C674&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/binary-2904980_1280.jpg?fit=1200%2C674&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/binary-2904980_1280.jpg?fit=1200%2C674&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3539,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_databases\/spring-data-cassandra-simplifying-java-development-with-apache-cassandra\/","url_meta":{"origin":3925,"position":1},"title":"Spring Data Cassandra: Simplifying Java Development with Apache Cassandra","author":"Jeffery Miller","date":"September 22, 2025","format":false,"excerpt":"Apache Cassandra is a powerful NoSQL database known for its scalability and high availability. Spring Data Cassandra seamlessly integrates Spring\u2019s familiar programming model with Cassandra, boosting developer productivity. Why Spring Data Cassandra? Simplified Configuration: Spring Boot auto-configuration minimizes manual setup. Object-Relational Mapping (ORM): Easily map Java objects to Cassandra tables.\u2026","rel":"","context":"In &quot;Spring Databases&quot;","block_context":{"text":"Spring Databases","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_databases\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/network-3396348_1280.jpg?fit=1200%2C720&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/network-3396348_1280.jpg?fit=1200%2C720&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/network-3396348_1280.jpg?fit=1200%2C720&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/network-3396348_1280.jpg?fit=1200%2C720&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/network-3396348_1280.jpg?fit=1200%2C720&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3530,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_databases\/spring-jpa-auditing-track-data-changes\/","url_meta":{"origin":3925,"position":2},"title":"Spring JPA Auditing: Track Data Changes","author":"Jeffery Miller","date":"September 22, 2025","format":false,"excerpt":"In the dynamic world of software development, understanding the complete history of your data is crucial. Who made a change? When did it occur? Who viewed the data? Spring JPA Auditing, combined with custom solutions, offers a comprehensive way to answer these questions, acting as a time machine for your\u2026","rel":"","context":"In &quot;Spring Databases&quot;","block_context":{"text":"Spring Databases","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_databases\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/image.png?fit=1200%2C686&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/image.png?fit=1200%2C686&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/image.png?fit=1200%2C686&ssl=1&resize=525%2C300 1.5x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/image.png?fit=1200%2C686&ssl=1&resize=700%2C400 2x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/06\/image.png?fit=1200%2C686&ssl=1&resize=1050%2C600 3x"},"classes":[]},{"id":3820,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_rest\/effortless-api-creation-generating-crud-endpoints-with-spring-data-rest\/","url_meta":{"origin":3925,"position":3},"title":"Effortless API Creation: Generating CRUD Endpoints with Spring Data REST","author":"Jeffery Miller","date":"December 24, 2025","format":false,"excerpt":"Building RESTful APIs for your data can often feel like a repetitive task. Defining endpoints, handling HTTP methods (GET, POST, PUT, DELETE), serializing and deserializing data \u2013 it all adds up. But what if you could significantly reduce this boilerplate and focus on your core domain logic? Enter Spring Data\u2026","rel":"","context":"In &quot;Spring Rest&quot;","block_context":{"text":"Spring Rest","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_rest\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/05\/ai-generated-8041774_640.jpg?fit=640%2C479&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/05\/ai-generated-8041774_640.jpg?fit=640%2C479&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/05\/ai-generated-8041774_640.jpg?fit=640%2C479&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":3471,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_databases\/spring-jpa-auditing\/","url_meta":{"origin":3925,"position":4},"title":"Spring JPA Auditing","author":"Jeffery Miller","date":"September 22, 2025","format":false,"excerpt":"Simplifying Entity Auditing with Spring Data JPA Annotations Keeping track of who created and modified your entities is crucial for various purposes, including audit trails, security, and data lineage. Spring Data JPA offers a convenient and efficient way to achieve this through dedicated annotations: @CreatedBy, @LastModifiedBy, @CreatedDate, and @LastModifiedDate. Leveraging\u2026","rel":"","context":"In &quot;Spring Databases&quot;","block_context":{"text":"Spring Databases","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_databases\/"},"img":{"alt_text":"","src":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/02\/audit-4190944_640.jpg?fit=640%2C377&ssl=1&resize=350%2C200","width":350,"height":200,"srcset":"https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/02\/audit-4190944_640.jpg?fit=640%2C377&ssl=1&resize=350%2C200 1x, https:\/\/i0.wp.com\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/02\/audit-4190944_640.jpg?fit=640%2C377&ssl=1&resize=525%2C300 1.5x"},"classes":[]},{"id":3836,"url":"https:\/\/www.mymiller.name\/wordpress\/spring_rest\/testing-the-waters-writing-effective-unit-tests-for-spring-data-rest-apis\/","url_meta":{"origin":3925,"position":5},"title":"Testing the Waters: Writing Effective Unit Tests for Spring Data REST APIs","author":"Jeffery Miller","date":"December 24, 2025","format":false,"excerpt":"Spring Data REST is a powerful tool for rapidly exposing your JPA entities as RESTful APIs with minimal code. However, the \u201cminimal code\u201d aspect doesn\u2019t absolve you from the crucial responsibility of writing unit tests. While Spring Data REST handles much of the underlying API infrastructure, your business logic, entity\u2026","rel":"","context":"In &quot;Spring Rest&quot;","block_context":{"text":"Spring Rest","link":"https:\/\/www.mymiller.name\/wordpress\/category\/spring_rest\/"},"img":{"alt_text":"","src":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/10\/ai-generated-8136170_1280-png.avif","width":350,"height":200,"srcset":"https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/10\/ai-generated-8136170_1280-png.avif 1x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/10\/ai-generated-8136170_1280-png.avif 1.5x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/10\/ai-generated-8136170_1280-png.avif 2x, https:\/\/www.mymiller.name\/wordpress\/wp-content\/uploads\/2024\/10\/ai-generated-8136170_1280-png.avif 3x"},"classes":[]}],"jetpack_sharing_enabled":true,"jetpack_likes_enabled":true,"_links":{"self":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3925","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/comments?post=3925"}],"version-history":[{"count":1,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3925\/revisions"}],"predecessor-version":[{"id":3927,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/posts\/3925\/revisions\/3927"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media\/3926"}],"wp:attachment":[{"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/media?parent=3925"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/categories?post=3925"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/tags?post=3925"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/www.mymiller.name\/wordpress\/wp-json\/wp\/v2\/series?post=3925"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}