Skip to content

String operations

For the complete documentation index see: llms.txt

All documentation pages available in markdown.

Applies to

  • Aerospike Developer SDK (Java 21+ and Python 3.11+)
  • Aerospike Database 6.0 or later unless a section states otherwise

Learn how to inspect and transform string bins on the server, without reading the whole value to the client first. This guide covers reading substrings and search results, and modifying strings in place (case conversion, trimming, padding, regex replace, and more). It also covers converting between string and other types, and applying the same operations inside filter and projection expressions.

Except where noted, snippets on this page use the imports below. A snippet lists additional import lines only when it needs a type not shown here. When this page includes a Complete example section, that block is fully self-contained with every import required to run it.

import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.StringWriteOptions;
import com.aerospike.client.sdk.exp.Exp;
import com.aerospike.client.sdk.exp.StringExp;

Two ways to work with strings

SurfaceRole
BinBuilder (session.query(key).bin("s").<op>() / session.upsert(key).bin("s").<op>())Fluent chain. Each method queues one server-side string operation on the named bin.
StringExp (Java) / Exp.string_* (Python)Expression builders for filters (.where(...)), selectFrom/select_from projections, and composing reads. Modify-style expressions return the transformed value, and don’t write it back to the bin.

Multiple operations on one bin

When a query or write queues several operations against the same bin, as in the examples throughout this page, the results come back in submission order, one slot per operation. Modify operations contribute a null/None slot: the server returns no value for them. Chain a trailing .get() on the bin when you want the value after the modifications.

Use Record.operationResult(index) with the zero-based position of the operation in the call chain, then a typed getter such as getLong() or getString(). This is distinct from record.bins.get(name), which reads a single bin’s current value (see Update records for that pattern with numeric bins).

Read operations

Indexes are Unicode codepoints, left to right. Negative indexes count from the end (-1 is the last codepoint). Out-of-range indexes are clamped and do not throw an error.

For example, for the string “hello”, substr(3, 100) / str_substr(3, 100) returns “lo”.

JavaPythonReturns
strlen()str_strlen()Codepoint count (int64)
substr(start[, end])str_substr(start[, end])Substring, half-open [start, end) when end is given, otherwise through the end of the string
charAt(index)str_char_at(index)Single-codepoint string at index
find(needle[, occurrence])str_find(needle[, occurrence])Codepoint index of the match, or -1. occurrence is 1-based (1 = first, -1 = last), not a start offset. Defaults to 1
contains(needle)str_contains(needle)Boolean
startsWith(prefix)str_starts_with(prefix)Boolean
endsWith(suffix)str_ends_with(suffix)Boolean
byteLength()str_byte_length()UTF-8 byte length
isNumeric([numericType])str_is_numeric([numeric_type])Boolean, optionally restricted to INT or FLOAT (see Type conversion)
isUpper() / isLower()str_is_upper() / str_is_lower()Boolean
split([separator])str_split([separator])List of strings. Omitted separator splits per codepoint
b64Decode()str_b64_decode()Blob decoded from base64
regexCompare(pattern[, regexFlags])str_regex_compare(pattern[, flags])Boolean match result (see Regex flags)
DataSet docs = DataSet.of("test", "docs");
String key = "row1";
session.upsert(docs.id(key)).bin("message").setTo("hello world").execute();
RecordStream stream = session.query(docs.id(key))
.bin("message").strlen()
.bin("message").substr(6)
.bin("message").substr(0, 5)
.bin("message").find("o", -1)
.bin("message").contains("world")
.execute();
Record rec = stream.getFirst().orElseThrow().recordOrThrow();
System.out.println("length: " + rec.operationResult(0).getLong());
System.out.println("substr(6): " + rec.operationResult(1).getString());
System.out.println("substr(0,5): " + rec.operationResult(2).getString());
System.out.println("last 'o' at: " + rec.operationResult(3).getLong());
System.out.println("contains 'world': " + rec.operationResult(4).getBoolean());
stream.close();

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.query(Key) | ChainableQueryBuilder.execute() | RecordStream.getFirst() | RecordStream.close() | RecordResult.recordOrThrow() | Record.operationResult(...)

Modify operations

Modify operations change the bin in place. They return no value: each one contributes a null/None slot to the positional results, so chain a trailing .get() on the bin to read the value after the modifications. All modify operations accept optional write flags. See String write flags.

