Best Practices
Module 5 of 5 · 1 lesson · ~15 min · ← Back to Learning Hub
Lesson: Error Handling and NRC Strategies
Writing a UDS client that works in the lab is straightforward. Writing one that holds up in production — across dozens of ECU variants, edge cases, and real-world communication errors — requires deliberate error handling.
The NRC Taxonomy
When a request fails, the ECU always responds with:
7F [SID] [NRC]NRCs fall into three categories:
Category 1 — Retry-safe (transient, try again)
| NRC | Name | Action |
|---|---|---|
0x78 | requestCorrectlyReceivedResponsePending | Wait and poll — final response is coming |
0x37 | requiredTimeDelayNotExpired | Wait the full delay (usually 10 s), then retry |
Category 2 — Fix required (something is wrong with your request or state)
| NRC | Name | Action |
|---|---|---|
0x12 | subFunctionNotSupported | Wrong sub-function — check spec |
0x13 | incorrectMessageLengthOrInvalidFormat | Wrong byte count — fix request |
0x22 | conditionsNotCorrect | Enter correct session or meet preconditions |
0x24 | requestSequenceError | Operations out of order — reset and retry from start |
0x31 | requestOutOfRange | Parameter outside valid range |
0x33 | securityAccessDenied | Unlock via SID 0x27 first |
0x35 | invalidKey | Security key calculation error |
0x36 | exceededNumberOfAttempts | Stop — wait for delay, then restart sequence |
Category 3 — Hard failures (not recoverable without investigation)
| NRC | Name | Action |
|---|---|---|
0x10 | generalReject | ECU rejected request — check documentation |
0x11 | serviceNotSupported | SID not implemented in this ECU |
0x7E | subFunctionNotSupportedInActiveSession | Change session first |
0x7F | serviceNotSupportedInActiveSession | Change session first |
Implementing Retry Logic
A robust retry handler should distinguish between these categories:
async function sendUDSRequest(request, { maxRetries = 3, pendingTimeout = 5000 } = {}) {
const deadline = Date.now() + pendingTimeout;
for (let attempt = 0; attempt < maxRetries; attempt++) {
const response = await transport.send(request);
// Positive response
if (response[0] !== 0x7F) return response;
const nrc = response[2];
// NRC 0x78: Response Pending — ECU is processing, wait for final response
if (nrc === 0x78) {
if (Date.now() >= deadline) throw new UDSError('P2* timeout exceeded', nrc);
await delay(50); // brief pause, then listen again
continue;
}
// NRC 0x37: Time delay — back off significantly
if (nrc === 0x37) {
await delay(10_000);
continue;
}
// All other NRCs are not retryable at this level
throw new UDSError(getNRCDescription(nrc), nrc);
}
throw new UDSError('Max retries exceeded');
}Handling NRC 0x78 Correctly
NRC 0x78 is not an error — it is the ECU’s way of saying “I received your request, I need more time, don’t timeout yet.” This is common during:
- Memory erase operations (
31 01 FF 00) - Flash programming blocks (
36 [seq] [data]) - Complex routine executions (
31 01 [routine])
The correct behavior is to keep the transport connection open and wait for the actual response within the P2* window.
Common mistake: Treating 0x78 as a failure and resending the request. This resets the ECU’s internal operation and may corrupt the flash.
Logging for Diagnostics
Every UDS exchange should be logged with enough context to diagnose issues post-deployment:
function logExchange(request, response, durationMs) {
const entry = {
timestamp: new Date().toISOString(),
requestHex: toHex(request),
sid: request[0],
responseHex: toHex(response),
success: response[0] !== 0x7F,
nrc: response[0] === 0x7F ? response[2] : null,
durationMs,
};
auditLog.write(entry);
}Log at minimum:
- Timestamp
- Full hex of request and response
- Duration (to identify timing issues)
- Session state at time of request
- ECU identifier / target address
Graceful Degradation
Design your UDS client to fall back when a service fails:
async function getVehicleSpeed() {
try {
// Primary: Read Data By Identifier (most ECUs)
const response = await sendUDSRequest([0x22, 0x01, 0x01]);
return parseSpeed(response);
} catch (err) {
if (err.nrc === 0x31) {
// DID not supported — try alternate DID
return getVehicleSpeedAlternateDID();
}
throw err; // Re-throw if not a known fallback condition
}
}Common graceful degradation patterns:
- If SID 0x22 DID fails: try SID 0x23 (Read Memory By Address) if address is known
- If Extended Session fails: attempt limited operations in Default Session
- If Security Access fails: skip protected operations, continue with unprotected diagnostics
User-Facing Error Messages
Never expose raw NRCs or hex dumps to end users. Map them to meaningful messages:
const NRC_MESSAGES = {
0x22: 'This operation is not available. Ensure the vehicle is stationary and ignition is on.',
0x33: 'Authentication required. Please complete the security access procedure.',
0x35: 'Authentication failed. Check your credentials and try again.',
0x36: 'Too many failed attempts. Please wait before retrying.',
};
function getUserMessage(nrc) {
return NRC_MESSAGES[nrc] ?? 'An unexpected diagnostic error occurred. Please check the vehicle connection.';
}Session Management Best Practices
- Enter the lowest session that meets your needs. Extended session has more access than Default, but also adds complexity (S3 timeout, Tester Present requirement).
- Always exit cleanly. Send
10 01when done — do not just disconnect. - Monitor S3 actively. Set a timer; send
3E 80(suppressed Tester Present) every 2 seconds when in a non-default session. - Handle session loss gracefully. If
0x7Fwith NRC0x7Fis received unexpectedly, the session expired — re-enter and retry.
Production Checklist
Before shipping a UDS client to production:
- All NRCs categorized and handled (retry-safe vs. fix-required vs. hard-fail)
- NRC
0x78handled as informational — no re-send, just wait - Retry logic with configurable backoff
- P2* deadline enforced (don’t wait forever for pending responses)
- Full hex logging of every exchange
- User-friendly error messages (no raw hex to end users)
- Graceful degradation for unsupported DIDs or services
- Session timeout monitoring with automatic Tester Present
- Comprehensive test coverage: both success and each failure NRC
Testing Error Conditions
Test your error handling explicitly — don’t assume it works:
- NRC 0x22: Send a write request in Default session
- NRC 0x33: Send a protected request without security unlock
- NRC 0x35: Send a deliberately wrong security key
- NRC 0x78: Trigger a long operation (memory erase) and verify your client waits correctly
- NRC 0x7F: Drop to Default session, then send an Extended-session-only request
- Timeout: Disconnect the transport mid-request and verify your client recovers
Congratulations
You’ve completed the UDS Learning Hub. You now understand:
- ISO 14229 protocol fundamentals — message structure, NRCs, positive/negative responses
- Timing parameters — P2, P2*, S3, and how to use them correctly
- Core services — Session Control (0x10), Security Access (0x27), Read DTC (0x19)
- Real-world workflows — complete DTC diagnosis from session entry to verification
- Production patterns — retry logic, NRC categorization, logging, graceful degradation
Where to Go Next
- Practice: Use the Interactive Simulator to send real UDS frames to a virtual ECU
- Deep dives: Browse the full SID reference for every UDS service
- Protocol Anatomy: Read the Protocol Anatomy Guide for ISO-TP transport layer details