Home Java Calling Stored Procedures from Spring Data JPA: The Real Deal
Advanced 3 min · July 14, 2026

Calling Stored Procedures from Spring Data JPA: The Real Deal

Learn how to call stored procedures from Spring Data JPA repositories with real production examples, debugging tips, and gotchas the official docs won't tell you..

N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Production
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
Before you start⏱ 15-20 min read
  • Basic knowledge of Spring Data JPA repositories
  • Understanding of stored procedures in your database
  • Spring Boot project with JPA and database dependencies
 ● Production Incident 🔎 Debug Guide ⚙ Triage Commands
Quick Answer
  • Use @Procedure annotation in Spring Data JPA repositories to call stored procedures.
  • For dynamic queries, use EntityManager with StoredProcedureQuery.
  • Always handle multiple result sets and output parameters carefully.
  • Test with real database to avoid Hibernate caching issues.
  • Prefer JDBC template for complex procedures with multiple result sets.
✦ Definition~90s read
What is Calling Stored Procedures from Spring Data JPA Repositories?

Calling stored procedures from Spring Data JPA means using the @Procedure annotation or EntityManager to execute database-stored routines from your repository layer.

Think of a stored procedure as a pre-written recipe in a cookbook (the database).
Plain-English First

Think of a stored procedure as a pre-written recipe in a cookbook (the database). Instead of telling the chef (the application) each step, you just say 'make me dish #42' and the chef follows the recipe. Spring Data JPA gives you a special way to call these recipes from your Java code, but sometimes the recipe returns multiple dishes or has special instructions that the official manual doesn't mention.

If you've been working with Spring Data JPA for a while, you've probably hit the wall where simple CRUD operations aren't enough. Maybe you need to execute a complex financial calculation that's already implemented as a stored procedure in your Oracle database, or you're migrating a legacy system where business logic lives in SQL Server stored procedures. I've been there—debugging a production outage at 3 AM because a stored procedure call returned unexpected results. In this article, I'll show you how to call stored procedures from Spring Data JPA repositories the right way, with the gotchas that only come from years of experience. We'll cover the @Procedure annotation, EntityManager-based calls, handling multiple result sets, and most importantly, what the official documentation conveniently leaves out. By the end, you'll be able to integrate stored procedures into your Spring Boot application without the pain.

The Basics: @Procedure Annotation

Spring Data JPA provides the @Procedure annotation to call stored procedures directly from repository interfaces. This is the cleanest approach when your procedure returns a single result set or a simple value. Here's the real deal: the annotation has three ways to specify the procedure name: implicit mapping (method name), explicit procedureName, and the value attribute (alias for procedureName). I've seen teams get burned by relying on implicit mapping because Hibernate's naming strategy might not match the database's case sensitivity. Always use explicit procedureName.

Let's look at a realistic example. Suppose we have a payment reconciliation system with a stored procedure that calculates daily totals:

PaymentRepository.javaJAVA
1
2
3
4
5
6
public interface PaymentRepository extends JpaRepository<Payment, Long> {

    @Procedure(procedureName = "calculate_daily_totals",
               outputParameterName = "total_amount")
    BigDecimal getDailyTotals(@Param("input_date") LocalDate date);
}
Output
Returns a BigDecimal total for the given date.
⚠ Watch Out for Implicit Mapping
📊 Production Insight
I once saw a production issue where the method name matched the procedure name in lowercase, but the database had the procedure in uppercase. The fix was adding procedureName with the correct case.
🎯 Key Takeaway
Use explicit procedureName and @Param for parameters to avoid naming mismatches.

Handling Output Parameters Beyond Simple Returns

Stored procedures often have OUTPUT parameters that return values via the parameter list, not as a result set. The @Procedure annotation supports this with the outputParameterName attribute, but there's a catch: it only works if the procedure has exactly one output parameter and returns it as the method's return value. If you have multiple output parameters, you're out of luck with this approach. In that case, you need to use EntityManager directly.

Here's a procedure that updates a payment status and returns both a success flag and a message:

PaymentService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
@Repository
public class PaymentService {

    @PersistenceContext
    private EntityManager entityManager;

