---
title: "String operations"
description: "Reference for the C# client's StringOperation builders and StringExp builders 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.

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

This reference covers the Aerospike C# client surface for developers already using [`client.Operate()`](https://aerospike.com/docs/develop/client/csharp/usage/atomic/multi) and [Expressions - C#](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions): the `StringOperation` builders for `Operate()` calls, and the `StringExp` builders for expressions. After reading this page, you can choose the right builder for a task, configure a `StringPolicy`, and interpret `Operate()` results.

It requires Aerospike Database 8.2.0 or later and Aerospike C# client 8.5.0 or later. See [Version requirements](#version-requirements) before upgrading 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).

## Setup

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

```csharp
using Aerospike.Client;

using System.Collections;

// Define host configuration

Host config = new Host("127.0.0.1", 3000);

// Establishes a connection to the server

AerospikeClient client = new AerospikeClient(null, config);

// Creates a key with the namespace "sandbox", set "users", and user key "jdoe123"

Key key = new Key("sandbox", "users", "jdoe123");
```

## Round-trip elimination

Without String operations, normalizing a bin takes a read, an application-side edit, and a write:

```csharp
// Before: fetch, modify, write

Record record = client.Get(null, key);

string email = record.GetString("email").Trim().ToLower();

client.Put(null, key, new Bin("email", email));
```

`StringOperation` runs the same edit inside a single `Operate` call, on the server:

```csharp
StringPolicy policy = StringPolicy.Default;

// After: one round trip

client.Operate(null, key,

    StringOperation.Trim(policy, "email"),

    StringOperation.Lower(policy, "email"));

// Verify: "  Jane.Doe@Example.com  " becomes "jane.doe@example.com"

Record record = client.Get(null, key);

Console.WriteLine(record.GetString("email"));
```

## Two surfaces

Every String operation is available in two forms:

