Endee is a Java client for a local vector database designed for maximum speed and efficiency. This package provides type-safe operations, modern Java features, and optimized code for rapid Approximate Nearest Neighbor (ANN) searches on vector data.
- Type Safe: Full compile-time type checking with builder patterns
- Fast ANN Searches: Efficient similarity searches on vector data
- Multiple Distance Metrics: Support for cosine, L2, and inner product distance metrics
- Hybrid Indexes: Support for dense vectors, sparse vectors, and hybrid (dense + sparse) searches
- Metadata Support: Attach and search with metadata and filters
- High Performance: HTTP/2, MessagePack serialization, and DEFLATE compression
- Modern Java: Requires Java 17+, uses modern APIs
- Java 17 or higher
- Endee Local server running (see Quick Start)
<dependency>
<groupId>io.endee</groupId>
<artifactId>endee-java-client</artifactId>
<version>1.0.0</version>
</dependency>implementation 'io.endee:endee-java-client:1.0.0'The Endee client connects to your local server (defaults to http://127.0.0.1:8080/api/v1):
import io.endee.client.Endee;
import io.endee.client.Index;
import io.endee.client.types.*;
// Connect to local Endee server (defaults to localhost:8080)
Endee client = new Endee();Using Authentication? If your server has NDD_AUTH_TOKEN set, pass the token when initializing:
// With Auth Token
Endee client = new Endee("auth-token");If your server runs on a different port, use setBaseUrl():
Endee client = new Endee();
// Set custom base URL for non-default port
client.setBaseUrl("http://0.0.0.0:8081/api/v1");import io.endee.client.types.CreateIndexOptions;
import io.endee.client.types.Precision;
import io.endee.client.types.SpaceType;
CreateIndexOptions options = CreateIndexOptions.builder("my_vectors", 384)
.spaceType(SpaceType.COSINE)
.precision(Precision.INT8D)
.build();
client.createIndex(options);Dense Index Parameters:
| Parameter | Description | Default |
|---|---|---|
name |
Unique name for your index (alphanumeric + underscore, max 48 chars) | Required |
dimension |
Vector dimensionality (must match your embedding model's output, max 10,000) | Required |
spaceType |
Distance metric - COSINE, L2, or IP (inner product) |
COSINE |
m |
Graph connectivity - higher values increase recall but use more memory | 16 |
efCon |
Construction-time parameter - higher values improve index quality | 128 |
precision |
Quantization precision | INT8D |
Hybrid indexes combine dense vector search with sparse vector search. Add the sparseDimension parameter:
CreateIndexOptions options = CreateIndexOptions.builder("hybrid_index", 384)
.sparseDimension(30000) // Sparse vector dimension (vocabulary size)
.spaceType(SpaceType.COSINE)
.precision(Precision.INT8D)
.build();
client.createIndex(options);// List all indexes (returns JSON string)
String indexes = client.listIndexes();
// Get reference to an existing index
Index index = client.getIndex("my_vectors");
// Delete an index
client.deleteIndex("my_vectors");The index.upsert() method adds or updates vectors in an existing index.
import io.endee.client.types.VectorItem;
import java.util.List;
import java.util.Map;
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)) // meta : {"title" : "First Document", "label" : 10 }
.filter(Map.of("category", "tech", "group" , 10)) // filter : {"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);VectorItem Fields:
| 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 |
Limits:
- Maximum 1,000 vectors per upsert call
- Vector dimension must match index dimension
- IDs must be unique within a single upsert batch
The index.query() method performs a similarity search.
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:
| Parameter | Description | Default | Max |
|---|---|---|---|
vector |
Query vector (must match index dimension) | Required | - |
topK |
Number of results to return | 10 | 512 |
ef |
Search quality parameter - higher values improve recall | 128 | 1024 |
includeVectors |
Include vector data in results | false | - |
Use the filter parameter to restrict results. 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()
);| 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))) |
Note: The
$rangeoperator supports values within [0 - 999]. Normalize larger values before upserting.
Provide both dense vectors and sparse representations:
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:
| 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 |
Important:
sparseIndicesandsparseValuesmust have the same length. Values insparseIndicesmust be within[0, sparseDimension).
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
Delete a vector with a specific vector ID.
index.deleteVector("vec1");Delete all vectors matching specific filters.
index.deleteWithFilter(List.of(
Map.of("category", Map.of("$eq", "tech"))
));Delete an entire index.
client.deleteIndex("my_index");Warning: Deletion operations are irreversible.
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());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=INT8D, m=16}boolean isHybrid = index.isHybrid();Endee supports different quantization precision levels:
import io.endee.client.types.Precision;
Precision.BINARY // Binary quantization (1-bit) - smallest storage, fastest search
Precision.INT8D // 8-bit integer quantization (default) - balanced performance
Precision.INT16D // 16-bit integer quantization - higher precision
Precision.FLOAT16 // 16-bit floating point - good balance
Precision.FLOAT32 // 32-bit floating point - highest precisionChoosing Precision:
| Precision | Use Case |
|---|---|
BINARY |
Very large datasets where speed and storage are critical |
INT8D |
Recommended for most use cases - good balance of accuracy and performance |
INT16D |
Better accuracy than INT8D but less storage than FLOAT32 |
FLOAT16 |
Good compromise between precision and storage for embeddings |
FLOAT32 |
Maximum precision when storage is not a concern |
import io.endee.client.types.SpaceType;
SpaceType.COSINE // Cosine similarity (default) - best for normalized embeddings
SpaceType.L2 // Euclidean distance - best for spatial data
SpaceType.IP // Inner product - best for unnormalized embeddingsThe client throws specific exceptions for different error scenarios:
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
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 |
import io.endee.client.Endee;
import io.endee.client.Index;
import io.endee.client.types.*;
import java.util.List;
import java.util.Map;
public class EndeeExample {
public static void main(String[] args) {
// Initialize client
Endee client = new Endee();
// Create a dense index
CreateIndexOptions createOptions = CreateIndexOptions.builder("documents", 384)
.spaceType(SpaceType.COSINE)
.precision(Precision.INT8D)
.build();
client.createIndex(createOptions);
// Get the index
Index index = client.getIndex("documents");
// Add vectors
List<VectorItem> vectors = List.of(
VectorItem.builder("doc1", new double[384]{/*...*/}) // 384 dimensions
.meta(Map.of("title", "First Document"))
.filter(Map.of("category", "tech"))
.build(),
VectorItem.builder("doc2", new double[384])
.meta(Map.of("title", "Second Document"))
.filter(Map.of("category", "science"))
.build()
);
index.upsert(vectors);
// Query the index
List<QueryResult> results = index.query(
QueryOptions.builder()
.vector(new double[384]) // Query vector
.topK(5)
.build()
);
for (QueryResult item : results) {
System.out.println("ID: " + item.getId() + ", Similarity: " + item.getSimilarity());
}
// Describe the index
IndexDescription description = index.describe();
System.out.println(description);
// Clean up
client.deleteIndex("documents");
}
}| Method | Parameters | Return Type | Description |
|---|---|---|---|
Endee() |
- | - | Create client without auth |
Endee(String token) |
token |
- | Create client with auth token |
setBaseUrl(String url) |
url |
String |
Set custom base URL |
createIndex(CreateIndexOptions) |
options |
String |
Create a new index |
listIndexes() |
- | String |
List all indexes (JSON) |
deleteIndex(String name) |
name |
String |
Delete an index |
getIndex(String name) |
name |
Index |
Get reference to an index |
| Method | Parameters | Return Type | Description |
|---|---|---|---|
upsert(List<VectorItem>) |
vectors |
String |
Insert or update vectors |
query(QueryOptions) |
options |
List<QueryResult> |
Search for similar vectors |
deleteVector(String id) |
id |
String |
Delete a vector by ID |
deleteWithFilter(List<Map>) |
filter |
String |
Delete vectors by filter |
getVector(String id) |
id |
VectorInfo |
Get a vector by ID |
describe() |
- | IndexDescription |
Get index metadata |
isHybrid() |
- | boolean |
Check if index is hybrid |
CreateIndexOptions.builder(String name, int dimension)
.spaceType(SpaceType) // Default: COSINE
.m(int) // Default: 16
.efCon(int) // Default: 128
.precision(Precision) // Default: INT8D
.sparseDimension(Integer) // Optional, for hybrid indexes
.build()QueryOptions.builder()
.vector(double[]) // Required for dense search
.topK(int) // Required
.ef(int) // Default: 128
.filter(List<Map<String, Object>>) // Optional
.includeVectors(boolean) // Default: false
.sparseIndices(int[]) // Optional, for hybrid search
.sparseValues(double[]) // Optional, for hybrid search
.build()VectorItem.builder(String id, double[] vector)
.meta(Map<String, Object>) // Optional
.filter(Map<String, Object>) // Optional
.sparseIndices(int[]) // Optional, for hybrid
.sparseValues(double[]) // Optional, for hybrid
.build()| Field | Type | Description |
|---|---|---|
id |
String |
Vector identifier |
similarity |
double |
Similarity score |
distance |
double |
Distance (1 - similarity) |
meta |
Map<String, Object> |
Metadata |
filter |
Map<String, Object> |
Filter values |
norm |
double |
Normalization factor |
vector |
double[] |
Vector data (if requested) |
| Field | Type | Description |
|---|---|---|
id |
String |
Vector identifier |
vector |
double[] |
Vector data |
meta |
Map<String, Object> |
Metadata |
filter |
Map<String, Object> |
Filter values |
norm |
double |
Normalization factor |
| Field | Type | Description |
|---|---|---|
name |
String |
Index name |
spaceType |
SpaceType |
Distance metric |
dimension |
int |
Vector dimension |
sparseDimension |
int |
Sparse vector dimension |
isHybrid |
boolean |
Whether index supports hybrid search |
count |
long |
Number of vectors |
precision |
Precision |
Quantization precision |
m |
int |
Graph connectivity |
- Jackson (JSON serialization)
- MessagePack (Binary serialization)
- SLF4J (Logging)
MIT
Pankaj Singh