---
title: "Incompatible API changes"
description: "Guide to non-backwards-compatible API changes in the Aerospike Java Client across multiple versions."
---

# Incompatible API changes

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

## Version 10.3.0

Aerospike Java Client 10.3.0 simplifies `sendKey` resolution by enabling `sendKey` when any batch write policy type `sendKey` is true. The batch policy types are:

-   `batch_write.send_key`: Dynamic config.
-   `AerospikeClient.batchPolicyDefault.sendKey`: Cluster default for parent batch read.
-   `AerospikeClient.batchParentPolicyWriteDefault.sendKey`: Cluster default for parent batch write/UDF/delete.
-   `AerospikeClient.batchWritePolicyDefault.sendKey`: Cluster default for key specific batch write/UDF/delete.
-   `BatchWrite.policy.sendKey`: Batch write policy for a specific key.
-   `BatchUDF.policy.sendKey`: Batch UDF policy for a specific key.
-   `BatchDelete.policy.sendKey`: Batch delete policy for a specific key.

The batch read policy type (`batchPolicyDefault`) is no longer recognized for batch writes because that policy is only intended for a read-only batch. Read commands do not need to send the user key to the server.

To enable `sendKey` for all writes to a cluster, set the following cluster defaults on startup:

```java
ClientPolicy cp = new ClientPolicy();

cp.writePolicyDefault.sendKey = true; // For single record writes.

cp.batchParentPolicyWriteDefault.sendKey = true; // For batch writes.

AerospikeClient client = new AerospikeClient(cp, seeds);
```

If any batch policy is overridden by the user, that policy `sendKey` should be consistent with the cluster defaults:

```java
// Override from system default.

BatchPolicy bp = new BatchPolicy();

bp.totalTimeout = 500;

bp.sendKey = true;

client.operate(bp, recs);

// Override from cluster default.

bp = client.copyBatchParentPolicyWriteDefault();

bp.totalTimeout = 500;

bp.sendKey = true;  // This line is not needed if the cluster default sendKey was already true.

client.operate(bp, recs);
```

## Version 8.1.1

Aerospike Java Client 8.1.1 introduces new cluster defaults for batch operations (`batchWritePolicyDefault`, `batchUDFPolicyDefault`, `batchDeletePolicyDefault`). These new defaults contain the `sendKey` field. However, the `sendKey` defaults are not applied consistently for each batch scenario. The `batchWritePolicyDefault sendKey` is used in a batch node sub-command with multiple keys. The existing cluster defaults (`batchPolicyDefault`, `batchParentPolicyWriteDefault`) are used in a batch node sub-command with a single key (different code path with conversion to a single key request).

To handle all cases, all `sendKey` defaults must be set and they must be consistently `true` or consistently `false`. In addition, if the `BatchPolicy/BatchWritePolicy` method arguments are set that override the default, the `sendKey` field must also be consistent with the cluster’s batch defaults.

Old:

```java
ClientPolicy policy = new ClientPolicy();

policy.writePolicyDefault.sendKey = true;

policy.batchPolicyDefault.sendKey = true;

policy.batchParentPolicyWriteDefault.sendKey = true;

AerospikeClient client = new AerospikeClient(policy, hosts);
```

New:

```java
ClientPolicy policy = new ClientPolicy();

policy.writePolicyDefault.sendKey = true;

policy.batchPolicyDefault.sendKey = true;

policy.batchParentPolicyWriteDefault.sendKey = true;

policy.batchWritePolicyDefault.sendKey = true;

policy.batchUDFPolicyDefault.sendKey = true;

policy.batchDeletePolicyDefault.sendKey = true;

AerospikeClient client = new AerospikeClient(policy, hosts);
```

This requirement will be relaxed in Java Client 10.3.0 where `sendKey` defaults are applied consistently. For example, setting either `batchParentPolicyWriteDefault sendKey` or `batchWritePolicyDefault sendKey` to `true` in 10.3.0 will result in an enabled `sendKey` for all applicable batch code paths.

## Version 8.0.1

If you are using JDK version equal to or later than 21 as your runtime environment (RTE):

-   Specify the Aerospike Java Client package `aerospike-client-jdk21` in your `pom.xml` or `build.gradle` build specification file.

