Server error details
For the complete documentation index see: llms.txt
All documentation pages available in markdown.
This page describes structured server error details and how clients and operators use them.
When Aerospike Database rejects an operation, the client receives a top-level error code. Several codes cover more than one failure mode. For example, AS_ERR_FORBIDDEN (22) can mean stop-writes, clock skew, truncation in progress, or an XDR write filter block.
Overview
Starting in Aerospike Database 8.2.0, the server can attach optional structured error details to failure responses: a stable numeric subcode, a short message authored at the failure site, and, for failures that involve an expression, a structured expression trace. Clients opt in per request. The default is off, so existing applications keep working unchanged.
Applies to
- Database 8.2.0 and later
- Client libraries that implement error-detail verbosity. See Client support.
- The operation paths listed in Coverage
Audience
- Developers debugging failed operations or dispatching on
(status, subcode)pairs - Operators who cap error-detail verbosity cluster-wide
Prerequisites
- Database 8.2.0 or later on the nodes you query
- A client library version that exposes error details (see Client support)
Outcome
After reading this page, you can opt in to structured error details on supported clients, branch on (status, subcode) instead of top-level status alone, debug development issues using error messages and expression traces, and use operator settings to limit verbosity when identifiers in error text or resource usage are a concern.
Client support
Most clients support error details in full. Some do not implement the feature, and some expose only part of it, for example subcodes and messages but not the expression trace. Check the error-handling page for your client for what it supports, and the client matrix for the client version that pairs with Database 8.2.0.
The mechanism is the same everywhere: you opt in by setting an error-detail verbosity on the operation policy, and the results come back as fields on the error object or exception your client already raises.
The examples on this page use the Java client.
The Developer SDKs use typed exceptions and recovery hints appropriate to their language. See Error handling.
How it works
Structured error details use a per-request client opt-in and verbosity level to return optional information about errors encountered while Aerospike processes a client request.
Client opt-in
Set the error-detail verbosity on the operation policy, for example errorDetailVerbosity on Policy in the Java client or error_detail_verbosity on as_policy_base in the C client.
| Level | Server returns |
|---|---|
0 | Top-level status only (default) |
1 | Numeric subcode |
2 | Subcode and human-readable message |
3 | Subcode, message, and expression trace |
A client might define constants for only some of these levels. Requesting a level your client cannot decode is harmless: the server returns the detail and the client ignores what it does not understand.
What your client exposes
On a failed operation with error details enabled, the client’s error object carries up to four things:
- Status code: the top-level error code, unchanged from earlier versions.
- Subcode: a stable integer scoped to the status. Absent on the wire when the failure has no dispatch-worthy subcode.
- Message: short text written at the exact failure site in the server, for example
can't create record: namespace or set is truncated. Returned at verbosity2and up. - Expression trace: a structured description of where an expression failed. Returned at verbosity
3, only on failures that involve an expression.
In the Java client these are AerospikeException.getResultCode(), getSubCode(), getMessage(), and getExpressionTrace(). Other clients name them differently, and some fold the server message into their existing error-message field rather than exposing it separately. See the error-handling page for your client.
On the wire, the details ride in one response field on error responses only. Older clients skip the field so the feature is safe across mixed versions.
Dispatch on (status, subcode)
Subcode integers are scoped to their parent status. The pair (AS_ERR_PARAMETER, 1) and (AS_ERR_FORBIDDEN, 1) are different conditions. Always branch on both the top-level status and the subcode.
Subcode values are stable: once published, an integer never changes meaning, and retired values are not reused. When the server has no dispatchable subcode for a failure, it omits the subcode entirely. Do not treat subcode == 0 as a sentinel: subcode numbering starts at 1 for every status, so the server never publishes 0 as a real subcode. The Python client has a constant to represent “no subcode” called aerospike.SUB_NONE, while the Go client exposes a named types.SubCodeNone constant. The C, Node.js, and Rust clients use the plain integer 0 (Rust names it sub_code::NONE). Check for absence using your client’s own idiom rather than hardcoding a comparison against the literal 0, and confirm a subcode is present before dispatching on its value.
Operator cap
Use error-details-max-verbosity to limit what clients can receive, regardless of what they request. The server default is all. Server values off, codes, messages, and all map to client levels 0, 1, 2, and 3. Effective verbosity is min(client-requested, server-max). The setting is dynamic. Changes apply immediately, with no restart.
Choosing a verbosity
Error details trade performance for specificity. The happy path is hardly affected: details are built and returned only when a command fails. On failures, though, each verbosity level costs more bandwidth and server CPU than the one below it, up to a cap of about 1 KB of detail per record. When a detail would exceed the cap, the server drops whole parts of the expression trace rather than sending any part of it partially. Long string values in a filter explanation are clipped to 47 bytes with no marker. See Truncation.
Each level serves a different job:
- Subcodes (level 1) are for your code, and are the only detail meant for regular production use. A subcode adds a few bytes to a failure response. Subcodes are stable integers: dispatch on the
(status, subcode)pair to route retries, backpressure, or alerts. Do not parse message text in application logic. Wording can change between releases. Subcodes cannot. - Messages (level 2) and expression traces (level 3) are for people debugging during development, and interactive tools such as data browsers or LLMs. Use them to see exactly what the server objected to. Because they can contain database content, treat them like record read results: showing them to someone who could read the data directly, such as a developer or operator using a database workbench, is fine and often the point. Think carefully before passing them through to application end users who have no database access of their own.
Leave production applications at level 1 or 0 unless you have a specific reason to run higher, for example, capturing messages temporarily while troubleshooting an incident. An application failing at a high rate pays the detail cost on every error, in response bandwidth and in server-side work to build the details.
Enable error details
Set an error-detail verbosity on the policy you pass to the operation, then read the subcode from the error your client raises. Every client supports all four levels; they differ in whether the levels are named constants and in how the trace is surfaced.
Behavior withDetails = Behavior.DEFAULT.deriveWithChanges("withDetails", builder -> builder .on(Behavior.Selectors.all(), ops -> ops .errorDetailVerbosity(ErrorDetailVerbosity.MESSAGE) ));
try (Cluster cluster = new ClusterDefinition("CLUSTER_HOST", 3000).connect()) { Session session = cluster.createSession(withDetails); DataSet set = DataSet.of("NAMESPACE_NAME", "SET_NAME");
try { session.upsert(set.id("RECORD_KEY")) .bin("BIN_NAME").setTo("BIN_VALUE") .execute(); } catch (AerospikeException ae) { if (ae.getResultCode() == ResultCode.FAIL_FORBIDDEN && ae.getSubCode() == SubCode.FORBID_TRUNCATED) { // The set is mid-truncate, so the record cannot be created. // Retry with backoff. } else { throw ae; } }}Verbosity levels are named constants on ErrorDetailVerbosity. The exception exposes
getResultCode(), getSubCode(), getMessage(), and getExpressionTrace().
behavior = Behavior.DEFAULT.derive_with_changes( "with_details", all=Settings(error_detail_verbosity=ErrorDetailVerbosity.MESSAGE),)
with ClusterDefinition("CLUSTER_HOST", 3000).connect() as cluster: session = cluster.create_session(behavior) ds = DataSet.of("NAMESPACE_NAME", "SET_NAME")
try: session.upsert(ds.id("RECORD_KEY")).bin("BIN_NAME").set_to("BIN_VALUE").execute() except AerospikeError as e: print(e.result_code, e.sub_code, e.server_message)The error exposes result_code, sub_code, server_message, exp_trace, and hint.
server_message is None at verbosity 0.
let mut policy = WritePolicy::default();policy.base_policy.error_detail_verbosity = 2;
let key = Key::new("NAMESPACE_NAME", "SET_NAME", Value::from("RECORD_KEY"))?;match client.put(&policy, &key, &[as_bin!("BIN_NAME", "BIN_VALUE")]).await { Ok(_) => {} Err(e) => println!( "{:?} sub_code={} detail={:?}", e.server_result_code(), e.sub_code(), e.server_error_detail().map(|d| d.message.clone()) ),}Verbosity is a u8 on base_policy. The error exposes server_result_code(),
sub_code(), and server_error_detail(), whose fields are all Option.
var policy = new WritePolicy();policy.errorDetailVerbosity = 2;
var key = new Key("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");try{ client.Put(policy, key, new Bin("BIN_NAME", "BIN_VALUE"));}catch (AerospikeException ae){ Console.WriteLine($"{ae.Result} subcode={ae.SubCode} {ae.Message}");}Verbosity is a plain int. The exception exposes Result, SubCode, Message, and
ExpTrace. Set a cluster-wide default through the client’s public default-policy
properties, for example client.WritePolicyDefault.errorDetailVerbosity, instead of
setting it on every per-call policy.
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"policy := as.NewWritePolicy(0, 0)policy.ErrorDetailVerbosity = 2
key, _ := as.NewKey("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY")if err := client.PutBins(policy, key, as.NewBin("BIN_NAME", "BIN_VALUE")); err != nil { ae, _ := err.(*as.AerospikeError) fmt.Printf("status=%d subcode=%d %s\n", ae.ResultCode, ae.SubCode, err.Error())}Verbosity is a plain int. Error is an interface: assert to *as.AerospikeError to
reach ResultCode, SubCode, and ExpTrace.
const Aerospike = require('aerospike')
const key = new Aerospike.Key('NAMESPACE_NAME', 'SET_NAME', 'RECORD_KEY')const policy = { errorDetailVerbosity: Aerospike.errorDetailVerbosity.MESSAGE }
try { await client.put(key, { BIN_NAME: 'BIN_VALUE' }, {}, policy)} catch (error) { console.error('status=%d subcode=%d message=%s', error.code, error.subcode, error.message)}Verbosity levels are on the Aerospike.errorDetailVerbosity module. The error object
exposes code, subcode, and message.
as_policy_write wp;as_policy_write_init(&wp);wp.base.error_detail_verbosity = 2;
as_key key;as_key_init(&key, "NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");
as_record rec;as_record_inita(&rec, 1);as_record_set_str(&rec, "BIN_NAME", "BIN_VALUE");
as_error err;as_error_init(&err);if (aerospike_key_put(&as, &err, &wp, &key, &rec) != AEROSPIKE_OK) { printf("status=%d subcode=%u %s\n", err.code, err.subcode, err.message);}as_record_destroy(&rec);Verbosity is a uint8_t on the policy’s base. as_error carries code, subcode,
and message; the expression trace arrives as a suffix on message.
AerospikeClient client = new AerospikeClient("CLUSTER_HOST", 3000);Key key = new Key("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");WritePolicy policy = new WritePolicy(client.writePolicyDefault);policy.errorDetailVerbosity = 2;
try { client.put(policy, key, new Bin("BIN_NAME", "BIN_VALUE"));}catch (AerospikeException ae) { if (ae.getResultCode() == ResultCode.FAIL_FORBIDDEN && ae.getSubCode() == SubCode.FORBID_TRUNCATED) { // The set is mid-truncate, so the record cannot be created. // Retry with backoff. } else { throw ae; }}finally { client.close();}Verbosity is a plain int. Subcode constants are in the SubCode class. When the server
returns a message at verbosity 2, the client surfaces it through getMessage().
policy = {"error_detail_verbosity": aerospike.ERROR_DETAIL_MESSAGE}key = ("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY")
try: client.put(key, {"BIN_NAME": "BIN_VALUE"}, policy=policy)except ex.AerospikeError as e: print(e.code, e.subcode, e.msg)Verbosity levels are named constants: aerospike.ERROR_DETAIL_NONE, _SUBCODE,
_MESSAGE, and _EXP_TRACE. The exception exposes code, subcode, and msg; the
expression trace arrives as a suffix on msg.
Expression traces
Expressions fail in ways a status code cannot explain: a filter built from the wrong types, an operation that hits a type mismatch at evaluation time, or a filter that correctly, but surprisingly, rejects a record. At verbosity 3, failures that involve an expression include a trace that answers “where, and why”.
The trace tells you:
- Phase: whether the expression failed to build (malformed request) or failed while evaluating against a record.
- Location: the failing operation’s name, its position in the expression, and the chain of operations from the root to the failure.
- Snippet: a rendered view of the failing part of the expression. For expressions written in Aerospike Expression Language (AEL), the snippet points at the offending characters of your source text.
- Filter explanations: for a record rejected by a filter, the trace identifies the comparison that decided the outcome.
Developer SDK authors who write AEL strings can find slot-specific build messages, aelOffset / aelSpan in source text, and filter explainers on single-key commands in Debug AEL expressions.
Every trace field is optional. Treat any individual field as best-effort.
Truncation
An error detail is capped at about 1 KB, and the cap applies per record, so each row of a batch gets its own budget.
When a trace would exceed that budget, the server drops whole parts of it until the rest fits, starting with the operand values of a filter explanation and then the snippet. Parts are dropped whole: the server never sends half a path or half a snippet. If even the core of the trace does not fit, no trace is sent. A very deep path keeps its outer frames and the failing operation, and marks the elided middle with a ... element.
Because a dropped part is simply an absent field, you cannot tell it from a part that never applied. Operand values are absent whenever something other than a comparison rejected the record, so their absence alone does not mean a trace was truncated.
Separately from that budget, a string value in a filter explanation is clipped to its first 47 bytes, or slightly fewer when 47 bytes would split a multi-byte character. Because the limit is in bytes, a value outside ASCII shows fewer than 47 characters. Numbers, booleans, and nil are always shown in full, and collections, blobs, and GeoJSON appear as type placeholders such as <collection> rather than as values.
Reading a trace
At verbosity 3, the trace comes back in one of two shapes depending on the client. Java,
the SDKs, C#, Go, and Rust expose it as a structured object with individually
addressable fields. C, Node.js, and Python legacy append a ; exp_trace={...} suffix
to the error message.
Behavior withTrace = Behavior.DEFAULT.deriveWithChanges("withTrace", builder -> builder .on(Behavior.Selectors.all(), ops -> ops .errorDetailVerbosity(ErrorDetailVerbosity.EXPRESSION_TRACE) ));
Session session = cluster.createSession(withTrace);try { session.query(set.id("RECORD_KEY")) .where(Exp.gt(Exp.intBin("age"), Exp.val(21))) .failOnFilteredOut() .execute();}catch (AerospikeException.FilteredException fe) { ExpressionTrace trace = fe.getExpressionTrace(); if (trace != null) { System.out.println(trace); }}behavior = Behavior.DEFAULT.derive_with_changes( "with_trace", all=Settings(error_detail_verbosity=ErrorDetailVerbosity.EXPRESSION_TRACE),)session = cluster.create_session(behavior)
try: session.query(ds.id("RECORD_KEY")).where("$.age > 21").fail_on_filtered_out().execute()except FilteredOutError as e: print(e.exp_trace)let mut rp = ReadPolicy::default();rp.base_policy.error_detail_verbosity = 3;rp.base_policy.filter_expression = Some(gt(int_bin("age".to_string()), int_val(21)));
if let Err(e) = client.get(&rp, &key, Bins::All).await { println!("{:?}", e.server_error_detail().and_then(|d| d.exp_trace.as_ref()));}var policy = new Policy();policy.errorDetailVerbosity = 3;policy.filterExp = Exp.Build(Exp.GT(Exp.IntBin("age"), Exp.Val(21)));policy.failOnFilteredOut = true;
try{ client.Get(policy, key);}catch (AerospikeException ae){ if (ae.ExpTrace != null) { Console.WriteLine(ae.ExpTrace); }}// Requires: import as "github.com/aerospike/aerospike-client-go/v8"rp := as.NewPolicy()rp.ErrorDetailVerbosity = 3rp.FilterExpression = as.ExpGreater(as.ExpIntBin("age"), as.ExpIntVal(21))
if _, err := client.Get(rp, key); err != nil { ae, _ := err.(*as.AerospikeError) if ae.ExpTrace != nil { fmt.Printf("%+v\n", *ae.ExpTrace) }}Go returns FILTERED_OUT for a non-matching filter without any opt-in.
const Aerospike = require('aerospike')
const policy = new Aerospike.ReadPolicy({ errorDetailVerbosity: Aerospike.errorDetailVerbosity.EXP_TRACE, filterExpression: Aerospike.exp.gt(Aerospike.exp.binInt('age'), Aerospike.exp.int(21))})
try { await client.get(key, policy)} catch (error) { // error.message carries the `; exp_trace={...}` suffix console.log(error.message)}The Node.js client returns FILTERED_OUT for a non-matching filter without any opt-in, like
Go. It does not decode the trace into a separate field: error.message carries the same
; exp_trace={...} suffix as the C client, since the Node.js binding relays the underlying
C client’s message text verbatim. See
Node.js client error handling.
as_exp_build(filter, as_exp_cmp_gt(as_exp_bin_int("age"), as_exp_int(21)));
as_policy_read rp;as_policy_read_init(&rp);rp.base.error_detail_verbosity = 3;rp.base.filter_exp = filter;
as_record* out = NULL;as_error err;as_error_init(&err);if (aerospike_key_get(&as, &err, &rp, &key, &out) != AEROSPIKE_OK) { // err.message carries the `; exp_trace={...}` suffix printf("%s\n", err.message);}as_exp_destroy(filter);Policy policy = new Policy(client.readPolicyDefault);policy.errorDetailVerbosity = 3;policy.filterExp = Exp.build(Exp.gt(Exp.intBin("age"), Exp.val(21)));policy.failOnFilteredOut = true;
try { client.get(policy, key);}catch (AerospikeException ae) { ExpressionTrace trace = ae.getExpressionTrace(); if (trace != null) { System.out.println(trace); }}policy = { "error_detail_verbosity": aerospike.ERROR_DETAIL_EXP_TRACE, "expressions": GT(IntBin("age"), 21).compile(),}
try: client.get(key, policy=policy)except ex.AerospikeError as e: print(e.msg) # carries the `; exp_trace={...}` suffixFor a record with age = 15, the structured clients print:
ExpressionTrace[phase=2, byteOffset=-1, op=gt, depth=1, path=[gt], snippet=gt(bin_int("age"), 21), outcome=2, operands=[15, 21], lang=1]The clients that append to the message print the same trace with named values rather than integers:
filtered out - filter expression evaluated to false; exp_trace={phase="eval" op="gt" depth=1 path=["gt"] snippet="gt(bin_int(\"age\"), 21)" outcome="false" operands=["15","21"]}Both describe the same failure. If you are parsing the suffix form, match on outcome="false"
rather than outcome=2.
phase=2 is the eval phase, outcome=2 is OUTCOME_FALSE, meaning the filter ran and the record genuinely did not match, and operands are the two values that decided it: the record’s age against the literal 21. Had the record no age bin at all, outcome would be 3 (OUTCOME_ABSENT) and operands would be absent. Individual fields are also available through accessors such as getOutcome(), getOperands(), getOp(), getPath(), and getSnippet().
The outcome is worth reading, because a record that did not match, a filter that referenced an absent bin, and a filter that failed to evaluate all arrive as the same FILTERED_OUT status.
Reading record values through a trace requires read permission: on a cluster with security enabled, a client authorized only to write does not receive filter explanations at all, not just the operand values, because the outcome and the deciding operation also reveal record contents.
Coverage
Database 8.2.0 returns error details for these areas:
| Area | What you get |
|---|---|
| Single-record read, write, operate, delete | Subcodes and messages for TTL validation, set limits, truncation, and more. Includes proxied commands: details follow the response back to your client. |
| Batch | Each record in the batch carries its own error detail. A batch where three records fail returns three independent subcode/message pairs. |
| Expressions | Failures in filter expressions and expression operations return messages and, at verbosity 3, an expression trace. |
| Collection data type (CDT) List/Map | Index/rank out of bounds, bounded-list overflow, per-operation messages |
| HLL | Index bits unset, fold/minhash mismatches |
| Bits | Offset/size out of range, resize exceeded |
| String operations | Invalid parameters, regular expressions, UTF-8, and conversion failures |
| Transactions | Record locked, transaction ID mismatch, expiry details |
| User-defined function (UDF), client-invoked | Definition, policy, and execution messages, plus expression traces for a malformed or faulting UDF filter |
Queries return details only when the query fails to start, for example, when the query’s filter expression is malformed. A malformed filter expression on a background UDF or background operate job can also return details before the job starts. Record-level errors and filter outcomes that occur after a query starts do not carry error details.
Not covered: in-job failures for background UDF and background operate jobs, and batch-wide admission errors such as batch queues full.
Verify
This procedure uses a write with an out-of-range TTL, which the server rejects before it writes anything. The ladder it produces is the same in every client: level 1 adds the subcode, level 2 replaces the client’s generic text with the server’s message.
-
Confirm the server is not capping verbosity. On any node:
asinfo -h CLUSTER_HOST -v "get-config:context=service" | tr ';' '\n' | grep error-details-max-verbosityExpect
error-details-max-verbosity=all, the default. A lower value caps what you see in the steps below. -
Run the same failing write at each verbosity level:
for (int verbosity = 0; verbosity <= 2; verbosity++) {final int level = verbosity;Behavior behavior = Behavior.DEFAULT.deriveWithChanges("verify" + level, builder -> builder.on(Behavior.Selectors.all(), ops -> ops.errorDetailVerbosity(level)));Session session = cluster.createSession(behavior);try {// One second past the server's 10-year maximum record TTL.session.upsert(set.id("RECORD_KEY")).bin("v").setTo(1).expireRecordAfter(Duration.ofSeconds(315360001)).execute();System.out.println("verbosity=" + level + " unexpectedly succeeded");}catch (AerospikeException ae) {System.out.println("verbosity=" + level+ " status=" + ae.getResultCode()+ " subcode=" + ae.getSubCode()+ " message=" + ae.getMessage());}}for level in (ErrorDetailVerbosity.NONE,ErrorDetailVerbosity.SUBCODE,ErrorDetailVerbosity.MESSAGE):behavior = Behavior.DEFAULT.derive_with_changes(f"verify{int(level)}", all=Settings(error_detail_verbosity=level))session = cluster.create_session(behavior)try:# One second past the server's 10-year maximum record TTL.session.upsert(ds.id("RECORD_KEY")).bin("v").set_to(1) \.expire_record_after(timedelta(seconds=315360001)).execute()print(f"verbosity={int(level)} unexpectedly succeeded")except AerospikeError as e:print(f"verbosity={int(level)} result_code={e.result_code} "f"sub_code={e.sub_code} server_message={e.server_message!r}")for verbosity in 0u8..=2 {let mut policy = WritePolicy::default();policy.base_policy.error_detail_verbosity = verbosity;// One second past the server's 10-year maximum record TTL.policy.expiration = aerospike::Expiration::Seconds(315_360_001);match client.put(&policy, &key, &[as_bin!("v", 1)]).await {Ok(_) => println!("verbosity={verbosity} unexpectedly succeeded"),Err(e) => println!("verbosity={verbosity} status={:?} sub_code={} detail={:?}",e.server_result_code(), e.sub_code(),e.server_error_detail().map(|d| d.message.clone())),}}var key = new Key("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");for (int verbosity = 0; verbosity <= 2; verbosity++){var policy = new WritePolicy();policy.errorDetailVerbosity = verbosity;// One second past the server's 10-year maximum record TTL.policy.expiration = 315360001;try{client.Put(policy, key, new Bin("v", 1));Console.WriteLine($"verbosity={verbosity} unexpectedly succeeded");}catch (AerospikeException ae){Console.WriteLine($"verbosity={verbosity} status={ae.Result} subcode={ae.SubCode} message={ae.Message}");}}// Requires: import as "github.com/aerospike/aerospike-client-go/v8"for v := 0; v <= 2; v++ {policy := as.NewWritePolicy(0, 0)policy.ErrorDetailVerbosity = v// One second past the server's 10-year maximum record TTL.policy.Expiration = 315360001if err := client.PutBins(policy, key, as.NewBin("v", 1)); err != nil {ae, _ := err.(*as.AerospikeError)fmt.Printf("verbosity=%d status=%d subcode=%d message=%s\n",v, ae.ResultCode, ae.SubCode, err.Error())} else {fmt.Printf("verbosity=%d unexpectedly succeeded\n", v)}}const Aerospike = require('aerospike')for (const v of [Aerospike.errorDetailVerbosity.NONE,Aerospike.errorDetailVerbosity.SUBCODE,Aerospike.errorDetailVerbosity.MESSAGE]) {const policy = { errorDetailVerbosity: v }try {// One second past the server's 10-year maximum record TTL.await client.put(key, { v: 1 }, { ttl: 315360001 }, policy)console.log(`verbosity=${v} unexpectedly succeeded`)} catch (error) {console.log(`verbosity=${v} status=${error.code} subcode=${error.subcode} message=${error.message}`)}}for (uint8_t v = 0; v <= 2; v++) {as_policy_write wp;as_policy_write_init(&wp);wp.base.error_detail_verbosity = v;as_record rec;as_record_inita(&rec, 1);as_record_set_int64(&rec, "v", 1);// One second past the server's 10-year maximum record TTL.rec.ttl = 315360001;as_error e;as_error_init(&e);if (aerospike_key_put(&as, &e, &wp, &key, &rec) != AEROSPIKE_OK) {printf("verbosity=%u status=%d subcode=%u message=%s\n",v, e.code, e.subcode, e.message);}as_record_destroy(&rec);}In the C client the TTL is set on the record, not the write policy.
AerospikeClient client = new AerospikeClient("CLUSTER_HOST", 3000);Key key = new Key("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");try {for (int verbosity = 0; verbosity <= 2; verbosity++) {WritePolicy policy = new WritePolicy(client.writePolicyDefault);policy.errorDetailVerbosity = verbosity;// One second past the server's 10-year maximum record TTL.policy.expiration = 315360001;try {client.put(policy, key, new Bin("v", 1));System.out.println("verbosity=" + verbosity + " unexpectedly succeeded");}catch (AerospikeException ae) {System.out.println("verbosity=" + verbosity+ " status=" + ae.getResultCode()+ " subcode=" + ae.getSubCode()+ " message=" + ae.getMessage());}}}finally {client.close();}for level in (aerospike.ERROR_DETAIL_NONE,aerospike.ERROR_DETAIL_SUBCODE,aerospike.ERROR_DETAIL_MESSAGE):try:# One second past the server's 10-year maximum record TTL.client.put(key, {"v": 1}, meta={"ttl": 315360001},policy={"error_detail_verbosity": level})print(f"verbosity={level} unexpectedly succeeded")except ex.AerospikeError as e:print(f"verbosity={level} code={e.code} subcode={e.subcode} msg={e.msg}") -
Confirm the output matches this ladder. Accessor names differ by client; the values do not.
Level Status Subcode Server message 04(parameter error)0or absentclient text only, no server detail 141(TTL invalid)client text only 241contains invalid record TTL 315360001Java calls these
getResultCode(),getSubCode(), andgetMessage(); Go usesResultCode/SubCode; the Python SDK usesresult_code/sub_code/server_message; Python legacy and Node.js usecode/subcode/msgormessage; C# usesResult/SubCode/Message; Rust usesserver_result_code()/sub_code(); C readserr.codeanderr.subcode. The values are identical across all of them.Level
0is the control. If the subcode is0at level1, or the server text is missing at level2, error details are not reaching your client: check the server version, the client version, and the cap from step 1. -
Optional. To verify verbosity
3, write a record and reject it with a filter that reads one of its bins. See Reading a trace for the same procedure in every client; the Java form is:import com.aerospike.client.ExpressionTrace;import com.aerospike.client.exp.Exp;import com.aerospike.client.policy.Policy;AerospikeClient client = new AerospikeClient("CLUSTER_HOST", 3000);Key key = new Key("NAMESPACE_NAME", "SET_NAME", "RECORD_KEY");client.put(null, key, new Bin("age", 20));Policy policy = new Policy(client.readPolicyDefault);policy.errorDetailVerbosity = 3;policy.filterExp = Exp.build(Exp.gt(Exp.intBin("age"), Exp.val(21)));policy.failOnFilteredOut = true;try {client.get(policy, key);}catch (AerospikeException ae) {ExpressionTrace trace = ae.getExpressionTrace();System.out.println("status=" + ae.getResultCode()+ " message=" + ae.getMessage());if (trace != null) {System.out.println(trace);}}finally {client.close();} -
Confirm
getResultCode()is27(ResultCode.FILTERED_OUT) and the trace reportsop=gt,path=[gt], andsnippet=gt(bin_int("age"), 21).The filter must reference a bin. A filter over record metadata alone, such as
Exp.lastUpdate(), is decided before the record is read and returns a message with no trace.
Next steps
- Error subcodes,
(status, subcode)catalog - Error codes, top-level server and client codes
- Java client error handling
- C client error handling
- Go client error handling
- Python client error handling
- Node.js client error handling
- C# client error handling
- Rust client error handling
error-details-max-verbosity, operator cap