The error occurs when you are working with a JPA native query that returns results as a TupleBackedMap
(used by Spring Data for unstructured or tuple-based query results). When you try to map or cast these results to a custom interface or class, Spring can’t automatically convert them unless explicitly instructed.
RootCause:
- TupleBackedMap: This is an immutable result representation returned by Spring Data JPA for native queries when projecting into an interface. It’s not a standard object or mutable DTO.
- Conversion Error: When trying to map a query result to a custom type like
ReportDetailImpl
orReportDetailsStatus
, Spring needs converters, which are missing. Let’s look at a example where a native SQL query is used to fetch data from a database, and we face the issue of immutability (TupleBackedMap
). I’ll walk you through the problem, the solution, and the reasoning behind it.Suppose we have two tables: Employee and Department.
public class Employee {
int id (Primary Key);
String name;
int department_id
}
public class Department{
int id (Primary key);
String name;
}
You want to fetch employee details along with their department name using a native query.
Repsository Code:
@Query(value = "SELECT e.id AS employeeId, e.name AS employeeName, d.name AS departmentName " +
"FROM employee e " +
"JOIN department d ON e.department_id = d.id " +
"WHERE d.name = :departmentName", nativeQuery = true)
List<EmployeeDetails> getEmployeeDetailsByDepartment(@Param("departmentName") String departmentName);
Interface for Projection:
public interface EmployeeDetails {
String getEmployeeId();
String getEmployeeName();
String getDepartmentName();
}
Service Code:
public List<EmployeeDetails> fetchEmployeeDetails(String departmentName) {
List<EmployeeDetails> employeeDetails = repository.getEmployeeDetailsByDepartment(departmentName);
employeeDetails.forEach(detail -> {
if ("HR".equals(detail.getDepartmentName())) {
detail.setDepartmentName("Human Resources"); // ERROR: Immutable TupleBackedMap
}
});
return employeeDetails;
}
Issues:
- Immutability:
The results returned by the query are backed by aTupleBackedMap
, which cannot be modified.
Attempting to call a setter or update the values directly will throw an error. - No Conversion:
Spring Data JPA cannot automatically convertTupleBackedMap
into mutable objects unless explicitly configured.
Solution:
Use DTO: Create a concrete class (EmployeeDetailsDTO
) to hold the query results.
EmployeeDetailsDto:
@Setter
@Getter
@AllConstructorArgs
public class EmployeeDetailsDTO {
private String employeeId;
private String employeeName;
private String departmentName;}
Update RepositoryCode:
@Query(value = "SELECT e.id AS employeeId, e.name AS employeeName, d.name AS departmentName " +
"FROM employee e " +
"JOIN department d ON e.department_id = d.id " +
"WHERE d.name = :departmentName", nativeQuery = true)
List<EmployeeDetailsDTO> getEmployeeDetailsByDepartment(@Param("departmentName") String departmentName);
Service Code:
public List<EmployeeDetailsDTO> fetchEmployeeDetails(String departmentName) {
List<EmployeeDetailsDTO> employeeDetails = repository.getEmployeeDetailsByDepartment(departmentName);
employeeDetails.forEach(detail -> {
if ("HR".equals(detail.getDepartmentName())) {
detail.setDepartmentName("Human Resources");
}
});
return employeeDetails;
}
This approach ensures the results are mutable and allows modification in the service layer.