eBook – Guide Spring Cloud – NPI EA (cat=Spring Cloud)
announcement - icon

Let's get started with a Microservice Architecture with Spring Cloud:

>> Join Pro and download the eBook

eBook – Mockito – NPI EA (tag = Mockito)
announcement - icon

Mocking is an essential part of unit testing, and the Mockito library makes it easy to write clean and intuitive unit tests for your Java code.

Get started with mocking and improve your application tests using our Mockito guide:

Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Reactive – NPI EA (cat=Reactive)
announcement - icon

Spring 5 added support for reactive programming with the Spring WebFlux module, which has been improved upon ever since. Get started with the Reactor project basics and reactive programming in Spring Boot:

>> Join Pro and download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Jackson – NPI EA (cat=Jackson)
announcement - icon

Do JSON right with Jackson

Download the E-book

eBook – HTTP Client – NPI EA (cat=Http Client-Side)
announcement - icon

Get the most out of the Apache HTTP Client

Download the E-book

eBook – Maven – NPI EA (cat = Maven)
announcement - icon

Get Started with Apache Maven:

Download the E-book

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

eBook – RwS – NPI EA (cat=Spring MVC)
announcement - icon

Building a REST API with Spring?

Download the E-book

Course – LS – NPI EA (cat=Jackson)
announcement - icon

Get started with Spring and Spring Boot, through the Learn Spring course:

>> LEARN SPRING
Course – RWSB – NPI EA (cat=REST)
announcement - icon

Explore Spring Boot 3 and Spring 6 in-depth through building a full REST API with the framework:

>> The New “REST With Spring Boot”

Course – LSS – NPI EA (cat=Spring Security)
announcement - icon

Yes, Spring Security can be complex, from the more advanced functionality within the Core to the deep OAuth support in the framework.

I built the security material as two full courses - Core and OAuth, to get practical with these more complex scenarios. We explore when and how to use each feature and code through it on the backing project.

You can explore the course here:

>> Learn Spring Security

Course – LSD – NPI EA (tag=Spring Data JPA)
announcement - icon

Spring Data JPA is a great way to handle the complexity of JPA with the powerful simplicity of Spring Boot.

Get started with Spring Data JPA through the guided reference course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (cat=Spring Boot)
announcement - icon

Refactor Java code safely — and automatically — with OpenRewrite.

Refactoring big codebases by hand is slow, risky, and easy to put off. That’s where OpenRewrite comes in. The open-source framework for large-scale, automated code transformations helps teams modernize safely and consistently.

Each month, the creators and maintainers of OpenRewrite at Moderne run live, hands-on training sessions — one for newcomers and one for experienced users. You’ll see how recipes work, how to apply them across projects, and how to modernize code with confidence.

Join the next session, bring your questions, and learn how to automate the kind of work that usually eats your sprint time.

Course – LJB – NPI EA (cat = Core Java)
announcement - icon

Code your way through and build up a solid, practical foundation of Java:

>> Learn Java Basics

Partner – LambdaTest – NPI EA (cat= Testing)
announcement - icon

Distributed systems often come with complex challenges such as service-to-service communication, state management, asynchronous messaging, security, and more.

Dapr (Distributed Application Runtime) provides a set of APIs and building blocks to address these challenges, abstracting away infrastructure so we can focus on business logic.

In this tutorial, we'll focus on Dapr's pub/sub API for message brokering. Using its Spring Boot integration, we'll simplify the creation of a loosely coupled, portable, and easily testable pub/sub messaging system:

>> Flexible Pub/Sub Messaging With Spring Boot and Dapr

1. Overview

When using Spring Data JPA to implement the persistence layer, the repository typically returns one or more instances of the root class. However, more often than not, we don’t need all the properties of the returned objects.

In such cases, we might want to retrieve data as objects of customized types. These types reflect partial views of the root class, containing only the properties we care about. This is where projections come in handy.

2. Initial Setup

The first step is to set up the project and populate the database.

2.1. Maven Dependencies

For dependencies, please check out section 2 of this tutorial.

2.2. Entity Classes

Let’s define two entity classes:

@Entity
public class Address {
 
    @Id
    private Long id;
 
    @OneToOne
    private Person person;
 
    private String state;
 
    private String city;
 
    private String street;
 
    private String zipCode;

    // getters and setters
}

And:

@Entity
public class Person {
 
    @Id
    private Long id;
 
    private String firstName;
 
    private String lastName;
 
    @OneToOne(mappedBy = "person")
    private Address address;

    // getters and setters
}

