Skip to content

How to Pass Multiple Query Parameters in Spring Data JPA Native Queries

Problem

When I tried to pass multiple list parameters from a filter object to a native query in Spring Data JPA, I couldn’t figure out how to bind them correctly.

My repository method looked like this:

EmployeeRepository.java
@Query(
value = "SELECT e.empname, c.countryName, r.RegionName " +
"FROM Employee e, Country c, Region r " +
"WHERE e.countryId = c.countryId " +
"AND e.employeeId IN (:empIds)", // How to pass empIds from request?
nativeQuery = true)
Collection<Object> findAllActiveUsersNative(CustomFilterRequest request, Pageable pageable);

The CustomFilterRequest contains multiple lists:

CustomFilterRequest.java
public class CustomFilterRequest {
private List<Long> empIds;
private List<Long> countryIds;
private List<Long> regionIds;
// getters and setters
}

I needed to filter results by employee IDs, country IDs, and region IDs, but I didn’t know how to pass these list parameters to the native query.

Environment

  • Java 17
  • Spring Boot 3.x
  • Spring Data JPA
  • PostgreSQL database

What Happened?

I wanted to run a native SQL query that joins three tables (Employee, Country, Region) and filters by multiple lists of IDs. The native query approach is useful for complex joins where JPQL becomes cumbersome.

ER diagram showing Employee, Country, and Region tables with their relationships and foreign keys

But when I tried to pass the CustomFilterRequest object directly, I realized I couldn’t access its properties in the SQL query using the standard :parameterName syntax.

How to Solve It?

I found two solutions that work.

Solution #1: Using SpEL Expressions

Spring Expression Language (SpEL) allows you to access properties from a single object parameter:

EmployeeRepository.java
@Query(
value = "SELECT e.empname, c.countryName, r.RegionName " +
"FROM Employee e, Country c, Region r " +
"WHERE e.countryId = c.countryId " +
"AND c.regionId = r.regionId " +
"AND e.employeeId IN (:#{#request.empIds}) " +
"AND c.countryId IN (:#{#request.countryIds}) " +
"AND r.regionId IN (:#{#request.regionIds})",
nativeQuery = true)
Collection<Object> findAllActiveUsersNative(CustomFilterRequest request, Pageable pageable);

The key syntax is :#{#request.propertyName}:

  • #request refers to the method parameter named request
  • .empIds accesses the empIds property of that object
  • The outer parentheses (...) wrap the entire expression for the IN clause

This approach keeps the method signature clean with just one parameter.

Solution #2: Using @Param Annotation

Alternatively, you can declare each parameter separately with @Param:

EmployeeRepository.java
@Query(
value = "SELECT e.empname, c.countryName, r.RegionName " +
"FROM Employee e, Country c, Region r " +
"WHERE e.countryId = c.countryId " +
"AND c.regionId = r.regionId " +
"AND e.employeeId IN :empIds " +
"AND c.countryId IN :countryIds " +
"AND r.regionId IN :regionIds",
nativeQuery = true)
Collection<Object> findAllActiveUsersNative(
@Param("empIds") List<Long> empIds,
@Param("countryIds") List<Long> countryIds,
@Param("regionIds") List<Long> regionIds,
Pageable pageable);

Then in your service layer, you pass each list individually:

EmployeeService.java
public Collection<Object> findActiveUsers(CustomFilterRequest request, Pageable pageable) {
return repository.findAllActiveUsersNative(
request.getEmpIds(),
request.getCountryIds(),
request.getRegionIds(),
pageable
);
}

The Reason

Both approaches work because Spring Data JPA handles parameter binding differently:

SpEL Approach:

  • SpEL evaluates :#{#request.empIds} at runtime
  • It extracts the list value from the request object
  • The result is safely bound to the SQL query
  • This prevents SQL injection by using parameterized queries

@Param Approach:

  • Each @Param("name") annotation explicitly maps a method parameter to a query placeholder
  • Spring Data JPA binds each parameter by name
  • More explicit but requires unpacking the request object in the service layer

Visual Flow

Here’s how the parameter binding works:

Parameter binding flow
┌─────────────────────────┐
│ CustomFilterRequest │
│ ┌─────────────────┐ │
│ │ empIds │────┼──→ :#{#request.empIds}
│ │ countryIds │────┼──→ :#{#request.countryIds}
│ │ regionIds │────┼──→ :#{#request.regionIds}
│ └─────────────────┘ │
└─────────────────────────┘
┌─────────────────────────┐
│ Repository Method │
│ @Query(nativeQuery=true)
│ parameter binding │
└─────────────────────────┘
┌─────────────────────────┐
│ Native SQL Execution │
│ SELECT ... WHERE ... │
│ AND empId IN (?) │
│ AND countryId IN (?) │
│ AND regionId IN (?) │
└─────────────────────────┘

Common Mistakes

When using SpEL, I made these mistakes:

  1. Missing parentheses around IN clause:
IN clause syntax
// Wrong - causes syntax error
AND e.employeeId IN :#{#request.empIds}
// Correct - wrap in parentheses
AND e.employeeId IN (:#{#request.empIds})
  1. Parameter name mismatch:
Parameter name matching
// If method param is named "request"
// Use #request in SpEL, not #filter or #req
(:#{#request.empIds}) // Correct
(:#{#filter.empIds}) // Wrong - no matching param
  1. Mixing SpEL and @Param inconsistently:

Don’t use both approaches in the same query. Pick one style and stick with it.

Summary

In this post, I showed two ways to pass multiple query parameters in Spring Data JPA native queries. Use SpEL expressions (:#{#request.propertyName}) when you want a clean method signature with a single object parameter. Use @Param annotations when you prefer explicit parameter binding with individual method parameters. Both approaches work correctly with list parameters and prevent SQL injection.

Final Words + More Resources

My intention with this article was to help others share my knowledge and experience. If you want to contact me, you can contact by email: Email me

Here are also the most important links from this article along with some further resources that will help you in this scope:

Oh, and if you found these resources useful, don’t forget to support me by starring the repo on GitHub!

Comments