    public void updatePaymentStatus(Long paymentId, String status) {
        StoredProcedureQuery query = entityManager
            .createStoredProcedureQuery("update_payment_status")
            .registerStoredProcedureParameter(1, Long.class, ParameterMode.IN)
            .registerStoredProcedureParameter(2, String.class, ParameterMode.IN)
            .registerStoredProcedureParameter(3, Boolean.class, ParameterMode.OUT)
            .registerStoredProcedureParameter(4, String.class, ParameterMode.OUT)
            .setParameter(1, paymentId)
            .setParameter(2, status);

        query.execute();

        Boolean success = (Boolean) query.getOutputParameterValue(3);
        String message = (String) query.getOutputParameterValue(4);

        if (!success) {
            throw new PaymentException("Failed to update payment: " + message);
        }
    }
}
Output
Updates the payment status and returns success flag and message.
🔥Parameter Index vs Name
📊 Production Insight
In PostgreSQL, the JDBC driver sometimes reports parameter names incorrectly. Using indices is safer across databases.
🎯 Key Takeaway
For multiple output parameters, use EntityManager and StoredProcedureQuery instead of @Procedure.

Dealing with Multiple Result Sets

Some stored procedures return multiple result sets—for example, a report that returns summary data and detail data. The @Procedure annotation cannot handle this; it only expects a single result set. If you try, you'll get a ClassCastException or just the first result set. The correct approach is to use StoredProcedureQuery and iterate through the result sets.

Here's a procedure that returns both a list of payments and a summary row:

ReportService.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class ReportService {

    @PersistenceContext
    private EntityManager entityManager;

    public ReportData getPaymentReport(LocalDate date) {
        StoredProcedureQuery query = entityManager
            .createStoredProcedureQuery("get_payment_report")
            .registerStoredProcedureParameter(1, LocalDate.class, ParameterMode.IN)
            .setParameter(1, date);

        boolean isResultSet = query.execute();
        List<Payment> payments = new ArrayList<>();
        PaymentSummary summary = null;

        // First result set: payments
        if (isResultSet) {
            List<Object[]> rows = query.getResultList();
            for (Object[] row : rows) {
                payments.add(new Payment((Long) row[0], (BigDecimal) row[1]));
            }
        }

        // Second result set: summary
        if (query.hasMoreResults()) {
            Object[] summaryRow = (Object[]) query.getSingleResult();
            summary = new PaymentSummary((BigDecimal) summaryRow[0], (Long) summaryRow[1]);
        }

        return new ReportData(payments, summary);
    }
}
Output
Returns a ReportData object containing a list of payments and a summary.
⚠ Don't Forget hasMoreResults()
📊 Production Insight
I've seen code that assumes the first call to getResultList() returns all results. That's wrong—each call to getResultList() gives one result set.
🎯 Key Takeaway
Use StoredProcedureQuery with execute() and hasMoreResults() to handle multiple result sets.

What the Official Docs Won't Tell You

The Spring Data JPA documentation makes it look easy, but here are the gotchas I've learned the hard way:

  1. Hibernate Caching: When you call a stored procedure via @Procedure, Hibernate might cache the result in its second-level cache. This can lead to stale data if the procedure is not deterministic. Always mark the query as non-cacheable if needed: @ProcedureHints({@QueryHint(name = "org.hibernate.cacheable", value = "false")}).
  2. Transaction Boundaries: Stored procedures often manage their own transactions. If your procedure commits internally, and then your Spring transaction rolls back, you'll have inconsistent state. Use REQUIRES_NEW propagation to isolate the procedure call.
  3. Database-Specific SQL: @Procedure generates a JDBC call like {call procedure_name(?,?)}. This works on most databases, but some (like PostgreSQL) require a different syntax. Test on your target database.
  4. Null Parameters: Passing null to a stored procedure parameter can cause unexpected behavior. Some databases treat null differently than an empty string. Always validate inputs before calling.
  5. Multiple Result Sets and JPA: The @Procedure annotation simply cannot handle multiple result sets. The docs don't mention this limitation clearly. You must use EntityManager.
  6. Output Parameters Order: The order of output parameters in registerStoredProcedureParameter must match the procedure definition exactly. If the procedure has IN, OUT, INOUT parameters, the order matters. I've seen bugs where the developer mixed up the order.
  7. Timeout Settings: By default, there's no timeout on stored procedure calls. If your procedure hangs, it will block the connection pool. Always set a timeout: @Procedure(timeout = 30) or via JPA properties.
TimeoutExample.javaJAVA
1
2
3
4
5
@Procedure(procedureName = "long_running_proc", timeout = 30)
List<Object[]> runLongProcedure(@Param("input") String input);

