---
title: "String operations"
description: "Reference for the Python client's string_operations builders and expressions.string 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 operations let the server search, transform, extract, and normalize text in a [String](https://aerospike.com/docs/develop/data-types/string) bin. That avoids fetching a bin, editing it in your application, and writing it back. This reference covers the Aerospike Python client surface, for developers already using `client.operate()` and expressions. Use `aerospike_helpers.operations.string_operations` for `operate()` calls and `aerospike_helpers.expressions.string` for expressions.

It requires Aerospike Database 8.2.0 or later and Python client 19.3.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:

```python
import aerospike

# Define host configuration

config = {

    'hosts': [ ('127.0.0.1', 3000) ]

}

# Establishes a connection to the server

client = aerospike.client(config)

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

key = ('sandbox', 'users', 'jdoe123')
```

## Round-trip elimination

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

```python
# Before: fetch, modify, write

(key_, meta, bins) = client.get(key)

email = bins['email'].strip().lower()

client.put(key, {'email': email})
```

`string_operations` runs the same edit inside a single `operate()` call, on the server:

```python
from aerospike_helpers.operations import string_operations as so

# After: one round trip

client.operate(key, [

    so.trim('email'),

    so.lower('email'),

])

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

(key_, meta, bins) = client.get(key)

print(bins['email'])
```

## Two surfaces

Every String operation is available in two forms:

| Surface | Module (common alias) | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `aerospike_helpers.operations.string_operations` (`so`) | `client.operate()` | Bin name first: `so.strlen(bin_name[, ctx])` |
| Expression | `aerospike_helpers.expressions.string` (`str_expr`) | [`expression_operations.expression_read`/`expression_write`](https://aerospike.com/docs/develop/client/python/usage/atomic/expressions#operation-expressions), filter policies (`{'expressions': ...}`) | Bin last: `str_expr.StrLen(bin=src)` |

`string_operations` functions read or modify a bin directly, returning a dictionary usable in `operate()`. `expressions.string` classes build an expression node that composes inside a larger expression, using `.compile()`.

A modify-style expression class (`Upper`, `Replace`, `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 on a top-level String bin, write it back with `expression_operations.expression_write` (see [Operation expressions](https://aerospike.com/docs/develop/client/python/usage/atomic/expressions#operation-expressions)), or use the `string_operations` equivalent instead. See [Nested strings](#nested-strings) for a worked filter and projection example, and for how persisting a transform applied to a _nested_ value differs.

Each `expressions.string` class name is the PascalCase form of its `string_operations` function name (`strlen`/`StrLen`, `is_upper`/`IsUpper`, `regex_compare`/`RegexCompare`), with two exceptions: `b64_decode` is `Base64Decode`, and `normalize_nfc` is `NormalizeNFC`. See [Read operations](#read-operations) and [Modify operations](#modify-operations) for the full name mapping.

Modify-op expression classes also take a policy as their first argument, ahead of the bin:

```python
from aerospike_helpers.string_helpers import StringPolicy

from aerospike_helpers.operations import string_operations as so

from aerospike_helpers.expressions import string as str_expr

# Operation: bin name first, policy defaults to None

so.upper('text')

# Expression: policy first (required, no default), bin last

str_expr.Upper(policy=StringPolicy(), bin='text')
```

Unlike `string_operations`, where `policy` defaults to `None`, every modify-op class in `expressions.string` requires an explicit `StringPolicy` argument. There is no default value. Pass `StringPolicy()` for default behavior.

`RegexCompare` is an exception to the “bin last” rule: its signature is `RegexCompare(pattern, bin, regex_flags=RegexFlags.DEFAULT)`, with `bin` second rather than last. Calling it positionally with `bin` in the final position passes arguments in the wrong order.

## String write policy

Modify operations take a `StringPolicy`, which wraps a bitmask of `WriteFlags`:

```python
from aerospike_helpers.string_helpers import StringPolicy, WriteFlags

policy = StringPolicy()                                             # DEFAULT (0)

custom = StringPolicy(WriteFlags.CREATE_ONLY | WriteFlags.NO_FAIL)  # combine with bitwise OR
```

| Flag | Value | Effect |
| --- | --- | --- |
| `DEFAULT` | 0 | Allow create or update. |
| `CREATE_ONLY` | 1 | Fail with `BinExistsError` if the bin already exists. Valid only on the eight operations that can create a missing bin: `insert`, `overwrite`, `concat`, `append`, `prepend`, `pad_start`, `pad_end`, `repeat`. Every other modify operation rejects it with `InvalidRequest`. |
| `UPDATE_ONLY` | 2 | No-op (bin not created) if the bin is missing. Valid on all modify operations. Mutually exclusive with `CREATE_ONLY`. Combining the two raises `InvalidRequest`. |
| `NO_FAIL` | 4 | Suppress runtime errors. The bin keeps its prior value. Does not suppress a wrong bin type, invalid UTF-8, or the argument-parsing rejections described for `CREATE_ONLY`. This includes an oversized result. See the following caution. |

`StringPolicy` is a per-operation argument, not client configuration. There is no string-policy entry on the client’s global policy dictionaries. Pass a `StringPolicy` to each call that needs non-default flags.

`CREATE_ONLY`, `UPDATE_ONLY`, and most argument errors raise an exception from `aerospike.exception` rather than failing silently. Catch it and inspect `code` to distinguish an expected condition from one you need to propagate:

```python
from aerospike.exception import BinExistsError

from aerospike_helpers.operations import string_operations as so

from aerospike_helpers.string_helpers import StringPolicy, WriteFlags

try:

    client.operate(key, [

        so.insert('email', 0, 'prefix-', policy=StringPolicy(WriteFlags.CREATE_ONLY)),

    ])

except BinExistsError:

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

    pass
```

`operate()` runs its whole operation list [all or nothing](https://aerospike.com/docs/develop/learn/bin-operations#how-operate-executes): a failure that `NO_FAIL` doesn’t cover discards the entire in-memory copy for that call, not just the operation that failed. `NO_FAIL` only keeps its own operation from triggering that discard. It does nothing for a sibling operation’s failure.

The following example combines an operation `NO_FAIL` covers (a `CREATE_ONLY` conflict on a bin that already exists) with one it doesn’t (a String operation on `visits`, an Integer bin):

```python
from aerospike.exception import BinIncompatibleType

from aerospike_helpers.operations import string_operations as so

from aerospike_helpers.string_helpers import StringPolicy, WriteFlags

# "username" is an existing String bin; "visits" is an Integer bin.

try:

    client.operate(key, [

        # 1. NO_FAIL-covered: CREATE_ONLY normally raises BinExistsError

        #    because "username" already has a value. NO_FAIL silently

        #    skips this operation instead, leaving "username" unchanged.

        so.overwrite('username', 0, 'nobody', policy=StringPolicy(WriteFlags.CREATE_ONLY | WriteFlags.NO_FAIL)),

        # 2. Not NO_FAIL-covered: a String operation on an Integer bin is a

        #    wrong-bin-type error, which NO_FAIL never suppresses.

        so.upper('visits'),

        # 3. Otherwise a normal, unconditionally successful operation.

        so.upper('username'),

    ])

except BinIncompatibleType:

    pass

# Confirm: nothing in the call applied, not even operation 1's silent

# no-op or operation 3's otherwise-successful uppercase, because

# operation 2's uncovered failure discarded the whole in-memory copy.

(key_, meta, bins) = client.get(key)

print(bins['username'])  # unchanged from before the operate() call
```

Because operation 2 isn’t covered by `NO_FAIL`, its failure discards the entire call. Operation 1’s `NO_FAIL`\-covered no-op and operation 3’s otherwise-successful `upper()` are both discarded along with it. `NO_FAIL` only changes the outcome when every failure in the call is one it covers. 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 is indistinguishable from 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 equivalent check) rather than trusting the absence of an exception.
:::

On `expressions.string`, only `NO_FAIL` is helpful for most use cases. `CREATE_ONLY` and `UPDATE_ONLY` only apply when the target is a bin, so they don’t carry over cleanly to a source expression that may not be a bin at all. `RegexReplace` is the exception: it accepts the same policy flags as `regex_replace()`.

## Read operations

All read operations take the bin name (and an optional `ctx` path to a value nested in a List or Map, covered in [Nested strings](#nested-strings)) as `string_operations` arguments, or the source as the `bin` keyword argument in `expressions.string`. Every operation requires the target to already be a String. Calling one against another bin type raises `BinIncompatibleType`.

In the following table, index and length values count Unicode codepoints, not bytes. Most characters are one codepoint, but some emoji and some non-Latin writing systems use multiple codepoints for one visible character. Several operations use Unicode canonical matching: equivalent character sequences (for example, composed and decomposed accents) match even when their byte sequences differ.

| Operation | Expression class | Returns | Description |
| --- | --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | `StrLen` | integer | Codepoint count. |
| [`byte_length`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | `ByteLength` | integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) / [`substr_range`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | `SubStr` / `SubStrRange` | string | Substring from `start` to end, or the range from `start` up to but not including `end` (`[start, end)`). Negative indexes count from the end. |
| [`char_at`](https://aerospike.com/docs/develop/data-types/string/operations#char_at) | `CharAt` | string | The one-codepoint string at `index`. Negative indexes count from the end. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | `Find` | integer | Codepoint index of the `occurrence`\-th match of `needle`, or `-1` if not found. |
| [`contains`](https://aerospike.com/docs/develop/data-types/string/operations#contains) | `Contains` | boolean | Whether the bin contains `needle`. |
| [`starts_with`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | `StartsWith` | boolean | Whether the bin begins with `prefix`. |
| [`ends_with`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | `EndsWith` | boolean | Whether the bin ends with `suffix`. |
| [`to_integer`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | `ToInteger` | integer | Parses the string as a 64-bit integer. Raises `OpNotApplicable` if it doesn’t parse. |
| [`to_double`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | `ToDouble` | float | Parses the string as a 64-bit float. Raises `OpNotApplicable` if it doesn’t parse. |
| [`is_numeric`](https://aerospike.com/docs/develop/data-types/string/operations#is_numeric) | `IsNumeric` | boolean | Whether the bin’s spelling matches `numeric_type` (`NumericType.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) | `IsUpper` / `IsLower` | boolean | Whether every codepoint is an uppercase / lowercase letter. Digits, spaces, and punctuation are not cased letters, so any of them yields `false`. Empty string returns `true`. |
| [`to_blob`](https://aerospike.com/docs/develop/data-types/string/operations#to_blob) | `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) / [`split_separator`](https://aerospike.com/docs/develop/data-types/string/operations#split) | `Split` / `SplitSeparator` | 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) | `Base64Decode` | blob | Decodes the bin as base64 text into a [Blob](https://aerospike.com/docs/develop/data-types/blob). Raises `OpNotApplicable` if it isn’t valid base64. |
| [`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) | `RegexCompare` | boolean | Matches an [ICU regex](https://aerospike.com/docs/develop/data-types/string#unicode-semantics) `pattern` against the bin. |

Only the eight operations marked in the [String write policy](#string-write-policy) table accept `WriteFlags.CREATE_ONLY`. The server rejects it with `InvalidRequest` 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.

::: two flag families collide numerically
`regex_replace` takes a `regex_flags` argument (`RegexFlags`) and a `policy` argument (`StringPolicy`, wrapping `WriteFlags`). `WriteFlags.NO_FAIL` (4) and `RegexFlags.DOTALL` (4) share a bit value, as do `WriteFlags.UPDATE_ONLY` (2) and `RegexFlags.MULTILINE` (2). Passing one flag set into the other’s argument silently selects the wrong behavior instead of failing. Always pass `regex_flags` and `policy` in their own keyword arguments.
:::

## Modify operations

Modify operations write a transformed value back to the bin and return `None` by default. See [A bin with more than one operation](#a-bin-with-more-than-one-operation) for how results come back when a bin has more than one operation in the same call. Each operation takes a `policy` (`StringPolicy`) as an optional `string_operations` argument, or a required first argument in `expressions.string`. See [String write policy](#string-write-policy) for `CREATE_ONLY`/`UPDATE_ONLY`/`NO_FAIL` eligibility.

| Operation | Expression class | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | `Insert` | Inserts `value` at codepoint `offset`. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | `Overwrite` | Overwrites the bin starting at codepoint `offset` with `value`. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | `Concat` | Concatenates additional string values onto the bin. |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | `Append` | Appends `value` to the bin (Unicode-aware). |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | `Prepend` | Prepends `value` to the bin (Unicode-aware). |
| [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | `PadStart` | Pads the start of the bin to `target_length` using `pad_string`. |
| [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | `PadEnd` | Pads the end of the bin to `target_length` using `pad_string`. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | `Repeat` | Repeats the bin `count` times. |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | `Replace` | Replaces the first occurrence of `find` with `replace`. |
| [`replace_all`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | `ReplaceAll` | Replaces all occurrences of `find` with `replace`. |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | `Snip` | Removes the codepoint range from `from` (inclusive) to `to` (exclusive). |
| [`trim`](https://aerospike.com/docs/develop/data-types/string/operations#trim) | `Trim` | Removes leading and trailing Unicode whitespace. |
| [`trim_start`](https://aerospike.com/docs/develop/data-types/string/operations#trim_start) | `TrimStart` | Removes leading Unicode whitespace. |
| [`trim_end`](https://aerospike.com/docs/develop/data-types/string/operations#trim_end) | `TrimEnd` | Removes trailing Unicode whitespace. |
| [`upper`](https://aerospike.com/docs/develop/data-types/string/operations#upper) | `Upper` | Converts the bin to uppercase. |
| [`lower`](https://aerospike.com/docs/develop/data-types/string/operations#lower) | `Lower` | Converts the bin to lowercase. |
| [`case_fold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | `CaseFold` | Applies Unicode case folding for case-insensitive comparison. |
| [`normalize_nfc`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | `NormalizeNFC` | Normalizes the bin to Unicode NFC form. |
| [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | `RegexReplace` | Replaces the first regex match, or every match when `RegexFlags.GLOBAL` is set. Shares `StringPolicy` flags with modify operations, unlike other expression classes. |

## Type conversion

`to_string` converts an Integer, Float, Boolean, String, or [Blob](https://aerospike.com/docs/develop/data-types/blob) bin to its string representation. It raises `BinIncompatibleType` for any other bin type, and `OpNotApplicable` if a Blob bin’s bytes aren’t valid UTF-8.

```python
_, _, bins = client.operate(key, [so.to_string('n')])

print(bins['n'])
```

`to_string` is the only operation that does not accept a `ctx`. It is a separate server operation that always reads the whole bin and cannot 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/context), extract the nested string first with [`list_operations.list_get_by_index`](https://aerospike.com/docs/develop/data-types/collections/list/operations#get_by_index)/[`map_operations.map_get_by_key`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_by_key) (using the same `ctx`), then convert it client-side. Or compose `str_expr.ToString` with [`list.ListGetByIndex`](https://aerospike.com/docs/develop/expressions/list#list_get_by_index)/[`map.MapGetByKey`](https://aerospike.com/docs/develop/expressions/map#map_get_by_key) inside an expression.

## Regex and numeric-type flags

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

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

`NumericType` narrows `is_numeric`: `ANY` (default), `INT`, or `FLOAT`. `FLOAT` requires a literal `.` followed by a digit, so `is_numeric('5', NumericType.FLOAT)` is `False` even though `'5'` parses as a double.

## Reading operate results

String operation results decode with Python-native types rather than raw server wire values.

### Booleans decode as booleans

`contains`, `starts_with`, `ends_with`, `is_numeric`, `is_upper`, `is_lower`, and `regex_compare` decode as a native `bool`, not an integer `0`/`1`:

```python
_, _, bins = client.operate(key, [so.contains('email', '@')])

has_at = bins['email']   # True or False
```

### A bin with more than one operation

`operate()`’s dict return does not group multiple results for the same bin into a list. Each operation’s result simply overwrites the previous one under that bin’s key, so `bins[bin_name]` reflects only the _last_ operation targeting that bin, in submission order. Results from every earlier operation on the same bin are lost, not combined.

To read every operation’s result for a bin with more than one operation in the same call, use [`operate_ordered()`](https://aerospike.com/docs/develop/client/python/usage/atomic/multi#returning-from-operate) instead of `operate()`. It returns bins as an ordered list of `(bin_name, value)` tuples, one tuple per operation, in submission order, instead of a dict:

```python
_, _, bins = client.operate_ordered(key, [

    so.trim('email'),                 # modify

    so.strlen('email'),               # read

    so.substr_range('email', 0, 5),   # read

])

for bin_name, value in bins:

    print(bin_name, value)
```

A single operation on a bin, with nothing else targeting that bin, returns its value directly from `operate()`, with no list or tuple wrapper:

```python
_, _, bins = client.operate(key, [so.strlen('email')])

length = bins['email']   # a plain integer, not a list
```

## Nested strings

`string_operations` functions take an optional trailing `ctx` list to reach a string nested inside a List or [Map](https://aerospike.com/docs/develop/data-types/collections/context). The path must already resolve to a string. A non-string nested value fails with `BinIncompatibleType`. An invalid path (an out-of-bounds index or a missing map key) also fails. See [nested context](https://aerospike.com/docs/develop/data-types/collections/context) for general `ctx` error behavior.

```python
from aerospike_helpers import cdt_ctx

# Uppercase a string nested in a list bin "items" at index 0.

client.operate(key, [so.upper('items', ctx=[cdt_ctx.cdt_ctx_list_index(0)])])

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

_, _, bins = client.operate(key, [so.strlen('profile', ctx=[cdt_ctx.cdt_ctx_map_key('bio')])])
```

`expressions.string` classes do not take a `ctx` at all. To apply a string expression to a nested value, project the value first with [`list.ListGetByIndex`](https://aerospike.com/docs/develop/expressions/list#list_get_by_index)/[`map.MapGetByKey`](https://aerospike.com/docs/develop/expressions/map#map_get_by_key) (which do take `ctx`), then pass the result as the `bin` argument. The following example builds a `StrLen` condition, then uses it two ways: as a read filter, and as a projected read value.

```python
import aerospike

from aerospike_helpers import expressions as exp

from aerospike_helpers.expressions import string as str_expr

from aerospike_helpers.operations import expression_operations

from aerospike.exception import FilteredOut

bio = exp.MapGetByKey(None, aerospike.MAP_RETURN_VALUE, exp.ResultType.STRING, 'bio', exp.MapBin('profile'))

is_long = exp.GT(str_expr.StrLen(bin=bio), 280).compile()

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

policy = {'expressions': is_long}

try:

    (key_, meta, bins) = client.get(key, policy=policy)

except FilteredOut:

    pass   # the filter excluded the record

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

ops = [expression_operations.expression_read('isLong', is_long, aerospike.EXP_READ_DEFAULT)]

(key_, meta, bins) = client.operate(key, ops)

bio_is_long = bins['isLong']
```

::: persisting a nested transform needs a reconstructed collection, not a direct expression_write
`expression_write` replaces the _entire_ value of the bin it targets. Passing a modify-style expression built from a projected nested value (as above) straight to `expression_write('profile', ...)` replaces the whole `profile` map with just the transformed string, discarding every other key. To persist a transform back into its original nested location, wrap it in `MapPut`/`ListSet` targeting the same key or index, then `expression_write` the reconstructed collection:

```python
from aerospike_helpers import expressions as exp

from aerospike_helpers.expressions import string as str_expr

from aerospike_helpers.operations import expression_operations

from aerospike_helpers.string_helpers import StringPolicy

bio = exp.MapGetByKey(None, aerospike.MAP_RETURN_VALUE, exp.ResultType.STRING, 'bio', exp.MapBin('profile'))

updated_profile = exp.MapPut(None, None, 'bio', str_expr.Upper(policy=StringPolicy(), bin=bio), exp.MapBin('profile'))

# Writes the whole reconstructed map back to "profile": "bio" is uppercased,

# every other key in the map is unchanged.

client.operate(key, [expression_operations.expression_write('profile', updated_profile.compile(), aerospike.EXP_WRITE_DEFAULT)])
```

For a String nested in a Map or List, `string_operations` with `ctx` (shown earlier in this section) reaches the nested location directly and writes only that value, without reconstructing the collection. Prefer it over the expression-based approach for this reason.
:::

`to_string`/`ToString` never accepts `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 Python client 19.3.0 or later. Aerospike Database 8.1.2 and earlier does not recognize the string opcodes and returns a generic “invalid request” error. Check the cluster version rather than branching on the error: run `asinfo -v build` against each node to confirm it reports 8.2.0 or later, and `pip show aerospike` 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 8.2.0 or later accept String operations, but nodes not yet upgraded reject them with the same generic “invalid request” error. Confirm every node reports 8.2.0 or later (see the verification command above) before enabling String operations in application code, rather than waiting a fixed amount of time.
:::

## 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/learn/bin-operations)
-   [Expressions - Python](https://aerospike.com/docs/develop/client/python/usage/atomic/expressions): building and using expressions, filter policies, and `expression_operations`
-   [Error handling - Python](https://aerospike.com/docs/develop/client/python/error-handling): general exception-handling pattern
-   [Error codes](https://aerospike.com/docs/database/reference/error-codes): full server status code list, including String-operation-specific entries
-   [API reference (Python)](https://aerospike-python-client.readthedocs.io/en/latest/)