---
title: "String operations"
description: "Reference for the Aerospike C client's as_operations_string_* functions and as_exp_string_* macros for server-side string operations and expressions."
---

# String operations

> For the complete documentation index see: [llms.txt](https://aerospike.com/docs/llms.txt)
> 
> All documentation pages available in markdown.

Server-side [String](https://aerospike.com/docs/develop/data-types/string) operations let you search, transform, extract, and normalize text in a String bin without fetching the bin, editing it in your application, and writing it back.

This reference covers the Aerospike C client surface for developers already using [`aerospike_key_operate()`](https://aerospike.com/docs/develop/client/c/usage/atomic/multi) and [filter expressions](https://aerospike.com/docs/develop/expressions): the `as_operations_string_*` functions for `operate()` calls, and the `as_exp_string_*` macros for expressions built with [`as_exp_build()`](https://aerospike.com/docs/develop/expressions). After reading this page, you can choose the right `as_operations_string_*` or `as_exp_string_*` call for a task, configure an `as_string_policy`, and interpret `aerospike_key_operate()` results, including multiple results for the same bin.

String operations require Aerospike Database 8.2.0 and later and Aerospike C client 7.6.2 and later. See [Version requirements](#version-requirements) before using String operations against a production cluster.

For operation semantics, argument details, and the full 37-operation catalog, see the [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations) and [String expressions reference](https://aerospike.com/docs/develop/expressions/string). To establish the cluster connection used in the [Setup](#setup) examples, see [Connecting](https://aerospike.com/docs/develop/client/c/connect).

## Setup

The examples on this page use the following connection and key:

```c
#include <aerospike/aerospike.h>

#include <aerospike/aerospike_key.h>

#include <aerospike/as_error.h>

#include <aerospike/as_key.h>

#include <aerospike/as_operations.h>

#include <aerospike/as_record.h>

#include <aerospike/as_string_operations.h>

// Establishes a connection to the server

as_config config;

as_config_init(&config);

as_config_add_host(&config, "127.0.0.1", 3000);

aerospike as;

aerospike_init(&as, &config);

as_error err;

if (aerospike_connect(&as, &err) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

// Creates a key with the namespace "test", set "users", and user key "userId"

as_key key;

as_key_init_str(&key, "test", "users", "userId");
```

## Round-trip elimination

Without String operations, normalizing a bin takes a read, an application-side edit, and a write. A hand-rolled, byte-wise lowercase loop also handles only ASCII (American Standard Code for Information Interchange) codepoints:

```c
// Before: fetch, modify (ASCII-only), write

as_record* rec = NULL;

if (aerospike_key_get(&as, &err, NULL, &key, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

char* email = as_record_get_str(rec, "email");

for (char* p = email; *p != '\0'; p++) {

    *p = tolower((unsigned char)*p);   // breaks on non-ASCII codepoints

}

if (aerospike_key_put(&as, &err, NULL, &key, rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

as_record_destroy(rec);
```

`as_operations_string_lower` runs the same edit inside a single `aerospike_key_operate()` call, on the server, correctly for any Unicode codepoint (a single character unit in a string, which can span multiple bytes in UTF-8):

```c
as_string_policy policy;

as_string_policy_init(&policy);

// After: one round trip, Unicode-aware

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_string_lower(&ops, "email", NULL, &policy);

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, NULL) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

as_operations_destroy(&ops);
```

To confirm a modify operation applied as expected instead of assuming success from a non-error status alone, read the bin back:

```c
as_record* rec = NULL;

if (aerospike_key_get(&as, &err, NULL, &key, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    printf("email = %s\n", as_record_get_str(rec, "email"));

}

as_record_destroy(rec);
```

## Two surfaces

Every String operation is available in two forms:

| Surface | Naming | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `as_operations_string_*` | `aerospike_key_operate()` | Bin name first: `as_operations_string_strlen(ops, name, ctx)` |
| Expression | `as_exp_string_*` | `as_exp_build()`, `as_operations_exp_read()`/`as_operations_exp_write()`, [`as_policy_read.filter_exp`](https://aerospike.com/docs/develop/expressions) and similar | Source expression last: `as_exp_string_strlen(bin)` |

`as_operations_string_*` functions read or modify a bin directly and return `bool` (whether the operation was added to the `as_operations` array). `as_exp_string_*` macros expand into `as_exp_entry` tokens that compose inside a larger `as_exp_build()` expression tree, with no separate build step for the sub-expression itself.

A modify-style `as_exp_string_*` macro (`as_exp_string_upper`, `as_exp_string_replace`, `as_exp_string_trim`, and similar) returns the transformed string as an expression value. Persist that value with `as_operations_exp_write()`, or call the matching `as_operations_string_*` function to write the bin directly. See [Nested strings](#nested-strings) for a worked filter and projection example.

Every `as_operations_string_*` function also takes an optional [`as_cdt_ctx`](https://aerospike.com/docs/develop/data-types/collections/context)`* ctx` argument to reach a string nested inside a list or map. Pass `NULL` for `ctx` at the top level. See [Nested strings](#nested-strings) for nested examples. On modify operations, the policy argument comes immediately after `ctx`, ahead of the operation’s own arguments (`index`, `value`, and similar):

```c
// Operation: name, ctx, then policy, then the operation's own arguments

as_operations_string_upper(&ops, "text", NULL, &policy);

// Expression: policy first, source expression last

as_exp_string_upper(&policy, as_exp_bin_str("text"));
```

## String write policy

Modify operations take an `as_string_policy`, which wraps a bitmask of `as_string_write_flags`:

```c
as_string_policy policy;

as_string_policy_init(&policy);                                              // AS_STRING_WRITE_FLAGS_DEFAULT (0)

as_string_policy custom;

as_string_policy_init(&custom);

as_string_policy_set(&custom, AS_STRING_WRITE_FLAGS_CREATE_ONLY | AS_STRING_WRITE_FLAGS_NO_FAIL); // combine with bitwise OR
```

| Flag | Value | Effect |
| --- | --- | --- |
| `AS_STRING_WRITE_FLAGS_DEFAULT` | 0 | Allow create or update, subject to the operation’s own create capability (see the `CREATE_ONLY` column in [Modify operations](#modify-operations)). |
| `AS_STRING_WRITE_FLAGS_CREATE_ONLY` | 1 | Apply only if the bin doesn’t already exist. Fails with `AEROSPIKE_ERR_BIN_EXISTS` against a live bin. Valid only on the eight operations that can create a missing bin (`insert`, `overwrite`, `concat`, `append`, `prepend`, `pad_start`, `pad_end`, `repeat`). See the `CREATE_ONLY` column in [Modify operations](#modify-operations). Every other modify operation, and any operation carrying a `ctx` path, rejects it with `AEROSPIKE_ERR_REQUEST_INVALID` during argument parsing. |
| `AS_STRING_WRITE_FLAGS_UPDATE_ONLY` | 2 | Apply only to an existing bin, disabling bin creation. Against a missing bin, the operation is a silent no-op, and the bin is not created. Valid on every modify operation. Mutually exclusive with `CREATE_ONLY`. Combining both returns `AEROSPIKE_ERR_REQUEST_INVALID`. |
| `AS_STRING_WRITE_FLAGS_NO_FAIL` | 4 | Don’t raise an error when the modify itself can’t be applied. The operation becomes a silent success, and the bin keeps its unmodified prior value. Doesn’t suppress `AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE`, ill-formed UTF-8, or the `CREATE_ONLY` argument-parsing rejections described in the `AS_STRING_WRITE_FLAGS_CREATE_ONLY` row. |

::: string update_only vs. expression update_only
`as_exp_write_flags`’ own `AS_EXP_WRITE_UPDATE_ONLY` fails with `AEROSPIKE_ERR_BIN_NOT_FOUND` against a missing bin. The String module’s `AS_STRING_WRITE_FLAGS_UPDATE_ONLY` does not: it is a silent no-op instead, with no error at all. Don’t assume the two modules’ `UPDATE_ONLY` behave the same way.
:::

`as_string_policy` is a per-operation argument, not client configuration: build one with `as_string_policy_init()`/`as_string_policy_set()` and pass it to each call that needs non-default flags.

`CREATE_ONLY`, `UPDATE_ONLY` argument-parsing rejections, and most other argument errors return a non-`AEROSPIKE_OK` status from `aerospike_key_operate()` rather than failing silently. Check the status and inspect `err.code` to distinguish an expected condition from one you must propagate. See [Error handling](https://aerospike.com/docs/develop/client/c/error-handling) for the general `as_error` pattern used throughout this page:

```c
as_string_policy create_only;

as_string_policy_init(&create_only);

as_string_policy_set(&create_only, AS_STRING_WRITE_FLAGS_CREATE_ONLY);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_string_insert(&ops, "email", NULL, &create_only, 0, "prefix-");

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, NULL) != AEROSPIKE_OK) {

    if (err.code == AEROSPIKE_ERR_BIN_EXISTS) {

        // Expected: the bin already had a value, so CREATE_ONLY rejected the insert

    }

    else {

        fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

    }

}

as_operations_destroy(&ops);
```

In a multi-operation `aerospike_key_operate()` call, `NO_FAIL` only suppresses the affected operation. Sibling operations in the same call still commit, and the client receives no error either way. Read the bin back to confirm which operations applied rather than assuming success from a non-error status.

On every `as_exp_string_*` modify macro, including `as_exp_string_regex_replace`, only `AS_STRING_WRITE_FLAGS_NO_FAIL` is meaningful. `CREATE_ONLY` and `UPDATE_ONLY` are bin-existence predicates, so they don’t carry over to a source expression that may not be a bin at all. See the following caution about numeric flag values.

::: two flag families share numeric values
`as_string_write_flags` and `as_string_regex_flags` are separate C enums, but `AS_STRING_WRITE_FLAGS_NO_FAIL` (4) has the same numeric value as `AS_STRING_REGEX_FLAGS_DOTALL` (4), and `AS_STRING_WRITE_FLAGS_UPDATE_ONLY` (2) matches `AS_STRING_REGEX_FLAGS_MULTILINE` (2). The C compiler accepts either enum’s constants wherever a plain integer is expected. Assigning the wrong constant to `as_string_policy.flags` (through `as_string_policy_set()`) or to `as_operations_string_regex_replace()`’s trailing `flags` argument compiles without warning and silently selects the wrong behavior. Keep the two flag families in clearly named variables, and never combine them.
:::

## Read operations

Read operations take the bin name, an optional `as_cdt_ctx* ctx`, and the operation’s own arguments. Pass `NULL` for `ctx` at the top level, or a path to a value nested in a list or map, covered in [Nested strings](#nested-strings). In the following table, index and length arguments count Unicode codepoints, not bytes. Most characters are one codepoint, but some emoji and other characters outside the Unicode Basic Multilingual Plane (BMP, codepoints U+0000 through U+FFFF) are more than one codepoint.

| Operation | C functions | Returns | Description |
| --- | --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | `as_operations_string_strlen` / `as_exp_string_strlen` | Integer | Codepoint count. |
| [`byte_length`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | `as_operations_string_byte_length` / `as_exp_string_byte_length` | Integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | `as_operations_string_substr` / `_substr_range`, `as_exp_string_substr` / `_substr_range` | String | Substring from a start index, or a half-open `[start, end)` range. |
| [`char_at`](https://aerospike.com/docs/develop/data-types/string/operations#char_at) | `as_operations_string_char_at` / `as_exp_string_char_at` | String | Single codepoint at an index, as a one-codepoint string. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | `as_operations_string_find` / `_find_occurrence`, `as_exp_string_find` / `_find_occurrence` | Integer | Codepoint index of `needle`, or a specific 1-based occurrence. `-1` if absent. |
| [`contains`](https://aerospike.com/docs/develop/data-types/string/operations#contains) | `as_operations_string_contains` / `as_exp_string_contains` | Boolean | Whether `needle` is a substring. |
| [`starts_with`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | `as_operations_string_starts_with` / `as_exp_string_starts_with` | Boolean | Whether the bin begins with `prefix`. |
| [`ends_with`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | `as_operations_string_ends_with` / `as_exp_string_ends_with` | Boolean | Whether the bin ends with `suffix`. |
| [`is_numeric`](https://aerospike.com/docs/develop/data-types/string/operations#is_numeric) | `as_operations_string_is_numeric` / `_is_numeric_type`, `as_exp_string_is_numeric` / `_is_numeric_type` | Boolean | Whether the bin is a valid integer or float, optionally filtered by `as_string_numeric_type`. |
| [`is_upper`](https://aerospike.com/docs/develop/data-types/string/operations#is_upper) | `as_operations_string_is_upper` / `as_exp_string_is_upper` | Boolean | Whether every cased codepoint is uppercase. |
| [`is_lower`](https://aerospike.com/docs/develop/data-types/string/operations#is_lower) | `as_operations_string_is_lower` / `as_exp_string_is_lower` | Boolean | Whether every cased codepoint is lowercase. |
| [`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) | `as_operations_string_regex_compare` / `_regex_compare_flags`, `as_exp_string_regex_compare` / `_regex_compare_flags` | Boolean | Whether an ICU (International Components for Unicode) regex `pattern` matches, optionally with `as_string_regex_flags`. |
| [`to_integer`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | `as_operations_string_to_integer` / `as_exp_string_to_integer` | Integer | Parse as an `int64`. Fails with `AEROSPIKE_ERR_OP_NOT_APPLICABLE` (`AS_SUB_OPNOT_STRING_CONVERSION_FAILED`, subcode 10) if unparsable. |
| [`to_double`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | `as_operations_string_to_double` / `as_exp_string_to_double` | Float | Parse as a 64-bit float. Same failure subcode as `to_integer` if unparsable. |
| [`to_blob`](https://aerospike.com/docs/develop/data-types/string/operations#to_blob) | `as_operations_string_to_blob` / `as_exp_string_to_blob` | Blob | The UTF-8 bytes of the string, as a [Blob](https://aerospike.com/docs/develop/data-types/blob). |
| [`split`](https://aerospike.com/docs/develop/data-types/string/operations#split) | `as_operations_string_split` / `_split_separator`, `as_exp_string_split` / `_split_separator` | List | Splits by Unicode codepoint, or by `separator` (a singleton list if `separator` isn’t found). |
| [`b64_decode`](https://aerospike.com/docs/develop/data-types/string/operations#b64_decode) | `as_operations_string_b64_decode` / `as_exp_string_b64_decode` | Blob | Decodes the bin as base64 text. Fails with `AEROSPIKE_ERR_OP_NOT_APPLICABLE` (`AS_SUB_OPNOT_STRING_B64_INVALID`, subcode 13) if it isn’t valid base64. |

Six read operations (`contains`, `starts_with`, `ends_with`, `is_numeric`, `is_upper`, `is_lower`) and `regex_compare` decode as `as_boolean`. See [Reading operate results](#reading-operate-results).

## Modify operations

Modify operations write the transformed value back to the bin (`as_operations_string_*`) or return it as an expression value (`as_exp_string_*`). An expression-side modify macro does not mutate the underlying bin unless its result is persisted with `as_operations_exp_write()`.

::: destructive, irreversible writes
`as_operations_string_*` modify calls, and `as_exp_string_*` modify macros persisted with `as_operations_exp_write()`, overwrite the stored bin value on the server, with no built-in undo. `snip`, `replace`, `replace_all`, `regex_replace`, and `overwrite` can truncate or permanently discard part of the value if the index, pattern, or range is wrong. Test destructive operations against sample data before running them against production records, and keep a backup of any bin you modify this way in production.
:::
::: modify operations are not idempotent
`insert`, `append`, `concat`, `prepend`, `pad_start`, `pad_end`, and `repeat` each apply their effect again on every call, with no check for a prior identical call. A client-side retry after a socket timeout on `aerospike_key_operate()` can resend the same modify operation, duplicating the appended, inserted, or repeated text in the bin, with `aerospike_key_operate()` returning `AEROSPIKE_OK` on the retry the same as on the original call. Disable retries for these operations, or read the bin back and check its generation before retrying, if a duplicated effect is unacceptable.
:::
::: legacy data can fail with invalid utf-8
String operations validate UTF-8 before they run. A bin written by legacy, non-UTF-8-aware code can fail with `AEROSPIKE_INVALID_ENCODING` (status code 29). See [String operations and UTF-8 validation](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#string-operations-and-utf-8-validation) before running these operations against existing bins.
:::

| Operation | `CREATE_ONLY` valid? | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | Yes | Splice `value` in at a codepoint index. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | Yes | Overwrite codepoints starting at an index. The result may grow beyond the original length when `value` extends past the end. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | Yes | Append one string (`as_operations_string_concat`), or each element of an `as_list` of strings in order (`_concat_list`). |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | Yes | Append `value`, with Unicode-aware concatenation. See [Deprecations](#deprecations) for the legacy byte-level alternative. |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | Yes | Prepend `value`, with Unicode-aware concatenation. See [Deprecations](#deprecations) for the legacy byte-level alternative. |
| [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | Yes | Left-pad with `pad_string` up to `target_length` codepoints. No-op if already at or above the target. |
| [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | Yes | Right-pad with `pad_string` up to `target_length` codepoints. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | Yes | Repeat the bin `count` times. |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | No | Remove a `[start, end)` range (`as_operations_string_snip`), or truncate from `start` to the end (`_snip_start`, see the caution following this table). |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | No | Replace the first occurrence of `needle` with `replacement`. |
| [`replace_all`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | No | Replace every occurrence of `needle`. |
| [`upper`](https://aerospike.com/docs/develop/data-types/string/operations#upper) / [`lower`](https://aerospike.com/docs/develop/data-types/string/operations#lower) | No | Uppercase or lowercase the stored bin value. |
| [`case_fold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | No | Locale-independent case fold, for comparison keys. |
| [`normalize_nfc`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | No | Normalize to Unicode NFC (Normalization Form Composed). Already-normalized strings are unchanged. |
| [`trim_start`](https://aerospike.com/docs/develop/data-types/string/operations#trim_start) / [`trim_end`](https://aerospike.com/docs/develop/data-types/string/operations#trim_end) / [`trim`](https://aerospike.com/docs/develop/data-types/string/operations#trim) | No | Remove Unicode whitespace from the start, end, or both. |
| [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | No | Replace regex `pattern` matches with `replacement`. Pass `AS_STRING_REGEX_FLAGS_GLOBAL` to replace every match instead of only the first. `NO_FAIL` also suppresses a regex compile failure. |

Only the eight operations marked “Yes” accept `AS_STRING_WRITE_FLAGS_CREATE_ONLY`. The server rejects it with `AEROSPIKE_ERR_REQUEST_INVALID` on every other modify operation, and on any operation that carries a `ctx` (nested) path. `UPDATE_ONLY` and `NO_FAIL` are valid on all of them.

::: the single-argument snip overload drops policy flags on the wire
`as_operations_string_snip_start(ops, name, ctx, policy, start)` exists for callers who only need a truncate-to-end `snip`. The server’s `snip` argument list is positional (`start`, `end`, `flags`), so this one-argument form can’t carry policy flags without also supplying an explicit `end`. `policy` is accepted for signature parity with the other modify operations, but it is not transmitted: `NO_FAIL` and every other flag are silently ignored. Use `as_operations_string_snip(ops, name, ctx, policy, start, end)` with an explicit `end` when write flags must be honored.
:::

## Type conversion

`as_operations_to_string`/`as_exp_to_string` convert an integer, double, boolean, string, or blob bin to its string representation:

```c
as_operations ops;

as_operations_inita(&ops, 1);

as_operations_to_string(&ops, "n");

as_record* rec = NULL;

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    printf("%s\n", as_record_get_str(rec, "n"));

}

as_operations_destroy(&ops);

as_record_destroy(rec);
```

`as_operations_to_string` is the only string operation that does not accept a `ctx` at all: its signature omits the parameter entirely, and it doesn’t send a MessagePack (msgpack) sub-operation payload. It is a distinct top-level wire operation that always reads the whole bin. It fails with `AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE` for any other bin type, and with `AEROSPIKE_ERR_OP_NOT_APPLICABLE` (`AS_SUB_OPNOT_STRING_UTF8_INVALID`, subcode 11) for a blob bin whose bytes aren’t valid UTF-8.

To convert a value nested inside a list or map, extract the nested string first with the equivalent List/Map get operation (using the same `ctx`), then convert it client-side. Or compose `as_exp_to_string` with `as_exp_list_get_by_index`/`as_exp_map_get_by_key` inside an expression.

## Regex and numeric-type flags

`as_string_regex_flags` (combine with bitwise OR) controls `regex_compare` and `regex_replace`:

| Flag | Applies to |
| --- | --- |
| `AS_STRING_REGEX_FLAGS_CASE_INSENSITIVE` | Both |
| `AS_STRING_REGEX_FLAGS_MULTILINE` | Both |
| `AS_STRING_REGEX_FLAGS_DOTALL` | Both |
| `AS_STRING_REGEX_FLAGS_UNIX_LINES` | Both |
| `AS_STRING_REGEX_FLAGS_GLOBAL` | `regex_replace` only. Replaces every match instead of only the first. |

`as_string_numeric_type` narrows `is_numeric_type`: `AS_STRING_NUMERIC_ANY` (default), `AS_STRING_NUMERIC_INT`, or `AS_STRING_NUMERIC_FLOAT`. `AS_STRING_NUMERIC_FLOAT` requires a literal `.` followed by a digit, so `is_numeric_type("5", AS_STRING_NUMERIC_FLOAT)` is `false` even though `"5"` parses as a double.

## Reading operate results

### Booleans decode as as\_boolean

`contains`, `starts_with`, `ends_with`, `is_numeric`, `is_upper`, `is_lower`, and `regex_compare` decode as `as_boolean`:

```c
as_operations ops;

as_operations_inita(&ops, 1);

as_operations_string_contains(&ops, "email", NULL, "@");

as_record* rec = NULL;

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    bool has_at = as_record_get_bool(rec, "email");   // correct

    // as_record_get_int64(rec, "email", 0) does not read the boolean correctly

}

as_operations_destroy(&ops);

as_record_destroy(rec);
```

### Multiple operations on one bin return multiple bin entries

When more than one operation in a single `aerospike_key_operate()` call targets the same bin, the C client does not group the results into one list-valued bin. Unlike the Java, Python, Go, C#, and Node.js clients, it returns separate entries: `as_record.bins.entries` contains one `as_bin` per operation, in submission order, each carrying the same bin name. This mirrors the equivalent CDT case already documented under [Multiple Results for the Same Bin](https://aerospike.com/docs/develop/client/c/usage/atomic/multi#multiple-results-for-the-same-bin) in Bin operations.

```c
as_string_policy policy;

as_string_policy_init(&policy);

as_operations ops;

as_operations_inita(&ops, 2);

as_operations_string_trim(&ops, "email", NULL, &policy);   // modify: entry 0

as_operations_add_read(&ops, "email");                      // read: entry 1

as_record* rec = NULL;

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    // rec->bins.size == 2 for this call

    as_bin* results = rec->bins.entries;

    const char* trimmed = as_string_get((as_string*)results[1].valuep);

}

as_operations_destroy(&ops);

as_record_destroy(rec);
```

The modify operation still occupies an entry rather than being skipped, so `results[1]` (not `results[0]`) holds the read result shown in the preceding example.

A single string operation on a bin, with nothing else targeting that bin, populates exactly one `as_bin` entry, accessible directly with `as_record_get_str()`/`as_record_get_int64()`/`as_record_get_bool()` and similar.

## Nested strings

`as_operations_string_*` functions take an optional `as_cdt_ctx* ctx` to reach a string nested inside a list or map. The path must already resolve to a string. A non-string leaf fails with `AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE`.

```c
// Uppercase a string nested in list bin "items" at index 0.

as_string_policy policy;

as_string_policy_init(&policy);

as_cdt_ctx ctx;

as_cdt_ctx_init(&ctx, 1);

as_cdt_ctx_add_list_index(&ctx, 0);

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_string_upper(&ops, "items", &ctx, &policy);

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, NULL) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

as_operations_destroy(&ops);

as_cdt_ctx_destroy(&ctx);
```

```c
// Read strlen of a string nested under map key "bio".

as_cdt_ctx bio_ctx;

as_cdt_ctx_init(&bio_ctx, 1);

as_cdt_ctx_add_map_key(&bio_ctx, (as_val*)as_string_new("bio", false));

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_string_strlen(&ops, "profile", &bio_ctx);

as_record* rec = NULL;

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    int64_t bio_len = as_record_get_int64(rec, "profile", 0);

}

as_operations_destroy(&ops);

as_record_destroy(rec);

as_cdt_ctx_destroy(&bio_ctx);   // also frees the as_string key: the ctx list takes ownership of it
```

`as_exp_string_*` macros take no `ctx` at all. To apply a string expression to a nested value, project the value first with `as_exp_list_get_by_index`/`as_exp_map_get_by_key` (which do take a `ctx`, for further nesting), then pass the result as the source expression. The following example builds an `as_exp_string_strlen` condition on a map key, then uses it two ways: as a read filter, and as a projected read value.

This example also requires `#include <aerospike/as_exp.h>` and `#include <aerospike/as_exp_operations.h>`, in addition to the includes in [Setup](#setup):

```c
// bio_len > 280: is the bin's bio over 280 codepoints?

as_exp_build(is_long,

    as_exp_cmp_gt(

        as_exp_string_strlen(

            as_exp_map_get_by_key(NULL, AS_MAP_RETURN_VALUE, AS_EXP_TYPE_STR,

                as_exp_str("bio"), as_exp_bin_map("profile"))),

        as_exp_int(280)));

// As a filter: fetch the record only if its bio is over 280 codepoints

as_policy_read read_policy;

as_policy_read_init(&read_policy);

read_policy.filter_exp = is_long;

as_record* filtered = NULL;

as_status status = aerospike_key_get(&as, &err, &read_policy, &key, &filtered);

if (status != AEROSPIKE_OK && status != AEROSPIKE_FILTERED_OUT) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

if (filtered != NULL) {

    as_record_destroy(filtered);

}

// As a projection: always fetch the record, with the condition's result in a computed bin

as_operations ops;

as_operations_inita(&ops, 1);

as_operations_exp_read(&ops, "isLong", is_long, AS_EXP_READ_DEFAULT);

as_record* projected = NULL;

if (aerospike_key_operate(&as, &err, NULL, &key, &ops, &projected) != AEROSPIKE_OK) {

    fprintf(stderr, "err(%d) %s at [%s:%d]\n", err.code, err.message, err.file, err.line);

}

else {

    bool bio_is_long = as_record_get_bool(projected, "isLong");

}

as_operations_destroy(&ops);

as_record_destroy(projected);

as_exp_destroy(is_long);
```

`as_operations_to_string`/`as_exp_to_string` never accept `ctx`, on either surface. See [Type conversion](#type-conversion).

## Version requirements

String operations require Aerospike Database 8.2.0 and later on every node, and Aerospike C client 7.6.2 and later. Aerospike Database prior to 8.2.0 does not recognize the string opcodes and returns a generic parameter error, not a distinct “unsupported feature” code. Check the cluster version rather than branching on the error.

::: rolling upgrades
During a rolling upgrade, nodes already running Aerospike Database 8.2.0 and later accept String operations, while nodes not yet upgraded reject them with the same generic parameter error. The same application code can succeed or fail depending on which node serves the request. Wait until every node in the cluster runs Aerospike Database 8.2.0 and later before enabling String operations in application code.
:::

## Deprecations

-   The legacy `as_operations_add_append_str`/`_strp` and `as_operations_add_prepend_str`/`_strp` are deprecated in Aerospike C client 7.5.0 in favor of `as_operations_string_append`/`as_operations_string_prepend`, which are Unicode-aware (the legacy pair does a raw byte concatenation). No removal version is scheduled.
-   The legacy `as_operations_add_append_raw`/`_rawp` and `as_operations_add_prepend_raw`/`_rawp` are not deprecated. They remain the only way to append or prepend a blob value, since the string module can’t target blob bins. For a blob bin, keep using these functions: the behavior is unchanged and fully supported.
-   The legacy `as_exp_cmp_regex` (POSIX regex) is deprecated in Aerospike C client 7.5.0 in favor of `as_exp_string_regex_compare`, which is Unicode-aware (ICU regex). No removal version is scheduled.

::: migration can surface invalid encoding
Each call to `as_operations_string_append` or `as_operations_string_prepend` validates the bin’s existing bytes as UTF-8. If existing data written through the legacy byte-concatenation path contains invalid UTF-8, the new call fails with `AEROSPIKE_INVALID_ENCODING` instead of succeeding as before. Audit affected bins for valid UTF-8 as described in [Repair legacy String bins](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#repair-legacy-string-bins) before switching write paths in production.
:::

## Next steps

-   [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations): full semantics, index-bounds behavior, and error codes for all 37 operations
-   [String expressions reference](https://aerospike.com/docs/develop/expressions/string)
-   [String examples](https://aerospike.com/docs/develop/data-types/string/examples)
-   [String operations and UTF-8 validation](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#string-operations-and-utf-8-validation)
-   [Bin operations](https://aerospike.com/docs/develop/client/c/usage/atomic/multi)
-   [Connecting](https://aerospike.com/docs/develop/client/c/connect): establishing the cluster connection used on this page
-   [Error handling](https://aerospike.com/docs/develop/client/c/error-handling): the `as_error` pattern used throughout this page
-   [API reference (C)](https://aerospike.com/apidocs/c/)