I’m attempting to build my own application and backend API to further my understanding of the Mongo Java driver after completing the M220J course, but am running into a blocker.
Context
I’m creating a bookmark managing app and rebuilding the backend in Java using the Mongo driver and Spring Boot. The error appears when using .iterator() or .into() after .find() to query my Atlas database. The error DOES NOT APPEAR when using .first() after .find().
My pom.xml includes version 4.2.2 of mongodb-driver-sync. I’m using Java 15.0.1.
The folders collection uses a CodecRegistry, much like how the Users or Sessions collection is created in the mflix app in the MongoDB University course M220J, meaning a query on foldersCollection should return documents of type Folder. I’ve verified that the data has the same fields in the database and Folder.java class.
Code
In the code, the user_id field is currently stored as a String, not ObjectId, in the Folder documents (acts like a foreign key) This does not cause the error. The .into() trick was taken from this article talking about Mongo and Java Pojos.
This is my current implementation, but it does not work.
// Returns an array of folders, given a user id
public List<Folder> getFolders(String id) {
if (id == null || id.isEmpty()) return null;
List<Folder> folders = foldersCollection.find(new Document("user_id", id)).into(new ArrayList<>());
return folders;
}
This code also does not work, which uses .iterator() after .find(), and excludes the id to get all folders. It also does not work if I include the id in the query.
public List<Folder> getAllFolders(){
List<Folder> folders = new ArrayList<>();
foldersCollection.find().iterator().forEachRemaining(folders::add);
return folders;
}
I have verified that this code works with a test in Java and in Postman:
// Gets a folder from db given folder _id
public Folder getFolder(String id) {
if (id == null || id.isEmpty()) return null;
return foldersCollection.find(new Document("_id", new ObjectId(id))).first();
}
NoSuchMethodError:
It looks like it’s pointing towards the .find() method when I use .into(), but I’ve confirmed that .find() works when chained with .first(), so I don’t think .find() is the issue. The same error appears when using .iterator().
java.lang.NoSuchMethodError: 'com.mongodb.internal.operation.ExplainableReadOperation com.mongodb.internal.operation.SyncOperations.find(org.bson.conversions.Bson, java.lang.Class, com.mongodb.internal.client.model.FindOptions)'
at com.mongodb.client.internal.FindIterableImpl.asReadOperation(FindIterableImpl.java:236)
at com.mongodb.client.internal.FindIterableImpl.asReadOperation(FindIterableImpl.java:40)
at com.mongodb.client.internal.MongoIterableImpl.execute(MongoIterableImpl.java:135)
at com.mongodb.client.internal.MongoIterableImpl.iterator(MongoIterableImpl.java:92)
at com.mongodb.client.internal.MongoIterableImpl.forEach(MongoIterableImpl.java:121)
at com.mongodb.client.internal.MongoIterableImpl.into(MongoIterableImpl.java:130)
at com.bookmarkd.api.daos.FolderDao.getFolders(FolderDao.java:46)
at com.bookmarkd.FolderTest.GetFolders(FolderTest.java:50) <31 internal lines>
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) <9 internal lines>
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511) <23 internal lines>
Lastly, here’s my Folder class and an image a of a few documents in the Atlas database:
public class Folder {
@BsonId
@JsonIgnore
private ObjectId oid;
@JsonProperty("_id")
@BsonIgnore
private String id;
@JsonProperty("user_id")
private String userId;
private String name;
private String icon;
private boolean shareable;
// Constructor, getters, and setters...
}
An image of a few Folder documents in the foldersCollection
Any help to debug this issue is greatly appreciated!
Additional code / explanation of issue:
(Though it makes the post really long, I'd thought it'd be easier to see all the code here rather than view it from a Github repo.)
Perhaps I'm over complicating things, but I'm attempting to learn and use Spring Boot MVC. There's a FolderController that handles the Http requests, which calls the FolderService, which calls the FolderDao that enacts CRUD methods on the Atlas cluster. All these files are stored in their respective packages: controllers, services, daos, models, and config.
The FolderDao extends an AbstractDao class that sets the client and database with values received from an @Autowired FolderDao constructor.
Lastly, the MongoDBConfiguration class configures the settings and connects to the Atlas cluster. I have configured the PojoRegistry here, as shown in this example. I believe this class actually is run immediately after the @SpringBootApplication in Application.java starts up. This was how it was done in the mflix app. You can see a completed mflix app here for reference.
Maybe the error is coming from Atlas? My M0 free-tier cluster is version 4.4.4, but I had no problems completing the M220J course where this logic was implemented with mflix.
Here's the full code for each class in the order I described it above:
FolderController
package com.bookmarkd.api.controllers;
import com.bookmarkd.api.models.Folder;
import com.bookmarkd.api.services.FolderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@CrossOrigin(origins = "*")
@RequestMapping("/api/folders")
@RestController
public class FolderController {
@Autowired
FolderService folderService;
public FolderController() { super(); }
public void getFolders() {}
// Get all folders, given the user id
@GetMapping("")
public ResponseEntity getFoldersByUserId(@RequestParam(value = "userid", required = false) String folderId) {
List<Folder> folders = folderService.getFolders(folderId);
if (folders == null) return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.status(HttpStatus.OK).body(folders);
}
// Get a single public folder, given the folder id
@GetMapping("/{id}")
public ResponseEntity getFolder(@PathVariable(value = "id") String folderId) {
Folder folder = folderService.getFolder(folderId);
if (folder == null) return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
return ResponseEntity.status(HttpStatus.OK).body(folder);
}
public void addFolder() {}
public void updateFolder() {}
public void deleteFolder() {}
}
FolderService
package com.bookmarkd.api.services;
import com.bookmarkd.api.daos.FolderDao;
import com.bookmarkd.api.models.Folder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
import java.util.List;
@Configuration
@Service
public class FolderService {
@Autowired
private FolderDao folderDao;
public FolderService() {
super();
}
public List<Folder> getFolders(String id) {
return folderDao.getFolders(id);
}
public Folder getFolder(String id) {
return folderDao.getFolder(id);
}
// Add folder, update folder, delete folder, to-be-implemented later...
}
FolderDao
package com.bookmarkd.api.daos;
import com.bookmarkd.api.models.Folder;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoCollection;
import org.bson.Document;
import org.bson.types.ObjectId;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
@Configuration
public class FolderDao extends AbstractDao {
private final MongoCollection<Folder> foldersCollection;
@Autowired
public FolderDao(MongoClient mongoClient, @Value("${spring.mongodb.database}") String databaseName) {
super(mongoClient, databaseName);
foldersCollection = db.getCollection("folders", Folder.class);
}
// Returns an array of folders, given a user _id
public List<Folder> getFolders(String id) {
if (id == null || id.isEmpty()) return null;
List<Folder> folders = foldersCollection.find(new Document("user_id", id)).into(new ArrayList<>());
// List<Folder> folders = new ArrayList<>();
// foldersCollection.find(new Document("user_id", id)); //.iterator().forEachRemaining(folders::add);
return folders;
}
// Gets a folder from db given folder _id
public Folder getFolder(String id) {
if (id == null || id.isEmpty()) return null;
return foldersCollection.find(new Document("_id", new ObjectId(id))).first();
}
}
AbstractDao
package com.bookmarkd.api.daos;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoDatabase;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
@Configuration
public abstract class AbstractDao {
protected String LBV_DATABASE;
protected MongoDatabase db;
protected MongoClient mongoClient;
protected AbstractDao(MongoClient mongoClient, String databaseName) {
this.mongoClient = mongoClient;
LBV_DATABASE = databaseName;
this.db = this.mongoClient.getDatabase(LBV_DATABASE);
}
}
MongoDBConfiguration (this is where I register the Pojo codec, which works for the User model and folderCollection.find().first() implementation)
package com.bookmarkd.api.config;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.WriteConcern;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.ConfigurableBeanFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Service;
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
// Used to configure connection to Atlas cluster
@Configuration
@Service
public class MongoDBConfiguration {
@Bean
@Scope(value = ConfigurableBeanFactory.SCOPE_SINGLETON)
public MongoClient mongoClient(@Value("${spring.mongodb.uri}") String connectionString) {
CodecRegistry pojoCodecRegistry = fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
fromProviders(PojoCodecProvider.builder().automatic(true).build())
);
ConnectionString connString = new ConnectionString(connectionString);
WriteConcern writeConcern = WriteConcern.MAJORITY; //.withWTimeout(2500, TimeUnit.MILLISECONDS);
MongoClientSettings settings = MongoClientSettings.builder()
.writeConcern(writeConcern)
.applyConnectionString(connString)
.codecRegistry(pojoCodecRegistry)
.build();
MongoClient mongoClient = MongoClients.create(settings);
return mongoClient;
}
}
Hopefully this full view of all the code gives you an insight into what is going wrong!
