If you're here to learn how to create reactive repositories with Spring Boot and MongoDB, then you've come to the right place.

Alas, this might not be the first place you landed, though. The other tutorials out there don't quite give you the whole picture. 

That's why I wrote this article. So my pain can be your gain.

I'll walk you through building a little Spring Boot application that retrieves info about U.S. zip codes. And it will use Spring WebFlux for the reactive work.

Okay, let's get started.

The Data

First, get the data loaded into MongoDB.

Note: you don't have to have real zip code data. You're welcome to fake it just to get the reactive part up and running.

I have real data, though. It's stored in a MongoDB collection imaginatively named "zips." The database name is not so imaginatively named "ecosystem."

Here's what some of that data looks like:

{ "_id" : ObjectId("61145b4c3bc83b0380c728ba"), "zip" : "00638", "city" : "Ciales", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Ciales", "timezone" : "FALSE", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }
{ "_id" : ObjectId("61145b4c3bc83b0380c728bb"), "zip" : "00641", "city" : "Utuado", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Utuado", "timezone" : "FALSE", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }
{ "_id" : ObjectId("61145b4c3bc83b0380c728bc"), "zip" : "00646", "city" : "Dorado", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Dorado", "timezone" : "FALSE", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }
{ "_id" : ObjectId("61145b4c3bc83b0380c728bd"), "zip" : "00647", "city" : "Ensenada", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Guánica", "timezone" : "FALSE", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }
{ "_id" : ObjectId("61145b4c3bc83b0380c728be"), "zip" : "00650", "city" : "Florida", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Florida", "timezone" : "Florida|Utuado|Barceloneta|Arecibo|Ciales", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }
{ "_id" : ObjectId("61145b4c3bc83b0380c728bf"), "zip" : "00652", "city" : "Garrochales", "stateId" : "PR", "state" : "Puerto Rico", "county" : "Arecibo", "timezone" : "America/Puerto_Rico", "_class" : "com.careydevelopment.ecosystem.geo.model.ZipInfo" }

You're welcome to use that data in your testing.

And don't worry about the "_class" property. You don't have to match the exact package and class name to make it work. 

Anyhoo, as you can see, each document in the collection includes lots of cool info about a particular U.S. zip code, such as the city name, state ID, state name, and so on.

Now let's build a Spring Boot application that retrieves the data.

Laying the Foundation

You can create the skeleton Spring Boot project as you see fit. If you're a Spring Initializr type (I'm not) then feel free to use that. Alternatively, you can just create a basic Maven project in your favorite IDE.

Now put some meat on the bones.

Here's my entire POM file:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <groupId>us.careydevelopment.playground</groupId>
    <artifactId>zip-lookup</artifactId>
    <version>1.0.0-SNAPSHOT</version>
    
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.4</version>
    </parent>
  
    <dependencies>
        <dependency>
	        <groupId>org.springframework.boot</groupId>
	        <artifactId>spring-boot-starter-data-mongodb-reactive</artifactId>
	    </dependency> 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-webflux</artifactId>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
        </dependency>     
    </dependencies>
  
    <properties>
        <java.version>11</java.version>
    </properties>
  
    <build>
        <plugins>
            <plugin> 
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
        <finalName>${project.artifactId}</finalName>
    </build>
    
</project>

As you can see, it's pretty simple. It's using the Spring Boot Starter Parent as the parent and includes only three (3) dependencies.

The first dependency, spring-boot-starter-data-mongodb-reactive, enables you to return publishers instead of blocking objects when you make your back-end requests to the MongoDB database. You'll see how that works in a moment.

The second dependency, spring-boot-starter-webflux, enables your controller to return publishers.

The third dependency is for some convenience methods and classes. 

High on Resources

Next, let's take a look at the application.properties file in src/main/resources:

server.port=36120

mongo.zip.collection=zips

mongo.db=mongodb://joe:password@10.128.3.4.23:27017/ecosystem
mongo.db.name=ecosystem

The first line, server.port, specifies the port that the Spring Boot application will listen on. I've set it to 36120 so that it doesn't interfere with anything else.

The next property, mongo.zip.collection, identifies the name of the collection that you're querying against in the MongoDB database. As I said earlier, it's imaginatively named "zips."

The next property, mongo.db, does much of the heavy lifting when it comes to connecting to the database. I've put in a dummy name, password, and IP address in the example above. You'll need to substitute your own credentials and IP address.

Note the name of the database after the forward slash: ecosystem. You'll need the name of your database in there.

And yes, you need to specify the name of the database again in mongo.db.name. The Spring framework needs that database name all by itself.

Booting 'Er Up

Now let's create the class that starts Spring Boot. I'm using the base package us.careydevelopment.playground. You can use whatever you want, just make sure you're scanning the right packages for Spring-aware components.

Or it won't work.

Here's my starter:

package us.careydevelopment.playground;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class ZipLookup {

    public static void main(String[] args) {
        SpringApplication.run(ZipLookup.class,args);
    }
}

That's the class you'll run to start your Spring Boot application.

Configgin'

Now you've got to create the class that reads the MongoDB configuration values from the application.properties file. Here's what that looks like:

package us.careydevelopment.playground.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.config.AbstractReactiveMongoConfiguration;
import org.springframework.data.mongodb.repository.config.EnableReactiveMongoRepositories;

import com.mongodb.reactivestreams.client.MongoClient;
import com.mongodb.reactivestreams.client.MongoClients;


@Configuration
@EnableReactiveMongoRepositories(basePackages= {"us.careydevelopment.playground.repository"})
public class MongoConfig extends AbstractReactiveMongoConfiguration  {

    private static final Logger LOG = LoggerFactory.getLogger(MongoConfig.class);
    
    @Value("${mongo.db}")
    private String mongoConnection;

    @Value("${mongo.db.name}")
    private String mongoDatabaseName;
    
    @Override
    protected String getDatabaseName() {
        return mongoDatabaseName;
    }
    
    @Override
    @Bean
    public MongoClient reactiveMongoClient() {
    	MongoClient client = MongoClients.create(mongoConnection);
        return client;
    }
}

Note: I'm including packages and imports in my code snippets because they're important here.

For example, there's a MongoClient class in a different package. You don't want to import that one. Use the one I have above.

Now let me explain what's going on with this class.

First of all, it's a configuration class. So it's annotated with @Configuration.

Next, the @EnableReactiveMongoRepositories annotation lives up to its name. The part in parentheses specifies the package to scan when looking for repos.

Again: make sure whatever package you include in there is where you put your repositories!

The mongoConnection and mongoDatabaseName properties read from their respective values in the application.properties file.

And yeah, that getDatabaseName() method is required. Otherwise Spring won't find your database.

The reactiveMongoClient() method also lives up to its name. It instantiates a MongoDB client that works with publishers.

The Hot Model

Now you need to create a model to reflect the data in the MongoDB documents.

Unsurprisingly, that model will contain the same properties that you saw in the documents you looked at earlier.

Here's what that model looks like:

@Document(collection = "#{@environment.getProperty('mongo.zip.collection')}")
public class Zip {

    @Id
    private String id;

    private String zip;
    private String city;
    private String stateId;
    private String state;
    private String county;
    private String timezone;

//getters and setters omitted

Pay close attention to that @Document annotation up at the top. That specifies the name of the collection holding the documents that match the model.

That awkward notation you see next to @Document is a fancy way of reading the collection name from the application.properties file. In this case, it's getting the value of mongo.zip.collection, which you saw earlier.

The properties that you see in the class map to the properties you saw in the MongoDB documents. The id property is annotated with @Id to indicate that it's the document identifier.

Repo Man

Next, it's time to make a repository. Here's the code:

package us.careydevelopment.playground.repository;

import org.springframework.data.mongodb.repository.ReactiveMongoRepository;
import org.springframework.stereotype.Repository;

import reactor.core.publisher.Mono;
import us.careydevelopment.playground.model.Zip;

@Repository
public interface ZipRepository extends ReactiveMongoRepository<Zip, String> {

    public Mono<Zip> findByZip(String zip);
    
}

Remember, in Spring repositories are interfaces instead of concrete classes. That principle holds even when you're working with reactive technology.

The interface, as usual, is annotated with @Repository so that Spring will discover it and make it eligible for autowiring.

Also note that it's in the same package as the package specified next to the @EnableReactiveMongoRepositories annotation in the MongoConfig class you saw earlier. That's important.

Next, the interface extends ReactiveMongoRepository. That's understandable because you want to work with reactive tech here instead of using blocking requests.

Note that the interface includes two type parameters: Zip and String

Zip is the class name used for the model. You saw that in the last section.

The String type parameter identifies the type of ID. It's a String because MongoDB uses hexadecimal numbers.

The interface includes a single method: findByZip(). That method accepts a String parameter that's a 5-digit zip code.

But it doesn't return a Zip class. Instead it returns a Mono (or publisher). Howeer, the type parameter specified for the Mono is a Zip.

With reactive programming a Mono returns just a single result. That makes sense here because there's a 1:1 mapping between zip codes and documents in the MongoDB collection.

If you wanted to return multiple results, you'd go with Flux instead of Mono.

Incidentally, both Flux and Mono are publishers. You've read that word a couple of times in this article already.

In reactive programming, a publisher transmits data to a subscriber. But it does so only:

  1. Once it has a subscriber
  2. When the data is ready to be transmitted

When both of those conditions are satisfied, the publisher sends data to the subscriber. 

You can think of the subscriber as an object that basically sits around twiddling its thumbs waiting for the publisher to send it something. Once it receives some data from the publisher, it reacts to that data by doing something.

Hence the name: reactive programming.

Controller Freak

Now let's expose this Spring Boot application to the outside world via a controller. Make it look like this:

package us.careydevelopment.playground.controller;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import reactor.core.publisher.Mono;
import us.careydevelopment.playground.model.Zip;
import us.careydevelopment.playground.repository.ZipRepository;

@RestController
@RequestMapping("/zips")
public class ZipController {
    
    @Autowired
    private ZipRepository zipRepo;
    
    @GetMapping(value = "/{zipCode}")
    public Mono<Zip> findZipInfo(@PathVariable String zipCode) {
        Mono<Zip> zip = zipRepo.findByZip(zipCode);
        return zip;
    }
}

The controller is annotated with @RestController because it's a... REST controller. 

The root path of all requests in this class is /zips so that's why the @RequestMapping annotation exists.

Next, the class autowires the repo you just saw in the previous section.

The only method in the class, findZipInfo(),  gets invoked when somebody hits the /zips/{zipCode} endpoint. For example: /zips/90210 would trigger this method.

The method itself uses the zip code from the URL path to look up its related info via the repository.

The repository returns a Mono<Zip> object which gets sent back to the client.

And that's it for the coding.

But Will It Fly?

Fire up this Spring Boot application by running the ZipLookup class you created many sections ago.

Next, head over to Postman and hit a URL like http://localhost:36120/zips/90210.

I'm using 90210 as the zip code here but you can use whatever zip code works with your data set.

Anyhoo, here's what I get when I run that:

 

And that's a winner beause that's exactly what I should get.

Wrapping It Up

Awesome. Now you know how to create reactive repositories with Spring Boot and MongoDB.

Undoubtedly, you'll have to adapt the code you see above to suit your own business requirements. But I think you're off to a great start.

Have fun!

Photo by Anna Alexes from Pexels