Java SDK Usage
This guide covers the core operations for working with vectors in Endee: upserting, querying, and deleting data.
Setting Up Your Domain
The Endee client allows you to configure a custom domain URL and port (default port is 8080):
import io.endee.client.Endee;
// Initialize client
Endee client = new Endee();
// Set custom base URL for non-default port
client.setBaseUrl("http://0.0.0.0:8081/api/v1");Use setBaseUrl() when running on a non-default port.
Upserting Vectors
The index.upsert() method adds or updates vectors in an existing index.
import io.endee.client.Endee;
import io.endee.client.Index;
import io.endee.client.types.VectorItem;
import java.util.List;
import java.util.Map;
Endee client = new Endee();
Index index = client.getIndex("my_index");
List<VectorItem> vectors = List.of(
VectorItem.builder("vec1", new double[] {0.1, 0.2, 0.3 /* ... */})
.meta(Map.of("title", "First document", "group", 10))
.filter(Map.of("category", "tech", "group", 10))
.build(),
VectorItem.builder("vec2", new double[] {0.3, 0.4, 0.5 /* ... */})
.meta(Map.of("title", "Second document"))
.filter(Map.of("category", "science"))
.build()
);
index.upsert(vectors);Vector Object Fields: (see VectorItem)
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier for the vector (non-empty string) |
vector | Yes | Array of doubles representing the embedding |
meta | No | Arbitrary metadata map |
filter | No | Key-value pairs for filtering during queries |
Maximum 1,000 vectors per upsert call. Vector dimension must match the index dimension. IDs must be unique within a single upsert batch.
Querying the Index
The index.query() method performs a similarity search using a query vector.
import io.endee.client.types.QueryOptions;
import io.endee.client.types.QueryResult;
List<QueryResult> results = index.query(
QueryOptions.builder()
.vector(new double[] {0.15, 0.25 /* ... */})
.topK(5)
.ef(128)
.includeVectors(true)
.build()
);
for (QueryResult item : results) {
System.out.println("ID: " + item.getId());
System.out.println("Similarity: " + item.getSimilarity());
System.out.println("Distance: " + item.getDistance());
System.out.println("Meta: " + item.getMeta());
}Query Parameters: (see QueryOptions)
| Parameter | Description |
|---|---|
vector | Query vector (must match index dimension) |
topK | Number of results to return (default: 10, max: 512) |
ef | Search quality parameter (default: 128, max: 1024) |
includeVectors | Include vector data in results (default: false) |
prefilterCardinalityThreshold | Switch to brute-force prefiltering when filter matches ≤ N vectors (default: 10,000, range: 1,000–1,000,000) |
filterBoostPercentage | Expand the internal candidate pool by this percentage before applying the filter (default: 0, max: 100) |
Results are returned as a list of QueryResult objects.
Filtered Querying
Use the filter parameter to restrict results based on filter conditions. All filters are combined with logical AND.
List<QueryResult> results = index.query(
QueryOptions.builder()
.vector(new double[] {0.15, 0.25 /* ... */})
.topK(5)
.filter(List.of(
Map.of("category", Map.of("$eq", "tech")),
Map.of("score", Map.of("$range", List.of(80, 100)))
))
.build()
);Filtering Operators
| Operator | Description | Example |
|---|---|---|
$eq | Exact match | Map.of("status", Map.of("$eq", "published")) |
$in | Match any in list | Map.of("tags", Map.of("$in", List.of("ai", "ml"))) |
$range | Numeric range (inclusive) | Map.of("score", Map.of("$range", List.of(70, 95))) |
The $range operator supports values within [0 – 999]. Normalize larger values before upserting.
Filter Tuning
Use prefilterCardinalityThreshold and filterBoostPercentage to fine-tune how filtering interacts with the ANN search:
List<QueryResult> results = index.query(
QueryOptions.builder()
.vector(new double[] {0.15, 0.25 /* ... */})
.topK(5)
.filter(List.of(
Map.of("category", Map.of("$eq", "tech"))
))
.prefilterCardinalityThreshold(50_000) // Use postfilter when >50k vectors match
.filterBoostPercentage(20) // Bias 20% toward filter-matching vectors
.build()
);| Parameter | Description | Default | Range |
|---|---|---|---|
prefilterCardinalityThreshold | When the estimated number of vectors matching the filter exceeds this value, postfiltering is used instead of prefiltering. | 10,000 | 1,000–1,000,000 |
filterBoostPercentage | Percentage by which filter-matching vectors are boosted during scoring. Set to 0 to disable. Higher values favor filtered results. | 0 | 0–100 |
Updating Filters
The index.updateFilters() method updates the filters for one or more vectors without modifying the vector data or metadata.
import io.endee.client.types.UpdateFilterParams;
import java.util.List;
import java.util.Map;
index.updateFilters(List.of(
new UpdateFilterParams("vec1", Map.of("category", "ml", "score", 95)),
new UpdateFilterParams("vec2", Map.of("category", "science", "score", 80))
));UpdateFilterParams Fields: (see UpdateFilterParams)
| Field | Required | Description |
|---|---|---|
id | Yes | ID of the vector to update |
filter | Yes | New filter object that replaces the existing filters |
Filter updates are destructive replacements. Any filter keys not included in the new filter object will be removed from the vector.
Hybrid Search
Hybrid indexes combine dense and sparse vector search. Create a hybrid index by specifying sparseDimension:
CreateIndexOptions options = CreateIndexOptions.builder("hybrid_index", 384)
.sparseDimension(30000)
.spaceType(SpaceType.COSINE)
.build();
client.createIndex(options);Upserting Hybrid Vectors
Provide both dense vectors and sparse vector representations using sparseIndices and sparseValues:
Index index = client.getIndex("hybrid_index");
List<VectorItem> vectors = List.of(
VectorItem.builder("doc1", new double[] {0.1, 0.2 /* ... */})
.sparseIndices(new int[] {10, 50, 200}) // Non-zero term positions
.sparseValues(new double[] {0.8, 0.5, 0.3}) // Weights for each position
.meta(Map.of("title", "Document 1"))
.build(),
VectorItem.builder("doc2", new double[] {0.3, 0.4 /* ... */})
.sparseIndices(new int[] {15, 100, 500})
.sparseValues(new double[] {0.9, 0.4, 0.6})
.meta(Map.of("title", "Document 2"))
.build()
);
index.upsert(vectors);Hybrid Vector Fields: (see VectorItem)
| Field | Required | Description |
|---|---|---|
id | Yes | Unique identifier |
vector | Yes | Dense embedding vector |
sparseIndices | Yes (hybrid) | Non-zero term positions in sparse vector |
sparseValues | Yes (hybrid) | Weights for each sparse index |
meta | No | Metadata map |
filter | No | Filter fields |
sparseIndices and sparseValues must have the same length. Values in sparseIndices must be within [0, sparseDimension).
Querying Hybrid Index
Provide both dense and sparse query vectors:
List<QueryResult> results = index.query(
QueryOptions.builder()
.vector(new double[] {0.15, 0.25 /* ... */}) // Dense query
.sparseIndices(new int[] {10, 100, 300}) // Sparse query positions
.sparseValues(new double[] {0.7, 0.5, 0.4}) // Sparse query weights
.topK(5)
.build()
);
for (QueryResult item : results) {
System.out.println("ID: " + item.getId() + ", Similarity: " + item.getSimilarity());
}You can also query with:
- Dense only: Provide only
vector - Sparse only: Provide only
sparseIndicesandsparseValues - Hybrid: Provide all three for combined results
Deletion Methods
Delete by ID
index.deleteVector("vec1");Delete by Filter
Delete all vectors matching specific filters:
index.deleteWithFilter(List.of(
Map.of("category", Map.of("$eq", "tech"))
));Delete Index
client.deleteIndex("my_index");Deletion operations are irreversible.
Additional Operations
Get Vector by ID
Returns a VectorInfo object:
import io.endee.client.types.VectorInfo;
VectorInfo vector = index.getVector("vec1");
System.out.println("ID: " + vector.getId());
System.out.println("Vector: " + Arrays.toString(vector.getVector()));
System.out.println("Meta: " + vector.getMeta());
System.out.println("Norm: " + vector.getNorm());Describe Index
Returns an IndexDescription object:
import io.endee.client.types.IndexDescription;
IndexDescription info = index.describe();
System.out.println(info);
// IndexDescription{name='my_index', spaceType=COSINE, dimension=384,
// sparseDimension=0, isHybrid=false, count=1000,
// precision=INT8, m=16}Check if Index is Hybrid
boolean isHybrid = index.isHybrid();Precision Options
Endee supports different quantization precision levels via the Precision enum:
import io.endee.client.types.Precision;
Precision.BINARY; // Binary quantization (1-bit) - smallest storage, fastest search
Precision.INT8; // 8-bit integer quantization - balanced performance
Precision.INT16; // 16-bit integer quantization (default) - higher precision
Precision.FLOAT16; // 16-bit floating point - good balance
Precision.FLOAT32; // 32-bit floating point - highest precisionChoosing Precision:
BINARY: Best for very large datasets where speed and storage are criticalINT8: Recommended for most use cases - good balance of accuracy and performanceINT16(default): When you need better accuracy than INT8 but less storage than FLOAT32FLOAT16: Good compromise between precision and storage for embeddingsFLOAT32: When you need maximum precision and storage is not a concern
See the Types Reference for complete details.
Error Handling
The Java SDK uses specific exception classes for error handling:
import io.endee.client.exception.EndeeException;
import io.endee.client.exception.EndeeApiException;
try {
client.createIndex(options);
} catch (EndeeApiException e) {
// API-specific errors (e.g., 400, 401, 404, 409, 500)
System.err.println("Status Code: " + e.getStatusCode());
System.err.println("Error Body: " + e.getErrorBody());
} catch (EndeeException e) {
// Client errors (network, serialization, etc.)
System.err.println("Client Error: " + e.getMessage());
} catch (IllegalArgumentException e) {
// Validation errors (invalid parameters)
System.err.println("Validation Error: " + e.getMessage());
}HTTP Status Codes:
| Code | Description |
|---|---|
| 400 | Bad Request - Invalid parameters |
| 401 | Unauthorized - Invalid or missing authentication |
| 403 | Forbidden - Insufficient permissions |
| 404 | Not Found - Index or vector doesn’t exist |
| 409 | Conflict - Index already exists |
| 500 | Internal Server Error |
See EndeeApiException and EndeeException for details.
API Reference
Endee Class
| Method | Description |
|---|---|
Endee() | Create client without auth (connects to localhost:8080) |
Endee(String token) | Create client with auth token |
setBaseUrl(String url) | Set a custom base URL |
createIndex(CreateIndexOptions) | Create a new index - accepts CreateIndexOptions |
listIndexes() | List all indexes (returns JSON string) |
deleteIndex(String name) | Delete an index |
getIndex(String name) | Get reference to an index - returns Index |
Index Class
| Method | Description |
|---|---|
upsert(List<VectorItem>) | Insert or update vectors - accepts VectorItem list |
query(QueryOptions) | Search for similar vectors - accepts QueryOptions, returns QueryResult list |
updateFilters(List<UpdateFilterParams>) | Replace filters for one or more vectors - accepts UpdateFilterParams list |
deleteVector(String id) | Delete a vector by ID |
deleteWithFilter(List<Map>) | Delete vectors by filter |
getVector(String id) | Get a vector by ID - returns VectorInfo |
describe() | Get index info - returns IndexDescription |
isHybrid() | Check if index supports hybrid search - returns boolean |