| Surface | Naming | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `StringOperation.*` | `client.Operate()` | Bin name first: `StringOperation.Strlen(binName, ctx...)` |
| Expression | `StringExp.*` | `Policy.filterExp`/`WritePolicy.filterExp` (using `Exp.Build(...)`), [operation expressions](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions#operation-expressions) ([`ExpOperation.Read`](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions#read-1)/[`ExpOperation.Write`](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions#write-1)) | Source expression last: `StringExp.Strlen(src)` |

`StringOperation` builders read or modify a bin directly, returning an `Operation` for `client.Operate()`. `StringExp` builders return an `Exp` node that composes inside a larger expression tree. Wrap the finished tree with `Exp.Build(...)` to get the `Expression` that `Policy.filterExp`/`WritePolicy.filterExp` and `ExpOperation.Read`/`ExpOperation.Write` expect.

A modify-style `StringExp` builder (`StringExp.Upper`, `StringExp.Replace`, `StringExp.Trim`, and similar) returns the transformed string as a value. It does not write the result back to the bin on its own. To persist a modify expression’s result, write it back with [`ExpOperation.Write`](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions#write-1), or use the `StringOperation` equivalent instead. See [Nested strings](#nested-strings) for a worked filter and projection example.

Modify operations also take a policy as their first argument, ahead of the bin name or source expression:

```csharp
// Operation: policy, then bin name

StringOperation.Upper(StringPolicy.Default, "text");

// Expression: policy, then source expression last

StringExp.Upper(StringPolicy.Default, Exp.StringBin("text"));
```

## String write policy

Modify operations take a `StringPolicy`, which wraps a `StringWriteFlags` value:

```csharp
StringPolicy policy = StringPolicy.Default;                       // DEFAULT (0)

StringPolicy custom = new StringPolicy(StringWriteFlags.NO_FAIL); // NO_FAIL (4)
```

| Flag | Value | Effect |
| --- | --- | --- |
| `DEFAULT` | 0 | Allow create or update. |
| `CREATE_ONLY` | 1 | Apply the operation only if the bin doesn’t already exist. Valid only on eight create-capable operations; see the caution below. |
| `UPDATE_ONLY` | 2 | Apply the operation only to an existing bin. Valid on every modify operation. Against a missing bin, the operation is a silent no-op: it returns success without creating the bin. |
| `NO_FAIL` | 4 | Return success and leave the bin at its prior value if the operation can’t be applied, instead of failing. String modify operations return no value in either case, so a suppressed operation and an applied one look the same in the response; read the bin to tell them apart. |

::: update_only can silently no-op
A typo’d bin name, or a bin another process already deleted, makes `UPDATE_ONLY` succeed without writing anything, which can mask a production bug the same way `NO_FAIL` can (see below). On any production write path that relies on `UPDATE_ONLY` to avoid creating a bin, verify the outcome with a read-after-write rather than trusting the absence of an error.
:::

### CREATE\_ONLY rules

`CREATE_ONLY` is valid only on the eight operations that can create a missing bin: [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert), [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite), [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat), [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append), [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend), [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start), [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end), and [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat).

The server rejects `CREATE_ONLY` with a `ResultCode.PARAMETER_ERROR` (server status `AS_ERR_PARAMETER`, code 4) in three cases:

-   On any modify operation outside the eight listed above.
-   Combined with `UPDATE_ONLY` in the same `StringWriteFlags` value (both bits set on one operation, not two different operations in the same `Operate()` call).
-   Combined with a nested context ([`CTX`](https://aerospike.com/docs/develop/data-types/collections/context)) path.

`NO_FAIL` does not suppress any of the three. The server raises them while parsing the operation’s arguments, before the no-fail check runs.

`StringPolicy` is a per-operation argument, not client configuration: there’s no string-policy field on `ClientPolicy`. Construct a `StringPolicy`, or use `StringPolicy.Default`, and pass it to each call that needs non-default flags.

In a multi-operation `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 the absence of an error.

::: no_fail can silently no-op
`NO_FAIL` also suppresses a result that would exceed the [per-operation size cap](https://aerospike.com/docs/develop/data-types/string/operations#result-size-limits). The operation returns success, but the bin stays unchanged. This looks identical to a normal success on the client, so it can mask a production bug where writes silently stop applying.

On any production write path that sets `NO_FAIL`, verify the outcome with a read-after-write, or an equivalent check, rather than trusting the absence of an error.
:::

Unlike the Go client, the C# client’s [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) honors the full `StringPolicy`. `DEFAULT`, `UPDATE_ONLY`, and `NO_FAIL` all take effect there. `CREATE_ONLY` is rejected, since `regex_replace` can’t create a bin. Pass `regexFlags` separately for regex behavior. See [Regex and numeric-type flags](#regex-and-numeric-type-flags).

::: the start-only snip overload drops policy flags silently
`StringOperation.Snip(policy, binName, start, ctx)` and `StringExp.Snip(policy, start, src)`, the overloads that remove from `start` to the end of the string, don’t encode `policy` on the wire at all. Their `policy` parameter exists only for signature consistency with the other modify builders. These overloads drop any flags you set on `policy`, including `NO_FAIL`. Use the four-argument overload with an explicit `end`, `StringOperation.Snip(policy, binName, start, end, ctx)` or `StringExp.Snip(policy, start, end, src)`, if you need `NO_FAIL` or another write flag to take effect.
:::

## Read operations

All read operations take the bin name as a `StringOperation` argument, or the source expression as the last `StringExp` argument. Both also take an optional [`CTX`](https://aerospike.com/docs/develop/data-types/collections/context) path to a value nested in a [List](https://aerospike.com/docs/develop/data-types/collections/list) or [Map](https://aerospike.com/docs/develop/data-types/collections/map), covered in [Nested strings](#nested-strings). Every operation requires the target to already be a String. Calling one against another bin type fails with `ResultCode.BIN_TYPE_ERROR` (server status `AS_ERR_INCOMPATIBLE_TYPE`, code 12; see [Error codes](https://aerospike.com/docs/database/reference/error-codes)):

```csharp
try

{

    Record record = client.Operate(null, key, StringOperation.Strlen("email"));

}

catch (AerospikeException ae) when (ae.Result == ResultCode.BIN_TYPE_ERROR)

{

    Console.Error.WriteLine($"'email' isn't a String bin: {ResultCode.GetResultString(ae.Result)}");

}
```

See [Error handling - C#](https://aerospike.com/docs/develop/client/csharp/error-handling) for the general `AerospikeException`/`ae.Result` pattern.

Index and length values count Unicode code points, not bytes. Most characters are one code point, but some emoji use multiple code points for one visible character, called a grapheme cluster. Characters outside the Basic Multilingual Plane (code points above U+FFFF, including many emoji and historic scripts) can also span multiple code points. Negative indexes count from the end of the string. `RegexCompare` uses [International Components for Unicode (ICU) regex](https://aerospike.com/docs/develop/data-types/string#unicode-semantics) syntax.

Substring matching in `find`, `contains`, `starts_with`, and `ends_with` (and in `replace`/`replace_all` in [Modify operations](#modify-operations)) treats canonically equivalent text as equal. Unicode canonical equivalence means two different code point sequences that represent the same character compare as identical, so a precomposed `é` (U+00E9) matches `e` followed by a combining acute accent (U+0301).

| Operation | C# builders | Returns | Description |
| --- | --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | `StringOperation.Strlen` / `StringExp.Strlen` | integer | Code point count. |
| [`byte_length`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | `StringOperation.ByteLength` / `StringExp.ByteLength` | integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | `StringOperation.Substr` (two overloads) / `StringExp.Substr` (two overloads) | string | Substring from `start` to the end, or the half-open range `[start, end)`. |
| [`char_at`](https://aerospike.com/docs/develop/data-types/string/operations#char_at) | `StringOperation.CharAt` / `StringExp.CharAt` | string | The one-code-point string at `index`. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | `StringOperation.Find` (two overloads) / `StringExp.Find` (two overloads) | integer | Code point index of `needle`, or of a specific `occurrence` (1 = first, -1 = last). `-1` if not found. |
| [`contains`](https://aerospike.com/docs/develop/data-types/string/operations#contains) | `StringOperation.Contains` / `StringExp.Contains` | boolean | Whether the bin contains `needle`. |
| [`starts_with`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | `StringOperation.StartsWith` / `StringExp.StartsWith` | boolean | Whether the bin begins with `prefix`. |
| [`ends_with`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | `StringOperation.EndsWith` / `StringExp.EndsWith` | boolean | Whether the bin ends with `suffix`. |
| [`to_integer`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | `StringOperation.ToInteger` / `StringExp.ToInteger` | integer | Parses the string as a 64-bit integer. Fails if it doesn’t parse. See [Type conversion](#type-conversion). |
| [`to_double`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | `StringOperation.ToDouble` / `StringExp.ToDouble` | float | Parses the string as a 64-bit float. Fails if it doesn’t parse. |
| [`is_numeric`](https://aerospike.com/docs/develop/data-types/string/operations#is_numeric) | `StringOperation.IsNumeric` (two overloads) / `StringExp.IsNumeric` (two overloads) | boolean | Whether the bin’s spelling matches an optional `StringNumericType` (`ANY`, `INT`, or `FLOAT`). |
| [`is_upper`](https://aerospike.com/docs/develop/data-types/string/operations#is_upper) / [`is_lower`](https://aerospike.com/docs/develop/data-types/string/operations#is_lower) | `StringOperation.IsUpper`, `StringOperation.IsLower` / `StringExp.IsUpper`, `StringExp.IsLower` | boolean | Whether every code point is an uppercase/lowercase letter. Digits, spaces, and punctuation aren’t cased letters, so any of them makes the result `false`. An empty string returns `true`. |
| [`to_blob`](https://aerospike.com/docs/develop/data-types/string/operations#to_blob) | `StringOperation.ToBlob` / `StringExp.ToBlob` | 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) | `StringOperation.Split` (two overloads) / `StringExp.Split` (two overloads) | list | Splits by Unicode code point, or by `separator` (a singleton list if `separator` isn’t found). |
| [`b64_decode`](https://aerospike.com/docs/develop/data-types/string/operations#b64_decode) | `StringOperation.B64Decode` / `StringExp.B64Decode` | blob | Decodes the bin as base64 text into a [Blob](https://aerospike.com/docs/develop/data-types/blob). Fails if it isn’t valid base64. |
| [`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) | `StringOperation.RegexCompare` (two overloads) / `StringExp.RegexCompare` (two overloads) | boolean | Matches an ICU regex `pattern` against the bin, optionally with `StringRegexFlags`. |

Seven read operations (`contains`, `starts_with`, `ends_with`, `is_numeric`, `is_upper`, `is_lower`, `regex_compare`) return a native boolean, not an integer `0`/`1`. See [Reading operate results](#reading-operate-results).

## Modify operations

Modify operations write a transformed value back to the bin (`StringOperation`) or return it as an expression value (`StringExp`, which does not mutate the underlying bin). Every modify operation accepts `DEFAULT`, `UPDATE_ONLY`, or `NO_FAIL`; the eight operations in this table’s first eight rows also accept `CREATE_ONLY`.

::: destructive, irreversible writes
`StringOperation` modify calls overwrite the stored bin value on the server, with no built-in undo. `StringOperation.Snip`, `StringOperation.Replace`, `StringOperation.ReplaceAll`, `StringOperation.RegexReplace`, and `StringOperation.Overwrite` are the highest-risk operations. Each can truncate or permanently discard part of the value if the index, pattern, or range is wrong. `StringRegexFlags.GLOBAL` raises the risk further on `RegexReplace`, since it applies the replacement to every match in the bin instead of only the first. Test destructive operations against sample data before running them against production records.
:::
::: 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 `ResultCode.INVALID_ENCODING` (server status `AS_ERR_INVALID_ENCODING`, 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 | C# builders | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | `StringOperation.Insert` / `StringExp.Insert` | Splices `value` in at code point `index`. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | `StringOperation.Overwrite` / `StringExp.Overwrite` | Overwrites code points starting at `index` with `value`. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | `StringOperation.Concat` (two overloads) / `StringExp.Concat` | Appends one string, or each element of a list of strings, in order. |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | `StringOperation.Append` / `StringExp.Append` | Appends `value`. Unicode-aware, including double-byte character sets (DBCS), unlike the legacy `Operation.Append`. |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | `StringOperation.Prepend` / `StringExp.Prepend` | Prepends `value`. Unicode-aware, including DBCS, unlike the legacy `Operation.Prepend`. |
| [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | `StringOperation.PadStart` / `StringExp.PadStart` | Left-pads with `padString` up to `targetLength` code points. No-op if already at or above the target. |
| [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | `StringOperation.PadEnd` / `StringExp.PadEnd` | Right-pads with `padString` up to `targetLength` code points. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | `StringOperation.Repeat` / `StringExp.Repeat` | Repeats the bin `count` times. |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | `StringOperation.Snip` (two overloads) / `StringExp.Snip` (two overloads) | Removes from `start` to the end, or the half-open range `[start, end)`. See the caution above about the start-only overload. |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | `StringOperation.Replace` / `StringExp.Replace` | Replaces the first occurrence of `needle` with `replacement`. |
| [`replace_all`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | `StringOperation.ReplaceAll` / `StringExp.ReplaceAll` | Replaces every occurrence of `needle` with `replacement`. |
| [`upper`](https://aerospike.com/docs/develop/data-types/string/operations#upper) / [`lower`](https://aerospike.com/docs/develop/data-types/string/operations#lower) | `StringOperation.Upper`, `StringOperation.Lower` / `StringExp.Upper`, `StringExp.Lower` | Uppercases or lowercases the bin. |
| [`case_fold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | `StringOperation.CaseFold` / `StringExp.CaseFold` | Maps characters to a common case for locale-independent, case-insensitive comparison keys. |
| [`normalize_nfc`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | `StringOperation.NormalizeNFC` / `StringExp.NormalizeNFC` | Normalizes the bin to Unicode Normalization Form C (NFC), the canonical composed form. Already-normalized strings are unchanged. |
| [`trim`](https://aerospike.com/docs/develop/data-types/string/operations#trim) / [`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) | `StringOperation.Trim`, `StringOperation.TrimStart`, `StringOperation.TrimEnd` / `StringExp.Trim`, `StringExp.TrimStart`, `StringExp.TrimEnd` | Removes Unicode whitespace from both ends, the start, or the end. |
| [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | `StringOperation.RegexReplace` / `StringExp.RegexReplace` | Replaces the first regex match, or every match when `StringRegexFlags.GLOBAL` is set. Honors the full `StringPolicy`; see [String write policy](#string-write-policy). |

## Type conversion

`StringOperation.ToString`/`StringExp.ToString` converts an Integer, Float, Boolean, String, or [Blob](https://aerospike.com/docs/develop/data-types/blob) bin to its string representation. It fails with `ResultCode.BIN_TYPE_ERROR` (see [Error codes](https://aerospike.com/docs/database/reference/error-codes)) for any other bin type, and with `ResultCode.OP_NOT_APPLICABLE` (server status `AS_ERR_OP_NOT_APPLICABLE`, code 26) if a Blob bin’s bytes aren’t valid UTF-8.

```csharp
Record record = client.Operate(null, key, StringOperation.ToString("age"));

Console.WriteLine(record.GetString("age"));
```

`StringOperation.ToString` is the only operation that does not accept a `CTX`. It’s a separate server operation that always reads the whole bin and can’t carry a context path in its payload.

To convert a value nested inside a List or [Map](https://aerospike.com/docs/develop/data-types/collections/map), extract the nested string first with [`ListOperation.GetByIndex`](https://aerospike.com/docs/develop/data-types/collections/list/operations#get_by_index)/[`MapOperation.GetByKey`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_by_key) (using the same `CTX`), then convert it client-side. Or compose `StringExp.ToString` with [`ListExp.GetByIndex`](https://aerospike.com/docs/develop/expressions/list#list_get_by_index)/[`MapExp.GetByKey`](https://aerospike.com/docs/develop/expressions/map#map_get_by_key) inside an expression.

`to_integer`/`to_double` parse failures and `to_string`’s invalid-UTF-8 case both surface as `ResultCode.OP_NOT_APPLICABLE`, per the [String operations error codes](https://aerospike.com/docs/develop/data-types/string/operations#error-codes).

## Regex and numeric-type flags

`StringRegexFlags` (combine with bitwise OR) controls `RegexCompare` and `RegexReplace`:

| Flag | Applies to |
| --- | --- |
| `CASE_INSENSITIVE` | Both |
| `MULTILINE` | Both |
| `DOTALL` | Both |
| `UNIX_LINES` | Both |
| `GLOBAL` | `RegexReplace` only. Replaces every match instead of only the first. |

`StringNumericType` narrows `IsNumeric`: `ANY` (default), `INT`, or `FLOAT`. `FLOAT` requires a literal `.` followed by a digit, so `StringOperation.IsNumeric(bin, StringNumericType.FLOAT)` against `"5"` returns `false` even though `"5"` parses as a double.

## Reading operate results

### Booleans decode as booleans

`Contains`, `StartsWith`, `EndsWith`, `IsNumeric`, `IsUpper`, `IsLower`, and `RegexCompare` decode as a native `bool`, not an integer `0`/`1`:

```csharp
Record record = client.Operate(null, key, StringOperation.Contains("email", "@"));

bool hasAt = record.GetBool("email"); // true or false
```

### Multiple operations on one bin return a list

When more than one operation in a single `Operate()` call targets the same bin, the client groups that bin’s results into an `IList`, using `record.GetList(binName)`, with one entry per operation. See [Returning from operate()](https://aerospike.com/docs/develop/client/csharp/usage/atomic/multi#returning-from-operate) in Bin operations for the general grouping rule.

### String operations always respond

Unlike some [collection data type (CDT)](https://aerospike.com/docs/develop/data-types/collections) operations, a String read or modify operation always contributes an entry to the grouped result list, with no policy change needed. Adding any String read or modify operation to an `Operate()` call makes the whole call behave as if `WritePolicy.respondAllOps` were set, whether or not you set it yourself.

Every String operation in that call gets a predictable, stable index, including a modify operation mixed with reads on the same bin. This applies to the entire `Operate()` call, not only its String operations: any other operation type in the same call that wouldn’t normally return a result on its own now also returns one, since the flag is set for the whole request.

A modify operation returns no value either way, whether it applied or a `NO_FAIL` flag suppressed it. Its list entry carries no value, so read the bin back if you need to confirm which modify operations actually applied.

```csharp
Record record = client.Operate(null, key,

    StringOperation.Trim(StringPolicy.Default, "email"), // modify: entry 0

    StringOperation.Strlen("email"),                      // read: entry 1

    StringOperation.Substr("email", 0, 5));               // read: entry 2

IList results = record.GetList("email");

long length = (long)results[1];

string head = (string)results[2];
```

A single string operation on a bin, with nothing else targeting that bin, returns its value directly, with no `IList` wrapper.

## Nested strings

`StringOperation` builders take an optional trailing `CTX` (or an array of them) to reach a string nested inside a [List](https://aerospike.com/docs/develop/data-types/collections/list) or [Map](https://aerospike.com/docs/develop/data-types/collections/map). The path must already resolve to a string: a non-string nested value fails with `ResultCode.BIN_TYPE_ERROR`. An invalid path, such as an out-of-bounds list index or a missing map key, also fails. See [Context for operations on nested elements](https://aerospike.com/docs/develop/data-types/collections/context) for general `CTX` error behavior.

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

client.Operate(null, key,

    StringOperation.Upper(StringPolicy.Default, "items", CTX.ListIndex(0)));

// Read strlen of a string nested under a map key.

Record record = client.Operate(null, key,

    StringOperation.Strlen("profile", CTX.MapKey(Value.Get("bio"))));
```

`StringExp` builders don’t take a `CTX` at all. To apply a string expression to a nested value, project the value first with [`ListExp.GetByIndex`](https://aerospike.com/docs/develop/expressions/list#list_get_by_index)/[`MapExp.GetByKey`](https://aerospike.com/docs/develop/expressions/map#map_get_by_key) (which do take `CTX`), then pass the result as the `src` argument. The following example builds a `StringExp.Strlen` condition, then uses it two ways: as a read filter, and as a projected read value.

```csharp
Exp bio = MapExp.GetByKey(MapReturnType.VALUE, Exp.Type.STRING,

    Exp.Val("bio"), Exp.MapBin("profile"));

Exp isLong = Exp.GT(StringExp.Strlen(bio), Exp.Val(280));

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

Policy readPolicy = new Policy();

readPolicy.filterExp = Exp.Build(isLong);

Record record = client.Get(readPolicy, key);

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

record = client.Operate(null, key,

    ExpOperation.Read("isLong", Exp.Build(isLong), ExpReadFlags.DEFAULT));

bool bioIsLong = record.GetBool("isLong");
```

A filtered-out record returns `null` by default. See the [`failOnFilteredOut`](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions#filter-expressions) note on the Expressions page to raise an exception instead.

`StringOperation.ToString`/`StringExp.ToString` never accepts a `CTX`, on either surface. See [Type conversion](#type-conversion).

## Version requirements

String operations require Aerospike Database 8.2.0 or later on every node, and Aerospike C# client 8.5.0 or later. A server prior to Database 8.2.0 doesn’t 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.

Run `asinfo -v build` against each node to confirm it reports Database 8.2.0 or later. Run `dotnet list package` in your project directory and check the `Aerospike.Client` row to confirm the installed client version.

::: rolling upgrades can cause intermittent failures
During a rolling upgrade, application code that calls String operations can succeed or fail on the same request depending on which node serves it. Nodes already on Database 8.2.0 or later accept String operations, but nodes not yet upgraded reject them with the same generic parameter error. Confirm every node reports Database 8.2.0 or later with `asinfo -v build` before enabling String operations in application code, rather than waiting a fixed amount of time.
:::

## Deprecations

-   The legacy `Operation.Append(bin)` and `Operation.Prepend(bin)` are deprecated for String bins only, in favor of `StringOperation.Append`/`StringOperation.Prepend`, which are Unicode-aware, including DBCS. The legacy pair does a raw byte concatenation and doesn’t support `StringPolicy` or `CTX`.
-   Both legacy operations also accept Blob bins, which the string package can’t target. For a Blob bin, keep using `Operation.Append`/`Operation.Prepend`. There’s no string-module replacement, and the legacy behavior is unchanged and fully supported for that case.
-   The legacy `Exp.RegexCompare` (POSIX regex) is deprecated in favor of `StringExp.RegexCompare`, which is Unicode-aware (ICU regex).

::: migration can cause ongoing write failures
Switching a bin’s write path from `Operation.Append`/`Operation.Prepend` to `StringOperation.Append`/`StringOperation.Prepend` starts UTF-8 validation on that bin. If existing data written through the legacy byte-concatenation path contains invalid UTF-8, every future `StringOperation.Append`/`StringOperation.Prepend` call against that bin fails with `ResultCode.INVALID_ENCODING` (server status `AS_ERR_INVALID_ENCODING`, code 29) instead of succeeding as before.

This isn’t a one-time failure. It repeats on every call until the bin’s content is repaired, so a hot code path can break continuously as soon as the change deploys. Before switching write paths in production, audit affected bins for valid UTF-8 and repair any that fail as described in [Repair legacy String bins](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#repair-legacy-string-bins). Roll out the switch per namespace or set and monitor for `INVALID_ENCODING`. If it appears in production, the immediate mitigation is to revert that bin’s write path to the legacy `Operation.Append`/`Operation.Prepend` call while you complete the repair.
:::

## 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 - C#](https://aerospike.com/docs/develop/client/csharp/usage/atomic/multi)
-   [Expressions - C#](https://aerospike.com/docs/develop/client/csharp/usage/atomic/expressions): building and using expressions, filter policies, and `ExpOperation.Read`/`ExpOperation.Write`
-   [Error handling - C#](https://aerospike.com/docs/develop/client/csharp/error-handling): the `AerospikeException`/`ae.Result` pattern
-   [Error codes](https://aerospike.com/docs/database/reference/error-codes): full server status code list, including String-operation-specific entries
-   [API reference (C#)](https://aerospike.com/apidocs/csharp/api/Aerospike.Client.html)