Quarkus Opensearch Extension
The quarkus-opensearch extension allows you to connect to an OpenSearch cluster using the clients provided by the OpenSearch project.
|
OpenSearch 3.x Support
This version supports OpenSearch 3.x which requires:
If you need OpenSearch 2.x support, use an earlier version of this extension. |
OpenSearch Clients
OpenSearch Java Client
This is the recommended and only supported OpenSearch client for OpenSearch 3.x. The OpenSearch Java client lets you interact with OpenSearch through Java methods and data structures, and provides both synchronous and asynchronous client implementations.
Installation
OpenSearch Java client
The java client does not have any dependencies on the REST client. All clients are sharing most of the configuration properties.
The AWS related configuration properties quarkus.opensearch.aws.* are only applicable to this java client.
If you want to use this client all you need to do is add the io.quarkiverse.opensearch:quarkus-opensearch-java-client extension first to your build file.
with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-java-client</artifactId>
<version>3.2.2</version>
</dependency>
Transport Provider (Required)
The Java client requires a transport provider to communicate with OpenSearch. You must add one of the following transport dependencies based on your deployment target.
Apache HttpClient5 Transport (Recommended)
Use this transport for:
-
Local development with Dev Services
-
Self-hosted OpenSearch clusters
-
Any non-AWS deployment
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-transport-apache</artifactId>
</dependency>
AWS SDK2 Transport
Use this transport only when connecting to AWS OpenSearch Service or AWS OpenSearch Serverless. This transport requires the quarkus.opensearch.aws.service configuration property to be set.
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-transport-aws</artifactId>
</dependency>
# Required for AWS transport to activate
quarkus.opensearch.aws.service=es # or 'aoss' for OpenSearch Serverless
quarkus.opensearch.aws.region=us-west-2
Using Both Transports
If you deploy to AWS but also use Dev Services for local development, you can include both transport dependencies. The appropriate transport will be selected automatically based on your configuration:
-
When
quarkus.opensearch.aws.serviceis set → AWS transport is used -
When
quarkus.opensearch.aws.serviceis not set → Apache transport is used
<!-- For local development and Dev Services -->
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-transport-apache</artifactId>
</dependency>
<!-- For AWS deployment -->
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-transport-aws</artifactId>
</dependency>
# Only set AWS config in production profile
%prod.quarkus.opensearch.aws.service=es
%prod.quarkus.opensearch.aws.region=us-west-2
%prod.quarkus.opensearch.hosts=search-mydomain.us-west-2.es.amazonaws.com
|
If no matching transport provider is found, the application will fail at runtime with a detailed error message explaining which providers are available and what configuration is needed. |
Configuring Additional Named Clients
This extension supports defining and injecting multiple named OpenSearch clients, useful when your application needs to connect to different OpenSearch clusters (e.g., for analytics, logging, or operational data).
A named client can be configured using quarkus.opensearch."client-name".*.
Example: Default and Additional Client
# Default client (unnamed)
quarkus.opensearch.hosts=localhost:9200
# Additional client named "analytics"
quarkus.opensearch.analytics.hosts=analytics-cluster.internal:9200
quarkus.opensearch.analytics.username=some-user
quarkus.opensearch.analytics.password=some-password
quarkus.opensearch.analytics.aws.region=us-west-2
Injecting the Default Client
If you’re using only a single OpenSearch cluster, you can inject the default client without any qualifiers:
import jakarta.inject.Inject;
import org.opensearch.client.opensearch.OpenSearchClient;
public class MyService {
@Inject
OpenSearchClient defaultClient;
public void run() {
// Use the defaultClient to interact with OpenSearch
}
}
Injecting Named Clients in Your Code
To use a named client in your application, inject it using the @OpenSearchClientName qualifier:
import jakarta.inject.Inject;
import io.quarkiverse.opensearch.OpenSearchClientName;
import org.opensearch.client.opensearch.OpenSearchClient;
public class ReportService {
@Inject
@OpenSearchClientName("analytics")
OpenSearchClient analyticsClient;
public void runQuery() {
// Use the analyticsClient to run a search
}
}
|
The |
OpenSearch REST High-Level Client
The REST High-Level Client depends on the REST client and does not require any additional configuration.
If you want to use this client all you need to do is add the io.quarkiverse.opensearch:quarkus-opensearch-rest-high-level-client extension first to your build file.
with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-rest-high-level-client</artifactId>
<version>3.2.2</version>
</dependency>
OpenSearch REST Low-Level Client
If you want to use this extension, you need to add the io.quarkiverse.opensearch:quarkus-opensearch-rest-client extension first to your build file.
For instance, with Maven, add the following dependency to your POM file:
<dependency>
<groupId>io.quarkiverse.opensearch</groupId>
<artifactId>quarkus-opensearch-rest-client</artifactId>
<version>3.2.2</version>
</dependency>
Configuring Opensearch
The main property to configure is the URL to connect to the Opensearch cluster.
For a typical clustered Opensearch service, a sample configuration would look like the following:
# configure the Elasticsearch client for a cluster of two nodes
quarkus.opensearch.hosts = opensearch-01:9200,opensearch-02:9200
In this case, we are using a single instance running on localhost:
# configure the Opensearch client for a single instance on localhost
quarkus.opensearch.hosts = localhost:9200
If you need a more advanced configuration, you can find the comprehensive list of supported configuration properties at the end of this guide.
SSL/TLS Configuration
When connecting to OpenSearch over HTTPS, you can configure TLS settings using the Quarkus TLS registry. This is the recommended approach for managing SSL/TLS configuration.
Basic HTTPS Connection
For HTTPS connections with valid certificates trusted by the JVM:
quarkus.opensearch.hosts=opensearch.example.com:9200
quarkus.opensearch.protocol=https
quarkus.opensearch.username=admin
quarkus.opensearch.password=admin
Using a Custom Trust Store
To use a custom trust store (e.g., for self-signed certificates):
quarkus.opensearch.hosts=opensearch.example.com:9200
quarkus.opensearch.protocol=https
quarkus.opensearch.tls.tls-configuration-name=opensearch-tls
# Define the TLS configuration
quarkus.tls.opensearch-tls.trust-store.p12.path=/path/to/truststore.p12
quarkus.tls.opensearch-tls.trust-store.p12.password=changeit
Disabling SSL Verification (Development Only)
|
Disabling SSL verification should only be used in development environments. Never disable SSL verification in production. |
For development environments where you don’t have valid certificates:
quarkus.opensearch.hosts=opensearch.example.com:9200
quarkus.opensearch.protocol=https
quarkus.opensearch.tls.tls-configuration-name=opensearch-dev
# Trust all certificates and disable hostname verification
quarkus.tls.opensearch-dev.trust-all=true
quarkus.tls.opensearch-dev.hostname-verification-algorithm=NONE
You can scope this to development mode only using profiles:
# Production uses proper certificates
%prod.quarkus.opensearch.protocol=https
# Development trusts all certificates
%dev.quarkus.opensearch.tls.tls-configuration-name=dev-tls
%dev.quarkus.tls.dev-tls.trust-all=true
%dev.quarkus.tls.dev-tls.hostname-verification-algorithm=NONE
|
The legacy |
mTLS with Certificate Reload
For environments using mutual TLS (mTLS) with automatic certificate rotation (e.g., Kubernetes with cert-manager), you can configure automatic certificate reload:
quarkus.opensearch.hosts=opensearch.example.com:9200
quarkus.opensearch.protocol=https
quarkus.opensearch.tls.tls-configuration-name=opensearch-mtls
# Client certificate (mTLS)
quarkus.tls.opensearch-mtls.key-store.p12.path=/certs/client.p12
quarkus.tls.opensearch-mtls.key-store.p12.password=${KEYSTORE_PASSWORD}
# Trust store for server certificate
quarkus.tls.opensearch-mtls.trust-store.pem.certs=/certs/ca.pem
# Automatic reload every 5 minutes
quarkus.tls.opensearch-mtls.reload-period=5m
When certificates are rotated (e.g., by cert-manager), the extension automatically picks up the new certificates without requiring an application restart. New connections will use the updated certificates.
|
For programmatic certificate reload, you can call:
|
Per-Request Transport Options
The extension provides an SPI (Service Provider Interface) for injecting per-request transport options into OpenSearch requests. This is useful for:
-
OIDC/OAuth2 token propagation - Pass bearer tokens to OpenSearch Security plugin
-
Multi-tenant headers - Inject tenant identifiers for Document Level Security (DLS)
-
Request tracing - Add correlation IDs and tracing headers
-
Custom authentication - Implement custom authentication schemes
Implementing a Transport Options Provider
Create a CDI bean that implements OpenSearchTransportOptionsProvider and annotate it with @OpenSearchTransportOptionsConfig:
import java.util.Optional;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import org.opensearch.client.transport.TransportOptions;
import io.quarkus.security.identity.SecurityIdentity;
import io.quarkiverse.opensearch.transport.OpenSearchTransportOptionsConfig;
import io.quarkiverse.opensearch.transport.spi.OpenSearchTransportOptionsProvider;
@ApplicationScoped
@OpenSearchTransportOptionsConfig // Required qualifier
public class OidcTokenProvider implements OpenSearchTransportOptionsProvider {
@Inject
SecurityIdentity securityIdentity;
@Override
public Optional<TransportOptions> getTransportOptions(String clientName) {
if (securityIdentity.isAnonymous()) {
return Optional.empty();
}
var credential = securityIdentity.getCredential(
io.quarkus.oidc.AccessTokenCredential.class);
if (credential == null) {
return Optional.empty();
}
return Optional.of(TransportOptions.builder()
.addHeader("Authorization", "Bearer " + credential.getToken())
.build());
}
@Override
public int priority() {
return 10; // Lower number = higher priority
}
}
|
The |
Using the Request-Scoped Client
To use per-request transport options, inject OpenSearchRequestScopedClient instead of the regular OpenSearchClient:
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import org.opensearch.client.opensearch.OpenSearchClient;
import org.opensearch.client.opensearch.core.SearchResponse;
import io.quarkiverse.opensearch.client.runtime.OpenSearchRequestScopedClient;
@Path("/search")
public class SearchResource {
@Inject
OpenSearchRequestScopedClient requestScopedClient;
@GET
public String search() throws Exception {
// Client automatically includes headers from all registered providers
OpenSearchClient client = requestScopedClient.getClient();
SearchResponse<MyDocument> response = client.search(s -> s
.index("my-index")
.query(q -> q.matchAll(m -> m)),
MyDocument.class);
return "Found " + response.hits().total().value() + " documents";
}
}
For named clients:
// Get a specific named client with per-request options
OpenSearchClient analyticsClient = requestScopedClient.getClient("analytics");
OpenSearchAsyncClient asyncClient = requestScopedClient.getAsyncClient("analytics");
Multiple Providers
You can register multiple providers. They are invoked in priority order (lower number = higher priority) and their transport options are merged:
// Provider 1: Authentication (priority 10 - runs first)
@ApplicationScoped
@OpenSearchTransportOptionsConfig
public class AuthProvider implements OpenSearchTransportOptionsProvider {
@Override
public Optional<TransportOptions> getTransportOptions(String clientName) {
return Optional.of(TransportOptions.builder()
.addHeader("Authorization", "Bearer " + getToken())
.build());
}
@Override
public int priority() { return 10; }
}
// Provider 2: Tenant headers (priority 50)
@ApplicationScoped
@OpenSearchTransportOptionsConfig
public class TenantProvider implements OpenSearchTransportOptionsProvider {
@Inject
SecurityIdentity identity;
@Override
public Optional<TransportOptions> getTransportOptions(String clientName) {
String tenant = identity.getAttribute("tenant_id");
return Optional.of(TransportOptions.builder()
.addHeader("X-Tenant-Id", tenant != null ? tenant : "default")
.build());
}
@Override
public int priority() { return 50; }
}
// Provider 3: Tracing (priority 100 - runs last)
@ApplicationScoped
@OpenSearchTransportOptionsConfig
public class TracingProvider implements OpenSearchTransportOptionsProvider {
@Override
public Optional<TransportOptions> getTransportOptions(String clientName) {
return Optional.of(TransportOptions.builder()
.addHeader("X-Request-Id", UUID.randomUUID().toString())
.build());
}
@Override
public int priority() { return 100; }
}
Client-Specific Options
Providers can return different options based on the client name:
@ApplicationScoped
@OpenSearchTransportOptionsConfig
public class ClientSpecificProvider implements OpenSearchTransportOptionsProvider {
@Override
public Optional<TransportOptions> getTransportOptions(String clientName) {
return switch (clientName) {
case "analytics" -> Optional.of(TransportOptions.builder()
.addHeader("X-Priority", "low")
.setParameter("timeout", "60s")
.build());
case "search" -> Optional.of(TransportOptions.builder()
.addHeader("X-Priority", "high")
.build());
default -> Optional.empty(); // No options for other clients
};
}
}
When to Use Request-Scoped vs Regular Client
| Use Case | Client | Reason |
|---|---|---|
Static credentials (username/password) |
|
Credentials don’t change per request |
OIDC token propagation |
|
Token is different for each user/request |
Multi-tenant applications |
|
Tenant context changes per request |
Background jobs (no user context) |
|
No request context available |
Request tracing/correlation |
|
Headers are request-specific |
Dev Services
Quarkus supports a feature called Dev Services that allows you to start various containers without any config.
In the case of Opensearch, this support extends to the default Opensearch connection.
What that means practically is that, if you have not configured quarkus.opensearch.hosts, Quarkus will automatically
start an Opensearch container when running tests or dev mode, and automatically configure the connection.
When running the production version of the application, the Opensearch connection needs to be configured as usual,
so if you want to include a production database config in your application.properties and continue to use Dev Services
we recommend that you use the %prod. profile to define your Opensearch settings.
Extension Configuration Reference
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property |
Type |
Default |
|---|---|---|
Whether a health check is published in case the smallrye-health extension is present. Environment variable: |
boolean |
|
If Dev Services for OpenSearch has been explicitly enabled or disabled. Dev Services are generally enabled by default, unless there is an existing configuration present. For OpenSearch, Dev Services starts a server unless Environment variable: |
boolean |
|
Optional fixed port the dev service will listen to. If not defined, the port will be chosen randomly. Environment variable: |
int |
|
The OpenSearch container image to use. Defaults to the opensearch image provided by OpenSearch. Environment variable: |
string |
|
The value for the OPENSEARCH_JAVA_OPTS env variable. Defaults to setting the heap to 512MB min - 1GB max. Environment variable: |
string |
|
Indicates if the OpenSearch server managed by Quarkus Dev Services is shared. When shared, Quarkus looks for running containers using label-based service discovery. If a matching container is found, it is used, and so a second one is not started. Otherwise, Dev Services for OpenSearch starts a new container. The discovery uses the Container sharing is only used in dev mode. Environment variable: |
boolean |
|
The value of the This property is used when you need multiple shared OpenSearch servers. Environment variable: |
string |
|
The list of hosts of the OpenSearch servers, when accessing AWS OpenSearch set to AWS endpoint name. Host Example: opensearch-01:9200,opensearch-02:9200 AWS Endpoint Example: search-domain-name-identifier.region.es.amazonaws.com Environment variable: |
list of string |
|
The protocol to use when contacting OpenSearch servers. Set to "https" to enable SSL/TLS. Environment variable: |
string |
|
The username for basic HTTP authentication. Environment variable: |
string |
|
The password for basic HTTP authentication. Environment variable: |
string |
|
The connection timeout. Environment variable: |
|
|
The connection timeout. Environment variable: |
|
|
The socket timeout. Environment variable: |
|
|
The maximum number of connections to all the OpenSearch servers. Environment variable: |
int |
|
The maximum number of connections per OpenSearch server. Environment variable: |
int |
|
The number of IO thread. By default, this is the number of locally detected processors. Thread counts higher than the number of processors should not be necessary because the I/O threads rely on non-blocking operations, but you may want to use a thread count lower than the number of processors. Environment variable: |
int |
|
AWS Region Environment variable: |
string |
|
Set to "es" or "aoss" to use AWS OpenSearch Service. es : Amazon OpenSearch Service aoss : Amazon OpenSearch Serverless Environment variable: |
string |
|
AWS Secret Access Key for setting up StaticCredentialsProvider Environment variable: |
string |
|
AWS Secret Access Key Secret for setting up StaticCredentialsProvider Environment variable: |
string |
|
The name of the TLS configuration to use. This refers to a configuration group defined under Environment variable: |
string |
required |
Defines if automatic discovery is enabled. Environment variable: |
boolean |
|
Refresh interval of the node list. Environment variable: |
|
|
Whether this client should be included in health checks. When set to false, this client will be excluded from the OpenSearch health check endpoint. Environment variable: |
boolean |
|
This property is deprecated since Optional keyStoreFile to be used when connecting to cluster nodes Environment variable: |
string |
|
This property is deprecated since Optional password for accessing keyStoreFile Environment variable: |
string |
|
This property is deprecated since SSL Verify Hostname Environment variable: |
boolean |
|
This property is deprecated since Verify SSL Certificates Environment variable: |
boolean |
|
|
About the Duration format
To write duration values, use the standard You can also use a simplified format, starting with a number:
In other cases, the simplified format is translated to the
|