Skip to content

Conversation

@cbullinger
Copy link
Collaborator

@cbullinger cbullinger commented Oct 24, 2025

Setup of Java Spring Boot backend for the MongoDB Sample MFlix application

This PR implements the initial setup of a Java Spring Boot backend for the MongoDB Sample MFlix application (parity with the current Express.js/TypeScript backend). The implementation demonstrates direct MongoDB Java Driver usage for educational purposes while following Spring Boot best practices.


What to Review

Database Configuration (config/)

  • Connection pool settings appropriate for production
  • Timeout values (10s) reasonable
  • Error handling in DatabaseVerification robust
  • Text index creation matches Express backend
  • Logging messages helpful

Domain Models (model/)

  • All fields match TypeScript interfaces
  • Nested class structure clean and maintainable
  • Lombok annotations used appropriately
  • Field types correct (Integer vs int, Date vs Instant)

DTOs (model/dto/)

  • Validation annotations appropriate
  • Optional fields correctly marked
  • DTOs match Express backend structures
  • Field naming consistent

Response Models (model/response/)

  • Generic type usage in SuccessResponse<T> correct
  • @JsonInclude(NON_NULL) behavior desired
  • Timestamp format matches Express backend
  • Error response structure matches Express backend

Repository Layer (repository/)

  • BSON Document conversion logic correct
  • Null-safe handling throughout
  • All nested object conversions working
  • MongoDB operations using correct methods
  • No data loss in conversion

Service Layer (service/)

  • Query building logic correct (buildFilter, buildSort)
  • Text search using $text operator properly
  • Regex patterns for genre search correct
  • Pagination logic with limit capping (max 100)
  • Validation logic appropriate
  • Exception handling comprehensive

Controller Layer (controller/)

  • All endpoints match Express backend
  • HTTP status codes correct (200, 201, 404, 400, 409, 500)
  • Request parameter mapping correct
  • Response wrapping consistent
  • @Valid annotations on request bodies

Exception Handling (exception/)

  • All exception types handled
  • Error response format consistent
  • HTTP status codes appropriate
  • Logging comprehensive
  • MongoDB-specific errors handled (duplicate key, write errors)

What's NOT in This PR

Testing (Future Work)

  • Unit tests for service layer
  • Integration tests for controller layer
  • Repository layer tests
  • Test coverage reporting

Advanced API Endpoints (Future Work)

  • Aggregation
  • FTS
  • Vector Search
  • Geospatial

Documentation (Partial)

  • API documentation (Swagger/OpenAPI) - configuration exists but not fully documented
  • README updates for Java backend

Build Verification

$ cd server/java-spring && ./mvnw clean package -DskipTests
[INFO] BUILD SUCCESS
[INFO] Compiling 22 source files
[INFO] Building jar: sample-mflix-spring-1.0.0.jar

API Endpoints

All endpoints are fully implemented and match the current Express backend:

Method Endpoint Description Status
GET / API information Implemented
GET /api/movies List movies with filtering, sorting, pagination Implemented
GET /api/movies/{id} Get movie by ID Implemented
POST /api/movies Create single movie Implemented
POST /api/movies/batch Create multiple movies Implemented
PUT /api/movies/{id} Update movie Implemented
PATCH /api/movies Update multiple movies Implemented
DELETE /api/movies/{id} Delete movie Implemented
DELETE /api/movies Delete multiple movies Implemented
DELETE /api/movies/{id}/find-and-delete Find and delete movie Implemented

Query Parameters (GET /api/movies)

  • q - Full-text search
  • genre - Filter by genre (case-insensitive)
  • year - Filter by year (exact match)
  • minRating - Minimum IMDB rating
  • maxRating - Maximum IMDB rating
  • limit - Results per page (default: 20, max: 100)
  • skip - Number of results to skip
  • sortBy - Field to sort by (default: title)
  • sortOrder - Sort order: asc or desc (default: asc)

Design Decisions

Why Lombok?

  • Reduces boilerplate (getters, setters, constructors, toString, equals, hashCode)
  • Improves readability
  • Changes to fields automatically update generated methods
  • Industry standard in Spring Boot projects

Why Custom Repository (Not Spring Data)?

  • Demonstrates direct MongoDB Driver usage for educational purposes
  • Matches Express backend approach (Node.js driver)
  • Full control over BSON document structure and queries
  • Shows explicit MongoDB operations
  • Better for learning MongoDB concepts

Why Manual BSON Conversion?

  • Educational value - shows how MongoDB stores data
  • Full control over serialization/deserialization
  • No hidden magic - explicit conversion logic
  • Easier to debug and understand data flow

Why Nested Classes?

  • Encapsulation (only used within parent context)
  • Namespace clarity (Movie.Awards vs separate MovieAwards class)
  • Matches MongoDB nested document structure
  • Reduces file count and improves organization

Testing Recommendations

