---
title: "String operations"
description: "Reference for the Go client's Str*Op builders and ExpString* 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 Go client surface for developers already using [`client.Operate()`](https://aerospike.com/docs/develop/client/go/usage/atomic/multi) and [expressions](https://aerospike.com/docs/develop/client/go/usage/atomic/expressions): the `Str*Op` builders for `Operate()` calls, and the `ExpString*` builders for expressions. After reading this page, you can choose the right builder for a task, configure a `StringPolicy`, and interpret `Operate()` results, including grouped `OpResults`.

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

```go
import (

    "fmt"

    "log"

    "github.com/aerospike/aerospike-client-go/v8"

)

// Establishes a connection to the server

client, err := aerospike.NewClient("127.0.0.1", 3000)

if err != nil {

    log.Fatal(err)

}

defer client.Close()

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

key, err := aerospike.NewKey("sandbox", "users", "jdoe123")

if err != nil {

    log.Fatal(err)

}
```

## Round-trip elimination

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

```go
import "strings"

// Before: fetch, modify, write

record, err := client.Get(nil, key)

if err != nil {

    log.Fatal(err)

}

email := strings.ToLower(strings.TrimSpace(record.Bins["email"].(string)))

if err := client.Put(nil, key, aerospike.BinMap{"email": email}); err != nil {

    log.Fatal(err)

}
```

`Str*Op` runs the same edit inside a single `Operate` call, on the server:

```go
policy := aerospike.DefaultStringPolicy

// After: one round trip

_, err := client.Operate(nil, key,

    aerospike.StrTrimOp(policy, "email"),

    aerospike.StrLowerOp(policy, "email"))

if err != nil {

    log.Fatal(err)

}

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

record, err := client.Get(nil, key)

if err != nil {

    log.Fatal(err)

}

fmt.Println(record.Bins["email"])
```

## Two surfaces

Every String operation is available in two forms, both exported directly at package level:

| Surface | Naming | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `Str*Op` | `client.Operate()` | Bin name first: `aerospike.StrLenOp(binName, ctx...)` |
| Expression | `ExpString*` | `WritePolicy.FilterExpression`/`Policy.FilterExpression`, [operation expressions](https://aerospike.com/docs/develop/client/go/usage/atomic/expressions#operation-expressions) (`ExpReadOp`/`ExpWriteOp`) | Source expression first, right after `policy` where present: `aerospike.ExpStringFind(src, needle)` |

`Str*Op` builders read or modify a bin directly, returning an `*Operation` for `client.Operate()`. `ExpString*` builders return an `*Expression` node that composes inside a larger expression, with no separate build or compile step, unlike the Java and Python clients. Assign the result directly to `FilterExpression`, or pass it to `ExpReadOp`/`ExpWriteOp`.

A modify-style `ExpString*` builder (`ExpStringUpper`, `ExpStringReplace`, `ExpStringTrim`, 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 [`ExpWriteOp`](https://aerospike.com/docs/develop/client/go/usage/atomic/expressions#write-1), or use the `Str*Op` 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:

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

aerospike.StrUpperOp(aerospike.DefaultStringPolicy, "text")

// Expression: policy, then source expression

aerospike.ExpStringUpper(aerospike.DefaultStringPolicy, aerospike.ExpStringBin("text"))
```

## String write policy

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

```go
policy := aerospike.DefaultStringPolicy                          // DEFAULT (0)

custom := aerospike.NewStringPolicy(aerospike.StringWriteNoFail) // NO_FAIL (4)
```

| Flag | Value | Effect |
| --- | --- | --- |
| `StringWriteDefault` | 0 | No create/update restriction. See the create-capability note in the following paragraph for what happens against a missing bin. |
| `StringWriteCreateOnly` | 1 | Applies the operation only if the bin doesn’t already exist. Valid only on the eight create-capable operations named below, and every other modify operation rejects it. Cannot be combined with `StringWriteUpdateOnly`, and rejected on an operation carrying a `CDTContext`. |
| `StringWriteUpdateOnly` | 2 | Applies the operation only to an existing bin, disabling bin creation. Valid on every modify operation. Cannot be combined with `StringWriteCreateOnly`. |
| `StringWriteNoFail` | 4 | Suppress the error if the operation can’t be applied to the bin, leaving the bin at its prior value and returning a `nil` result for that operation. |

Against a missing bin, `StringWriteDefault` never fails, but it also doesn’t make every operation create one. Only 8 of the 19 modify operations can create a bin from nothing: [`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). Calling one of those against a missing bin creates it, seeded from an empty string. The other 11 modify operations (`trim`, `upper`, `replace`, and similar) can’t create a bin at all. Against a missing bin, they leave the record unchanged and still return success, so the absence of an error doesn’t mean the operation did anything. Read the bin back if you need to confirm a write happened, or use `StringWriteCreateOnly`/`StringWriteUpdateOnly` below to have the client enforce that guarantee for you.

`StringWriteCreateOnly` and `StringWriteUpdateOnly` narrow that default behavior:

```go
import "github.com/aerospike/aerospike-client-go/v8/types"

createOnly := aerospike.NewStringPolicy(aerospike.StringWriteCreateOnly)

_, err := client.Operate(nil, key, aerospike.StrAppendOp(createOnly, "note", "first entry"))

if err != nil {

    if err.Matches(types.BIN_EXISTS_ERROR) {

        // Expected: "note" already exists

    } else {

        log.Fatal(err)

    }

}
```

`StringWriteCreateOnly` is valid only on the eight create-capable operations named above, and every other modify operation rejects it with `PARAMETER_ERROR` (server status `AS_ERR_PARAMETER`, code 4). It’s also invalid combined with `StringWriteUpdateOnly`, and invalid on an operation carrying a `CDTContext`. Both of those are `PARAMETER_ERROR` too. The server resolves all three of these cases while parsing the operation’s arguments, before any no-fail check runs, so `StringWriteNoFail` doesn’t suppress them.

`StringWriteUpdateOnly` is valid on every modify operation. Against a missing bin it’s a no-op rather than a create, and the call still returns success, so the same “success doesn’t mean it did anything” caveat from the `StringWriteDefault` case above applies.

`StringPolicy` is a per-operation argument, not client configuration: there’s no string-policy field on `ClientPolicy`. Pass a `NewStringPolicy` result to each call that needs a non-default flag.

In a multi-operation `Operate()` call, `StringWriteNoFail` 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
`StringWriteNoFail` 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 `StringWriteNoFail`, verify the outcome with a read-after-write (or equivalent check) rather than trusting the absence of an error.
:::

`regex_replace`/`ExpStringRegexReplace` takes two independent flag arguments: `regexFlags` (`StringRegexFlags`) for regex behavior, and `policy` (`*StringPolicy`) for write semantics. Both apply on the wire. See [Modify operations](#modify-operations) for how the write flags behave for this operation specifically.

## Read operations

All read operations take the bin name as the first `Str*Op` argument, or the source expression as the first `ExpString*` argument. Both also take an optional `CDTContext` (Go’s collection-data-type nested-path type) path to a value nested in a List or 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 `BIN_TYPE_ERROR` (server status `AS_ERR_INCOMPATIBLE_TYPE`, code 12. See [Error codes](https://aerospike.com/docs/database/reference/error-codes)).

Index and length values count Unicode codepoints, not bytes. Most characters are one codepoint, but some emoji use multiple codepoints for one visible character (a grapheme cluster), and characters outside the Basic Multilingual Plane (code points above U+FFFF, including many emoji and historic scripts) can also differ. Negative indexes count from the end of the string. `regexCompare` uses [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` under [Modify operations](#modify-operations)) treats canonically equivalent text as equal, so a precomposed `é` (U+00E9) matches `e` followed by a combining acute accent (U+0301).

| Operation | Go builders | Returns | Description |
| --- | --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | `StrLenOp` / `ExpStringLen` | integer | Codepoint count. |
| [`byte_length`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | `StrByteLengthOp` / `ExpStringByteLength` | integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | `StrSubstrFromOp`, `StrSubstrOp` / `ExpStringSubstrFrom`, `ExpStringSubstr` | 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) | `StrCharAtOp` / `ExpStringCharAt` | string | The one-codepoint string at `index`. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | `StrFindOp`, `StrFindNthOp` / `ExpStringFind`, `ExpStringFindNth` | integer | Codepoint 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) | `StrContainsOp` / `ExpStringContains` | boolean | Whether the bin contains `needle`. |
| [`starts_with`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | `StrStartsWithOp` / `ExpStringStartsWith` | boolean | Whether the bin begins with `prefix`. |
| [`ends_with`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | `StrEndsWithOp` / `ExpStringEndsWith` | boolean | Whether the bin ends with `suffix`. |
| [`to_integer`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | `StrToIntegerOp` / `ExpStringToInteger` | integer | Parses the string as an int64. Fails if it doesn’t parse. See [Type conversion](#type-conversion). |
| [`to_double`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | `StrToDoubleOp` / `ExpStringToDouble` | 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) | `StrIsNumericOp`, `StrIsNumericTypedOp` / `ExpStringIsNumeric`, `ExpStringIsNumericTyped` | boolean | Whether the bin’s spelling matches an optional `StringNumericType` (`StringNumericAny`, `StringNumericInt`, or `StringNumericFloat`). |
| [`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) | `StrIsUpperOp`, `StrIsLowerOp` / `ExpStringIsUpper`, `ExpStringIsLower` | boolean | Whether every codepoint 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) | `StrToBlobOp` / `ExpStringToBlob` | 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) | `StrSplitOp`, `StrSplitBySeparatorOp` / `ExpStringSplit`, `ExpStringSplitBySeparator` | 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) | `StrB64DecodeOp` / `ExpStringB64Decode` | 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) | `StrRegexCompareOp`, `StrRegexCompareWithFlagsOp` / `ExpStringRegexCompare`, `ExpStringRegexCompareWithFlags` | boolean | Matches an ICU regex `pattern` against the bin, optionally with `StringRegexFlags`. |

Seven read operations (`contains`, `startsWith`, `endsWith`, `isNumeric`, `isUpper`, `isLower`, `regexCompare`) 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 (`Str*Op`) or return it as an expression value (`ExpString*`, which does not mutate the underlying bin). Every modify operation accepts `StringWriteDefault`, `StringWriteUpdateOnly`, or `StringWriteNoFail`. `StringWriteCreateOnly` is valid only on the eight operations that can create a missing bin. See [String write policy](#string-write-policy).

::: destructive, irreversible writes
`Str*Op` modify calls overwrite the stored bin value on the server, with no built-in undo. `StrSnipOp`, `StrReplaceOp`, `StrReplaceAllOp`, `StrRegexReplaceOp`, and `StrOverwriteOp` 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.
:::
::: 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 `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 | Go builders | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | `StrInsertOp` / `ExpStringInsert` | Splices `value` in at codepoint `index`. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | `StrOverwriteOp` / `ExpStringOverwrite` | Overwrites codepoints starting at `index` with `value`. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | `StrConcatOp`, `StrConcatListOp` / `ExpStringConcat` | Appends one string, or each element of a list of strings, in order. |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | `StrAppendOp` / `ExpStringAppend` | Appends `value`. Unicode/DBCS-aware (handles double-byte character sets correctly), unlike the legacy `AppendOp`. |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | `StrPrependOp` / `ExpStringPrepend` | Prepends `value`. Unicode/DBCS-aware, unlike the legacy `PrependOp`. |
| [`pad_start`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | `StrPadStartOp` / `ExpStringPadStart` | Left-pads with `padString` up to `targetLength` codepoints. No-op if the bin already meets or exceeds `targetLength` codepoints. |
| [`pad_end`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | `StrPadEndOp` / `ExpStringPadEnd` | Right-pads with `padString` up to `targetLength` codepoints. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | `StrRepeatOp` / `ExpStringRepeat` | Repeats the bin `count` times. |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | `StrSnipOp` / `ExpStringSnip` | Removes the half-open codepoint range `[start, end)`. Both `start` and `end` are required arguments. |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | `StrReplaceOp` / `ExpStringReplace` | Replaces the first occurrence of `needle` with `replacement`. |
| [`replace_all`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | `StrReplaceAllOp` / `ExpStringReplaceAll` | 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) | `StrUpperOp`, `StrLowerOp` / `ExpStringUpper`, `ExpStringLower` | Uppercases or lowercases the bin. |
| [`case_fold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | `StrCaseFoldOp` / `ExpStringCaseFold` | Applies locale-independent case folding, for comparison keys. |
| [`normalize_nfc`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | `StrNormalizeNFCOp` / `ExpStringNormalizeNFC` | Normalizes the bin to Unicode NFC 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) | `StrTrimOp`, `StrTrimStartOp`, `StrTrimEndOp` / `ExpStringTrim`, `ExpStringTrimStart`, `ExpStringTrimEnd` | Removes Unicode whitespace from both ends, the start, or the end. |
| [`regex_replace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | `StrRegexReplaceOp` / `ExpStringRegexReplace` | Replaces the first regex match, or every match when `StringRegexGlobal` is set. Honors `StringWriteDefault`, `StringWriteUpdateOnly`, and `StringWriteNoFail`, and rejects `StringWriteCreateOnly` like the other 11 non-creating modify operations. See [String write policy](#string-write-policy). |

::: no_fail also covers a regex-compile failure here
`StringWriteNoFail` suppresses a malformed `pattern` on `regex_replace`/`ExpStringRegexReplace`, in addition to the general no-fail behavior described in [String write policy](#string-write-policy). `StringRegexDotAll` and `StringWriteNoFail` happen to share the same numeric value (4), but they can’t be confused in Go: `regexFlags` and `policy` are distinct typed parameters (`StringRegexFlags` and `*StringPolicy`), so the compiler rejects passing one where the other is expected.
:::

## Type conversion

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

```go
record, err := client.Operate(nil, key, aerospike.StrToStringOp("n"))

if err != nil {

    log.Fatal(err)

}

fmt.Println(record.Bins["n"])
```

`StrToStringOp` is the only operation that does not accept a `CDTContext`. 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/context), extract the nested string first with [`ListGetByIndexOp`](https://aerospike.com/docs/develop/data-types/collections/list/operations#get_by_index)/[`MapGetByKeyOp`](https://aerospike.com/docs/develop/data-types/collections/map/operations#get_by_key) (using the same `CDTContext`), then convert it client-side. Or compose `ExpStringToString` with [`ExpListGetByIndex`](https://aerospike.com/docs/develop/expressions/list#list_get_by_index)/[`ExpMapGetByKey`](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 `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 |
| --- | --- |
| `StringRegexCaseInsensitive` | Both |
| `StringRegexMultiline` | Both |
| `StringRegexDotAll` | Both |
| `StringRegexUnixLines` | Both |
| `StringRegexGlobal` | `regexReplace` only. Replaces every match instead of only the first. |

`StringNumericType` narrows `isNumeric`: `StringNumericAny` (default), `StringNumericInt`, or `StringNumericFloat`. `StringNumericFloat` requires a literal `.` followed by a digit, so `StrIsNumericTypedOp(bin, StringNumericFloat)` 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`:

```go
record, err := client.Operate(nil, key, aerospike.StrContainsOp("email", "@"))

if err != nil {

    log.Fatal(err)

}

hasAt := record.Bins["email"].(bool) // 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 `OpResults`, a slice (`[]interface{}`) with one entry per operation. See [Returning from operate()](https://aerospike.com/docs/develop/client/go/usage/atomic/multi#returning-from-operate) in Bin operations for the general grouping rule and `WritePolicy.RespondPerEachOp`.

::: string ops keep indices aligned automatically
Whenever an `Operate()` call includes a String read or modify operation, the Go client automatically requests a result slot for every operation in that call, the same behavior `WritePolicy.RespondPerEachOp` enables manually. A String modify operation’s slot is `nil`, and read operations keep their submitted index. You don’t need to set `RespondPerEachOp` yourself for this case. Reserve it for the cases called out in [Returning from operate()](https://aerospike.com/docs/develop/client/go/usage/atomic/multi#returning-from-operate), such as mixing String operations with non-String operations that don’t get the same automatic handling.
:::

```go
record, err := client.Operate(nil, key,

    aerospike.StrTrimOp(aerospike.DefaultStringPolicy, "email"), // modify: entry 0 (nil)

    aerospike.StrLenOp("email"),                                  // read: entry 1

    aerospike.StrSubstrOp("email", 0, 5))                         // read: entry 2

if err != nil {

    log.Fatal(err)

}

results := record.Bins["email"].(aerospike.OpResults)

length := results[1].(int)

head := results[2].(string)
```

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

## Nested strings

`Str*Op` builders take an optional trailing `CDTContext` (or a list of them) to reach a string nested inside a 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 `BIN_TYPE_ERROR`. An invalid path, such as an out-of-bounds list index or a missing map key, also fails. See [nested context](https://aerospike.com/docs/develop/data-types/collections/context) for general `CDTContext` error behavior.

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

_, err := client.Operate(nil, key,

    aerospike.StrUpperOp(aerospike.DefaultStringPolicy, "items", aerospike.CtxListIndex(0)))

if err != nil {

    log.Fatal(err)

}

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

record, err := client.Operate(nil, key,

    aerospike.StrLenOp("profile", aerospike.CtxMapKey(aerospike.StringValue("bio"))))

if err != nil {

    log.Fatal(err)

}
```

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

```go
import "github.com/aerospike/aerospike-client-go/v8/types"

bio := aerospike.ExpMapGetByKey(aerospike.MapReturnType.VALUE, aerospike.ExpTypeSTRING,

    aerospike.ExpStringVal("bio"), aerospike.ExpMapBin("profile"))

isLong := aerospike.ExpGreater(aerospike.ExpStringLen(bio), aerospike.ExpIntVal(280))

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

readPolicy := aerospike.NewPolicy()

readPolicy.FilterExpression = isLong

record, err := client.Get(readPolicy, key)

if err != nil {

    if err.Matches(types.FILTERED_OUT) {

        // Expected: the filter excluded the record

    } else {

        log.Fatal(err)

    }

}

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

record, err = client.Operate(nil, key, aerospike.ExpReadOp("isLong", isLong, aerospike.ExpReadFlagDefault))

if err != nil {

    log.Fatal(err)

}

bioIsLong := record.Bins["isLong"].(bool)
```

`StrToStringOp`/`ExpStringToString` never accepts a `CDTContext`, on either surface. See [Type conversion](#type-conversion).

## Version requirements

String operations require Aerospike Database 8.2.0 or later on every node, and Go client 8.9.0 or later. A server prior to 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 8.2.0 or later, and `go list -m github.com/aerospike/aerospike-client-go/v8` 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 parameter error. Confirm every node reports 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 `AppendOp(bin)` and `PrependOp(bin)` are deprecated for String bins only, in favor of `StrAppendOp`/`StrPrependOp`, which are Unicode/DBCS-aware. The legacy pair does a raw byte concatenation and doesn’t support `StringPolicy` or `CDTContext`.
-   Both legacy operations also accept Blob bins, which the string package can’t target. For a Blob bin, keep using `AppendOp`/`PrependOp`. There’s no string-module replacement, and the legacy behavior is unchanged and fully supported for that case.
-   The legacy `ExpRegexCompare` (POSIX regex) is deprecated in favor of `ExpStringRegexCompare`/`ExpStringRegexCompareWithFlags`, which are Unicode-aware (ICU regex).

::: migration can cause ongoing write failures
Switching a bin’s write path from `AppendOp`/`PrependOp` to `StrAppendOp`/`StrPrependOp` starts UTF-8 validation on that bin. If existing data written through the legacy byte-concatenation path contains invalid UTF-8, every future `StrAppendOp`/`StrPrependOp` call against that bin fails with `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 `AppendOp`/`PrependOp` 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 - Go](https://aerospike.com/docs/develop/client/go/usage/atomic/multi)
-   [Expressions - Go](https://aerospike.com/docs/develop/client/go/usage/atomic/expressions): building and using expressions, filter policies, and `ExpReadOp`/`ExpWriteOp`
-   [Error handling - Go](https://aerospike.com/docs/develop/client/go/error-handling): the `AerospikeError`/`Error.Matches()` pattern
-   [Error codes](https://aerospike.com/docs/database/reference/error-codes): full server status code list, including String-operation-specific entries
-   [API reference (Go)](https://pkg.go.dev/github.com/aerospike/aerospike-client-go/v8)