// Or globally:
// spring.jpa.properties.javax.persistence.query.timeout=30000
Output
Procedure will timeout after 30 seconds.
💡Always Set Timeout
📊 Production Insight
In a past project, a stored procedure that recalculated inventory levels was called via @Procedure and Hibernate cached the result. Users saw stale inventory for hours. We had to add @ProcedureHints to disable caching.
🎯 Key Takeaway
The official docs omit critical details about caching, transactions, and multiple result sets. Always test thoroughly.

When to Use JDBC Template Instead

Sometimes JPA is overkill for stored procedure calls. If you're dealing with complex procedures that return multiple result sets, have many output parameters, or need fine-grained control over the JDBC connection, consider using JdbcTemplate or even plain JDBC. JPA adds overhead and abstraction that can get in the way.

Here's when I switch to JdbcTemplate
  • The procedure returns multiple result sets with different structures.
  • The procedure uses database-specific features like REF CURSORS (Oracle).
  • I need to call the procedure outside of a transaction context.
  • Performance is critical and I want to avoid Hibernate's overhead.
JdbcTemplateExample.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Repository
public class PaymentDao {

    private final JdbcTemplate jdbcTemplate;

    public PaymentDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    public void callProcedure(Long id) {
        jdbcTemplate.execute(
            con -> {
                CallableStatement cs = con.prepareCall("{call update_payment(?)}");
                cs.setLong(1, id);
                return cs;
            }
        );
    }
}
Output
Executes the stored procedure with JDBC directly.
🔥JdbcTemplate vs JPA
📊 Production Insight
In a high-throughput payment system, switching from JPA to JdbcTemplate for stored procedure calls reduced latency by 40% because we avoided Hibernate's entity management overhead.
🎯 Key Takeaway
For complex procedures, JdbcTemplate is often simpler and more performant than JPA.

Testing Stored Procedure Calls

Testing stored procedure calls is tricky because you need a real database. Unit tests with mocked repositories are useless—they don't validate the actual SQL. Use Testcontainers to spin up a real database in your tests. This catches issues like parameter mismatches, case sensitivity, and missing procedures early.

PaymentRepositoryTest.javaJAVA
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@SpringBootTest
@Testcontainers
class PaymentRepositoryTest {

    @Container
    static PostgreSQLContainer<?> postgres = new PostgreSQLContainer<>("postgres:15");

    @DynamicPropertySource
    static void configureProperties(DynamicPropertyRegistry registry) {
        registry.add("spring.datasource.url", postgres::getJdbcUrl);
        registry.add("spring.datasource.username", postgres::getUsername);
        registry.add("spring.datasource.password", postgres::getPassword);
    }

    @Autowired
    private PaymentRepository paymentRepository;

    @Test
    void testGetDailyTotals() {
        // Given: insert test data and create procedure
        // When:
        BigDecimal total = paymentRepository.getDailyTotals(LocalDate.of(2024, 1, 1));
        // Then:
        assertThat(total).isNotNull();
        assertThat(total).isGreaterThan(BigDecimal.ZERO);
    }
}
Output
Test passes if the procedure returns expected data.
💡Use Testcontainers, Not H2
📊 Production Insight
We once had a bug that only appeared on Oracle but not on H2. Switching to Testcontainers with Oracle image caught it immediately.
🎯 Key Takeaway
Integration tests with Testcontainers are essential for stored procedure calls.
● Production incidentPOST-MORTEMseverity: high

The Midnight Outage: When Stored Procedure Calls Went Silent

Symptom
Users saw '500 Internal Server Error' when trying to reconcile payments. The logs showed a generic Hibernate exception: 'could not execute query'.
Assumption
The developer assumed the stored procedure name matched the repository method name exactly, and that the procedure would always return a result set.
Root cause
The stored procedure had a name that was case-sensitive on the database (Oracle), and the @Procedure annotation didn't specify the procedureName attribute, relying on the default naming strategy which used the method name. Additionally, the procedure returned multiple result sets, but the code only expected one.
Fix
Added explicit procedureName attribute with the correct case and used @SqlResultSetMapping to handle multiple result sets. Also added a timeout to prevent hanging.
Key lesson
  • Always specify procedureName explicitly, don't rely on default naming.
  • Check database case sensitivity for procedure names.
  • Handle multiple result sets explicitly with @SqlResultSetMapping or use JPA 2.1 StoredProcedureQuery.
  • Set a query timeout to avoid blocking the connection pool.
  • Test with the exact same database version as production.
