# Migrating complex search from Hibernate Search 5 to 6

**URL:** <https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145>\
**Category:** Hibernate Search\
**Created:** [January 16, 2023, 6:59pm UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145 "2023-01-16T18:59:08Z")\
**Posts on this page:** 6\
**Page:** 1

<div class="post-metadata">

**Author:** ![horvoje](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/horvoje/32/779_2.png) [@horvoje](https://discourse.hibernate.org/u/horvoje)\
**Post date:** [January 16, 2023, 6:59pm UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/1 "2023-01-16T18:59:08Z")

</div>

I’m trying to migrate my search queries from Hibernate Search 5 to 6 while using Lucene.

There are few components I made:

- SearchRequest POJO that holds all the data needed for single search request
- SearchService which manages all searches for my project
- many util classes that creates tiny Query objects that are later linked by BooleanJunction into the main query

Is there any chance to do similar with Hibernate Search 6?  
I’m really lost with all those new classes and syntax and I just don’t know how to convert.

* * *

Few code snippets to get idea what I’m using while building my search query:

```auto
@Builder
class SearchReq {
    private String q;
    private Integer categoryId;
    private Integer manufacturerId;
    private Integer manufacturerName;
    private Integer providerId;
    private Integer providerName;
    private Integer countryId;
    private Integer cityId;
    private Integer tagId;
    // ... etc.
    private Integer pageSize;
    private Integer page;
    private String sortField;    
}

```

Creating search request:

```auto
SearchReq.builder().q("tofu").cityId(12).build();

```

Properties holding NULL are skipped, so in this case you’d get everything that contains “tofu” and is available in city with ID == 12.

Once I have POJO filled up, I can create list of tiny queryies that will be later joined.

```auto
final List<Query> queries = new LinkedList<>();
if( q != null ) { queries.add( QueryUtil.byQ( q ) ); }
if( categoryId != null ) { queries.add( QueryUtil.byCategoryId( categoryId ) ); }
// etc.

```

**QueryUtil.java**

```auto
private static Query byCategoryId(final QueryBuilder queryBuilder, final Integer categoryId) {
	return queryBuilder.createBooleanQuery("category", categoryId);
}

```

Another step is to apply boosting factors:

```auto
public static void article(final QueryBuilder queryBuilder, final String searchTerm, final BooleanJunction<?> bj) {
    	bj.should(QueryForField.q(SearchField.NAME, searchTerm, queryBuilder)).boostedTo(SearchField.NAME.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.SHORT_NAME, searchTerm, queryBuilder)).boostedTo(SearchField.SHORT_NAME.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.DESCRIPTION, searchTerm, queryBuilder)).boostedTo(SearchField.DESCRIPTION.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.SEARCH_AID, searchTerm, queryBuilder)).boostedTo(SearchField.SEARCH_AID.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.MANUFACTURER, searchTerm, queryBuilder)).boostedTo(SearchField.MANUFACTURER.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.PRICE_INFOS, searchTerm, queryBuilder)).boostedTo(SearchField.PRICE_INFOS.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.CATEGORIES, searchTerm, queryBuilder)).boostedTo(SearchField.CATEGORIES.getBoostFactor());
    	bj.should(QueryForField.q(SearchField.TAGS, searchTerm, queryBuilder)).boostedTo(SearchField.TAGS.getBoostFactor());
    }

```

Finally I join all the queries into main query:

```auto
final BooleanJunction<?> master = queryBuilder.bool();
queries.stream().filter(Objects::nonNull).forEach(master::must);
final Query theQuery = master.createQuery();

```

---

<div class="post-metadata">

**Author:** ![yrodiere](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/yrodiere/32/1760_2.png) [@yrodiere](https://discourse.hibernate.org/u/yrodiere)\
**Post date:** [January 17, 2023, 7:26am UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/2 "2023-01-17T07:26:13Z")

</div>

Hello,

Please read the migration guide, there’s an example that does pretty much exactly what you want: [Hibernate Search 6.0.11.Final: Migration Guide from 5.11](https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#queries-basics)

> As to migrating existing complex queries, let’s consider the query below:
> 
> ```java
> MySearchParameters searchParameters = ...;
> FullTextEntityManager fullTextEntityManager = Search.getFullTextEntityManager( em );
> QueryBuilder qb = fullTextEntityManager.getSearchFactory()
> .buildQueryBuilder().forEntity( Book.class ).get();
> 
> BooleanJunction junction = qb.bool();
> junction.must(qb.all().createQuery());
> 
> if ( searchParameters.getSearchTerms() != null ) {
> junction.must( qb.simpleQueryString().onFields( "title", "description" )
> .withAndAsDefaultOperator()
> .matching( searchParameters.getSearchTerms() )
> .createQuery() );
> }
> if ( searchParameters.getMaxBookLength() != null ) {
> junction.must( qb.range().onField( "pageCount" )
> .below( searchParameters.getMaxBookLength() ) );
> }
> if ( !searchParameters.getGenres().isEmpty() ) {
> BooleanJunction junction2 = qb.bool();
> for ( Genre genre : searchParameters.getGenres() ) {
> junction2.should( qb.keyword().onField( "genre" )
> .matching( genre ) );
> }
> junction.must( junction2.createQuery() );
> }
> 
> org.apache.lucene.search.Query luceneQuery = junction.createQuery();
> 
> FullTextQuery fullTextQuery = fullTextEntityManager.createFullTextQuery( luceneQuery, Book.class );
> fullTextQuery.setFirstResult( params.getPageIndex() * params.getPageSize() );
> fullTextQuery.setMaxResults( params.getPageSize() );
> 
> List hits = fullTextQuery.getResultList();
> 
> ```
> 
> It would look like this in Hibernate Search 6:
> 
> ```java
> MySearchParameters searchParameters = ...;
> SearchSession session = Search.session( entityManager );
> List<Book> hits = searchSession.search( Book.class )
> .where( f -> f.bool( b -> {
> b.must( f.matchAll() );
> if ( searchParameters.getSearchTerms() != null ) {
> b.must( f.simpleQueryString().fields( "title", "description" )
> .matching( searchParameters.getSearchTerms() )
> .defaultOperator( BooleanOperator.AND ) );
> }
> if ( searchParameters.getMaxBookLength() != null ) {
> b.must( f.range().field( "pageCount" )
> .atMost( searchParameters.getMaxBookLength() ) );
> }
> if ( !searchParameters.getGenres().isEmpty() ) {
> b.must( f.bool( b2 -> {
> for ( Genre genre : searchParameters.getGenres() ) {
> b2.should( f.match().field( "genre" )
> .matching( genre ) );
> }
> } ) );
> }
> } ) )
> .fetchHits( searchParameters.getPageIndex() * searchParameters.getPageSize(),
> searchParameters.getPageSize() );
> 
> ```
> 
> Alternatively, if for some reasons predicate objects are necessary:
> 
> ```java
> MySearchParameters searchParameters = ...;
> SearchSession session = Search.session( entityManager );
> SearchPredicateFactory pf = session.scope( Book.class ).predicate();
> List<SearchPredicate> predicates = new ArrayList<>();
> 
> if ( searchParameters.getSearchTerms() != null ) {
> predicates.add( pf.simpleQueryString().fields( "title", "description" )
> .matching( searchParameters.getSearchTerms() )
> .defaultOperator( BooleanOperator.AND )
> .toPredicate() );
> }
> if ( searchParameters.getMaxBookLength() != null ) {
> predicates.add( pf.range().field( "pageCount" )
> .atMost( searchParameters.getMaxBookLength() )
> .toPredicate() );
> }
> if ( !searchParameters.getGenres().isEmpty() ) {
> predicates.add( f.bool( b -> {
> for ( Genre genre : searchParameters.getGenres() ) {
> b.should( f.match().field( "genre" )
> .matching( genre ) );
> }
> } )
> .toPredicate() );
> }
> 
> SearchPredicate topLevelPredicate = pf.bool( b -> {
> b.must( f.matchAll() );
> for ( SearchPredicate predicate : predicates ) {
> b.must( predicate );
> }
> } );
> 
> List<Book> hits = searchSession.search( Book.class )
> .where( topLevelPredicate )
> .fetchHits( searchParameters.getPageIndex() * searchParameters.getPageSize(),
> searchParameters.getPageSize() );
> 
> ```

If that doesn’t help, I’ll need a more precise question, for example with a snippet of code showing where you got and what got you stuck.

---

<div class="post-metadata">

**Author:** ![horvoje](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/horvoje/32/779_2.png) [@horvoje](https://discourse.hibernate.org/u/horvoje)\
**Post date:** [January 17, 2023, 10:50am UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/3 "2023-01-17T10:50:59Z")

</div>

Another situation ☹  
This won’t compile because I get this error:

_Type mismatch: cannot convert from PredicateFinalStep to SearchPredicate_

```auto
import org.hibernate.search.engine.search.predicate.SearchPredicate;
import org.hibernate.search.engine.search.predicate.dsl.SearchPredicateFactory;
import org.hibernate.search.mapper.orm.session.SearchSession;

// ...

SearchPredicateFactory pf = searchSession.scope( Article.class ).predicate();
List<SearchPredicate> predicates = new ArrayList<>();

// ... build the list of predicates ...

SearchPredicate classPredicate = pf.bool( b -> {
	for( SearchPredicate predicate : predicates ) {
		b.should( predicate );
	}
	b.minimumShouldMatchNumber( 1 );
} );

```

When I put a cast in front of `pf.bool()` the error is gone.  
The example in migration guide does not have cast and I’m not sure I’m doing it right.  
Since I have tons of code to convert, I’m not able to test it and I guess I won’t be able for days and days.

```auto
SearchPredicate classPredicate = (SearchPredicate) pf.bool( b -> {
	for( SearchPredicate predicate : predicates ) {
		b.should( predicate );
	}
	b.minimumShouldMatchNumber( 1 );
} );

```

---

<div class="post-metadata">

**Author:** ![mbekhta](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/mbekhta/32/2044_2.png) [@mbekhta](https://discourse.hibernate.org/u/mbekhta)\
**Post date:** [January 17, 2023, 11:00am UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/4 "2023-01-17T11:00:07Z")

</div>

hey,

There’s `PredicateFinalStep#toPredicate()` which would return you a `SearchPredicate`. You wouldn’t want to do the cast.

---

<div class="post-metadata">

**Author:** ![yrodiere](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/yrodiere/32/1760_2.png) [@yrodiere](https://discourse.hibernate.org/u/yrodiere)\
**Post date:** [January 17, 2023, 11:02am UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/5 "2023-01-17T11:02:44Z")

</div>

> [@mbekhta](#):
>
> There’s `PredicateFinalStep#toPredicate()` which would return you a `SearchPredicate`. You wouldn’t want to do the cast.

This ^. The cast would fail anyway.

EDIT: created [[HSEARCH-4786] - Hibernate JIRA](https://hibernate.atlassian.net/browse/HSEARCH-4786) to fix the migration guide.

> [@horvoje](#):
>
> Since I have tons of code to convert, I’m not able to test it and I guess I won’t be able for days and days.

Use the migration helper: [Hibernate Search 6.0.11.Final: Migration Guide from 5.11](https://docs.jboss.org/hibernate/search/6.0/migration/html_single/#migration-helper)

---

<div class="post-metadata">

**Author:** ![horvoje](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.hibernate.org/horvoje/32/779_2.png) [@horvoje](https://discourse.hibernate.org/u/horvoje)\
**Post date:** [January 17, 2023, 12:16pm UTC](https://discourse.hibernate.org/t/migrating-complex-search-from-hibernate-search-5-to-6/7145/6 "2023-01-17T12:16:30Z")

</div>

Thank you both very much!!!  
I am willing to rewrite all of my search code from scratch if needed, just to avoid helpers and hacks.  
I want to say, I’m going for clean code written for Hibernate Search 6, not some adjusted code hacked just to work.

I’ll see what to do with hint from @mbekhta and really hope not to waste more of your time.

Thanks again!

P.S. My project is already live with Hibernate Search 5 and doing well which means I have all the time in the world to make it the right way. 🙂
