Rules 3 and 4 together leave one gap. The Resilience4j retry around createAccount fires on timeouts, but a timeout does not mean the account wasn't created. The downstream API may have finished the work and only the response got lost. Because the new ID and status are saved only after a successful response, the retry sends a second create and you get two accounts, which is exactly what the state machine was meant to prevent. Passing a stable idempotency key derived from the external ID (if the internal API supports one), or looking up by external reference before creating on a retry, closes it. Second, Resilience4j retries on every exception by default. Without retryExceptions or ignoreExceptions in the config, a 400 caused by bad data is also retried three times, which contradicts your Rule 4 table. A small one on Rule 2: when two duplicates arrive at the same time, the unique constraint makes the second save throw DataIntegrityViolationException. Catching it and still returning 202 stops the sender from retrying a request you already stored.
Rules 3 and 4 together leave one gap. The Resilience4j retry around createAccount fires on timeouts, but a timeout does not mean the account wasn't created. The downstream API may have finished the work and only the response got lost. Because the new ID and status are saved only after a successful response, the retry sends a second create and you get two accounts, which is exactly what the state machine was meant to prevent. Passing a stable idempotency key derived from the external ID (if the internal API supports one), or looking up by external reference before creating on a retry, closes it. Second, Resilience4j retries on every exception by default. Without retryExceptions or ignoreExceptions in the config, a 400 caused by bad data is also retried three times, which contradicts your Rule 4 table. A small one on Rule 2: when two duplicates arrive at the same time, the unique constraint makes the second save throw DataIntegrityViolationException. Catching it and still returning 202 stops the sender from retrying a request you already stored.