---
title: "String operation examples"
description: "Common server-side string operation patterns for normalization, extraction, classification, and safe updates."
---

# String operation examples

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

Aerospike [string operations](https://aerospike.com/docs/develop/data-types/string/operations) run on the server, so text is normalized, searched, and reshaped without a fetch-modify-write round trip. Each operation has a detailed description with its own code examples in the [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations).

The patterns below combine several of them. To filter or project string values inside a query rather than on one record, see [String expressions](https://aerospike.com/docs/develop/expressions/string).

## Examples

### Normalizing an email address

Starting from an `email` bin holding a value with stray whitespace and mixed case:

```plaintext
"  Ana@Corp.IO  "
```

Trim and lowercase it in one atomic [`operate`](https://aerospike.com/docs/develop/learn/bin-operations) call, replacing a read-modify-write loop in the application. Modify operations return no value, so the same call carries a read of the bin to get the normalized string back.

-   [Java SDK](#tab-panel-4567)
-   [Python SDK](#tab-panel-4568)
-   [Rust](#tab-panel-4569)
-   [C#](#tab-panel-4570)
-   [Go](#tab-panel-4571)
-   [Node.js](#tab-panel-4572)
-   [C](#tab-panel-4573)
-   [Java](#tab-panel-4574)
-   [Python](#tab-panel-4575)

```java
try (RecordStream rs = session.upsert(key)

    .bin("email").trim()

    .bin("email").lower()

    .bin("email").get()

    .execute()) {

    Record rec = rs.next().recordOrThrow();

    String normalized = rec.operationResult(2).getString();

    // normalized: "ana@corp.io"

}
```

```python
stream = (session.upsert(key)

          .bin("email").str_trim()

          .bin("email").str_lower()

          .bin("email").get()

          .execute())

normalized = stream.first_or_raise().record_or_raise().operation_result(2)

# normalized: "ana@corp.io"
```

```rust
// Requires: use aerospike::operations::{scalar, string as str_op};

let policy = StringPolicy::default();

let rec = client.operate(&WritePolicy::default(), &key, &[

    str_op::trim(&policy, "email"),

    str_op::lower(&policy, "email"),

    scalar::get_bin("email"),

]).await?;

let normalized = rec.bins.get("email");

// normalized: "ana@corp.io"
```

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

    StringOperation.Trim(StringPolicy.Default, "email"),

    StringOperation.Lower(StringPolicy.Default, "email"),

    Operation.Get("email"));

string normalized = (string)rec.GetList("email")[2];

// normalized: "ana@corp.io"
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

p := as.DefaultStringPolicy

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

    as.StrTrimOp(p, "email"),

    as.StrLowerOp(p, "email"),

    as.GetBinOp("email"))

normalized := rec.Bins["email"].(as.OpResults)[2].(string)

// normalized: "ana@corp.io"
```

```javascript
const Aerospike = require('aerospike')

const strings = Aerospike.strings

const op = Aerospike.operations

const record = await client.operate(key, [

    strings.trim('email'),

    strings.lower('email'),

    op.read('email')

])

const normalized = record.bins.email

// normalized: "ana@corp.io"
```

```c
as_operations ops;

as_operations_init(&ops, 3);

as_operations_string_trim(&ops, "email", NULL, NULL);

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

as_operations_add_read(&ops, "email");

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

as_operations_destroy(&ops);

// as_record_get() answers with the first entry for a bin name, which here is a

// modify's nil. Walk rec->bins.entries and keep the last entry for "email".

const char* normalized = NULL;

for (uint16_t i = 0; i < rec->bins.size; i++) {

    as_bin* b = &rec->bins.entries[i];

    if (strcmp(as_bin_get_name(b), "email") == 0) {

        as_val* v = (as_val*)as_bin_get_value(b);

        if (as_val_type(v) == AS_STRING) normalized = as_string_get((as_string*)v);

    }

}

// normalized: "ana@corp.io"
```

```java
Record rec = client.operate(null, key,

    StringOperation.trim(StringPolicy.Default, "email"),

    StringOperation.lower(StringPolicy.Default, "email"),

    Operation.get("email"));

String normalized = (String) rec.getList("email").get(2);

// normalized: "ana@corp.io"
```

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

_, _, bins = client.operate(key, [

    so.trim("email"),

    so.lower("email"),

    operations.read("email"),

])

normalized = bins["email"]

# normalized: "ana@corp.io"
```

### Extracting the domain from an email address

Starting from an `email` bin holding:

```plaintext
"ana@corp.io"
```

[`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) returns the codepoint index of the first `@`, or `-1` when the needle is absent.

-   [Java SDK](#tab-panel-4576)
-   [Python SDK](#tab-panel-4577)
-   [Rust](#tab-panel-4578)
-   [C#](#tab-panel-4579)
-   [Go](#tab-panel-4580)
-   [Node.js](#tab-panel-4581)
-   [C](#tab-panel-4582)
-   [Java](#tab-panel-4583)
-   [Python](#tab-panel-4584)

```java
try (RecordStream rs = session.query(key)

    .bin("email").find("@")

    .execute()) {

    int at = (int) rs.next().recordOrThrow().operationResult(0).getLong();

    // at: 3

}
```

```python
stream = session.query(key).bin("email").str_find("@").execute()

at = stream.first_or_raise().record_or_raise().operation_result(0)

# at: 3
```

```rust
// Requires: use aerospike::operations::string as str_op;

let rec = client.operate(&WritePolicy::default(), &key,

    &[str_op::find("email", "@")]).await?;

let at = rec.bins.get("email");

// at: 3
```

```csharp
int at = client.Operate(null, key,

    StringOperation.Find("email", "@")).GetInt("email");

// at: 3
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

rec, err := client.Operate(nil, key, as.StrFindOp("email", "@"))

at := rec.Bins["email"].(int)

// at: 3
```

```javascript
const Aerospike = require('aerospike')

const strings = Aerospike.strings

const record = await client.operate(key, [strings.find('email', '@')])

const at = record.bins.email

// at: 3
```

```c
as_operations ops;

as_operations_init(&ops, 1);

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

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

as_operations_destroy(&ops);

int64_t at = as_record_get_int64(rec, "email", -1);   // at: 3
```

```java
int at = client.operate(null, key,

    StringOperation.find("email", "@")).getInt("email");

// at: 3
```

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

_, _, bins = client.operate(key, [so.find("email", "@")])

at = bins["email"]

# at: 3
```

Then take the rest of the string from the codepoint after it. The single-argument form of [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) runs to the end of the value, so no length is needed.

-   [Java SDK](#tab-panel-4585)
-   [Python SDK](#tab-panel-4586)
-   [Rust](#tab-panel-4587)
-   [C#](#tab-panel-4588)
-   [Go](#tab-panel-4589)
-   [Node.js](#tab-panel-4590)
-   [C](#tab-panel-4591)
-   [Java](#tab-panel-4592)
-   [Python](#tab-panel-4593)

```java
try (RecordStream rs = session.query(key)

    .bin("email").substr(at + 1)

    .execute()) {

    String domain = rs.next().recordOrThrow().operationResult(0).getString();

    // domain: "corp.io"

}
```

```python
stream = session.query(key).bin("email").str_substr(at + 1).execute()

domain = stream.first_or_raise().record_or_raise().operation_result(0)

# domain: "corp.io"
```

```rust
// Requires: use aerospike::operations::string as str_op;

let rec = client.operate(&WritePolicy::default(), &key,

    &[str_op::substr_from("email", at + 1)]).await?;

let domain = rec.bins.get("email");

// domain: "corp.io"
```

```csharp
string domain = client.Operate(null, key,

    StringOperation.Substr("email", at + 1)).GetString("email");

// domain: "corp.io"
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

rec, err := client.Operate(nil, key, as.StrSubstrFromOp("email", at+1))

domain := rec.Bins["email"].(string)

// domain: "corp.io"
```

```javascript
const Aerospike = require('aerospike')

const strings = Aerospike.strings

const record = await client.operate(key, [strings.substr('email', at + 1)])

const domain = record.bins.email

// domain: "corp.io"
```

```c
as_operations ops;

as_operations_init(&ops, 1);

as_operations_string_substr(&ops, "email", NULL, at + 1);

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

as_operations_destroy(&ops);

const char* domain = as_record_get_str(rec, "email");   // domain: "corp.io"
```

```java
String domain = client.operate(null, key,

    StringOperation.substr("email", at + 1)).getString("email");

// domain: "corp.io"
```

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

_, _, bins = client.operate(key, [so.substr("email", at + 1)])

domain = bins["email"]

# domain: "corp.io"
```

Both round trips collapse into one if the result feeds an expression instead: compose `find` and `substr` in an `expression_read` projection. See [String expressions](https://aerospike.com/docs/develop/expressions/string).

### Classifying a value with a regular expression

Starting from a `sku` bin holding:

```plaintext
"A-1024"
```

[`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) returns a boolean rather than the match, so it classifies a record without moving the value to the client.

-   [Java SDK](#tab-panel-4594)
-   [Python SDK](#tab-panel-4595)
-   [Rust](#tab-panel-4596)
-   [C#](#tab-panel-4597)
-   [Go](#tab-panel-4598)
-   [Node.js](#tab-panel-4599)
-   [C](#tab-panel-4600)
-   [Java](#tab-panel-4601)
-   [Python](#tab-panel-4602)

```java
try (RecordStream rs = session.query(key)

    .bin("sku").regexCompare("^A-[0-9]+$")

    .execute()) {

    boolean matched = rs.next().recordOrThrow().operationResult(0).getBoolean();

    // matched: true

}
```

```python
stream = session.query(key).bin("sku").str_regex_compare(r"^A-[0-9]+$").execute()

matched = stream.first_or_raise().record_or_raise().operation_result(0)

# matched: True
```

```rust
// Requires: use aerospike::operations::string as str_op;

let rec = client.operate(&WritePolicy::default(), &key,

    &[str_op::regex_compare("sku", r"^A-[0-9]+$")]).await?;

let matched = rec.bins.get("sku");

// matched: true
```

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

    StringOperation.RegexCompare("sku", "^A-[0-9]+$"));

bool matched = (bool)rec.GetValue("sku");

// matched: true
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

rec, err := client.Operate(nil, key, as.StrRegexCompareOp("sku", `^A-[0-9]+$`))

matched := rec.Bins["sku"].(bool)

// matched: true
```

```javascript
const Aerospike = require('aerospike')

const strings = Aerospike.strings

const record = await client.operate(key, [

    strings.regexCompare('sku', '^A-[0-9]+$')

])

const matched = record.bins.sku

// matched: true
```

```c
as_operations ops;

as_operations_init(&ops, 1);

as_operations_string_regex_compare(&ops, "sku", NULL, "^A-[0-9]+$");

as_record* rec = NULL;

aerospike_key_operate(&as, &err, NULL, &key, &ops, &rec);

as_operations_destroy(&ops);

bool matched = as_record_get_bool(rec, "sku");   // matched: true
```

```java
Record rec = client.operate(null, key,

    StringOperation.regexCompare("sku", "^A-[0-9]+$"));

boolean matched = (boolean) rec.getValue("sku");

// matched: true
```

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

_, _, bins = client.operate(key, [so.regex_compare("sku", r"^A-[0-9]+$")])

matched = bins["sku"]

# matched: True
```

Prefer `regex_compare` over the legacy `cmp_regex` comparison, which uses POSIX regex and is not Unicode-aware. See [`regex_compare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare).

### Modifying a bin whose type is uncertain

A string modify against a non-String bin fails with `AS_ERR_INCOMPATIBLE_TYPE` (12), and `NO_FAIL` does not suppress it. Starting from a `maybe_string` bin that holds an integer:

```plaintext
42
```

Gate the write with a bin-type filter, so the server skips records whose bin is not a String. The two SDKs take the test as [AEL](https://aerospike.com/docs/develop/client/sdk/concepts/ael/reference) text; the other clients build the same comparison from expression calls. Clients differ in how they report the skip — some raise, some return an empty result, some say nothing — so the reliable signal is that the bin is unchanged.

-   [Java SDK](#tab-panel-4603)
-   [Python SDK](#tab-panel-4604)
-   [Rust](#tab-panel-4605)
-   [C#](#tab-panel-4606)
-   [Go](#tab-panel-4607)
-   [Node.js](#tab-panel-4608)
-   [C](#tab-panel-4609)
-   [Java](#tab-panel-4610)
-   [Python](#tab-panel-4611)

```java
// Returns an empty stream when the filter skips the record.

try (RecordStream rs = session.upsert(key)

    .where("$.maybe_string.type() == STRING")

    .bin("maybe_string").upper()

    .execute()) {

    RecordResult result = rs.next();   // null: the record was skipped

}
```

```python
# first() is None when the filter skips the record.

stream = (session.upsert(key).where("$.maybe_string.type() == STRING")

          .bin("maybe_string").str_upper().execute())
```

```rust
// Requires: use aerospike::expressions as exp;

let mut gated = WritePolicy::default();

gated.base_policy.filter_expression = Some(exp::eq(

    exp::bin_type("maybe_string".to_string()),

    exp::int_val(3),

));

// Returns Err with ResultCode::FilteredOut when the record is skipped.

client.operate(&gated, &key,

    &[str_op::upper(&StringPolicy::default(), "maybe_string")]).await?;
```

```csharp
WritePolicy wp = new WritePolicy();

wp.filterExp = Exp.Build(

    Exp.EQ(Exp.BinType("maybe_string"), Exp.Val((int)ParticleType.STRING)));

// Skipped silently; set wp.failOnFilteredOut to be told instead.

client.Operate(wp, key,

    StringOperation.Upper(StringPolicy.Default, "maybe_string"));
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

wp := as.NewWritePolicy(0, 0)

wp.FilterExpression = as.ExpEq(as.ExpBinType("maybe_string"), as.ExpIntVal(3))

// Returns an error matching FILTERED_OUT when the record is skipped.

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

    as.StrUpperOp(as.DefaultStringPolicy, "maybe_string"))
```

```javascript
const Aerospike = require('aerospike')

const exp = Aerospike.exp

const strings = Aerospike.strings

const policy = new Aerospike.OperatePolicy({

    filterExpression: exp.eq(exp.binType('maybe_string'), exp.int(3))

})

try {

    await client.operate(key, [strings.upper('maybe_string')], null, policy)

} catch (error) {

    if (error.code !== Aerospike.status.FILTERED_OUT) throw error

    // Bin is missing or not a String; the record is unchanged.

}
```

```c
as_exp_build(is_string,

    as_exp_cmp_eq(as_exp_bin_type("maybe_string"), as_exp_int(AS_BYTES_STRING)));

as_policy_operate gated;

as_policy_operate_init(&gated);

gated.base.filter_exp = is_string;

as_operations ops;

as_operations_init(&ops, 1);

as_operations_string_upper(&ops, "maybe_string", NULL, NULL);

// Returns AEROSPIKE_FILTERED_OUT when the record is skipped.

as_record* rec = NULL;

aerospike_key_operate(&as, &err, &gated, &key, &ops, &rec);

as_operations_destroy(&ops);

as_exp_destroy(is_string);
```

```java
WritePolicy wp = new WritePolicy(client.writePolicyDefault);

wp.filterExp = Exp.build(

    Exp.eq(Exp.binType("maybe_string"), Exp.val(ParticleType.STRING)));

// Skipped silently; set wp.failOnFilteredOut to be told instead.

client.operate(wp, key,

    StringOperation.upper(StringPolicy.Default, "maybe_string"));
```

```python
import aerospike

from aerospike.exception import FilteredOut

from aerospike_helpers import expressions as exp

from aerospike_helpers.operations import string_operations as so

policy = {"expressions": exp.Eq(exp.BinType("maybe_string"),

                                aerospike.AS_BYTES_STRING).compile()}

try:

    client.operate(key, [so.upper("maybe_string")], policy=policy)

except FilteredOut:

    pass    # Bin is missing or not a String; the record is unchanged.
```

Or send the modify ungated and handle the type error, which costs a round trip on every record with the wrong type but needs no expression. Ask for [error detail](https://aerospike.com/docs/develop/data-types/string/operations#error-detail-subcodes) at verbosity 2 and the server says which operation wanted what — `string_upper requires string bin, got integer` — rather than leaving the handler to interpret code 12 on its own.

-   [Java SDK](#tab-panel-4612)
-   [Python SDK](#tab-panel-4613)
-   [Rust](#tab-panel-4614)
-   [C#](#tab-panel-4615)
-   [Go](#tab-panel-4616)
-   [Node.js](#tab-panel-4617)
-   [C](#tab-panel-4618)
-   [Java](#tab-panel-4619)
-   [Python](#tab-panel-4620)

```java
Behavior verbose = Behavior.DEFAULT.deriveWithChanges("verbose", b ->

    b.on(Selectors.all(), ops ->

        ops.errorDetailVerbosity(ErrorDetailVerbosity.MESSAGE)));

Session session = cluster.createSession(verbose);

try (RecordStream rs = session.upsert(key)

    .bin("maybe_string").upper()

    .execute()) {

    rs.next().recordOrThrow();

} catch (AerospikeException e) {

    String reason = e.getBaseMessage();

    // reason: "string_upper requires string bin, got integer"

}
```

```python
from aerospike_sdk.exceptions import BinTypeError

from aerospike_sdk.policy.behavior_settings import Settings

session = cluster.create_session(Behavior.DEFAULT.derive_with_changes(

    "verbose", writes=Settings(error_detail_verbosity=2)))

try:

    session.upsert(key).bin("maybe_string").str_upper().execute()

except BinTypeError as e:

    reason = e.server_message

    # reason: "string_upper requires string bin, got integer"
```

```rust
// Requires: use aerospike::ResultCode;

let mut policy = WritePolicy::default();

policy.base_policy.error_detail_verbosity = 2;   // 2 = subcode + message

if let Err(e) = client.operate(&policy, &key,

    &[str_op::upper(&StringPolicy::default(), "maybe_string")]).await

{

    let reason = e.server_message();

    // e.server_result_code() is Some(ResultCode::BinTypeError)

    // reason: Some("string_upper requires string bin, got integer")

}
```

```csharp
WritePolicy policy = new WritePolicy();

policy.errorDetailVerbosity = 2;   // 2 = subcode + message

try

{

    client.Operate(policy, key,

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

}

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

{

    string reason = e.BaseMessage;

    // reason: "string_upper requires string bin, got integer"

}
```

```go
// Requires: import as "github.com/aerospike/aerospike-client-go/v8"

//       and "errors" and "github.com/aerospike/aerospike-client-go/v8/types"

wp := as.NewWritePolicy(0, 0)

wp.ErrorDetailVerbosity = 2 // 2 = subcode + message

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

    as.StrUpperOp(as.DefaultStringPolicy, "maybe_string"))

var ae *as.AerospikeError

if errors.As(err, &ae) && ae.Matches(types.BIN_TYPE_ERROR) {

    // ae.ServerMessage: "string_upper requires string bin, got integer"

}
```

```javascript
const Aerospike = require('aerospike')

const strings = Aerospike.strings

const policy = new Aerospike.OperatePolicy({

    errorDetailVerbosity: 2   // 2 = subcode + message

})

try {

    await client.operate(key, [strings.upper('maybe_string')], null, policy)

} catch (error) {

    if (error.code !== Aerospike.status.ERR_BIN_INCOMPATIBLE_TYPE) throw error

    const reason = error.message

    // This client prepends the node address:

    // reason: "127.0.0.1:3000 string_upper requires string bin, got integer"

}
```

```c
as_policy_operate policy;

as_policy_operate_init(&policy);

policy.base.error_detail_verbosity = 2;   // 2 = subcode + message

as_operations ops;

as_operations_init(&ops, 1);

as_operations_string_upper(&ops, "maybe_string", NULL, NULL);

as_record* rec = NULL;

as_status status = aerospike_key_operate(&as, &err, &policy, &key, &ops, &rec);

as_operations_destroy(&ops);

if (status == AEROSPIKE_ERR_BIN_INCOMPATIBLE_TYPE) {

    // This client prepends the node address:

    // err.message: "127.0.0.1:3000 string_upper requires string bin, got integer"

}
```

```java
WritePolicy policy = new WritePolicy(client.writePolicyDefault);

policy.errorDetailVerbosity = 2;   // 2 = subcode + message

try {

    client.operate(policy, key,

        StringOperation.upper(StringPolicy.Default, "maybe_string"));

} catch (AerospikeException e) {

    String reason = e.getBaseMessage();

    // reason: "string_upper requires string bin, got integer"

}
```

```python
from aerospike.exception import BinIncompatibleType

from aerospike_helpers.operations import string_operations as so

try:

    client.operate(key, [so.upper("maybe_string")],

                   policy={"error_detail_verbosity": 2})   # 2 = subcode + message

except BinIncompatibleType as e:

    reason = e.msg

    # This client prepends the node address:

    # reason: "127.0.0.1:3000 string_upper requires string bin, got integer"
```

::: note
`NO_FAIL` covers a different class of failure: an out-of-range [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) index or a negative repeat count, for example. It does not suppress a type mismatch, invalid UTF-8, or malformed arguments, and read operations do not accept it at all. See [Operation flags](https://aerospike.com/docs/develop/data-types/string/operations#operation-flags).
:::