Lazy generate document numbers from sequence
Goal: Technical
Developer: Heng Sin Low
Feature Ticket: IDEMPIERE-7185
Version: iDempiere 14
The problem
When creating documents such as sales orders, purchase orders, invoices, material receipts, shipments, or payments, iDempiere traditionally generates the document number from the sequence table (AD_Sequence) as soon as the header record is first saved.
Updating the sequence row acquires an exclusive row lock on that sequence record. Because this lock occurs at the beginning of document creation, the transaction holds the lock across its entire lifecycle. This includes creating line items, calculating taxes, updating storage reservations, running workflows, and executing business logic.
Under high-volume workloads or concurrent transactions (such as web store integrations, EDI processing, or point-of-sale checkouts), threads queue up waiting for the sequence lock. When lock contention exceeds the timeout specified by MSEQUENCE_GETNEXT_TIMEOUT (default 30 seconds), transactions fail with a GenerateDocumentNoTimeOut error.
What changed
This feature introduces lazy generation of document numbers from sequences:
- When a new document record is saved, iDempiere assigns a temporary document number instead of querying and locking
AD_Sequence. The temporary number uses the prefix@TMP-followed by a base64-encoded UUIDv7, which fits within standard 30-character document number columns. - Generation of the actual document number from
AD_Sequenceis deferred until:- The transaction pre-commit phase (
beforeCommittransaction event), or - Business logic explicitly reads the document number via
getDocumentNo().
- The transaction pre-commit phase (
- When business logic does not read the document number during processing, the duration of the sequence lock is shortened to the brief moment just before database commit, minimizing lock contention among concurrent transactions. If code calls
getDocumentNo()earlier in the transaction, the sequence is locked at that point and held until the transaction completes or rolls back. - If a transaction rolls back before commit, the temporary document number is discarded and the sequence record is never queried or updated. This avoids burning sequence numbers or blocking other transactions.
- When the sequence generates the actual document number, related records created earlier in the transaction that captured the temporary number are updated automatically:
AD_ChangeLog: The insert change log entry for theDocumentNocolumn is updated to record the actual document number.M_StorageReservationLog: Storage reservation log entries recorded during order or receipt processing are updated with the actual document number.
How to set it up
Lazy document number generation can be enabled tenant-wide, or configured individually for specific document types.
Configure tenant-wide lazy generation
To configure lazy document number generation across the system or for a specific Tenant:
- Open the System Configurator window as a System Administrator or Tenant Administrator.
- Search for the
LAZY_GENERATE_DOCUMENTNO_FROM_SEQUENCEconfiguration key. - Configure the setting:
- For a system-wide setting: Select the existing record under the "System" Tenant and set Configured Value to
Y. - For a tenant-only setting: Create a copy of the record for the specific Tenant, and set Configured Value to
Y(to enable) orN(to disable).
- For a system-wide setting: Select the existing record under the "System" Tenant and set Configured Value to
- Save the record and reset the cache.

Configure per document type
You can override or configure lazy generation on specific Document Types:
- Open the Document Type window.
- Select the Document Type record (such as "Standard Order").
- Verify that Document is Number Controlled is checked and Overwrite Sequence on Complete is unchecked.
- Set the Lazy Generate Document No field:
- Leave empty (null) to follow the tenant-level
LAZY_GENERATE_DOCUMENTNO_FROM_SEQUENCEconfiguration. - Select
Yesto enable lazy generation for this document type, even if the tenant setting isN. - Select
Noto disable lazy generation for this document type, even if the tenant setting isY.
- Leave empty (null) to follow the tenant-level
- Save the record.

Configuration
| System Configurator key | Default | Level | What it does |
|---|---|---|---|
LAZY_GENERATE_DOCUMENTNO_FROM_SEQUENCE | N | Client | Defers generation of DocumentNo from sequence to transaction pre-commit when set to Y. |
Precedence and bypass rules
- Document Type override: Setting Lazy Generate Document No on a Document Type explicitly to
YesorNotakes precedence overLAZY_GENERATE_DOCUMENTNO_FROM_SEQUENCE. - Overwrite Sequence on Complete: If Overwrite Sequence on Complete (
IsOverwriteSeqOnComplete) is checked on the Document Type, lazy generation is bypassed. The initial save uses immediate numbering or temporary numbering according to sequence rules, and the final sequence number is assigned when the document is completed. - Not number controlled: If Document is Number Controlled (
IsDocNoControlled) is unchecked, no document sequence is used. - Definite sequence requests: When code explicitly requests a definitive document number (
definite=true) or when SQL logging scripts are generated (isLogSQLScript()), lazy generation is bypassed. - UUID Sequence: If a Document Type is using a UUID Sequence, lazy generation is bypassed.
Technical notes
For core and plugin developers:
- Temporary DocumentNo format: Formatted as
MSequence.TEMP_DOCUMENTNO_PREFIX(@TMP-) concatenated with a 22-character URL-safe Base64 representation of a 16-byte UUIDv7. MSequencehelper methods:MSequence.isTemporaryDocumentNo(String documentNo): Returnstrueif the string starts with@TMP-.MSequence.isUsingTemporaryDocumentNo(PO po): Checks whether a persistent object holds a pending temporary document number.MSequence.getDocumentNo(int C_DocType_ID, String trxName, boolean definite, PO po, boolean deferToPreCommit): Overloaded method that acceptsdeferToPreCommit.MSequence.getDocumentNo(int AD_Client_ID, String TableName, String trxName, PO po, boolean deferToPreCommit): Overloaded method for table-level sequences.
POmethods:po.is_UsingTemporaryDocumentNo(): Checks if the currentPOinstance holds a temporary document number.po.get_TemporaryDocumentNo(): Returns the temporary document number without triggering sequence generation.po.addDeferredDocumentNoCallback((tempDocNo, actualDocNo) -> ...): Registers a callback invoked when the actual document number is generated.- Calling
po.getDocumentNo()orpo.get_Value("DocumentNo")triggers sequence generation immediately if deferred generation is still pending.
- Transaction pre-commit event:
TrxfiresbeforeCommit(Trx trx)on all registeredTrxEventListenerlisteners prior to committing the database connection.- If a
beforeCommitlistener throws an exception, the transaction is safely rolled back.
Migration impact
None. This feature is disabled by default (LAZY_GENERATE_DOCUMENTNO_FROM_SEQUENCE defaults to N and C_DocType.IsLazyGenerateDocNo defaults to null). Existing installations continue generating document numbers immediately unless the feature is explicitly enabled.