If you are using JDK version equal to or later than 8 and earlier than 21 as your RTE:

-   Specify the Aerospike Java Client package `aerospike-client-jdk8` in your `pom.xml` or `build.gradle` build specification file.

From Aerospike Java Client version 8.0.1 and later, the `aerospike-client-jdk21` and `aerospike-client-jdk8` packages are functionally equivalent, with equivalent APIs. The only difference is that the `aerospike-client-jdk21` package supports the use of virtual threads and may give a throughput performance boost for applications which use sync operations.

The developer API of the packages is the same. The packages differ in their internal implementations.

## Version 8.0.0

### JDK 21 required

Aerospike Java Client version 8.0.0 requires [virtual threads](https://docs.oracle.com/en/java/javase/21/core/virtual-threads.html) support provided by [JDK version 21](https://www.oracle.com/java/technologies/downloads/). If your application must use an earlier version of the JDK, use the new `aerospike-client-jdk8` package in version 8.0.1.

### Remove policy variables related to sync thread pools

Virtual threads support eliminated the need for sync thread pools. The following policy variables have been removed.

-   From `ClientPolicy`:
    -   `threadPool`
    -   `sharedThreadPool`
-   From `BatchPolicy`:
    -   `maxConcurrentThreads` : All sync batch node commands are now run in parallel on virtual threads.

**Note**: These policy variables were restored in version 8.0.1 to preserve compatibility between the new `aerospike-client-jdk8` and `aerospike-client-jdk21` packages. These variables are ignored in the `aerospike-client-jdk21` package.

## Version 7.2.0

### Add returnType argument to `MapExp.removeBy()` and `ListExp.removeBy()`.

A new returnType argument has been added to relevant `MapExp.removeBy()` and `ListExp.removeBy()` methods. To upgrade existing `MapExp.removeBy()` methods, pass in `MapReturnType.NONE`. To upgrade existing `ListExp.removeBy()` methods, pass in `ListReturnType.NONE`.

Old:

```java
MapExp.removeByKeyList(keys, bin);

ListExp.removeByValue(value, bin);
```

New:

```java
MapExp.removeByKeyList(MapReturnType.NONE, keys, bin);

ListExp.removeByValue(ListReturnType.NONE, value, bin);
```

## Version 7.1.0

### Java serialized blobs returned as raw byte\[\]

Java serialized blobs are now returned as a raw byte\[\] instead of throwing an exception. The byte\[\] can be deserialized using the following code:

```java
try (ByteArrayInputStream bis = new ByteArrayInputStream(bytes, 0, bytes.length)) {

    try (ObjectInputStream ois = new ObjectInputStream(bis)) {

        return ois.readObject();

    }

}
```

This is considered a temporary solution until the java serialized blobs are converted to a safer format.

## Version 7.0.0

### Java serialization/deserialization functionality removed

Deserialization of java serialized objects has been identified as a security risk (CVE-2023-36480). Therefore, all functionality that ultimately called `ObjectInputStream.readObject()` or `ObjectOutputStream.writeObject()` has been removed. This includes the following constructors and methods:

```plaintext
public Bin(String name, Object value);

public static Bin asBlob(String name, Object value);

public static Value getAsBlob(Object value);
```

All existing database objects that are serialized using this serialization format should be converted to a safer format (Aerospike native types, protobuf, msgpack, json, xml …) using a previous client version before upgrading to client version 7.0.0. Here is an example:

Old java serialization:

```java
public class MyClass implements Serializable {

    private static final long serialVersionUID = 1L;

    public String a;

    public int b;

}

MyClass mc = new MyClass();

mc.a = "stringa";

mc.b = 55;

Key key = new Key("test", "set", "key1");

client.put(null, key, new Bin("bin", mc));

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

MyClass val = (MyClass)rec.getValue("bin");
```

New list serialization:

```java
List<Object> list = new ArrayList<Object>();

list.add("stringa");

list.add(55);

Key key = new Key("test", "set", "key1");

client.put(null, key, new Bin("bin", list));

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

List<?> val = rec.getList("bin");
```

New map serialization:

```java
Map<String,Object> map = new HashMap<String,Object>();

map.put("a", "stringa");

map.put("b", 55);

Key key = new Key("test", "set", "key1");

client.put(null, key, new Bin("bin", map));

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

Map<?,?> val = rec.getMap("bin");
```

### Value.UseBoolBin now defaults to true

Previous client versions wrote a boolean value to a bin by default, using the Aerospike integer particle type. Client versions 7.0.0+ write a boolean value to a bin by default, using the Aerospike boolean particle type. The client can read boolean values from either integer or boolean particle types via `Record.getBoolean()`.

To restore old behavior, set `Value.UseBoolBin` to false. This stores the Aerospike integer particle type for booleans. Set this value once at the beginning of the program and before instantiating any AerospikeClient instances. This applies to all `AerospikeClient` instances. This guarantees it occurs before any write commands:

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

import com.aerospike.client.AerospikeClient;

Value.UseBoolBin = false;

AerospikeClient client = new AerospikeClient(policy, hosts);
```

## Version 6.0.0 - Remove old PredExp

The old deprecated PredExp functionality has been removed. Code using old PredExp must be converted to the new expression syntax. See [Version 5.0.0 - Replace predicate expressions with Aerospike Expressions](#version-500---replace-predicate-expressions-with-aerospike-expressions)

## Version 5.1.0 - Remove scan fields

The following scan fields have been removed since they are no longer supported by the server.

-   `Policy.priority`: Use `ScanPolicy.recordsPerSecond` instead to limit throughput.
-   `ScanPolicy.scanPercent`: Use `ScanPolicy.maxRecords` instead to limit records returned.
-   `ScanPolicy.failOnClusterChange`: This field is not used with partition scans.

## Version 5.0.0 - Replace predicate expressions with Aerospike Expressions

The old PredExp functionality has been replaced by **[Expressions](https://aerospike.com/docs/develop/expressions)**. Expressions include all previous predicate expression functionality (with a different, improved syntax) plus support for record metadata expressions and list/map/bit/hyperloglog expression methods analogous to those used in operations.

-   Removed PredExp class.
-   Added Exp (expression builder) class.
-   Added Expression (expression byte code) class.
-   Added ListExp, MapExp, BitExp and HLLExp classes.

Client 5.0.0 requires server 5.2.0.4+. Client 5.0.1 restored old PredExp functionality for a period of one year in order to ease the burden of converting from old PredExp to Expressions. Client 5.0.1+ requires server 4.9+ until the year is up. At that time, old PredExp will be permanently removed.

Code using old PredExp must be converted to the new syntax. Here is an example:

Old:

```java
Statement stmt = new Statement();

stmt.setNamespace("test");

stmt.setSetName("set");

stmt.setFilter(Filter.range("bin1", 0, 100));

// ! (bin2 >= 15 && bin2 <= 42)

stmt.setPredExp(

    PredExp.integerBin("bin2"),

    PredExp.integerValue(15),

    PredExp.integerGreaterEq(),

    PredExp.integerBin("bin2"),

    PredExp.integerValue(42),

    PredExp.integerLessEq(),

    PredExp.and(2),

    PredExp.not()

    );

RecordSet rs = client.query(null, stmt);
```

New:

```java
Statement stmt = new Statement();

stmt.setNamespace("test");

stmt.setSetName("set");

stmt.setFilter(Filter.range("bin1", 0, 100));

// ! (bin2 >= 15 && bin2 <= 42)

QueryPolicy policy = new QueryPolicy();

policy.filterExp = Exp.build(

    Exp.not(

        Exp.and(

            Exp.ge(Exp.intBin("bin2"), Exp.val(15)),

            Exp.le(Exp.intBin("bin2"), Exp.val(42)))));

RecordSet rs = client.query(policy, stmt);
```

## Version 4.4.0 - Read consistency level changes

### Read policy changes for commands involving AP (availability) namespaces

-   Policy variable `ConsistencyLevel consistencyLevel` moved to `ReadModeAP readModeAP`.

### Read policy changes for commands involving SC (strong consistency) namespaces

-   Policy variable `boolean linearizeRead` moved to `ReadModeSC readModeSC`.
-   Add `ReadModeSC` enum below:

```java
/**

 * Read policy for SC (strong consistency) namespaces.

 * <p>

 * Determines SC read consistency options.

 */

public enum ReadModeSC {

    /**

     * Ensures this client will only see an increasing sequence of record versions.

     * Server only reads from master.  This is the default.

     */

    SESSION,

    /**

     * Ensures ALL clients will only see an increasing sequence of record versions.

     * Server only reads from master.

     */

    LINEARIZE,

    /**

     * Server may read from master or any full (non-migrating) replica.

     * Increasing sequence of record versions is not guaranteed.

     */

    ALLOW_REPLICA,

    /**

     * Server may read from master or any full (non-migrating) replica or from unavailable

     * partitions.  Increasing sequence of record versions is not guaranteed.

     */

    ALLOW_UNAVAILABLE

}
```

## Version 4.3.0 - Remove obsolete code

The following old obsoleted code has been removed.

-   `AsyncClient`: This old async class was replaced by the new async methods in AerospikeClient. Users should migrate to the new async functionality.
    
-   `Statement.setFilters()` and `Statement.getFilters()`: These methods were replaced by `Statement.setFilter()` and `Statement.getFilter()`. The server accepts only one filter.
    
-   `Value.UseDoubleType`: This configuration variable is no longer necessary because all server versions that this client supports also support the double type. This client supports server versions >= 3.6.0.
    
-   `ClientPolicy.requestProleReplicas`: This configuration variable is no longer necessary because prole replicas are now always requested.
    
-   Batch direct protocol code: This old batch direct protocol code was replaced by the batch index functionality. This code was no longer accessible by the external API.
    

## Version 4.2.3 - Remove BatchPolicy.useBatchDirect

`BatchPolicy.useBatchDirect` was removed because the server has removed support for the old batch direct protocol. Remove any references to this policy field.

## Version 4.2.0 - Require minimum java version 1.8

We require minimum java version 1.8.

## Version 4.1.0 - LDT Removal and Index Exceptions

LDT methods have been removed on client because large data types are no longer supported by Aerospike Server.

Index exception handling has changed on the client. In the past, the client swallowed INDEX\_ALREADY\_EXISTS errors on createIndex(). The client now throws an AerospikeException when an index already exists on createIndex().

The client also used to swallow INDEX\_NOTFOUND errors on dropIndex(). The client now throws an AerospikeException when an index does not already exist on dropIndex().

To preserve old behavior, calling code can be changed to:

```java
try {

    IndexTask task = client.createIndex(policy, ns, set, indexName, binName, indexType);

    task.waitTillComplete();

}

catch (AerospikeException ae) {

    if (ae.getResultCode() != ResultCode.INDEX_ALREADY_EXISTS) {

        throw ae;

    }

}

try {

  client.dropIndex(policy, ns, set, indexName);

}

catch (AerospikeException ae) {

    if (ae.getResultCode() != ResultCode.INDEX_NOTFOUND) {

        throw ae;

    }

}
```

## Version 4.0.0 - Policy changes

The **[Policy](https://github.com/aerospike/aerospike-client-java/blob/master/client/src/com/aerospike/client/policy/Policy.java)** class has been redefined to support greater control on timeout behavior and provide defaults that improve retry success.

### Split `timeout` into `socketTimeout` and `totalTimeout`

socketTimeout is the socket idle timeout in milliseconds. For synchronous methods, socketTimeout is the socket timeout (SO\_TIMEOUT). For asynchronous methods, the socketTimeout is implemented using a HashedWheelTimer and socketTimeout is only used if totalTimeout is not defined.

If socketTimeout is not zero and the socket has been idle for at least socketTimeout, both maxRetries and totalTimeout are checked. If maxRetries is not exceeded and totalTimeout is not reached, the command is retried. If both socketTimeout and totalTimeout are non-zero and socketTimeout > totalTimeout, then socketTimeout will be set to totalTimeout. If socketTimeout is zero, there will be no socket idle limit.

totalTimeout is the maximum time allowed in milliseconds to process a command. totalTimeout is tracked on the client and sent to the server along with the command in the wire protocol. The client will most likely timeout first, but the server also has the capability to timeout the command. If totalTimeout is zero, there will be no total time limit.

To keep old timeout behavior, set socketTimeout equal to totalTimeout.

### Removed `retryOnTimeout`

retryOnTimeout has been removed because it is no longer necessary. If a command timed out on socketTimeout, retries will now occur until maxRetries is exceeded or totalTimeout is reached.

### Changed Policy `replica` default to `Replica.SEQUENCE`

The sequence replica algorithm starts with the node containing the key’s master partition. If a connection error occurs, the node containing the key’s replicated partition is used on retry. Writes now switch to replicated nodes on connection errors because the replicated node is most likely to become the new master when the old master goes down.

Reads also switch to replicated nodes on timeouts, but writes do not switch nodes on timeouts. Timeouts are not a good indicator of downed nodes.

### Changed Policy `maxRetries` default to 2

maxRetries now defaults to 2 to give a better chance of retry success.

### Changed Policy `sleepBetweenRetries` default to zero for reads

Reads do not have to sleep when a node goes down because the cluster does not shut out reads during cluster reformation.

The sleepBetweenRetries default for writes remains at 500ms. Writes need to wait for the cluster to reform when a node goes down. Immediate write retries on node failure have been shown to consistently result in errors.

### Changed ClientPolicy `requestProleReplicas` default to true.

Prole replica maps are required when using `Replica.SEQUENCE` algorithm.

## Version 3.2.0 - Max connections is a now a hard limit

Past versions used to create new connections when a node’s outstanding connections exceeded `ClientPolicy.maxThreads`. This connection would then be closed (not put back into connection pool) when the command finished. This could handle unexpected bursts of commands (with a performance penalty), but it also resulted in excessive socket opens/closes and too many sockets in a CLOSED/WAIT state.

Version 3.2.0 renames `ClientPolicy.maxThreads` to `ClientPolicy.maxConnsPerNode` and enforces a strict limit on maximum number of connections allowed per server node. Synchronous commands will now fail with `ResultCode.NO_MORE_CONNECTIONS` (after going through retry logic) if the maximum number of connections would be exceeded.

The number of connections used per node depends on how many concurrent threads issue database commands plus sub-threads used for parallel multi-node commands (batch, scan, and query). One connection will be used for each thread.

## Version 3.1.4 - New Aerospike Server double type

Aerospike Server versions >= 3.6.0 can store 64-bit double floating point values natively. Previous server versions did not recognize a double data type, so clients sent doubles as long bits instead. The java client can now send double as the new server double type when the following flag is set:

```java
Value.UseDoubleType = true;
```

If the flag is false, double values will be sent as long bits. The flag currently defaults to false because both Aerospike Server and XDR (if used) must also be upgraded to versions >= 3.6.0 before the client uses the double data type.

The client can handle both return types (double or long) when receiving data from the server using Record.getDouble():

```java
double val = record.getDouble("bin");
```

## Version 3.1.0 - Bin constructors for list/map

Two new Bin constructors have been added:

```java
public Bin(String name, List<?> value)

  public Bin(String name, Map<?,?> value)
```

These constructors serialize list/map using a custom format supported natively by Aerospike 3 servers. These constructors duplicate old static initializers which have now been marked deprecated.

```java
@Deprecated

  public static Bin asList(String name, List<?> value)

  @Deprecated

  public static Bin asMap(String name, Map<?,?> value)
```

Previous code that wrote list/map may have used the default Bin object constructor:

```java
public Bin(String name, Object value)
```

This object constructor serializes list/map using the java’s default serializer format. The new custom format is faster and can be introspected by the server, but the user may still want to preserve the old default java serialization (Running with Aerospike 2 servers is one reason). If so, the following code modification is necessary:

Old:

```java
new Bin(name, list)

    new Bin(name, map)
```

New:

```java
new Bin(name, (Object)list)

    new Bin(name, (Object)map)
```

Finally, the underlying list/map Value methods have been modified as well:

```java
public static Value get(List<?> value)

    public static Value get(Map<?,?> value)

  @Deprecated

  public static Value getAsList(List<?> value)

  @Deprecated

  public static Value getAsMap(Map<?,?> value)
```

## Version 3.0.34 - Integer Bin Value Casting

Always return 64 bit integers (long) for numbers retrieved from the server. Code that casts record values to Integer must be changed to call “Record.getInt()” which performs the proper casting.

Old:

```java
int value = (Integer)record.getValue("binname");
```

New:

```java
int value = record.getInt("binname");
```