os-persistence-v3/MIGRATION_GUIDE.md
This document guides the migration from OpenSearch High-Level REST Client (used in v2) to the new opensearch-java 3.x client (for v3).
Old (v2):
import org.opensearch.client.RestHighLevelClient;
import org.opensearch.client.RestClient;
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost(host, port, "http"))
);
New (v3):
import org.opensearch.client.opensearch.OpenSearchClient;
import org.opensearch.client.json.jackson.JacksonJsonpMapper;
import org.opensearch.client.transport.OpenSearchTransport;
import org.opensearch.client.transport.rest_client.RestClientTransport;
RestClient restClient = RestClient.builder(new HttpHost(host, port, "http")).build();
OpenSearchTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
OpenSearchClient client = new OpenSearchClient(transport);
Old (v2):
import org.opensearch.index.query.QueryBuilder;
import org.opensearch.index.query.QueryBuilders;
import org.opensearch.index.query.BoolQueryBuilder;
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery()
.must(QueryBuilders.matchQuery("field", "value"))
.filter(QueryBuilders.rangeQuery("age").gte(18));
New (v3):
import org.opensearch.client.opensearch._types.query_dsl.Query;
import org.opensearch.client.opensearch._types.query_dsl.BoolQuery;
Query query = Query.of(q -> q
.bool(b -> b
.must(m -> m.match(t -> t.field("field").query("value")))
.filter(f -> f.range(r -> r.field("age").gte(JsonData.of(18))))
)
);
Old (v2):
import org.opensearch.action.index.IndexRequest;
import org.opensearch.action.index.IndexResponse;
import org.opensearch.common.xcontent.XContentType;
IndexRequest request = new IndexRequest("index")
.id("1")
.source(jsonString, XContentType.JSON);
IndexResponse response = client.index(request, RequestOptions.DEFAULT);
New (v3):
import org.opensearch.client.opensearch.core.IndexRequest;
import org.opensearch.client.opensearch.core.IndexResponse;
IndexResponse response = client.index(i -> i
.index("index")
.id("1")
.document(myObject) // Auto-serialized via Jackson
);
Old (v2):
import org.opensearch.action.search.SearchRequest;
import org.opensearch.action.search.SearchResponse;
import org.opensearch.search.builder.SearchSourceBuilder;
import org.opensearch.search.SearchHit;
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder()
.query(queryBuilder)
.from(0)
.size(10);
SearchRequest searchRequest = new SearchRequest("index")
.source(sourceBuilder);
SearchResponse response = client.search(searchRequest, RequestOptions.DEFAULT);
SearchHit[] hits = response.getHits().getHits();
New (v3):
import org.opensearch.client.opensearch.core.SearchRequest;
import org.opensearch.client.opensearch.core.SearchResponse;
import org.opensearch.client.opensearch.core.search.Hit;
SearchResponse<MyDoc> response = client.search(s -> s
.index("index")
.query(query)
.from(0)
.size(10),
MyDoc.class
);
List<Hit<MyDoc>> hits = response.hits().hits();
Old (v2):
import org.opensearch.action.bulk.BulkRequest;
import org.opensearch.action.bulk.BulkResponse;
BulkRequest bulkRequest = new BulkRequest();
bulkRequest.add(new IndexRequest("index").id("1").source(...));
bulkRequest.add(new DeleteRequest("index", "2"));
BulkResponse response = client.bulk(bulkRequest, RequestOptions.DEFAULT);
New (v3):
import org.opensearch.client.opensearch.core.BulkRequest;
import org.opensearch.client.opensearch.core.BulkResponse;
import org.opensearch.client.opensearch.core.bulk.BulkOperation;
BulkResponse response = client.bulk(b -> b
.operations(op -> op.index(i -> i.index("index").id("1").document(doc)))
.operations(op -> op.delete(d -> d.index("index").id("2")))
);
Old (v2):
import org.opensearch.action.delete.DeleteRequest;
import org.opensearch.action.delete.DeleteResponse;
DeleteRequest request = new DeleteRequest("index", "id");
DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
New (v3):
import org.opensearch.client.opensearch.core.DeleteRequest;
import org.opensearch.client.opensearch.core.DeleteResponse;
DeleteResponse response = client.delete(d -> d
.index("index")
.id("id")
);
Old (v2):
import org.opensearch.action.get.GetRequest;
import org.opensearch.action.get.GetResponse;
GetRequest request = new GetRequest("index", "id");
GetResponse response = client.get(request, RequestOptions.DEFAULT);
String sourceAsString = response.getSourceAsString();
New (v3):
import org.opensearch.client.opensearch.core.GetRequest;
import org.opensearch.client.opensearch.core.GetResponse;
GetResponse<MyDoc> response = client.get(g -> g
.index("index")
.id("id"),
MyDoc.class
);
MyDoc document = response.source();
Old (v2):
import org.opensearch.client.core.CountRequest;
import org.opensearch.client.core.CountResponse;
CountRequest countRequest = new CountRequest("index")
.query(queryBuilder);
CountResponse response = client.count(countRequest, RequestOptions.DEFAULT);
long count = response.getCount();
New (v3):
import org.opensearch.client.opensearch.core.CountRequest;
import org.opensearch.client.opensearch.core.CountResponse;
CountResponse response = client.count(c -> c
.index("index")
.query(query)
);
long count = response.count();
Old: Frequently used string JSON
.source(jsonString, XContentType.JSON)
New: Use typed POJOs
.document(myTypedObject) // Jackson handles serialization
Old: Imperative chaining
BoolQueryBuilder query = QueryBuilders.boolQuery();
query.must(QueryBuilders.matchQuery("field", "value"));
query.filter(QueryBuilders.rangeQuery("age").gte(18));
New: Functional lambda builders
Query query = Query.of(q -> q.bool(b -> b
.must(m -> m.match(t -> t.field("field").query("value")))
.filter(f -> f.range(r -> r.field("age").gte(JsonData.of(18))))
));
Old:
import org.opensearch.common.xcontent.XContentType;
.source(jsonBytes, XContentType.JSON)
New:
import org.opensearch.client.json.JsonData;
.document(JsonData.of(value)) // For raw JSON
implementation 'org.opensearch.client:opensearch-java:3.0.0'
implementation "org.opensearch.client:opensearch-rest-client:3.0.0"
implementation "org.opensearch.client:opensearch-rest-high-level-client:3.0.0" // Keep for transition
File: OpenSearchRestDAO.java constructor
Change:
// Remove:
private final RestHighLevelClient openSearchClient;
// Add:
private final OpenSearchClient openSearchClient;
private final RestClient restClient;
// Update constructor to build new client
Method to migrate: boolQueryBuilder(String structuredQuery, String freeTextQuery)
Current signature:
private QueryBuilder boolQueryBuilder(String structuredQuery, String freeTextQuery)
New signature:
private Query boolQuery(String structuredQuery, String freeTextQuery)
Implementation changes:
QueryBuilders.* with lambda buildersQuery instead of QueryBuilderMethods to update:
searchObjectsViaExpression()searchObjects()searchWorkflowSummary()searchTaskSummary()Key changes:
SearchRequest import (old → new package)SearchSourceBuilder with lambda buildersSearchHits → hits().hits())SearchResponse<T>)Methods to update:
indexObject()updateObject()addTaskExecutionLogs()Key changes:
IndexRequestXContentType.JSON with typed documentsMethods to update:
deleteObject()asyncBulkDelete()Methods to update:
bulkIndexObjects()asyncBulkIndexObjects()Major changes needed:
BulkRequest.add() with lambda buildersBulkOperation for each operationBulkProcessor initialization (if used)Old:
import org.opensearch.search.sort.FieldSortBuilder;
import org.opensearch.search.sort.SortOrder;
searchSourceBuilder.sort(new FieldSortBuilder(field).order(order));
New:
import org.opensearch.client.opensearch._types.SortOrder;
.sort(s -> s.field(f -> f.field(fieldName).order(sortOrder)))
Old:
catch (IOException e) {
// Handle
}
New: Same, but also handle:
catch (OpenSearchException e) {
// New exception types from opensearch-java client
}
Unit tests first
Integration tests
Side-by-side comparison
Total: ~40 hours (1 week for experienced dev, 2 weeks with testing/review)