Stage 13: MongoDB connectivity

Stage 13: connect the Stage 12 application to MongoDB Atlas

Goal: keep our Stage 12 settings and bean configuration, but load the product table and details from MongoDB. Stage 11 introduced database CRUD; this stage focuses on configuring and verifying the connection in the Stage 12 application.

Download spring-products-mongodb-connectivity.zip · Complete file-by-file guide

Connection settings → Boot configures MongoDB helper beans → ProductRepository → MongoDB

1. Start from Stage 12

Extract this project into a new folder. Use JDK 21+ and Maven 3.9+. Open a terminal in the folder containing pom.xml. The first build needs internet access to download dependencies. This project defaults to port 8091. It can run alongside Stage 12 on 8090. It includes every source file; do not copy fragments over the older project.

Check your tools — one command at a time
java -version
javac -version
mvn -version

2. Understand the one storage change

Stage 12 reads the product text file into memory. In this stage, ProductRepository extends MongoRepository<Product, Integer> and FileProductRepository is removed. ProductService keeps its existing stock policy and still receives CatalogSettings. CatalogConfig still supplies the JsonMapper and settings beans.

The text file is now only seed input: ProductDataSeeder copies its 30 products into an empty products collection at startup. If that collection already contains products, it is left unchanged. Subsequent requests read MongoDB. Deleting every product and restarting will seed again. Use the separate learning database spring_stage13_products.

3. Add the MongoDB dependency

The supplied pom.xml already contains this dependency. It brings the Spring Data MongoDB and driver libraries into the application.

pom.xml — dependency excerpt
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-mongodb</artifactId>
</dependency>

4. Prepare your Atlas connection

  1. Open your Atlas project and create or choose an available cluster. Wait until it is ready.
  2. Create a database user with read/write access to spring_stage13_products. This database user is separate from the account you use to sign into the Atlas website.
  3. In Network Access, add your current public IP address to the IP access list. If your network changes, update that entry.
  4. Choose the cluster’s Connect option, then Drivers and Java. Copy its connection string. Use the hostname Atlas gives you.
  5. Replace the username and password placeholders. Percent-encode reserved characters in the credential values; for example @ becomes %40 in a password. Do not encode the entire URI or double-encode an already encoded password.

Example shape only: mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority. YOUR_USER, YOUR_ENCODED_PASSWORD and YOUR_CLUSTER are placeholders, not runnable credentials. See the complete Atlas setup guide for the installation and setup details.

5. Read the connection settings

src/main/resources/application.properties — complete file
# HTTP port: change this if another application uses 8091.
server.port=8091

# Set MONGODB_URI in your terminal to use your own Atlas cluster.
# Without that variable, use a running local MongoDB server on 27017.
spring.mongodb.uri=${MONGODB_URI:mongodb://localhost:27017}
spring.mongodb.database=${MONGODB_DATABASE:spring_stage13_products}

server.port is our web-server port. spring.mongodb.uri is the database connection address. spring.mongodb.database selects the database used by this application. The expression with MONGODB_URI reads an environment variable; the part after the colon is the fallback when the variable is absent. This tutorial uses Boot 4.1.1 property names.

6. Set the variables and run

Choose the commands for your terminal. Replace the URI placeholders with your own values. Set the variables in the same terminal where you start Maven. These commands affect this terminal session; repeat them in a new terminal. A .env file is not automatically loaded by these commands. Avoid committing or sharing your real URI.

On Windows, use PowerShell for the URI commands. If your terminal is Command Prompt, first enter the following command to switch; this also avoids Command Prompt interpreting percent signs in encoded passwords.

Windows Command Prompt — switch to PowerShell
powershell
Windows PowerShell
$env:MONGODB_URI='mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority'
$env:MONGODB_DATABASE='spring_stage13_products'
mvn spring-boot:run
macOS / Linux terminal
export MONGODB_URI='mongodb+srv://YOUR_USER:YOUR_ENCODED_PASSWORD@YOUR_CLUSTER.mongodb.net/?retryWrites=true&w=majority'
export MONGODB_DATABASE='spring_stage13_products'
mvn spring-boot:run

Local alternative: with a running local MongoDB server, use mongodb://127.0.0.1:27017 as MONGODB_URI instead. Keep the same database name. Running the app does not install or start MongoDB. The MongoDB port and the application’s HTTP port are separate.

7. Verify the connection in the browser

Keep the terminal open and visit http://localhost:8091/database-info. With a fresh database, expect:

Expected browser output
Connected to MongoDB
Database: spring_stage13_products
Ping result: 1.0
Product count: 30

The endpoint executes a ping against the configured database server, then counts products through the repository. It displays no URI, username or password. MongoTemplate is a Spring Data helper supplied by Boot; we inject it through the constructor just like our other dependencies. It is used for this diagnostic page, while product list and details reads stay in ProductRepository. This page is a local learning diagnostic; a public application should protect or remove it.

8. Verify the product pages

  1. Open http://localhost:8091/products: expect 30 product rows on the first run, currency INR and minimum stock 20.
  2. Click More details for product 1: the second Thymeleaf template shows its fields.
  3. Click Products meeting the stock policy: expect 24 rows for the supplied seed data. The service still applies minimumStock from CatalogSettings.
  4. Open /bean-info: productRepository replaces fileProductRepository in the bean listing. The registered beans and singleton checks should be true.
  5. In Atlas Browse Collections, choose spring_stage13_products → products. Find numeric _id 1, change its name to Stage 13 connection verified, and save. Refresh /products/1: the new name appears.
  6. Stop the app with Ctrl+C and restart it with the same database settings. The changed name remains. The existing collection is not overwritten by the file. Restore the name in Atlas when finished.

If you use a local server, perform the same document edit in MongoDB Compass connected to that server. An unavailable product ID such as /products/99999 returns 404. This stage intentionally keeps a read-only product UI; Stage 11 already contains create, update and delete forms.

9. Who creates which objects?

ObjectHow it becomes available
JsonMapper and CatalogSettingsOur @Bean factory methods in CatalogConfig.
Controller, service, file reader and seederOur component annotations and constructor injection.
MongoDB client and MongoTemplateBoot’s automatic configuration uses the MongoDB dependency and connection settings.
ProductRepository implementationSpring Data creates the supported repository implementation.

We do not need another manual @Bean method for the connection in this example. Boot already supplies that setup. Having a client bean does not prove the remote server is reachable; the successful ping and repository read verify actual communication.

10. Troubleshooting

SymptomWhat to check
Authentication failedDatabase username/password, encoded credential characters and permissions for spring_stage13_products.
Connection timeoutCluster readiness, current IP access-list entry, network/firewall access and the copied hostname. For a local URI, confirm the local server is running.
SRV / DNS lookup failureUse the exact Atlas hostname. Check whether your network’s DNS can resolve it. Do not replace it with an invented hostname.
App still connects to localhostMONGODB_URI was not set in the terminal or IDE that launches Java. Set it there and restart.
Wrong count or old products/database-info shows the selected database. An existing collection is intentionally not reseeded; check the database name and existing documents.
Port 8091 already in useChange server.port to a free port, restart and use the changed URL.
Startup fails before the page opensThe seeder needs a working database connection. Read the first connection error in the terminal and fix the URI, access or server status, then restart.

Complete project files

Each block below is the complete file at the indicated path.

.gitignore
.gitignore
/target/
pom.xml
pom.xml
<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>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>4.1.1</version>
    <relativePath/>
  </parent>
  <groupId>com.edupoly</groupId>
  <artifactId>spring-products-mongodb-connectivity</artifactId>
  <version>1.0.0</version>
  <properties>
    <java.version>21</java.version>
  </properties>
  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-webmvc</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-thymeleaf</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-data-mongodb</artifactId>
    </dependency>
  </dependencies>
  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
    </plugins>
  </build>
</project>
src/main/java/com/edupoly/products/BeanInfoController.java
src/main/java/com/edupoly/products/BeanInfoController.java
package com.edupoly.products;

import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class BeanInfoController {
    private final ConfigurableApplicationContext context;

    public BeanInfoController(ConfigurableApplicationContext context) {
        this.context = context;
    }

    @GetMapping(value = "/bean-info", produces = "text/plain")
    @ResponseBody
    public String beanInfo() {
        StringBuilder text = new StringBuilder();
        for (String name : new String[] {"jsonMapper", "catalogSettings", "productFileReader",
                "productRepository", "productService", "productController"}) {
            text.append(name)
                .append(" | definition registered: ").append(context.containsBeanDefinition(name))
                .append(" | singleton: ").append(context.isSingleton(name)).append("\n");
        }
        text.append("Same settings bean on two lookups: ")
            .append(context.getBean(CatalogSettings.class) == context.getBean(CatalogSettings.class));
        return text.toString();
    }
}
src/main/java/com/edupoly/products/CatalogConfig.java
src/main/java/com/edupoly/products/CatalogConfig.java
package com.edupoly.products;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import tools.jackson.databind.json.JsonMapper;

@Configuration(proxyBeanMethods = false)
public class CatalogConfig {
    @Bean
    public JsonMapper jsonMapper() {
        return JsonMapper.builder().build();
    }

    @Bean
    public CatalogSettings catalogSettings() {
        return new CatalogSettings("INR", 20);
    }
}
src/main/java/com/edupoly/products/CatalogSettings.java
src/main/java/com/edupoly/products/CatalogSettings.java
package com.edupoly.products;

public class CatalogSettings {
    private final String currency;
    private final int minimumStock;

    public CatalogSettings(String currency, int minimumStock) {
        this.currency = currency;
        this.minimumStock = minimumStock;
    }

    public String getCurrency() { return currency; }
    public int getMinimumStock() { return minimumStock; }
}
src/main/java/com/edupoly/products/DatabaseInfoController.java
src/main/java/com/edupoly/products/DatabaseInfoController.java
package com.edupoly.products;

import org.bson.Document;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
public class DatabaseInfoController {
    private final MongoTemplate mongoTemplate;
    private final ProductRepository repository;

    public DatabaseInfoController(MongoTemplate mongoTemplate,
                                  ProductRepository repository) {
        this.mongoTemplate = mongoTemplate;
        this.repository = repository;
    }

    @GetMapping(value = "/database-info", produces = "text/plain")
    @ResponseBody
    public String databaseInfo() {
        var database = mongoTemplate.getDb();
        Document result = database.runCommand(new Document("ping", 1));
        return "Connected to MongoDB\n"
                + "Database: " + database.getName() + "\n"
                + "Ping result: " + result.get("ok") + "\n"
                + "Product count: " + repository.count();
    }
}
src/main/java/com/edupoly/products/Product.java
src/main/java/com/edupoly/products/Product.java
package com.edupoly.products;

import java.math.BigDecimal;
import org.springframework.data.annotation.Id;
import org.springframework.data.mongodb.core.mapping.Document;

@Document("products")
public class Product {
    @Id
    private int id;
    private String name;
    private String category;
    private BigDecimal price;
    private String brand;
    private int stock;
    private String description;
    private int warrantyMonths;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getCategory() {
        return category;
    }

    public void setCategory(String category) {
        this.category = category;
    }

    public BigDecimal getPrice() {
        return price;
    }

    public void setPrice(BigDecimal price) {
        this.price = price;
    }

    public String getBrand() {
        return brand;
    }

    public void setBrand(String brand) {
        this.brand = brand;
    }

    public int getStock() {
        return stock;
    }

    public void setStock(int stock) {
        this.stock = stock;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    public int getWarrantyMonths() {
        return warrantyMonths;
    }

    public void setWarrantyMonths(int warrantyMonths) {
        this.warrantyMonths = warrantyMonths;
    }
}
src/main/java/com/edupoly/products/ProductApplication.java
src/main/java/com/edupoly/products/ProductApplication.java
package com.edupoly.products;

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

@SpringBootApplication
public class ProductApplication {
    public static void main(String[] args) {
        SpringApplication.run(ProductApplication.class, args);
    }
}
src/main/java/com/edupoly/products/ProductController.java
src/main/java/com/edupoly/products/ProductController.java
package com.edupoly.products;

import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.server.ResponseStatusException;

@Controller
public class ProductController {
    private final ProductService service;
    private final CatalogSettings settings;

    public ProductController(ProductService service, CatalogSettings settings) {
        this.service = service;
        this.settings = settings;
    }

    @GetMapping({"/", "/products"})
    public String listProducts(
            @RequestParam(name = "inStockOnly", defaultValue = "false") boolean inStockOnly,
            Model model) {
        model.addAttribute("products", service.findAll(inStockOnly));
        model.addAttribute("settings", settings);
        return "products";
    }

    @GetMapping("/products/{id}")
    public String productDetails(@PathVariable("id") int id, Model model) {
        Product product = service.findById(id).orElseThrow(() ->
                new ResponseStatusException(HttpStatus.NOT_FOUND, "Product not found"));
        model.addAttribute("product", product);
        return "product-details";
    }
}
src/main/java/com/edupoly/products/ProductDataSeeder.java
src/main/java/com/edupoly/products/ProductDataSeeder.java
package com.edupoly.products;

import java.util.Arrays;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;

@Component
public class ProductDataSeeder implements CommandLineRunner {
    private final ProductRepository repository;
    private final ProductFileReader reader;

    public ProductDataSeeder(ProductRepository repository, ProductFileReader reader) {
        this.repository = repository;
        this.reader = reader;
    }

    @Override
    public void run(String... args) {
        if (repository.count() == 0) {
            repository.saveAll(Arrays.asList(reader.getProducts()));
            System.out.println("Seeded 30 products into the empty demo database");
        }
    }
}
src/main/java/com/edupoly/products/ProductFileReader.java
src/main/java/com/edupoly/products/ProductFileReader.java
package com.edupoly.products;

import java.io.IOException;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;
import tools.jackson.databind.json.JsonMapper;

@Component
public class ProductFileReader {
    private final Product[] products;

    public ProductFileReader(JsonMapper mapper) throws IOException {
        var resource = new ClassPathResource("data/products.txt");
        try (var input = resource.getInputStream()) {
            products = mapper.readValue(input, Product[].class);
        }
        System.out.println("Created ProductFileReader");
    }

    public Product[] getProducts() {
        return products;
    }
}
src/main/java/com/edupoly/products/ProductRepository.java
src/main/java/com/edupoly/products/ProductRepository.java
package com.edupoly.products;

import org.springframework.data.mongodb.repository.MongoRepository;

public interface ProductRepository extends MongoRepository<Product, Integer> {
}
src/main/java/com/edupoly/products/ProductService.java
src/main/java/com/edupoly/products/ProductService.java
package com.edupoly.products;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.stereotype.Service;

@Service
public class ProductService {
    private final ProductRepository repository;
    private final CatalogSettings settings;

    public ProductService(ProductRepository repository, CatalogSettings settings) {
        this.repository = repository;
        this.settings = settings;
    }

    public List<Product> findAll(boolean inStockOnly) {
        List<Product> selected = new ArrayList<>();
        for (Product product : repository.findAll()) {
            if (!inStockOnly || product.getStock() >= settings.getMinimumStock()) {
                selected.add(product);
            }
        }
        return selected;
    }

    public Optional<Product> findById(int id) {
        return repository.findById(id);
    }
}
src/main/resources/application.properties
src/main/resources/application.properties
# HTTP port: change this if another application uses 8091.
server.port=8091

# Set MONGODB_URI in your terminal to use your own Atlas cluster.
# Without that variable, use a running local MongoDB server on 27017.
spring.mongodb.uri=${MONGODB_URI:mongodb://localhost:27017}
spring.mongodb.database=${MONGODB_DATABASE:spring_stage13_products}
src/main/resources/data/products.txt
src/main/resources/data/products.txt
[
  {
    "id": 1,
    "name": "Wireless Mouse",
    "category": "Electronics",
    "price": 699,
    "brand": "EduTech",
    "stock": 12,
    "description": "Wireless Mouse for everyday learning, study and work. Product 1 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 2,
    "name": "Mechanical Keyboard",
    "category": "Electronics",
    "price": 3499,
    "brand": "EduTech",
    "stock": 19,
    "description": "Mechanical Keyboard for everyday learning, study and work. Product 2 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 3,
    "name": "USB-C Hub",
    "category": "Electronics",
    "price": 1599,
    "brand": "EduTech",
    "stock": 26,
    "description": "USB-C Hub for everyday learning, study and work. Product 3 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 4,
    "name": "Laptop Stand",
    "category": "Electronics",
    "price": 1299,
    "brand": "EduTech",
    "stock": 33,
    "description": "Laptop Stand for everyday learning, study and work. Product 4 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 5,
    "name": "Webcam",
    "category": "Electronics",
    "price": 2199,
    "brand": "EduTech",
    "stock": 0,
    "description": "Webcam for everyday learning, study and work. Product 5 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 6,
    "name": "Bluetooth Speaker",
    "category": "Electronics",
    "price": 2499,
    "brand": "EduTech",
    "stock": 47,
    "description": "Bluetooth Speaker for everyday learning, study and work. Product 6 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 7,
    "name": "Noise-Cancelling Headphones",
    "category": "Electronics",
    "price": 4999,
    "brand": "EduTech",
    "stock": 54,
    "description": "Noise-Cancelling Headphones for everyday learning, study and work. Product 7 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 8,
    "name": "Portable SSD",
    "category": "Electronics",
    "price": 6499,
    "brand": "EduTech",
    "stock": 61,
    "description": "Portable SSD for everyday learning, study and work. Product 8 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 9,
    "name": "USB Flash Drive",
    "category": "Electronics",
    "price": 599,
    "brand": "EduTech",
    "stock": 68,
    "description": "USB Flash Drive for everyday learning, study and work. Product 9 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 10,
    "name": "Monitor",
    "category": "Electronics",
    "price": 12999,
    "brand": "EduTech",
    "stock": 15,
    "description": "Monitor for everyday learning, study and work. Product 10 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 11,
    "name": "Desk Lamp",
    "category": "Electronics",
    "price": 899,
    "brand": "EduTech",
    "stock": 22,
    "description": "Desk Lamp for everyday learning, study and work. Product 11 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 12,
    "name": "Power Bank",
    "category": "Electronics",
    "price": 1799,
    "brand": "EduTech",
    "stock": 29,
    "description": "Power Bank for everyday learning, study and work. Product 12 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 13,
    "name": "Smartphone Stand",
    "category": "Electronics",
    "price": 399,
    "brand": "EduTech",
    "stock": 36,
    "description": "Smartphone Stand for everyday learning, study and work. Product 13 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 14,
    "name": "HDMI Cable",
    "category": "Electronics",
    "price": 499,
    "brand": "EduTech",
    "stock": 43,
    "description": "HDMI Cable for everyday learning, study and work. Product 14 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 15,
    "name": "Ethernet Adapter",
    "category": "Electronics",
    "price": 999,
    "brand": "EduTech",
    "stock": 50,
    "description": "Ethernet Adapter for everyday learning, study and work. Product 15 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 16,
    "name": "Graphics Tablet",
    "category": "Electronics",
    "price": 3999,
    "brand": "EduTech",
    "stock": 57,
    "description": "Graphics Tablet for everyday learning, study and work. Product 16 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 17,
    "name": "Microphone",
    "category": "Electronics",
    "price": 2999,
    "brand": "EduTech",
    "stock": 64,
    "description": "Microphone for everyday learning, study and work. Product 17 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 18,
    "name": "Laptop Backpack",
    "category": "Electronics",
    "price": 1499,
    "brand": "EduTech",
    "stock": 71,
    "description": "Laptop Backpack for everyday learning, study and work. Product 18 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 19,
    "name": "Screen Cleaner",
    "category": "Electronics",
    "price": 249,
    "brand": "EduTech",
    "stock": 18,
    "description": "Screen Cleaner for everyday learning, study and work. Product 19 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 20,
    "name": "Mouse Pad",
    "category": "Electronics",
    "price": 299,
    "brand": "EduTech",
    "stock": 25,
    "description": "Mouse Pad for everyday learning, study and work. Product 20 includes a simple user guide and protective packaging.",
    "warrantyMonths": 12
  },
  {
    "id": 21,
    "name": "Notebook",
    "category": "Study Essentials",
    "price": 149,
    "brand": "StudyWorks",
    "stock": 32,
    "description": "Notebook for everyday learning, study and work. Product 21 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 22,
    "name": "Ballpoint Pen Set",
    "category": "Study Essentials",
    "price": 199,
    "brand": "StudyWorks",
    "stock": 39,
    "description": "Ballpoint Pen Set for everyday learning, study and work. Product 22 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 23,
    "name": "Water Bottle",
    "category": "Study Essentials",
    "price": 599,
    "brand": "StudyWorks",
    "stock": 46,
    "description": "Water Bottle for everyday learning, study and work. Product 23 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 24,
    "name": "Desk Organizer",
    "category": "Study Essentials",
    "price": 449,
    "brand": "StudyWorks",
    "stock": 53,
    "description": "Desk Organizer for everyday learning, study and work. Product 24 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 25,
    "name": "Reading Light",
    "category": "Study Essentials",
    "price": 699,
    "brand": "StudyWorks",
    "stock": 60,
    "description": "Reading Light for everyday learning, study and work. Product 25 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 26,
    "name": "Calculator",
    "category": "Study Essentials",
    "price": 499,
    "brand": "StudyWorks",
    "stock": 67,
    "description": "Calculator for everyday learning, study and work. Product 26 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 27,
    "name": "Whiteboard",
    "category": "Study Essentials",
    "price": 1299,
    "brand": "StudyWorks",
    "stock": 14,
    "description": "Whiteboard for everyday learning, study and work. Product 27 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 28,
    "name": "Marker Set",
    "category": "Study Essentials",
    "price": 299,
    "brand": "StudyWorks",
    "stock": 21,
    "description": "Marker Set for everyday learning, study and work. Product 28 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 29,
    "name": "Book Stand",
    "category": "Study Essentials",
    "price": 799,
    "brand": "StudyWorks",
    "stock": 28,
    "description": "Book Stand for everyday learning, study and work. Product 29 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  },
  {
    "id": 30,
    "name": "Travel Mug",
    "category": "Study Essentials",
    "price": 649,
    "brand": "StudyWorks",
    "stock": 35,
    "description": "Travel Mug for everyday learning, study and work. Product 30 includes a simple user guide and protective packaging.",
    "warrantyMonths": 6
  }
]
src/main/resources/static/style.css
src/main/resources/static/style.css
body{margin:0;padding:28px 16px;background:#f5f7fb;color:#17253d;font:16px/1.6 system-ui,sans-serif}main{max-width:1100px;margin:20px auto;padding:28px;background:#fff;border:1px solid #dbe2ec;border-radius:12px}h1{margin-top:0}a{color:#2457a6;text-underline-offset:3px}.table-scroll{overflow-x:auto}table{width:100%;border-collapse:collapse}th,td{text-align:left;padding:12px;border-bottom:1px solid #dbe2ec}th{background:#edf3fc}tbody tr:hover{background:#f8faff}.detail{max-width:760px}dl{display:grid;grid-template-columns:160px minmax(0,1fr);gap:12px 20px;margin:24px 0}dt{font-weight:650}dd{margin:0;overflow-wrap:anywhere}@media(max-width:500px){main{padding:16px}dl{grid-template-columns:1fr;gap:4px}dd{margin-bottom:12px}}

.catalog-nav{margin:20px 0}form{margin:18px 0}button{padding:9px 14px;cursor:pointer}input{padding:8px;max-width:140px}form button{margin-left:8px}
src/main/resources/templates/product-details.html
src/main/resources/templates/product-details.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title th:text="${product.name}">Product details</title>
    <link rel="stylesheet" th:href="@{/style.css}">
</head>
<body>
<main class="detail">
    <h1>Product details</h1>
    <h2 th:text="${product.name}">Wireless Mouse</h2>
    <dl>
        <dt>ID</dt><dd th:text="${product.id}">1</dd>
        <dt>Name</dt><dd th:text="${product.name}">Wireless Mouse</dd>
        <dt>Category</dt><dd th:text="${product.category}">Electronics</dd>
        <dt>Price (INR)</dt><dd th:text="${product.price}">699</dd>
        <dt>Brand</dt><dd th:text="${product.brand}">EduTech</dd>
        <dt>Stock</dt><dd th:text="${product.stock}">12</dd>
        <dt>Description</dt><dd th:text="${product.description}">Product description</dd>
        <dt>Warranty (months)</dt><dd th:text="${product.warrantyMonths}">12</dd>
    </dl>
    <a th:href="@{/products}">Back to products</a>
</main>
</body>
</html>
src/main/resources/templates/products.html
src/main/resources/templates/products.html
<!doctype html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Products</title>
    <link rel="stylesheet" th:href="@{/style.css}">
</head>
<body>
<main>
    <h1>Products</h1>
    <p>Stage 13: Products loaded from MongoDB. Select More details for a product.</p>
    <p>Currency: <strong th:text="${settings.currency}">INR</strong>. Minimum stock for the policy: <strong th:text="${settings.minimumStock}">20</strong>.</p>
    <p><a th:href="@{/database-info}">Check the MongoDB connection</a></p>
    <p><a th:href="@{/bean-info}">Inspect the registered beans</a></p>
    <nav class="catalog-nav"><a th:href="@{/products}">All products</a> · <a th:href="@{/products(inStockOnly=true)}">Products meeting the stock policy</a></nav>
    <div class="table-scroll">
    <table>
        <thead>
        <tr><th>ID</th><th>Name</th><th>Category</th><th>Price (INR)</th><th>Action</th></tr>
        </thead>
        <tbody>
        <tr th:each="product : ${products}">
            <td th:text="${product.id}">1</td>
            <td th:text="${product.name}">Wireless Mouse</td>
            <td th:text="${product.category}">Electronics</td>
            <td th:text="${product.price}">699</td>
            <td><a th:href="@{/products/{id}(id=${product.id})}">More details</a></td>
        </tr>
        </tbody>
    </table>
    </div>
</main>
</body>
</html>

Practice

Explain which settings choose the web port, database server and database name. Point to one bean supplied by your @Bean method and one supplied by Boot. Verify that a database edit remains after restarting the Java app.

References: Spring Boot MongoDB support, Atlas driver connections.

After Stage 13: the Spring words to recognize

TermOur concrete exampleIts place in Spring
Connection URIMONGODB_URI identifies your MongoDB server/cluster and connection credentials.Spring Boot reads spring.mongodb.uri to configure database connectivity.
Auto-configuration — observedWe add the MongoDB starter and connection settings.Boot supplies MongoDB infrastructure beans without us writing a client factory.
MongoTemplateThe helper injected into DatabaseInfoController.A Spring Data helper used here only to run a connection check. Product reads still use our repository.
Repository — reusedProductRepository extends MongoRepository.Spring Data supplies the implementation that reads product documents.

Say it simply: I provide the database address and credentials; Boot configures the connection infrastructure; Spring Data supplies the repository; my service uses that repository.

Next: Stage 14 — Todo list application