Manual Testing

  1. Start the application: ./mvnw spring-boot:run
  2. Test root endpoint: curl http://localhost:3001/
  3. Test GET all movies: curl http://localhost:3001/api/movies?limit=5
  4. Test GET by ID: curl http://localhost:3001/api/movies/{id}
  5. Test POST create: curl -X POST http://localhost:3001/api/movies -H "Content-Type: application/json" -d '{"title":"Test Movie"}'
  6. Test PUT update: curl -X PUT http://localhost:3001/api/movies/{id} -H "Content-Type: application/json" -d '{"year":2024}'
  7. Test DELETE: curl -X DELETE http://localhost:3001/api/movies/{id}

@cbullinger cbullinger marked this pull request as ready for review October 24, 2025 21:45

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One root .gitignore is considered to be a standard most repositories use. Is there a reason we want to have more than one?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this change does not seem to be related to java backend


## Technology Stack

- **Framework**: Spring Boot 3.2.0

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Spring Boot 3.2.x will be out of support in a couple of month - https://spring.io/projects/spring-boot#support
Should we consider taking 3.5.x?

## Technology Stack

- **Framework**: Spring Boot 3.2.0
- **Java Version**: 17

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Java 17 permissive licence support was ended in September 2024 - http://oracle.com/au/java/technologies/java-se-support-roadmap.html (see "End of Permissive Licensing of Java SE 17" paragraph).
I think we should consider Java 21 (I wouldn't jump to 25 yet, as it was just released).


- **Framework**: Spring Boot 3.2.0
- **Java Version**: 17
- **MongoDB Driver**: MongoDB Java Driver 5.1.4 (Sync)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seem to me a bit old. I usually go with the latest. 5.6.1?


This application provides a REST API for managing movie data from MongoDB's sample_mflix database. It demonstrates:

- Direct usage of the MongoDB Java Driver (not Spring Data MongoDB)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there any reason why we do not want to use Spring Data for MongoDB? It will allow to avoid boiler plate code in such a simple app. You can still use mongoDB driver for more complex scenarios in the same app.

InsertOneResult result = movieRepository.insertOne(movie);

if (!result.wasAcknowledged()) {
throw new RuntimeException("Movie insertion was not acknowledged by the database");

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is a good practice to use a custom exception instead of naked RuntimeException, especially when we already have some in the codebase.

}

return movieRepository.findById(result.getInsertedId().asObjectId().getValue())
.orElseThrow(() -> new RuntimeException("Failed to retrieve created movie"));

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should we use ResourceNotFoundException?

public InsertManyResult insertMany(List<Movie> movies) {
List<Document> documents = movies.stream()
.map(this::movieToDocument)
.collect(Collectors.toList());

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

.collect(Collectors.toList()) -> toList()

* Spring automatically manages the lifecycle and closes the client on shutdown.
*/
@Configuration
public class MongoConfig {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it will be extremely useful to have the setup for @transactional support as it is disabled by default in spring with mongoDB which I consider a huge spring data implementation gap.
It looks like the application code does not actually leverage transactions but any non-trivial app implementation will need this and it will be good if our sample has it as well.

I'm talking about this snippet of code from
https://github.com/johnlpage/MongoEnterpriseMicroserviceExamples/blob/main/memex/src/main/java/com/johnlpage/memex/config/MongoConfig.java

  @Bean
  public MongoTransactionManager transactionManager() {
    LOG.info("MongoDB Native Transactions Enabled");

    return new MongoTransactionManager(mongoDatabaseFactory);
  }

More details if needed - https://docs.spring.io/spring-data/mongodb/reference/mongodb/client-session-transactions.html#mongo.transactions.reactive-tx-manager

// MongoDB will automatically ignore this if the index already exists
moviesCollection.createIndex(
Indexes.compoundIndex(
Indexes.text("plot"),

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would create string constants in Movie.java class and use them here. Otherwise there is no clear way to document in Movie.java that there is coupling on the names of the fields of the class and there is no better way than full text search to find all such dependencies. If we introduce a constant it would allow IDE to show those dependencies in 'Find Usages' menu and hence make the dependency more transparent.
Of course this solution will look ugly if our code will have dependency on each and every field of the model (which is currently the case), however I'm going to suggest a solution to make it a bit cleaner as well.

@Repository
public class MovieRepositoryImpl implements MovieRepository {

private final MongoCollection<Document> moviesCollection;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We need to make this MongoCollection moviesCollection and add pojo codec.
Then we will be able to get rid of all the boiler plate pojo conversion methods: movieToDocument, documentToMovie, awardsToDocument, documentToAwards, imdbToDocument, documentToImdb, tomatoesToDocument, documentToTomatoes, viewerToDocument, documentToViewer, criticToDocument, documentToCritic.
Please find the patch attached:
remove_pojo_conversion_boilerplate.patch

* @param movie the Movie object
* @return the BSON Document
*/
private Document movieToDocument(Movie movie) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shared the patch in the comment above

return new Document(field, order);
}

private boolean isUpdateRequestEmpty(UpdateMovieRequest request) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be simplified to just 2 lines:

        Map<String, Object> requestMap = objectMapper.convertValue(request, Map.class);
        return requestMap.values().stream().allMatch(java.util.Objects::isNull);

Same with the next method. Please find the patch attached -
objectmapper.patch

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants