Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

iTechGuides is reader-supported. When you buy through links on our site, we may earn an affiliate commission. As an Amazon Associate I earn from qualifying purchases. Learn more

To coordinate a short, application-defined critical section across Spring Boot instances, acquire a PostgreSQL transaction-level advisory lock and perform the protected database work in the same Spring-managed transaction. Every participating code path must use the same lock key and compatible lock operation; PostgreSQL does not make other callers obey the lock automatically.

Why a clustered application needs a shared lock

A Java monitor or other in-process synchronization mechanism can coordinate threads inside one JVM, but it cannot coordinate separate Spring Boot instances. If multiple instances share a PostgreSQL database, an advisory lock can provide a shared coordination point for callers that agree on the lock’s meaning and key.

PostgreSQL describes advisory locks as locks with application-defined meanings. That flexibility is also their main limitation: the database does not require every writer to acquire the lock. If one code path skips it, uses a different key, or uses an incompatible lock mode, that path can still race with protected work.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Use an advisory lock when the invariant is a broader application-defined critical section that is awkward to express as a row lock. If a unique constraint, another database constraint, or ordinary row locking directly expresses the invariant, prefer that database guarantee where it fits. PostgreSQL notes that proper use of MVCC generally provides better performance than locks.

Use one transaction for the lock and protected work

For a short database critical section, prefer a transaction-level advisory lock. PostgreSQL releases it automatically when the transaction ends, whether the transaction commits or rolls back. The essential requirement is that lock acquisition and the protected SQL execute on the same transaction-bound connection.

Spring JDBC example

With Spring JDBC, JdbcTemplate participates in an ongoing Spring transaction. The following example uses PostgreSQL’s single-bigint key form of pg_advisory_xact_lock; adapt the key mapping and method signatures to the application’s actual versions and domain:

@Service
public class InvoiceService {
    private final JdbcTemplate jdbcTemplate;

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

    @Transactional
    public void processInvoice(long invoiceId) {
        jdbcTemplate.queryForObject(
            "SELECT pg_advisory_xact_lock(?)",
            Object.class,
            invoiceId
        );

        // Protected database work goes here, through this transaction.
        jdbcTemplate.update(
            "UPDATE invoice SET status = 'PROCESSING' WHERE id = ?",
            invoiceId
        );
    }
}

The annotation must be applied through Spring’s transaction handling, and the method’s database operations must remain within that transaction. Check proxy and transaction configuration in the application rather than assuming that an annotation on any method invocation starts a transaction.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Direct JDBC and connection affinity

If using JDBC directly instead of JdbcTemplate, obtain the connection through Spring’s transaction-aware mechanisms, such as DataSourceUtils. Opening an unrelated connection can put the lock on one connection while the protected SQL runs on another, defeating the intended protection.

Confirm the SQL and configuration against the application’s exact Spring Boot, Spring Framework, PostgreSQL, JDBC driver, and connection-pool versions. A lock call that succeeds is not sufficient proof that the protected operation shares its transaction.

Choose a stable key and lock scope

PostgreSQL supports advisory-lock keys as either one bigint or a pair of integers. Define a stable mapping from the resource or task being protected to one of those shapes, document it, and ensure every participating instance derives the same key.

  • Use the same key convention in every code path that protects the same resource.
  • Separate unrelated domains deliberately, for example by assigning a documented namespace/key convention for a pair of integer keys.
  • Account for accidental collisions: two unrelated operations that map to the same advisory key can block one another.
  • Remember that advisory locks are local to one PostgreSQL database. Separate databases do not coordinate through the same advisory-lock namespace, even when hosted by the same PostgreSQL cluster.

Transaction locks versus session locks

Lock type Lifetime and release Practical consequence
Transaction-level Released automatically when the transaction ends; no explicit unlock function is needed. Usually the safer choice for database work in a Spring-managed transaction.
Session-level Persists until explicitly released or the database session ends; transaction rollback does not release it. Repeated acquisition by the owning session is allowed and requires matching unlock operations. Easy to retain unexpectedly when a pooled connection is returned for reuse; use only when session lifetime is deliberately managed.

A pooled connection can outlive the request or transaction that borrowed it. A session-level lock can therefore remain associated with that database session after application code believes its work has ended. Prefer transaction-level locks unless there is a deliberate session-scoped design with reliable acquisition and release handling.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Keep the critical section short and thread-bound

In imperative Spring transaction management, transaction resources are bound to the executing thread. Do not acquire the lock in one thread and assume an operation sent to a newly started thread will inherit the transaction. Keep the lock acquisition and relevant JDBC work on the transaction’s executing thread.

Reactive transactions follow different rules: use the reactive transaction manager and preserve the same Reactor context across participating data-access operations. Do not treat imperative thread-bound transaction behavior as a model for reactive execution.

  • Do not hold a database lock while waiting for user interaction or doing unrelated slow work.
  • If an operation needs multiple locks, acquire them in a consistent order across callers to reduce deadlock risk.
  • Handle deadlock and serialization failures intentionally. Retry only when the complete operation, including any application-level side effects, is safe to repeat.

Advisory locks do not eliminate deadlocks or transaction contention. PostgreSQL’s advisory locks also consume shared lock resources; the available lock pool is governed by max_locks_per_transaction and max_connections, so very high lock counts can exhaust it.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Inspect lock holders and waiters

PostgreSQL exposes advisory lock entries through pg_locks. Join them to pg_stat_activity by process ID to identify the session and its query context. A granted lock is held; an ungranted lock represents a waiter.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
SELECT a.pid,
       a.datname,
       a.usename,
       a.state,
       a.wait_event_type,
       a.wait_event,
       l.granted,
       l.classid,
       l.objid,
       l.objsubid,
       a.xact_start,
       a.query
FROM pg_locks AS l
JOIN pg_stat_activity AS a ON a.pid = l.pid
WHERE l.locktype = 'advisory'
ORDER BY l.granted, a.xact_start;

Use the lock’s database and key columns together with the application’s documented key mapping to determine which resource is involved. Investigate unexpectedly long waits by checking the holder’s transaction age and query, and verify that callers use the same key convention. A short wait can be normal when another transaction is performing the protected work.

Be cautious when calling advisory-lock functions in a query that also uses ORDER BY and LIMIT: expression evaluation order can lead to locks being acquired for rows beyond those expected. When selection must happen first, isolate the selected rows in a subquery before applying the lock call.

When another database guarantee is a better fit

  • Unique constraints and other constraints: use them when the invariant is about which values or relationships may be stored. They protect against writers that do not follow an application lock convention.
  • Row locks: use them when the critical section is naturally tied to existing rows and their updates.
  • Transaction isolation: consider it when the correctness requirement concerns a transaction’s view of concurrent data changes.
  • Advisory locks: consider them when coordination spans an application-defined task or resource and all relevant callers can consistently cooperate through the same database and key.

These mechanisms address different invariants; an advisory lock is not a universal replacement for MVCC, constraints, or row locking.

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.