---
title: "String operations"
description: "Reference for the Java client's StringOperation 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. This approach avoids fetching a bin, editing it in your application, and writing it back. This reference covers the Java client surface, for Java developers already using `client.operate()` and expressions: `StringOperation` for `operate()` calls and `StringExp` for expressions. It requires Aerospike Database 8.2.0 or later and Java client 10.4.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:

```java
import com.aerospike.client.AerospikeClient;

import com.aerospike.client.Bin;

import com.aerospike.client.Key;

import com.aerospike.client.Record;

// Establishes a connection to the server

AerospikeClient client = new AerospikeClient("127.0.0.1", 3000);

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

Key key = new Key("test", "users", 1);
```

## Round-trip elimination

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

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

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

String email = record.getString("email").strip().toLowerCase();

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

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

```java
import com.aerospike.client.operation.StringOperation;

import com.aerospike.client.operation.StringPolicy;

// After: one round trip

client.operate(null, key,

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

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

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

Record verify = client.get(null, key);

System.out.println(verify.getString("email"));
```

## Two surfaces

Every String operation is available in two forms, in packages `com.aerospike.client.operation` and `com.aerospike.client.exp`:

| Surface | Class | Used with | Argument order |
| --- | --- | --- | --- |
| Operation | `StringOperation` | `client.operate()` | Bin name first: `StringOperation.strlen(binName, ctx...)` |
| Expression | `StringExp` | `Exp.build()`, [`ExpOperation.read()`](https://aerospike.com/docs/develop/client/java/usage/atomic/expressions#read-1), filter policies | Source expression last: `StringExp.strlen(src)` |

`StringOperation` builders read or modify a bin directly. `StringExp` builders produce an `Exp` node that composes inside a larger expression. A modify-style `StringExp` (`upper`, `replace`, `trim`, and similar) returns the transformed string as a value and does not write it back to the bin on its own. To persist a modify expression’s result, write it back with [`Operation.put`](https://aerospike.com/docs/develop/client/java/usage/atomic/update#update)/[`ExpOperation.write`](https://aerospike.com/docs/develop/client/java/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:

```java
// 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 bitmask of `StringWriteFlags`:

```java
import com.aerospike.client.operation.StringPolicy;

import com.aerospike.client.operation.StringWriteFlags;

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

StringPolicy custom = new StringPolicy(

    StringWriteFlags.CREATE_ONLY | StringWriteFlags.NO_FAIL);        // combine with bitwise OR
```

| Flag | Value | Effect |
| --- | --- | --- |
| `DEFAULT` | 0 | Allow create or update. |
| `CREATE_ONLY` | 1 | Fail with `BIN_EXISTS_ERROR` if the bin already exists. Valid only on the eight operations that can create a missing bin: `insert`, `overwrite`, `concat`, `append`, `prepend`, `padStart`, `padEnd`, `repeat`. Every other modify operation rejects it with `PARAMETER_ERROR`. |
| `UPDATE_ONLY` | 2 | Silently no-op (bin not created) if the bin is missing. Valid on all modify operations. Mutually exclusive with `CREATE_ONLY`. |
| `NO_FAIL` | 4 | Suppress errors raised while the operation runs, leaving the bin at its prior value. Does not suppress a wrong bin type, invalid UTF-8, or argument-parsing errors like an invalid flag combination. |

`StringPolicy` is a per-operation argument, not client configuration: there is no `stringPolicyDefault` on `ClientPolicy`. Pass a new `StringPolicy` to each call that needs non-default flags.

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

```java
import com.aerospike.client.AerospikeException;

import com.aerospike.client.ResultCode;

try {

    client.operate(null, key,

        StringOperation.insert(new StringPolicy(StringWriteFlags.CREATE_ONLY), "email", 0, "prefix-"));

} catch (AerospikeException ae) {

    if (ae.getResultCode() == ResultCode.BIN_EXISTS_ERROR) {

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

    } else {

        throw ae;

    }

}
```

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 exception either way. Read the bin back to confirm which operations applied rather than assuming success from the absence of an error. `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 and leaves the bin unchanged, which looks identical to success from the client.

On `StringExp`, only `NO_FAIL` is meaningful for most modify builders. `CREATE_ONLY` and `UPDATE_ONLY` only make sense when the target is a bin, so they don’t carry over to a source expression that may not be a bin at all. `regexReplace` is the exception: it accepts `DEFAULT`, `UPDATE_ONLY`, and `NO_FAIL` like its `StringOperation` counterpart, and rejects `CREATE_ONLY`. See the “Two flag families collide numerically” caution later on this page, under [Modify operations](#modify-operations).

## 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 `StringOperation` arguments, or the source expression as the last `StringExp` argument. Index and length values below count Unicode codepoints, not bytes: most characters are one codepoint, but a codepoint can differ from a UTF-16 `char` for characters outside the Basic Multilingual Plane (for example, some emoji).

| Operation | Returns | Description |
| --- | --- | --- |
| [`strlen`](https://aerospike.com/docs/develop/data-types/string/operations#strlen) | integer | Codepoint count. |
| [`byteLength`](https://aerospike.com/docs/develop/data-types/string/operations#byte_length) | integer | UTF-8 byte count. Differs from `strlen` for non-ASCII text. |
| [`substr`](https://aerospike.com/docs/develop/data-types/string/operations#substr) | string | Substring from a start index, or a `[start, end)` range. |
| [`charAt`](https://aerospike.com/docs/develop/data-types/string/operations#char_at) | string | Single codepoint at an index. |
| [`find`](https://aerospike.com/docs/develop/data-types/string/operations#find) | integer | Codepoint index of `needle`, or a specific 1-based occurrence. `-1` if absent. |
| [`contains`](https://aerospike.com/docs/develop/data-types/string/operations#contains) | boolean | Whether `needle` is a substring. |
| [`startsWith`](https://aerospike.com/docs/develop/data-types/string/operations#starts_with) | boolean | Whether the bin begins with `prefix`. |
| [`endsWith`](https://aerospike.com/docs/develop/data-types/string/operations#ends_with) | boolean | Whether the bin ends with `suffix`. |
| [`isNumeric`](https://aerospike.com/docs/develop/data-types/string/operations#is_numeric) | boolean | Whether the bin is a valid Integer or float, optionally filtered by `StringNumericType`. |
| [`isUpper`](https://aerospike.com/docs/develop/data-types/string/operations#is_upper) | boolean | Whether every cased codepoint is uppercase. |
| [`isLower`](https://aerospike.com/docs/develop/data-types/string/operations#is_lower) | boolean | Whether every cased codepoint is lowercase. |
| [`regexCompare`](https://aerospike.com/docs/develop/data-types/string/operations#regex_compare) | boolean | Whether an ICU (International Components for Unicode) regex `pattern` matches, optionally with `StringRegexFlags`. |
| [`toInteger`](https://aerospike.com/docs/develop/data-types/string/operations#to_integer) | integer | Parse as an int64. |
| [`toDouble`](https://aerospike.com/docs/develop/data-types/string/operations#to_double) | float | Parse as a double. |
| [`toBlob`](https://aerospike.com/docs/develop/data-types/string/operations#to_blob) | blob | UTF-8 bytes of the string. |
| [`split`](https://aerospike.com/docs/develop/data-types/string/operations#split) | list | Split by codepoint, or by a `separator` substring. |
| [`b64Decode`](https://aerospike.com/docs/develop/data-types/string/operations#b64_decode) | blob | Decode the bin as base64 text. |

Six read operations (`contains`, `startsWith`, `endsWith`, `isNumeric`, `isUpper`, `isLower`) and `regexCompare` return a native boolean, not an integer `0`/`1`. See [Reading operate results](#reading-operate-results).

## Modify operations

Modify operations write the transformed value back to the bin (`StringOperation`) or return it as an expression value (`StringExp`). An expression-side modify builder does not mutate the underlying bin.

::: destructive, irreversible writes
`StringOperation` modify calls overwrite the stored bin value on the server, with no built-in undo. `snip`, `replace`, `replaceAll`, `regexReplace`, and `overwrite` can truncate or permanently discard part of the value if the index, pattern, or range is wrong. Test destructive operations against sample data before running them against production records.
:::
::: 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 `AS_ERR_INVALID_ENCODING`. See [String operations and UTF-8 validation](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#string-operations-and-utf-8-validation) before running these operations against existing bins.
:::

| Operation | `CREATE_ONLY` valid? | Description |
| --- | --- | --- |
| [`insert`](https://aerospike.com/docs/develop/data-types/string/operations#insert) | Yes | Splice `value` in at a codepoint index. |
| [`overwrite`](https://aerospike.com/docs/develop/data-types/string/operations#overwrite) | Yes | Overwrite codepoints starting at an index. The resolved index must be in range, or the server returns a parameter error. |
| [`concat`](https://aerospike.com/docs/develop/data-types/string/operations#concat) | Yes | Append one string, or each element of a list of strings, in order. |
| [`append`](https://aerospike.com/docs/develop/data-types/string/operations#append) | Yes | Append `value`. Unicode-aware, unlike the legacy `Operation.append`. |
| [`prepend`](https://aerospike.com/docs/develop/data-types/string/operations#prepend) | Yes | Prepend `value`. Unicode-aware, unlike the legacy `Operation.prepend`. |
| [`padStart`](https://aerospike.com/docs/develop/data-types/string/operations#pad_start) | Yes | Left-pad with `padString` up to `targetLength` codepoints. No-op if already at or above the target. |
| [`padEnd`](https://aerospike.com/docs/develop/data-types/string/operations#pad_end) | Yes | Right-pad with `padString` up to `targetLength` codepoints. |
| [`repeat`](https://aerospike.com/docs/develop/data-types/string/operations#repeat) | Yes | Repeat the bin `count` times. |
| [`snip`](https://aerospike.com/docs/develop/data-types/string/operations#snip) | No | Remove a `[start, end)` range, or truncate from `start` to the end. |
| [`replace`](https://aerospike.com/docs/develop/data-types/string/operations#replace) | No | Replace the first occurrence of `needle` with `replacement`. |
| [`replaceAll`](https://aerospike.com/docs/develop/data-types/string/operations#replace_all) | No | Replace every occurrence of `needle`. |
| [`upper`](https://aerospike.com/docs/develop/data-types/string/operations#upper) / [`lower`](https://aerospike.com/docs/develop/data-types/string/operations#lower) | No | Uppercase or lowercase the stored bin value. |
| [`caseFold`](https://aerospike.com/docs/develop/data-types/string/operations#case_fold) | No | Locale-independent case fold, for comparison keys. |
| [`normalizeNFC`](https://aerospike.com/docs/develop/data-types/string/operations#normalize_nfc) | No | Normalize to Unicode NFC (Normalization Form Composed). |
| [`trimStart`](https://aerospike.com/docs/develop/data-types/string/operations#trim_start) / [`trimEnd`](https://aerospike.com/docs/develop/data-types/string/operations#trim_end) / [`trim`](https://aerospike.com/docs/develop/data-types/string/operations#trim) | No | Remove Unicode whitespace from the start, end, or both. |
| [`regexReplace`](https://aerospike.com/docs/develop/data-types/string/operations#regex_replace) | No | Replace regex `pattern` matches with `replacement`. Pass `StringRegexFlags.GLOBAL` to replace every match. |

Only the eight operations marked “Yes” accept `StringWriteFlags.CREATE_ONLY`. The server rejects it with `PARAMETER_ERROR` 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
`regexReplace` takes a `regexFlags` argument and a `StringPolicy` argument. `StringWriteFlags.NO_FAIL` (4) and `StringRegexFlags.DOTALL` (4) share a bit value, as do `StringWriteFlags.UPDATE_ONLY` (2) and `StringRegexFlags.MULTILINE` (2). Passing one flag set into the other’s argument silently selects the wrong behavior instead of failing. Always pass `regexFlags` and `policy.flags` in their own arguments.
:::

## Type conversion

`toString` converts an integer, float, boolean, string, or blob bin to its string representation:

```java
Record record = client.operate(null, key, StringOperation.toString("n"));

String s = record.getString("n");
```

`toString` 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, 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.

## 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 `isNumeric("5", StringNumericType.FLOAT)` is `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 `boolean`, not an integer `0`/`1`:

```java
Record record = client.operate(null, key, StringOperation.contains("email", "@"));

boolean hasAt = record.getBoolean("email");   // correct

// record.getLong("email") throws a ClassCastException
```

### Multiple operations on one bin return a list

When more than one operation in a single `operate()` call targets the same bin, the server returns an ordered list in that bin’s result, one entry per operation in submission order. Modify operations contribute an empty entry rather than being skipped. See [Returning from operate()](https://aerospike.com/docs/develop/client/java/usage/atomic/multi#returning-from-operate) in Bin operations.

```java
import java.util.List;

Record record = client.operate(null, key,

    StringOperation.trim(StringPolicy.Default, "email"),   // modify: empty entry

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

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

List<?> results = record.getList("email");

long len = (Long) results.get(1);

String head = (String) results.get(2);
```

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

## Nested strings

`StringOperation` takes an optional trailing `CTX...` to reach a String nested inside a List or Map. The path must already resolve to a String. A non-string nested value fails with [`AS_ERR_INCOMPATIBLE_TYPE`](https://aerospike.com/docs/database/reference/error-codes).

```java
import com.aerospike.client.Value;

import com.aerospike.client.cdt.CTX;

// 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 do not 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` condition, then uses it two ways: as a read filter, and as a projected read value.

```java
import com.aerospike.client.exp.Exp;

import com.aerospike.client.exp.MapExp;

import com.aerospike.client.exp.StringExp;

import com.aerospike.client.exp.ExpOperation;

import com.aerospike.client.exp.ExpReadFlags;

import com.aerospike.client.cdt.MapReturnType;

import com.aerospike.client.policy.Policy;

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 codepoints

Policy policy = new Policy();

policy.filterExp = Exp.build(isLong);

Record filtered = client.get(policy, key);   // null if the filter excludes the record

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

Record projected = client.operate(null, key,

    ExpOperation.read("isLong", Exp.build(isLong), ExpReadFlags.DEFAULT));

boolean bioIsLong = projected.getBoolean("isLong");
```

`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 Java client 10.4.0 or later. A server prior to 8.2.0 does not recognize the string opcodes and returns a generic parameter error, not a distinct “unsupported feature” code. Check the cluster version rather than branching on the error.

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

## 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 (the legacy pair does a raw byte concatenation). Both legacy operations also accept Blob bins, which the string package cannot target. For a Blob bin, keep using `Operation.append`/`Operation.prepend`: there is no string-module replacement, and the legacy behavior is unchanged and fully supported for that case.
-   The legacy `Exp.regexCompare(String, int, Exp)` is deprecated in favor of `StringExp.regexCompare(Exp, int, Exp)`, which is Unicode-aware. The legacy version uses POSIX regex semantics.

::: migration can surface invalid encoding
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, the new call fails with `AS_ERR_INVALID_ENCODING` instead of succeeding as before. Audit affected bins for valid UTF-8 as described in [Repair legacy String bins](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#repair-legacy-string-bins) before switching write paths in production.
:::

## Next steps

-   [String operations reference](https://aerospike.com/docs/develop/data-types/string/operations): full semantics, index-bounds behavior, and error codes for all 37 operations
-   [String expressions reference](https://aerospike.com/docs/develop/expressions/string)
-   [String examples](https://aerospike.com/docs/develop/data-types/string/examples)
-   [String operations and UTF-8 validation](https://aerospike.com/docs/database/advanced/special-upgrades/820-upgrade#string-operations-and-utf-8-validation)
-   [Bin operations](https://aerospike.com/docs/develop/learn/bin-operations)
-   [Expressions - Java](https://aerospike.com/docs/develop/client/java/usage/atomic/expressions): building and using `Exp`, filter policies, and `ExpOperation`
-   [API reference (Java)](https://javadoc.io/doc/com.aerospike/aerospike-client-jdk21/latest/index.html)