Sometimes you don't want to return all the results from a MongoDB aggregation pipeline. On some occasions, you might like to skip the first few documents.

That's when you should use the aggregation operation intuitively named "skip."

In this guide, I'll show you how to use skip within your MongoDB aggregation pipeline in your Spring Boot app. You can easily fulfill this requirement with the assistance of MongoTemplate.

If you'd like to jump straight to the code, it's on GitHub.

Otherwise, read on.

The Business Requirements

Your boss Smithers walks into your office after getting back from his leadership conference.

"Hello, sir!" he says, acting all professional.

"First, I want you to know what an amazing job you're doing! Everything looks great!"

He smiles.

"What we need from you now, Superman, is a little change on that CRM app you're working on. You've already created an outstanding service request that filters contacts based on source. Now, we need to allow clients to skip a specified number of results. Do these requirements make sense to you?"

You nod your head.

"Thanks! I knew you could do it! Talk atcha later!"

He leaves your office with his back perfectly straight.

Updating What You've Got

You've alread created a service request that handles filtering contacts by source. That code uses a MongoDB aggregation pipeline with MongoTemplate

So you don't need to reinvent that wheel.

If you're unfamiliar with the basics of MongoDB aggregations or MongoTemplate, please consult that guide as a starting point. Then, I'll see you back here.

Here's the code you have for ContactService right now:

@Service
public class ContactService {

    private static final Logger LOG = LoggerFactory.getLogger(ContactService.class);
	
    
	@Autowired
	private MongoTemplate mongoTemplate;
	
	
	public List<Contact> findContactsBySource(Source source) {
		AggregationOperation match = Aggregation.match(Criteria.where("source").is(source));
		AggregationOperation sort = Aggregation.sort(Direction.ASC, "lastName"); 
		
		Aggregation aggregation = Aggregation.newAggregation(match, sort);
		
		List<Contact> contacts = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Contact.class), Contact.class).getMappedResults();
		
		return contacts;
	}
}

That code filters contacts by source and returns them sorted by last name in ascending order. 

But now you need to give clients the ability to skip the first n documents. How can you do that?

That's where the skip aggregation operation comes into play. It lives up to its name.

You can use skip to skip any number of documents. It's great for pagination, but you're not going to get quite that sophisticated here.

Instead, you're just going to let the client specify how many documents to skip. 

But first, take a look at the results list without any skips. That's how you'll eventually know if skip is working properly.

Add this initialization code to your Spring Boot app:

@Component
public class ApplicationListenerInitialize implements ApplicationListener<ApplicationReadyEvent>  {
	
	@Autowired
	private ContactService contactService;
	
    public void onApplicationEvent(ApplicationReadyEvent event) {
    	List<Contact> contacts = contactService.findContactsBySource(Source.EMAIL);
    	
    	try {
	    	ObjectMapper objectMapper = new ObjectMapper();
	    	objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
	    	objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
	    	System.err.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(contacts));
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
}

Then, launch your Spring Boot app. After everything settles, you should see something like this output all in red:

[ {
  "id" : "5fde1ac084dad94dbb7f82ae",
  "firstName" : "R2D2",
  "lastName" : "Droid",
  "email" : "r2d2@xmail.com",
  "source" : "EMAIL",
  "status" : "ACTIVE",
  "statusChange" : 0,
  "linesOfBusiness" : [ "JAVA_ENTERPRISE" ],
  "company" : "For Luke",
  "title" : "Droid",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
}, {
  "id" : "5fdd0cedaac5f75d62564ee7",
  "firstName" : "Jabba",
  "lastName" : "Hutt",
  "email" : "jabba@xmail.com",
  "source" : "EMAIL",
  "status" : "NEW",
  "statusChange" : 0,
  "linesOfBusiness" : [ "ANGULAR" ],
  "company" : "Sandz",
  "title" : "Large",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
}, {
  "id" : "5fd5f37bde602d3bacef69db",
  "firstName" : "Luke",
  "lastName" : "Skywalker",
  "email" : "luke@tat2.com",
  "source" : "EMAIL",
  "sourceDetails" : "He emailed me",
  "status" : "NEW",
  "statusChange" : 0,
  "linesOfBusiness" : [ "ANGULAR" ],
  "company" : "International Business",
  "title" : "President",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
}, {
  "id" : "5fdd0f2aea599836ca3ddbf1",
  "firstName" : "Han",
  "lastName" : "Solo",
  "email" : "han@xmail.com",
  "addresses" : [ {
    "street1" : "111 Millennium Way",
    "city" : "Nessy",
    "state" : "CO",
    "addressType" : "HOME"
  } ],
  "source" : "EMAIL",
  "status" : "ACTIVE",
  "statusChange" : 0,
  "title" : "Pirate",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
} ]

As you can see, R2D2 is the first name in the list. So if you return a list that skips the first document, R2D2 should not appear at the top. Or anywhere else.

Now it's time to make some changes.

Skipworthy

Go ahead and update the service method so that it accepts a new long parameter. That parameter represents the number of documents to skip.

Then, update the aggregation. 

The whole thing should look like this:

@Service
public class ContactService {

