| name | error-handling |
| description | Use when handling errors in SAP CAP applications: req.reject, req.warn, req.info, req.error, cds.error, HTTP status codes, i18n error messages, @SAP.Messages, validation errors, field-level errors, error targets, global error handlers, and business errors.
|
| metadata | {"category":"cap","version":"1.0.0","keywords":["req.reject","req.warn","req.error","req.info","HTTP status","validation error","field target","i18n message","@SAP.Messages","cds.error"],"related":{"service-handlers":"handlers that need to reject or warn on invalid input","testing":"test that errors are thrown with correct codes and messages","localization":"i18n error messages for multi-language apps"}} |
Error Handling — CAP Best Practices
Primary reference: https://cap.cloud.sap/docs/node.js/events#errors
Messages: https://cap.cloud.sap/docs/node.js/events#req-messages
The four messaging methods
req.reject(422, 'Quantity {0} exceeds available stock {1}', [qty, stock])
req.warn(200, 'STOCK_LOW', [product.title])
req.info(200, 'Order will be shipped by {0}', [shipDate])
req.error(400, 'Field {0} is required', ['title'])
req.reject()
HTTP status codes — when to use what
| Code | When |
|---|
| 400 | Bad request / validation failure (malformed input) |
| 401 | Not authenticated |
| 403 | Authenticated but not authorized |
| 404 | Entity not found |
| 409 | Conflict (e.g. duplicate, already processed) |
| 422 | Unprocessable entity (semantic/business rule failure) |
| 500 | Unexpected server error (use sparingly, prefer specific codes) |
i18n error messages
Define messages in _i18n/messages.properties:
ORDER_CLOSED=Order {0} is already closed
STOCK_INSUFFICIENT=Insufficient stock: requested {0}, available {1}
DUPLICATE_ORDER=An order for product {0} already exists today
Reference by key in handlers:
req.reject(409, 'ORDER_CLOSED', [req.data.orderID])
req.reject(422, 'STOCK_INSUFFICIENT', [qty, stock])
CAP resolves the key using the user's locale automatically.
Structured errors with targets
Target a specific field in OData error responses (Fiori shows inline validation):
req.reject({
code: 422,
message: 'Price must be positive',
target: 'price',
status: 422
})
req.error({ code: 400, message: 'Title is required', target: 'title' })
req.error({ code: 400, message: 'Price is required', target: 'price' })
req.reject()
Global error handler
Register in server.js or your AppService:
cds.on('error', (err, req) => {
console.error(`[${req?.user?.id}] ${err.code}: ${err.message}`)
if (err.status === 500) {
err.message = 'An internal error occurred. Please contact support.'
}
})
Error handling for remote service calls
try {
const result = await S4.run(SELECT.from('A_BusinessPartner').where({ BusinessPartner: id }))
return result
} catch (err) {
if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
req.reject(503, 'Backend system is currently unavailable')
} else if (err.status === 404) {
req.reject(404, 'Business partner {0} not found in S/4HANA', [id])
} else {
throw err
}
}
Custom error class
For reusable domain errors:
class BusinessError extends cds.error {
constructor(code, message, args = []) {
super(message, { code, status: 422 })
this.messageArgs = args
}
}
throw new BusinessError('ORDER_CLOSED', 'Order {0} is already closed', [id])
Common mistakes to avoid
- ❌ Using
throw new Error(...) directly — bypasses CAP's error formatting
- ❌ Returning error objects instead of calling
req.reject() — agent gets no error
- ❌ Sending stack traces to clients in production
- ❌ Using status 500 for business rule violations (use 409/422)
- ❌ Forgetting
target on validation errors — Fiori Elements needs it for inline display
- ❌ Swallowing errors in remote service calls (silent failures)