JavaPythonEffect
insert(index, value[, options])str_insert(index, value[, flags=])Splice value into the bin at codepoint index
overwrite(index, value[, options])str_overwrite(index, value[, flags=])Overwrite codepoints starting at index. Result can grow past the original length
concat(fragment or fragments[, options])str_concat(value[, flags=])Append one fragment or, given an ordered list, append each fragment in order
append(fragment[, options])str_append(value[, flags=])Append one fragment
prepend(fragment[, options])str_prepend(value[, flags=])Prepend one fragment
snip(start[, end][, options])str_snip(start[, end][, flags=])Remove codepoints from start through end (exclusive), or through the end of the string if end is omitted
replace(needle, replacement[, options])str_replace(needle, replacement[, flags=])Replace the first occurrence of needle
replaceAll(needle, replacement[, options])str_replace_all(needle, replacement[, flags=])Replace every occurrence of needle
upper([options]) / lower([options])str_upper([flags=]) / str_lower([flags=])Uppercase / lowercase the bin
caseFold([options])str_case_fold([flags=])Locale-independent case fold (for normalized comparison keys)
normalizeNfc([options])str_normalize_nfc([flags=])Normalize to Unicode Normalization Form C (NFC)
trimStart() / trimEnd() / trim()str_trim_start() / str_trim_end() / str_trim()Remove Unicode whitespace from one or both ends. Each accepts [options] / [flags=]
padStart(targetLength, padString[, options])str_pad_start(target_length, pad_string[, flags=])Left-pad to a minimum length
padEnd(targetLength, padString[, options])str_pad_end(target_length, pad_string[, flags=])Right-pad to a minimum length
repeat(count[, options])str_repeat(count[, flags=])Repeat the bin’s contents count times
regexReplace(pattern, replacement[, regexFlags][, options])str_regex_replace(pattern, replacement[, flags][, write_flags=])Regex-based replace. See Regex flags
DataSet docs = DataSet.of("test", "docs");
String key = "row1";
session.upsert(docs.id(key)).bin("title").setTo(" the Quick Brown Fox ").execute();
RecordStream stream = session.update(docs.id(key))
.bin("title").trim()
.bin("title").upper()
.bin("title").padEnd(30, ".")
.bin("title").get()
.execute();
Record rec = stream.getFirst().orElseThrow().recordOrThrow();
// Slots 0-2 are the modify operations and hold null; slot 3 is the trailing get.
System.out.println("after trim, upper, padEnd: " + rec.operationResult(3).getString());
stream.close();
// CREATE_ONLY plus NO_FAIL: create the bin if it's missing, otherwise leave it
// unchanged instead of failing with BIN_EXISTS_ERROR. "subtitle" doesn't exist
// yet, so the first run creates it and a rerun leaves it alone.
session.update(docs.id(key))
.bin("subtitle").concat("draft", opt -> opt.createOnly().noFail())
.execute();

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.update(DataSet) | ChainableQueryBuilder.execute() | RecordStream.getFirst() | RecordStream.close() | RecordResult.recordOrThrow() | Record.operationResult(...) | StringWriteOptions.createOnly() | StringWriteOptions.noFail()

String write flags

Java exposes the flags as StringWriteOptions builder methods on the fluent API (opt -> opt.createOnly(), opt -> opt.noFail()) and as StringWriteFlags int constants on StringExp. Python uses the StringWriteFlags enum everywhere. Combine members with bitwise OR.

FlagJavaPythonEffect
DefaultStringWriteFlags.DEFAULTStringWriteFlags.DEFAULTOn the operations that can create a bin, create it if missing, otherwise update it. On every other operation a missing bin is a silent no-op, not an error
Create onlyStringWriteFlags.CREATE_ONLY / opt -> opt.createOnly()StringWriteFlags.CREATE_ONLYApply only if the bin doesn’t exist yet. Fails with BIN_EXISTS_ERROR on an existing bin. Valid only on operations that can create a bin (insert, overwrite, concat, append, prepend, padStart, padEnd, repeat), and never with a nested collection data type (CDT) path
Update onlyStringWriteFlags.UPDATE_ONLY / opt -> opt.updateOnly()StringWriteFlags.UPDATE_ONLYApply only if the bin exists. On a missing bin the operation is a silent no-op and doesn’t create the bin
No-failStringWriteFlags.NO_FAIL / opt -> opt.noFail()StringWriteFlags.NO_FAILTurn an in-operation failure into a silent no-op: BIN_EXISTS_ERROR from CREATE_ONLY, or OP_NOT_APPLICABLE from a nested path that doesn’t resolve. Leaves the bin unchanged and returns a null/None slot for that operation

CREATE_ONLY and UPDATE_ONLY are mutually exclusive. The server rejects the combination with PARAMETER_ERROR.

Regex flags

Used with regexCompare()/str_regex_compare() and regexReplace()/str_regex_replace(). Combine with bitwise OR. Both SDKs expose them as StringRegexFlags.

FlagEffect
DEFAULTNo flags
CASE_INSENSITIVECase-insensitive matching
MULTILINE^ and $ match the start/end of any line, not just the start/end of the input
DOTALL. matches any character, including line terminators
UNIX_LINESTreat only \n as a line terminator
GLOBALReplace every match, not just the first. Only applicable to regexReplace/str_regex_replace

Patterns use International Components for Unicode (ICU) regex syntax.

Type conversion

JavaPythonEffect
readAsString()read_as_string()Convert the bin’s value to its string representation. Accepts int, float, string, bool, or valid UTF-8 blob bins. This is type-agnostic: it operates on any convertible bin type, not just strings, so it’s not named toString()/to_string(), avoiding a naming collision with generic conversions
stringToInteger()str_to_integer()Parse the string bin as an int64
stringToDouble()str_to_double()Parse the string bin as a float64
stringToBlob()str_to_blob()Reinterpret the string bin’s UTF-8 bytes as a blob

In Java, readAsString() is available on the write builders (session.update(...)/session.upsert(...)), not on session.query(key).bin(...). Python offers read_as_string() on both.

A parse failure (for example, stringToInteger() / str_to_integer() on a non-numeric string) fails the call with OP_NOT_APPLICABLE, raised as BinOpInvalidException (Java) / BinOpInvalidError (Python). Guard with isNumeric(StringNumericType.INT) / str_is_numeric(StringNumericType.INT) first when the input isn’t trusted, or catch the exception. Calling readAsString()/read_as_string() on a bin type outside the supported list (for example, a List or Map bin) fails with BIN_TYPE_ERROR.

DataSet docs = DataSet.of("test", "docs");
String key = "row1";
session.upsert(docs.id(key)).bin("count").setTo("42").execute();
RecordStream stream = session.query(docs.id(key))
.bin("count").stringToInteger()
.execute();
Record rec = stream.getFirst().orElseThrow().recordOrThrow();
System.out.println("parsed: " + rec.operationResult(0).getLong());
stream.close();

📖 API reference: DataSet.of(...) | DataSet.id(...) | Session.query(Key) | ChainableQueryBuilder.execute() | RecordStream.getFirst() | RecordStream.close() | RecordResult.recordOrThrow() | Record.operationResult(...)

String expressions

StringExp (Java) and Exp.string_* (Python) mirror the same operations as expressions, for use in .where(...) filters and selectFrom/select_from projections. Modify-style expressions such as upper and replace return the transformed value without writing it back to the bin. Pass the result to a write operation if you need to persist it. Modify-style expressions take the write flags as their first argument (0 for the default), followed by the operation’s arguments and the source expression last.

DataSet docs = DataSet.of("test", "docs");
String key = "row1";
session.upsert(docs.id(key)).bin("email").setTo("Alice@Example.com").execute();
// Filter plus a projection that lowercases the email, both on a key read.
RecordStream stream = session.query(docs.id(key))
.where(StringExp.startsWith(
Exp.val("alice"),
StringExp.lower(0, Exp.stringBin("email"))))
.bin("emailLower").selectFrom(StringExp.lower(0, Exp.stringBin("email")))
.execute();
Record rec = stream.getFirst().orElseThrow().recordOrThrow();
System.out.println("lowercased email: " + rec.getString("emailLower"));
stream.close();

📖 API reference: StringExp.startsWith(...) | StringExp.lower(...) | Exp.stringBin(...) | Exp.val(...) | ChainableQueryBuilder.where(...) | QueryBinBuilder.selectFrom(...)

Nested strings

Both APIs can target a string value nested inside a list or map through the same CDT path methods used for collection operations (onMapKey() / on_map_key(), onListIndex() / on_list_index(), and similar). The nested value must already be a string. Operations on a non-string leaf return a bin-type error. CREATE_ONLY can’t be combined with a nested path (PARAMETER_ERROR).

// Additional import for this example:
import java.util.Map;
DataSet docs = DataSet.of("test", "docs");
String key = "row1";
session.upsert(docs.id(key))
.bin("profile").setTo(Map.of("bio", " Loves hiking "))
.execute();
// Trim the nested "bio" string inside the "profile" map, then read the map back.
RecordStream stream = session.update(docs.id(key))
.bin("profile").onMapKey("bio").trim()
.bin("profile").get()
.execute();
Record rec = stream.getFirst().orElseThrow().recordOrThrow();
System.out.println("profile after trim: " + rec.operationResult(1).getMap());
stream.close();

📖 API reference: BinBuilder.onMapKey(...) | Session.update(DataSet) | Record.operationResult(...)

Version requirements

String read/modify operations require Aerospike Database 8.2.0 or later. Sending these operations to an older server returns an error. During a rolling upgrade, don’t issue these operations until every node in the cluster reports Database 8.2.0 or later — a partially upgraded cluster accepts them on upgraded nodes and rejects them on nodes that aren’t upgraded yet. Check your SDK release notes for the minimum client version. See Version Compatibility.

Complete example

This example is self-contained. It lists every import needed to run standalone.

import com.aerospike.client.sdk.Cluster;
import com.aerospike.client.sdk.ClusterDefinition;
import com.aerospike.client.sdk.DataSet;
import com.aerospike.client.sdk.Record;
import com.aerospike.client.sdk.RecordStream;
import com.aerospike.client.sdk.Session;
import com.aerospike.client.sdk.policy.Behavior;
public class StringOperationsExample {
public static void main(String[] args) {
try (Cluster cluster = new ClusterDefinition("localhost", 3000).connect()) {
Session session = cluster.createSession(Behavior.DEFAULT);
DataSet docs = DataSet.of("test", "docs");
String key = "string-example-doc";
// Seed data so the example is repeatable.
session.upsert(docs.id(key)).bin("title").setTo(" the Quick Brown Fox ").execute();
// Read operations: inspect the string without modifying it.
RecordStream readStream = session.query(docs.id(key))
.bin("title").strlen()
.bin("title").contains("Fox")
.execute();
Record read = readStream.getFirst().orElseThrow().recordOrThrow();
System.out.println("length: " + read.operationResult(0).getLong());
System.out.println("contains 'Fox': " + read.operationResult(1).getBoolean());
readStream.close();
// Modify operations: trim, uppercase, and pad the bin in place,
// then read the result back in the same call.
RecordStream modifyStream = session.update(docs.id(key))
.bin("title").trim()
.bin("title").upper()
.bin("title").padEnd(30, ".")
.bin("title").get()
.execute();
Record modified = modifyStream.getFirst().orElseThrow().recordOrThrow();
System.out.println("after modify: " + modified.operationResult(3).getString());
modifyStream.close();
// Type conversion: parse a numeric-looking string bin.
session.upsert(docs.id(key)).bin("count").setTo("42").execute();
RecordStream convertStream = session.query(docs.id(key))
.bin("count").stringToInteger()
.execute();
Record converted = convertStream.getFirst().orElseThrow().recordOrThrow();
System.out.println("parsed count: " + converted.operationResult(0).getLong());
convertStream.close();
}
}
}

📖 API reference: ClusterDefinition(String,int) | ClusterDefinition.connect() | Cluster.createSession(Behavior) | DataSet.of(...) | Session.upsert(DataSet) | Session.query(Key) | Session.update(DataSet) | RecordStream.getFirst() | RecordStream.close() | Record.operationResult(...)

API reference summary

CategoryJavaPython
Readstrlen(), substr(...), charAt(...), find(...), contains(...), startsWith(...), endsWith(...), byteLength(), isNumeric(...), isUpper(), isLower(), split(...), b64Decode(), regexCompare(...)str_strlen(), str_substr(...), str_char_at(...), str_find(...), str_contains(...), str_starts_with(...), str_ends_with(...), str_byte_length(), str_is_numeric(...), str_is_upper(), str_is_lower(), str_split(...), str_b64_decode(), str_regex_compare(...)
Modifyinsert(...), overwrite(...), concat(...), append(...), prepend(...), snip(...), replace(...), replaceAll(...), upper(...), lower(...), caseFold(...), normalizeNfc(...), trimStart(...), trimEnd(...), trim(...), padStart(...), padEnd(...), repeat(...), regexReplace(...)str_insert(...), str_overwrite(...), str_concat(...), str_append(...), str_prepend(...), str_snip(...), str_replace(...), str_replace_all(...), str_upper(...), str_lower(...), str_case_fold(...), str_normalize_nfc(...), str_trim_start(...), str_trim_end(...), str_trim(...), str_pad_start(...), str_pad_end(...), str_repeat(...), str_regex_replace(...)
Type conversionreadAsString(), stringToInteger(), stringToDouble(), stringToBlob()read_as_string(), str_to_integer(), str_to_double(), str_to_blob()
ExpressionsStringExp.*Exp.string_*
Write flagsStringWriteOptions (fluent) / StringWriteFlags.DEFAULT, CREATE_ONLY, UPDATE_ONLY, NO_FAILStringWriteFlags.DEFAULT, CREATE_ONLY, UPDATE_ONLY, NO_FAIL (see String write flags)
Regex flagsStringRegexFlags.*StringRegexFlags.*
Positional resultsRecord.operationResult(i)RecordResult.operation_result(i)

Next steps

AEL query language

Filter records with readable expression syntax.

AEL →