Sometimes less is more. When you're running a MongoDB aggregation in your Spring Boot app and you don't want to return every field, use the project operation.

And, as I'm so fond of saying, that's easy to do with Spring Boot.

In this guide, I'll go over a simple aggregation that uses project. You can easily adapt it to suit your own business requirements.

Or just go get the code from GitHub.

The Business Requirements

Your boss Smithers walks into your office, clearly feeling under the weather.

"I seem to have come down with a cold," he says.

You wonder why he didn't stay home. You also wonder why he's bringing his contagious condition into your office.

"But I need you to do something before I take my next dose of Dayquil," Smithers says. "You recently created a new service request in our CRM app. It filters contacts based on how they were reached. Unfortunately, it returns too much information."

Smithers coughs in your face.

"I need you to only return the contact's first name, last name, and the source."

He coughs again and walks out of your office, wheezing.

You've Been Here Before

As Smithers stated, you've already created a service request that handles filtering contacts based on source.

Here, "source" means how the contacts were initially reached. A valid source could be an email contact, inbound sales call, walk-in, website form completion, referral, or some other means.

And your service request is working just fine.

By the way, if you want to know how to handle that initial request, you can read the guide. It also covers the basics of using MongoTemplate.

For the purposes of this guide, I'll assume you know the basics and start from there.

Updating the Service

Remember: the requirement here is to filter the contacts just as you've filtered them before, but now you need to return less info to the calling client. That's the way it is sometimes.

You used an aggregation to filter the contacts initially. Fortunately, you can use that same aggregation to reduce the number of fields that you'll return.

You do that with the "project" aggregation operation.

The project operation, in my opinion, is a bit oddly named. It means that you're only going to send the specified fields to the next stage of the aggregation. In this case, there is no next stage so you'll just send those fields back to the calling client.

It's easy to do, but first take a look at what happens if you don't use project.

The Whole Enchilada

Here's what the ContactService class looks like now. Again, this is from the last guide.

@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;
	}
}

Now use that service for a quick-and-dirty test in some initialization code like this:

@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();
    	}
    }
}

Launch your Spring Boot app and after everything loads, you should see output in pretty red that looks something like this:

[ {
  "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"
  }
} ]

And that's what Smithers is calling TMI: too much information.

The clients who make that request don't need all that info. They just need the contact's first name, last name, and source.

Now it's time to make that happen.

The Projected Winner

Update the service class to 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) {
		AggregationOperation match = Aggregation.match(Criteria.where("source").is(source));
		AggregationOperation sort = Aggregation.sort(Direction.ASC, "lastName"); 
		AggregationOperation project = Aggregation.project("firstName", "lastName", "source");
		
		Aggregation aggregation = Aggregation.newAggregation(match, sort, project);
		
		List<Contact> contacts = mongoTemplate.aggregate(aggregation, mongoTemplate.getCollectionName(Contact.class), Contact.class).getMappedResults();
		
		return contacts;
	}
}

Make note of the new AggregationOperation up there. It's the third one in the list, called project.

You use that to specify the fields you want passed on to the next stage. In this case, you just want the three feilds you see listed on that line: firstName, lastName, and source.

All the other fields get left behind.

You also need to change the Aggregation line as well. Just add project to the list of operations in the newAggregation() method.

And that's it.

Now run it again as before and you should get this:

[ {
  "id" : "5fde1ac084dad94dbb7f82ae",
  "firstName" : "R2D2",
  "lastName" : "Droid",
  "source" : "EMAIL"
}, {
  "id" : "5fdd0cedaac5f75d62564ee7",
  "firstName" : "Jabba",
  "lastName" : "Hutt",
  "source" : "EMAIL"
}, {
  "id" : "5fd5f37bde602d3bacef69db",
  "firstName" : "Luke",
  "lastName" : "Skywalker",
  "source" : "EMAIL"
}, {
  "id" : "5fdd0f2aea599836ca3ddbf1",
  "firstName" : "Han",
  "lastName" : "Solo",
  "source" : "EMAIL"
} ]

Hmmmm... well that's almost right.

It looks like MongoTemplate included the id field by default. 

Why did it do that? Because that's what it does.

Now normally that would be a cool thing. In fact, I'd recommend it under almost every circumstance.

But you're working with Smithers here. And he didn't say anything about the id field.

Fortunately, you can override the default.

Update that third AggregationOperation line to look like this:

AggregationOperation project = Aggregation.project("firstName", "lastName", "source").andExclude("_id");

Pay attention to the andExclude() method at the end. That's how you exclude the id field.

Now save that file and rerun your Spring Boot app to check out the results. You should see this:

[ {
  "firstName" : "R2D2",
  "lastName" : "Droid",
  "source" : "EMAIL"
}, {
  "firstName" : "Jabba",
  "lastName" : "Hutt",
  "source" : "EMAIL"
}, {
  "firstName" : "Luke",
  "lastName" : "Skywalker",
  "source" : "EMAIL"
}, {
  "firstName" : "Han",
  "lastName" : "Solo",
  "source" : "EMAIL"
} ]

Bingo! That's what you're looking for.

Now Smithers will be happy.

Wrapping It Up

And now you know how to use project in a MongoDB aggregation within your Spring Boot app.

Why don't you take it to the next level? Add some different fields. Create another operation that just uses those projected fields.

In other words: mess around.

As always, feel free to check out the source code on GitHub.

Have fun!

Photo by Vlada Karpovich from Pexels