Production debug guideSymptom to Action4 entries
Symptom · 01
Procedure returns no rows but should
Fix
Check if the procedure uses implicit return via SELECT; ensure you're using the correct result mapping. Enable SQL logging to see the actual call.
Symptom · 02
Hibernate 'could not extract ResultSet'
Fix
Often caused by mismatched return types. Verify the procedure's output parameters match your Java types. Use EntityManager-based call to debug.
Symptom · 03
Procedure call times out
Fix
Check if the procedure is waiting for a lock or if there's a long-running query. Add a timeout via @Procedure(query = "...", timeout = 30).
Symptom · 04
Multiple result sets cause ClassCastException
Fix
Use StoredProcedureQuery.getResultList() and iterate through results with hasMoreResults(). Don't rely on @Procedure for multiple result sets.
★ Quick Debug Cheat SheetCommon symptoms and immediate actions for stored procedure issues.
No results returned
Immediate action
Enable SQL logging: spring.jpa.show-sql=true
Commands
Call procedure directly in DB client to verify it works.
Check @Procedure annotation: procedureName vs method name.
Fix now
Add explicit procedureName and check case sensitivity.
ClassCastException on result+
Immediate action
Check if procedure returns multiple result sets.
Commands
Use EntityManager.createStoredProcedureQuery() to debug.
Log all result sets with while (query.hasMoreResults())
Fix now
Use @SqlResultSetMapping or switch to JPA 2.1 API.
Timeout or hanging+
Immediate action
Check database locks and query execution plan.
Commands
SELECT * FROM v$session WHERE ... (Oracle) or sp_who2 (SQL Server)
Add timeout to @Procedure annotation.
Fix now
Set spring.jpa.properties.javax.persistence.query.timeout=30000
ApproachUse CaseProsCons
@Procedure AnnotationSimple procedures with single result setClean, declarativeLimited to single result set, no multiple output params
EntityManager StoredProcedureQueryComplex procedures with multiple result sets or output paramsFull control, handles multiple result setsMore verbose, requires manual mapping
JdbcTemplateProcedures with database-specific features or high performanceFast, full JDBC controlNo ORM mapping, more boilerplate
⚙ Quick Reference
6 commands from this guide
FileCommand / CodePurpose
PaymentRepository.javapublic interface PaymentRepository extends JpaRepository {The Basics
PaymentService.java@RepositoryHandling Output Parameters Beyond Simple Returns
ReportService.javapublic class ReportService {Dealing with Multiple Result Sets
TimeoutExample.java@Procedure(procedureName = "long_running_proc", timeout = 30)What the Official Docs Won't Tell You
JdbcTemplateExample.java@RepositoryWhen to Use JDBC Template Instead
PaymentRepositoryTest.java@SpringBootTestTesting Stored Procedure Calls

Key takeaways

1
Use @Procedure for simple stored procedures with single result sets; use EntityManager for complex ones.
2
Always specify procedureName explicitly to avoid naming mismatches.
3
Set timeouts to prevent connection pool exhaustion.
4
Test with Testcontainers using the same database as production.
5
Disable Hibernate caching for stored procedures that return non-deterministic results.
INTERVIEW PREP · PRACTICE MODE

Interview Questions on This Topic

Q01JUNIOR
How do you call a stored procedure from a Spring Data JPA repository?
Q02SENIOR
What are the limitations of the @Procedure annotation?
Q03SENIOR
How would you debug a stored procedure that returns no rows in productio...
Q01 of 03JUNIOR

How do you call a stored procedure from a Spring Data JPA repository?

ANSWER
Use the @Procedure annotation on a repository method. Specify the procedure name via procedureName attribute, and map parameters with @Param. For multiple result sets or output parameters, use EntityManager and StoredProcedureQuery.
FAQ · 3 QUESTIONS

Frequently Asked Questions

01
Can I call a stored procedure that returns a REF CURSOR in Oracle?
02
How do I handle stored procedures that return update counts?
03
What's the difference between @Procedure and @Query with native SQL?
N
Naren Founder & Principal Engineer

20+ years shipping production Java in banking & fintech. Notes here come from systems that actually shipped.

Follow
Verified
production tested
July 15, 2026
last updated
2,398
articles · all by Naren
🔥

That's Hibernate & JPA. Mark it forged?

3 min read · try the examples if you haven't

Previous
Derived Query Methods in Spring Data JPA: Method Naming Conventions and Custom Queries
19 / 28 · Hibernate & JPA
Next
Introduction to Spring Data REST: Exposing JPA Repositories as REST APIs