Convert a JPA Tuple to a Custom Object (Quartet) in Java Spring

 

When working with Spring Data JPA, it’s common to use custom queries to retrieve partial data from entities. But what happens when you want to return a custom object — like a quartet of values — instead of a full entity or raw Object[]? By default, JPA and Hibernate can return results as Tuple or Object[], but to convert those into a custom class such as a Quartet, some additional setup is required.

In this comprehensive guide, we’ll explore how to cast query results (especially tuples) into a custom object in a clean, reusable, and type-safe way in a Spring application.


📌 Table of Contents

  • What Is a Tuple in JPA?
  • Common Use Case: Return Multiple Fields
  • Approach 1: Constructor Expression in JPQL
  • Approach 2: Native Query with Manual Mapping
  • Approach 3: Using Interface Projections
  • Troubleshooting: “No Converter Found” Error
  • Conclusion

What Is a Tuple in JPA?

In JPA, a Tuple represents a single row from a query result that returns multiple columns, especially when you’re using a custom SELECT that doesn’t map to an entity. It’s similar to a Map<String, Object> but allows indexed access via aliases or positions.

Example:

Tuple result = ...;
String consentId = result.get("consent_id", String.class);

But using Tuple directly is cumbersome — let’s look at more structured alternatives.


Common Use Case

Suppose you’re working with a SecuritiesAccountEntity and want to retrieve only the following fields:

  • consentId
  • iban
  • currency
  • bban

You don’t need the entire entity — just these four values, and you want them grouped into a custom object like a Quartet<String, String, String, String>.


✅ Approach 1: Constructor Expression in JPQL (Recommended)

If you have a DTO class with a constructor that matches the fields, you can use JPQL’s constructor expression.

Step 1: Create the DTO or Quartet Class

public class Quartet<A, B, C, D> {
    private A first;
    private B second;
    private C third;
    private D fourth;

    public Quartet(A first, B second, C third, D fourth) {
        this.first = first;
        this.second = second;
        this.third = third;
        this.fourth = fourth;
    }

    // Getters and toString(), equals(), hashCode() as needed
}

Step 2: Write the Query in the Repository

@Query("SELECT new com.example.dto.Quartet(e.consentId, e.iban, e.currency, e.bban) " +
       "FROM SecuritiesAccountEntity e WHERE e.consentId IN (?1)")
List<Quartet<String, String, String, String>> findQuartetByConsentIds(List<String> consentIds);

📌 Note: This only works with JPQL, not native SQL.


✅ Approach 2: Native Query with Manual Mapping

For complex queries or when JPQL falls short, native queries can be used — but JPA won’t automatically map the result.

Step 1: Use Native Query in a Custom Repository

@Repository
public class CustomAccountRepositoryImpl {

    @PersistenceContext
    private EntityManager entityManager;

    public List<Quartet<String, String, String, String>> findQuartets(List<String> consentIds) {
        List<Object[]> rows = entityManager.createNativeQuery(
            "SELECT consent_id, iban, currency, bban FROM securities_account_entity WHERE consent_id IN :ids"
        ).setParameter("ids", consentIds)
         .getResultList();

        return rows.stream()
            .map(row -> new Quartet<>((String) row[0], (String) row[1], (String) row[2], (String) row[3]))
            .collect(Collectors.toList());
    }
}

This gives you full control over how the result is mapped.


✅ Approach 3: Using Interface-Based Projections

Spring Data JPA also supports interface projections, a clean and lightweight way to fetch only required fields.

Step 1: Define a Projection Interface

public interface AccountProjection {
    String getConsentId();
    String getIban();
    String getCurrency();
    String getBban();
}

Step 2: Use It in the Query

@Query("SELECT e.consentId as consentId, e.iban as iban, e.currency as currency, e.bban as bban " +
       "FROM SecuritiesAccountEntity e WHERE e.consentId IN (?1)")
List<AccountProjection> findByConsentId(List<String> consentIds);

Then, convert the result to Quartet if needed:

List<Quartet<String, String, String, String>> quartets = projectionList.stream()
    .map(p -> new Quartet<>(p.getConsentId(), p.getIban(), p.getCurrency(), p.getBban()))
    .collect(Collectors.toList());

⚠️ Troubleshooting: “No Converter Found” Error

If you get this error:

No converter found for return value of type: Iterable<Quartet<...>>

It usually means:

  • You used a native query without mapping the result.
  • You returned a custom class that Spring JPA doesn’t know how to instantiate.
  • You didn’t use a constructor expression or projection properly.

✅ Fix:

  • Use new com.example.YourDTO(...) in JPQL.
  • Use Object[] and map manually for native queries.

🏁 Conclusion

Converting query results to a Quartet or other custom structure in Spring Data JPA is a clean and maintainable way to avoid bloated entity returns. Depending on your query type (JPQL vs native), choose:

ScenarioRecommended Approach
JPQL + Simple DTO MappingConstructor Expression
Native SQL + Complex MappingManual Mapping from Object[]
Clean Read-Only ViewsInterface Projections + Mapping

Using these strategies ensures your code remains modular, testable, and optimized.

This article is inspired by real-world challenges we tackle in our projects. If you're looking for expert solutions or need a team to bring your idea to life,

Let's talk!

    Please fill your details, and we will contact you back

      Please fill your details, and we will contact you back