The relationship between Person and Address entities is bidirectional one-to-one; Address is the owning side, and Person is the inverse side.

Notice in this tutorial, we use an embedded database, H2.

When an embedded database is configured, Spring Boot automatically generates underlying tables for the entities we defined.

2.3. SQL Scripts

We’ll use the projection-insert-data.sql script to populate both the backing tables:

INSERT INTO person(id,first_name,last_name) VALUES (1,'John','Doe');
INSERT INTO address(id,person_id,state,city,street,zip_code) 
  VALUES (1,1,'CA', 'Los Angeles', 'Standford Ave', '90001');

To clean up the database after each test run, we can use another script, projection-clean-up-data.sql:

DELETE FROM address;
DELETE FROM person;

2.4. Test Class

Then, to confirm the projections produce the correct data, we need a test class:

@DataJpaTest
@RunWith(SpringRunner.class)
@Sql(scripts = "/projection-insert-data.sql")
@Sql(scripts = "/projection-clean-up-data.sql", executionPhase = AFTER_TEST_METHOD)
public class JpaProjectionIntegrationTest {
    // injected fields and test methods
}

With the given annotations, Spring Boot creates the database, injects dependencies, and populates and cleans up tables before and after each test method’s execution.

3. Interface-Based Projections

When projecting an entity, it’s natural to rely on an interface, as we won’t need to provide an implementation.

3.1. Closed Projections

Looking back at the Address class, we can see it has many properties, yet not all of them are helpful. For example, sometimes a zip code is enough to indicate an address.

Let’s declare a projection interface for the Address class:

public interface AddressView {
    String getZipCode();
}

Then we’ll use it in a repository interface:

public interface AddressRepository extends Repository<Address, Long> {
    List<AddressView> getAddressByState(String state);
}

It’s easy to see that defining a repository method with a projection interface is pretty much the same as with an entity class.

The only difference is that the projection interface, rather than the entity class, is used as the element type in the returned collection.

Let’s do a quick test of the Address projection:

@Autowired
private AddressRepository addressRepository;

@Test
public void whenUsingClosedProjections_thenViewWithRequiredPropertiesIsReturned() {
    AddressView addressView = addressRepository.getAddressByState("CA").get(0);
    assertThat(addressView.getZipCode()).isEqualTo("90001");
    // ...
}

Behind the scenes, Spring creates a proxy instance of the projection interface for each entity object, and all calls to the proxy are forwarded to that object.

We can use projections recursively. For instance, here’s a projection interface for the Person class:

public interface PersonView {
    String getFirstName();

    String getLastName();
}

Now we’ll add a method with the return type PersonView, a nested projection, in the Address projection:

public interface AddressView {
    // ...
    PersonView getPerson();
}

Notice the method that returns the nested projection must have the same name as the method in the root class that returns the related entity.

We’ll verify nested projections by adding a few statements to the test method we’ve just written:

// ...
PersonView personView = addressView.getPerson();
assertThat(personView.getFirstName()).isEqualTo("John");
assertThat(personView.getLastName()).isEqualTo("Doe");

Note that recursive projections only work if we traverse from the owning side to the inverse side. If we do it the other way around, the nested projection would be set to null.

3.2. Nested JPA Projection Using Interface and Custom Query

In the previous section, we explored nested JPA projection using an Interface with a derived query. We can also achieve this using a custom query:

@Query("SELECT c.zipCode as zipCode, c.person as person FROM Address c WHERE c.state = :state")
List<AddressView> getViewAddressByState(@Param("state") String state);

In the code above, we write a custom query to fetch an Address by zip code and include the associated Person. Spring maps the query result to the AddressView and PersonView interfaces based on matching field names.

Let’s write a unit test for the nested JPA projection:

@Test
void whenUsingCustomQueryForNestedProjection_thenViewWithRequiredPropertiesIsReturned() {
    AddressView addressView = addressRepository.getViewAddressByState("CA").get(0);
    assertThat(addressView.getZipCode()).isEqualTo("90001");

    PersonView personView = addressView.getPerson();
    assertThat(personView.getFirstName()).isEqualTo("John");
    assertThat(personView.getLastName()).isEqualTo("Doe");
}

In the test above, we invoke the custom repository method on AddressRepository to fetch an address and the corresponding person. Then we assert that the return result is equal to the expected result.

Additionally, custom queries allow us to specify the type of join operation, potentially improving database lookup and preventing N+1 problems.

3.3. Open Projections

Up to this point, we’ve gone through closed projections, which indicate projection interfaces whose methods exactly match the names of entity properties.