    private static final Logger LOG = LoggerFactory.getLogger(ContactService.class);
	
    
	@Autowired
	private MongoTemplate mongoTemplate;
	
	
	public List<Contact> findContactsBySource(Source source, long elementsToSkip) {
		AggregationOperation match = Aggregation.match(Criteria.where("source").is(source));
		AggregationOperation sort = Aggregation.sort(Direction.ASC, "lastName"); 
		AggregationOperation skip = Aggregation.skip(elementsToSkip);
		
		Aggregation aggregation = Aggregation.newAggregation(match, sort, skip);
		
		List<Contact> contacts = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Contact.class), Contact.class).getMappedResults();
		
		return contacts;
	}
}

Look at the third AggregationOperation up there. That's the newbie.

It accepts a single long parameter. That's the value that gets passed into the method.

Then you need to add skip to the newAggregation() method.

Everything else is the same from the previous guide.

Now test it out by updating the initialization code as follows:

@Component
public class ApplicationListenerInitialize implements ApplicationListener<ApplicationReadyEvent>  {
	
	@Autowired
	private ContactService contactService;
	
    public void onApplicationEvent(ApplicationReadyEvent event) {
    	List<Contact> contacts = contactService.findContactsBySource(Source.EMAIL, 1);
    	
    	try {
	    	ObjectMapper objectMapper = new ObjectMapper();
	    	objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
	    	objectMapper.setSerializationInclusion(JsonInclude.Include.NON_EMPTY);
	    	System.err.println(objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(contacts));
    	} catch (Exception e) {
    		e.printStackTrace();
    	}
    }
}

Save that file, relaunch your Spring Boot app, and you should see this in red:

[ {
  "id" : "5fdd0cedaac5f75d62564ee7",
  "firstName" : "Jabba",
  "lastName" : "Hutt",
  "email" : "jabba@xmail.com",
  "source" : "EMAIL",
  "status" : "NEW",
  "statusChange" : 0,
  "linesOfBusiness" : [ "ANGULAR" ],
  "company" : "Sandz",
  "title" : "Large",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
}, {
  "id" : "5fd5f37bde602d3bacef69db",
  "firstName" : "Luke",
  "lastName" : "Skywalker",
  "email" : "luke@tat2.com",
  "source" : "EMAIL",
  "sourceDetails" : "He emailed me",
  "status" : "NEW",
  "statusChange" : 0,
  "linesOfBusiness" : [ "ANGULAR" ],
  "company" : "International Business",
  "title" : "President",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
}, {
  "id" : "5fdd0f2aea599836ca3ddbf1",
  "firstName" : "Han",
  "lastName" : "Solo",
  "email" : "han@xmail.com",
  "addresses" : [ {
    "street1" : "111 Millennium Way",
    "city" : "Nessy",
    "state" : "CO",
    "addressType" : "HOME"
  } ],
  "source" : "EMAIL",
  "status" : "ACTIVE",
  "statusChange" : 0,
  "title" : "Pirate",
  "authority" : false,
  "salesOwner" : {
    "id" : "5f78d8fbc1d3246ab4303f2b",
    "firstName" : "Darth",
    "lastName" : "Vader",
    "email" : "darth@xmail.com",
    "username" : "darth",
    "phoneNumber" : "474-555-1212"
  }
} ]

Take a look at the first document. It's not R2D2!

That's what you want.

The contact that appeared in second position initially (Jabba the Hutt) is now in first position. That's because the MongoDB skip operation worked perfectly.

In other words, your test is a success.

Wouldn't it be nice if everything was this easy?

Wrapping It Up

That's it. Now it's over to you.

Think about how you can use skip in your own business applications. I mentioned one great way earlier: pagination.

Why don't you give that a shot?

Also, check out the source code on GitHub if you'd like to see the whole thing.

Have fun!

Photo by Gabby K from Pexels