Got a MongoDB database that stores articles, blog posts, reviews, e-books, or something else with lots of text?

Would you just love to give your users the ability to search through all those documents using a text string and return what they're looking for?

If so, you're on the right page. I'll show you how to do it here.

Specifically: I'll show you how to do it if Spring Boot is your framework of choice and you're using MongoTemplate to make calls.

You could also do what I'm describing here with a MongoRepository interface but that can get hairy if you want to put other queries together with your full text query.

So I'll show you how to do it with MongoTemplate.

By the way, if you're unclear about how to get your Spring Boot application situated with MongoTemplate, feel free to read my first guide on aggregations that covers that subject

You're Soaking in It

The full text search solution I'll show you here is exactly what I use for this blog. So I literally practice what I preach.

(Disclaimer: the full text search aspect of it might not be visible on the UI as of yet.)

Now that you understand a little more about the code you'll see here, take a look at an update I needed to make to the BlogPost class.

@Document(collection = "#{@environment.getProperty('mongo.blogpost.collection')}")
public class BlogPost {  
...	
	@TextIndexed(weight=5)
	private String title;

	@TextIndexed(weight=1)
	private String body;
	
	@TextIndexed(weight=3)
	private List<String> tags;

	@TextIndexed(weight=4)
	private String category;
...

I added the @TextIndexed annotation to several properties.

But it's not alone. In parentheses next to the annotation you'll see a weight assigned to the property its annotating.

Before I explain that, let me first explain what @TextIndexed is doing.

It's telling MongoDB to create a full text index on the given collection. In this case, that's a collection of blog posts.

Although there can be one and only one full text index per collection, you can index multiple properties. That's what you see in the code above as many fields are annotated with @TextIndexed.

Now about the weight: that tells MongoDB which fields get the highest score when a search string matches.

For example, if I search for the word "Hallelujah" and it's in the title, that match gets a higher score than if MongoDB finds that same word in the article body.

Take a look at the code above and you'll see that the body field has a weight of 1 but the title field has a weight of 5.

And that makes sense. If someone searches for a word or phrase, and that word or phrase shows up in the title, it's probably a really good match for the whole document. Hence the weighting.

That full text index will get created with those weights the next time Spring Boot starts up.

Of course, you can create the full text index yourself in the Mongo client. I actually prefer that way.

But this approach seems to be what the cool kids are doing these days so I just rolled with it.

An Unsurprising Service

Now let's take a look at a little class I imaginatively named BlogService:

@Service
public class BlogService {

    private static final Logger LOG = LoggerFactory.getLogger(BlogService.class);
 
    
    @Autowired
    private MongoTemplate mongoTemplate;
    
    
    public List<BlogPost> fullTextSearch(String searchPhrase) {
        TextCriteria criteria = TextCriteria
                                    .forDefaultLanguage()
                                    .matchingPhrase(searchPhrase);
        
        Query query = TextQuery.queryText(criteria).sortByScore();
        
        List<BlogPost> posts = mongoTemplate.find(query, BlogPost.class);

        return posts;
    }    
}

Right now, that class only has one method. It's a public method that handles full text searches.

The method accepts the search phrase as its only parameter in String format.

Right now the blog only searches for matching phrases. I'm not getting into any of that AND and OR stuff at this time.

But I will one day.

(Sidebar: if you're interested in a more generic, Google-like approach to matching, go with the matching() method instead.)

For now, it just takes a simple phrase.

And what does it do with that phrase? It creates a TextCriteria object.

The TextCriteria Class

What is this TextCriteria class?

It's what you'll use to handle full text searches.

It's different than it's older brother, the Criteria class. That's the one you'll run with if you want to search based on something like: status="Open".

In other words, a simple match of a field.

Full text searching is a horse of a different color. So you need to use a specific class to handle it.

That TextCriteria class, by the way, includes some static methods that create a TextCriteria instance.

Further, it uses method chaining so you can consolidate your code.

The forDefaultLanguage() method you see above returns a TextCriteria instance that's ready to search for English words and phrases. That's because English is the default language in MongoDB full text indices.

That matchingPhrase() method also returns a TextCriteria instance. Unsurprisingly, you'll use that object to search for documents that match the given phrase.

Now once you've got that TextCriteria object, you could go in a couple of different directions. I prefer to wrap it in a Query object.

A Query Object?

For the purposes of this guide, a Query object represents one or more criteria.

Right now, I've only got one criteria: find me something that matches this phrase using a full text search.

So the code above creates a Query object using that criteria with this line:

Query query = TextQuery.queryText(criteria).sortByScore();

That TextQuery class employs a familiar a pattern: a static method that creates an instance of itself.

In this case queryText() accepts a TextCriteria object and returns an instance of TextQuery.

But wait. There's more.

With full text searching, you usually want to sort by score. In other words, the matches with the highest scores should go to the top.

That sortByScore() method also returns an instance of TextQuery (method chaining again) that sorts the results by score.

By the way, that means you need to add a score field to your model. In my case, that's the BlogPost class.

Here's what that looks like:

@TextScore
private Float score;

That's going to get populated with the score after the search results are returned.

But back to TextQuery. It's an extension of Query. So the whole thing returns a Query object at the end of the day.

And now you can use that Query object just like you'd use any other Query object when searching for documents in your MongoDB collection.

So that's why the code above uses the find() method on the MongoTemplate object to finish its work:

List<BlogPost> posts = mongoTemplate.find(query, BlogPost.class);

And that's going to do the trick.

Wrapping It Up

Now you know how to do a full text search against a MongoDB collection from within your Spring Boot application.

Feel free to take what you've learned here and make it your own.

Have fun!