There’s also another sort of interface-based projection, open projections. These projections enable us to define interface methods with unmatched names and with return values computed at runtime.

Let’s go back to the Person projection interface and add a new method:

public interface PersonView {
    // ...

    @Value("#{target.firstName + ' ' + target.lastName}")
    String getFullName();
}

The argument to the @Value annotation is a SpEL expression, in which the target designator indicates the backing entity object.

Now we’ll define another repository interface:

public interface PersonRepository extends Repository<Person, Long> {
    PersonView findByLastName(String lastName);
}

To make it simple, we’ll only return a single projection object instead of a collection.

This test confirms the open projections work as expected:

@Autowired
private PersonRepository personRepository;

@Test 
public void whenUsingOpenProjections_thenViewWithRequiredPropertiesIsReturned() {
    PersonView personView = personRepository.findByLastName("Doe");
 
    assertThat(personView.getFullName()).isEqualTo("John Doe");
}

Open projections do have a drawback though; Spring Data can’t optimize query execution, as it doesn’t know in advance which properties will be used. Thus, we should only use open projections when closed projections aren’t capable of handling our requirements.

4. Class-Based Projections

Instead of using proxies Spring Data creates from projection interfaces, we can define our own projection classes.

For example, here’s a projection class for the Person entity:

public class PersonDto {
    private String firstName;
    private String lastName;

    public PersonDto(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    // getters, equals and hashCode
}

For a projection class to work in tandem with a repository interface, the parameter names of its constructor must match the properties of the root entity class.

We must also define equals and hashCode implementations; they allow Spring Data to process projection objects in a collection.

The requirements above can be addressed by java records, thus making our code more precise and expressive:

public record PersonDto(String firstName, String lastName) {

}

Now let’s add a method to the Person repository:

public interface PersonRepository extends Repository<Person, Long> {
    // ...

    PersonDto findByFirstName(String firstName);
}

This test verifies our class-based projection:

@Test
public void whenUsingClassBasedProjections_thenDtoWithRequiredPropertiesIsReturned() {
    PersonDto personDto = personRepository.findByFirstName("John");
 
    assertThat(personDto.getFirstName()).isEqualTo("John");
    assertThat(personDto.getLastName()).isEqualTo("Doe");
}

Notice with the class-based approach, we can’t use nested projections.

4.1. Nested Projection Using Classes

Furthermore, we can use nested DTO classes to map associated entities together:

class AddressDto {
    private final String zipCode;
    private final PersonDto person;

    public AddressDto(String zipCode, PersonDto person) {
        this.zipCode = zipCode;
        this.person = person;
    }
}

Here, we declare the PersonDto as a field in the AddressDto and pass it to the constructor. Notably, the constructor parameter names must match the DTO class fields.

Then, we can use a derived query or a custom query for the database lookup:

@Query("SELECT new com.baeldung.jpa.projection.view.AddressDto(a.zipCode," +
  "new com.baeldung.jpa.projection.view.PersonDto(p.firstName, p.lastName)) " +
  "FROM Address a JOIN a.person p WHERE a.state = :state")
List<AddressDto> findAddressByState(@Param("state") String state);

In our query, we use the new keyword with a fully qualified class name of the DTOs to invoke their constructors. This is essential for creating DTO instances during a database lookup.

In the case of a derived query, we only need to ensure that the query method name ends with a valid entity field:

List<AddressDto> findAddressByState(String state);

However, custom query provides flexibility, allowing complex join operations and more refined data retrieval.

4.2. With JPA Native Queries

JPA native queries allow us to write SQL queries directly instead of using JPQL. We can also map the result from the native query to the DTO class.

@NamedNativeQuery is used to define the SQL query with the resultSetMapping param which is set to the mapped DTO annotation. @SqlResultSetMapping annotation is used to define the mapping of the query results to the DTO class:

@Entity
@NamedNativeQuery(
  name = "person_native_query_dto",
  query = "SELECT p.first_name, p.last_name From Person p where p.first_name LIKE :firstNameLike",
  resultSetMapping = "person_query_dto"
)
@SqlResultSetMapping(
  name = "person_query_dto", 
  classes = @ConstructorResult(
    targetClass = PersonDto.class, 
    columns = { 
        @ColumnResult(name = "first_name", type = String.class), 
        @ColumnResult(name = "last_name", type = String.class), 
    }
  )
)
public class Person {
   // properties, getters and setters
}

In the above logic, we map the result of the native query to PersonDto class, where we define columns that are mapped to the property on the PersonDto class.

Once we’ve annotated our entity, we can define the repository method:

public interface PersonRepository extends Repository<Person, Long> {
    @Query(name = "person_native_query_dto", nativeQuery = true)
    List<PersonDto> findByFirstNameLike(@Param("firstNameLike") String firstNameLike);
}

In the findByFirstNameLike() method, we annotate with @Query, which receives the name of the native query we defined on the entity.

We can write a simple unit test to verify the result:

@Test
void whenUsingClassBasedProjectionsAndJPANativeQuery_thenDtoWithRequiredPropertiesIsReturned() {
    List<PersonDto> personDtos = personRepository.findByFirstNameLike("Jo%");
    assertThat(personDtos.size()).isEqualTo(2);
    assertThat(personDtos).isEqualTo(Arrays.asList(new PersonDto("John", "Doe"), new PersonDto("Job", "Doe")));
}

5. Dynamic Projections

An entity class may have many projections. In some cases, we may use a certain type, but in other cases, we may need another type. Sometimes, we also need to use the entity class itself.

Defining separate repository interfaces or methods just to support multiple return types is cumbersome. To deal with this problem, Spring Data provides a better solution, dynamic projections.

We can apply dynamic projections just by declaring a repository method with a Class parameter:

public interface PersonRepository extends Repository<Person, Long> {
    // ...

    <T> T findByLastName(String lastName, Class<T> type);
}

By passing a projection type or the entity class to such a method, we can retrieve an object of the desired type:

@Test
public void whenUsingDynamicProjections_thenObjectWithRequiredPropertiesIsReturned() {
    Person person = personRepository.findByLastName("Doe", Person.class);
    PersonView personView = personRepository.findByLastName("Doe", PersonView.class);
    PersonDto personDto = personRepository.findByLastName("Doe", PersonDto.class);

    assertThat(person.getFirstName()).isEqualTo("John");
    assertThat(personView.getFirstName()).isEqualTo("John");
    assertThat(personDto.getFirstName()).isEqualTo("John");
}

6. Conclusion

In this article, we discussed various types of Spring Data JPA projections.

The code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
Baeldung Pro – NPI EA (cat = Baeldung)
announcement - icon

Baeldung Pro comes with both absolutely No-Ads as well as finally with Dark Mode, for a clean learning experience:

>> Explore a clean Baeldung

Once the early-adopter seats are all used, the price will go up and stay at $33/year.

eBook – HTTP Client – NPI EA (cat=HTTP Client-Side)
announcement - icon

The Apache HTTP Client is a very robust library, suitable for both simple and advanced use cases when testing HTTP endpoints. Check out our guide covering basic request and response handling, as well as security, cookies, timeouts, and more:

>> Download the eBook

eBook – Java Concurrency – NPI EA (cat=Java Concurrency)
announcement - icon

Handling concurrency in an application can be a tricky process with many potential pitfalls. A solid grasp of the fundamentals will go a long way to help minimize these issues.

Get started with understanding multi-threaded applications with our Java Concurrency guide:

>> Download the eBook

eBook – Java Streams – NPI EA (cat=Java Streams)
announcement - icon

Since its introduction in Java 8, the Stream API has become a staple of Java development. The basic operations like iterating, filtering, mapping sequences of elements are deceptively simple to use.

But these can also be overused and fall into some common pitfalls.

To get a better understanding on how Streams work and how to combine them with other language features, check out our guide to Java Streams:

>> Join Pro and download the eBook

eBook – Persistence – NPI EA (cat=Persistence)
announcement - icon

Working on getting your persistence layer right with Spring?

Explore the eBook

Course – LS – NPI EA (cat=REST)

announcement - icon

Get started with Spring Boot and with core Spring, through the Learn Spring course:

>> CHECK OUT THE COURSE

Partner – Moderne – NPI EA (tag=Refactoring)
announcement - icon

Modern Java teams move fast — but codebases don’t always keep up. Frameworks change, dependencies drift, and tech debt builds until it starts to drag on delivery. OpenRewrite was built to fix that: an open-source refactoring engine that automates repetitive code changes while keeping developer intent intact.

The monthly training series, led by the creators and maintainers of OpenRewrite at Moderne, walks through real-world migrations and modernization patterns. Whether you’re new to recipes or ready to write your own, you’ll learn practical ways to refactor safely and at scale.

If you’ve ever wished refactoring felt as natural — and as fast — as writing code, this is a good place to start.

eBook Jackson – NPI EA – 3 (cat = Jackson)