9

I've been trying for about an hour now to register a codec I made for one of my classes in a game I am working on. The class is called Item. I tried the code and suggestions from these 3 places:

and here is the code I cam up with:

CodecRegistry defaultCodecRegistry = MongoClient.getDefaultCodecRegistry();
MyCodecProvider myCodecProvider = new MyCodecProvider();
ItemCodec itemCodec = new ItemCodec(defaultCodecRegistry);

CodecRegistry codecRegistry = CodecRegistries.fromRegistries(CodecRegistries.fromCodecs(itemCodec), CodecRegistries.fromProviders(myCodecProvider), defaultCodecRegistry);;
MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();

client = new MongoClient("localhost:27017", options);

So I built a Codec and codec provider called MyCodecProvider, so what am I doing wrong, how can this be so complicated? Am I missing something? It seems more complicated than it needs to be. If you need more code please ask. Thanks.

Edit: The exact error I get is org.bson.codecs.configuration.CodecConfigurationException: Can't find a codec for class [Lnet.hollowbit.archipeloserver.items.Item;. Also, I am trying to parse an Item array, do I need to make a codec specifically for the array too?

2
  • I just found out that it has to do with me using Item[], apparently it's not possible with MongoDB to use arrays, that sucks. Commented Jan 12, 2017 at 17:01
  • For those that follow, the default Lists/ArrayLists work fine; and if you really need an array it can be done! Commented Jan 15, 2022 at 3:29

5 Answers 5

9

You can do Mongo codecs for arrays using ArrayList as follows:

com.example.model.Order

This is the class representing an Order. It includes an ArrayList of items.

package com.example.model;

import java.util.ArrayList;
import org.bson.types.ObjectId;

/**
 * Class representing an Order.
 */
public class Order
{
    private ObjectId id;
    private ArrayList<Item> items;

    /**
     * Default constructor. Needed for testing.
     */
    public Order() {
        this.items = new ArrayList<>();
    }

    /**
     * Sets the id of the Order.
     * @param id The new id of the Order.
     */
    public void setId(ObjectId id) {
        this.id = id;
    }

    /**
     * Gets the id of the Order.
     * @return The id of the Order.
     */
    public ObjectId getId() {
        return this.id;
    }

    /**
     * Sets the items for the Order.
     * @param items The items for the Order.
     */
    public void setItems(ArrayList<Item> items) {
        this.items = items;
    }

    /**
     * Gets the items for the Order.
     * @return The items for the Order.
     */
    public ArrayList<Item> getItems() {
        return items;
    }

    /**
     * Adds an item to the Order.
     * @param item The new Item to add to the Order.
     */
    public void addItem(Item item) {
        this.items.add(item);
    }
}

com.example.model.Item

This is the class representing an Order Item. There can be any number of items that are a part of an order. Items are embedded within the Order document in Mongo.

package com.example.model;

import org.bson.types.ObjectId;

/**
 * Class representing an order item.
 */
public class Item
{
    private ObjectId id;
    private String name;

    /**
     * Constructor.
     */
    public Item() {
        //
    }

    /**
     * Sets the id of the Item.
     * @param id The new id of the Item.
     */
    public void setId(ObjectId id) {
        this.id = id;
    }

    /**
     * Gets the id of the Item.
     * @return The id of the Item.
     */
    public ObjectId getId() {
        return this.id;
    }

    /**
     * Sets the name of the Item.
     * @param name The new name of the Item.
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * Gets the name of the Item.
     * @return The name of the Item.
     */
    public String getName() {
        return this.name;
    }
}

com.example.mongo.ItemConverter

Simple converter class that converts Items to/from Documents.

package com.example.mongo;

import com.example.model.Item;
import org.bson.Document;

/**
 * Converts Mongo Documents to/from Items.
 */
public class ItemConverter {
    /**
     * Convert the passed Item into a Mongo Document.
     * @param item The Item that you want to convert into a Mongo Document.
     * @return Returns the Document that was created from the passed Item.
     */
    public Document convert(Item item) {
        Document document = new Document();
        document.put("_id", item.getId());
        document.put("name", item.getName());

        return document;
    }

    /**
     * Convert the passed Mongo Document into an Item.
     * @param document The Document that you want to convert into an Item.
     * @return Returns the Item that was created from the passed Mongo Document.
     */
    public Item convert(Document document) {
        Item item = new Item();
        item.setId(document.getObjectId("_id"));
        item.setName(document.getString("name"));

        return item;
    }
}

com.example.mongo.ItemCodec

Codec for encoding and decoding Items.

package com.example.mongo;

import com.example.model.Item;
import com.mongodb.MongoClient;
import org.bson.BsonReader;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.types.ObjectId;

/**
 * Mongo Decoder for Items.
 */
public class ItemCodec implements CollectibleCodec<Item> {

    private final CodecRegistry registry;
    private final Codec<Document> documentCodec;
    private final ItemConverter converter;

    /**
     * Default constructor.
     */
    public ItemCodec() {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = this.registry.get(Document.class);
        this.converter = new ItemConverter();
    }

    /**
     * Codec constructor.
     * @param codec The existing codec to use.
     */
    public ItemCodec(Codec<Document> codec) {
        this.documentCodec = codec;
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.converter = new ItemConverter();
    }

    /**
     * Registry constructor.
     * @param registry The CodecRegistry to use.
     */
    public ItemCodec(CodecRegistry registry) {
        this.registry = registry;
        this.documentCodec = this.registry.get(Document.class);
        this.converter = new ItemConverter();
    }

    /**
     * Encode the passed Item into a Mongo/BSON document.
     * @param writer The writer to use for encoding.
     * @param item The Item to encode.
     * @param encoderContext The EncoderContext to use for encoding.
     */
    @Override
    public void encode(
                    BsonWriter writer,
                    Item item,
                    EncoderContext encoderContext
                ) {
        Document document = this.converter.convert(item);

        documentCodec.encode(writer, document, encoderContext);
    }

    /**
     * Get the class that this Codec works with.
     * @return Returns the class that this Codec works with.
     */
    @Override
    public Class<Item> getEncoderClass() {
        return Item.class;
    }

    /**
     * Decodes a Mongo/BSON document into an Item.
     * @param reader The reader containing the Document.
     * @param decoderContext The DecoderContext to use for decoding.
     * @return Returns the decoded Item.
     */
    @Override
    public Item decode(BsonReader reader, DecoderContext decoderContext) {
        Document document = documentCodec.decode(reader, decoderContext);
        Item item = this.converter.convert(document);

        return item;
    }

    /**
     * Generates a new ObjectId for the passed Item (if absent).
     * @param item The Item to work with.
     * @return Returns the passed Item with a new id added if there
     * was none.
     */
    @Override
    public Item generateIdIfAbsentFromDocument(Item item) {
        if (!documentHasId(item)) {
            item.setId(new ObjectId());
        }

        return item;
    }

    /**
     * Returns whether or not the passed Item has an id.
     * @param Item The Item that you want to check for
     * the presence of an id.
     * @return Returns whether or not the passed Item has an id.
     */
    @Override
    public boolean documentHasId(Item Item) {
        return (Item.getName() != null);
    }

    /**
     * Gets the id of the passed Item. If there is no id, it will
     * throw an IllegalStateException (RuntimeException).
     * @param Item The Item whose id you want to get.
     * @return Returns the id of the passed Item as a BsonValue.
     */
    @Override
    public BsonValue getDocumentId(Item Item)
    {
        if (!documentHasId(Item)) {
            throw new IllegalStateException("The document does not contain an _id");
        }

        return new BsonString(Item.getName());
    }

}

com.example.mongo.OrderCodec

Codec for encoding/decoding Orders.

package com.example.mongo;

import com.example.model.Item;
import com.example.model.Order;
import com.mongodb.MongoClient;
import java.util.ArrayList;
import org.bson.BsonReader;
import org.bson.BsonString;
import org.bson.BsonValue;
import org.bson.BsonWriter;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.CollectibleCodec;
import org.bson.codecs.DecoderContext;
import org.bson.codecs.EncoderContext;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.types.ObjectId;

/**
 * Mongo decoder for Orders.
 */
public class OrderCodec implements CollectibleCodec<Order> {

    private final CodecRegistry registry;
    private final Codec<Document> documentCodec;
    private final ItemConverter itemConverter;

    /**
     * Default constructor.
     */
    public OrderCodec() {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = this.registry.get(Document.class);
        this.itemConverter = new ItemConverter();
    }

    /**
     * Codec constructor.
     * @param codec The existing codec to use.
     */
    public OrderCodec(Codec<Document> codec) {
        this.registry = MongoClient.getDefaultCodecRegistry();
        this.documentCodec = codec;
        this.itemConverter = new ItemConverter();
    }

    /**
     * Registry constructor.
     * @param registry The CodecRegistry to use.
     */
    public OrderCodec(CodecRegistry registry) {
        this.registry = registry;
        this.documentCodec = this.registry.get(Document.class);
        this.itemConverter = new ItemConverter();
    }

    /**
     * Encode the passed Order into a Mongo/BSON document.
     * @param writer The writer to use for encoding.
     * @param order The Order to encode.
     * @param encoderContext The EncoderContext to use for encoding.
     */
    @Override
    public void encode(
                    BsonWriter writer,
                    Order order,
                    EncoderContext encoderContext
                ) {
        Document document = new Document();
        document.put("_id", order.getId());
        document.put("items", order.getItems());

        documentCodec.encode(writer, document, encoderContext);
    }

    /**
     * Get the class that this Codec works with.
     * @return Returns the class that this Codec works with.
     */
    @Override
    public Class<Order> getEncoderClass() {
        return Order.class;
    }

    /**
     * Decodes a Mongo/BSON document into an Order.
     * @param reader The reader containing the Document.
     * @param decoderContext The DecoderContext to use for decoding.
     * @return Returns the decoded Order.
     */
    @Override
    public Order decode(BsonReader reader, DecoderContext decoderContext) {
        Document document = documentCodec.decode(reader, decoderContext);

        Order order = new Order();

        order.setId(document.getObjectId("_id"));

        ArrayList<Document> docArr = (ArrayList) document.get("items");
        for (Document doc : docArr) {
            Item item = this.itemConverter.convert(doc);
            order.addItem(item);
        }

        return order;
    }

    /**
     * Generates a new ObjectId for the passed Order (if absent).
     * @param order The Order to work with.
     * @return Returns the passed Order with a new id added if there
     * was none.
     */
    @Override
    public Order generateIdIfAbsentFromDocument(Order order) {
        if (!documentHasId(order)) {
            order.setId(new ObjectId());
        }

        return order;
    }

    /**
     * Returns whether or not the passed Order has an id.
     * @param order The Order that you want to check for
     * the presence of an id.
     * @return Returns whether or not the passed Order has an id.
     */
    @Override
    public boolean documentHasId(Order order) {
        return (order.getId() != null);
    }

    /**
     * Gets the id of the passed Order. If there is no id, it will
     * throw an IllegalStateException (RuntimeException).
     * @param order The Order whose id you want to get.
     * @return Returns the id of the passed Order as a BsonValue.
     */
    @Override
    public BsonValue getDocumentId(Order order) {
        if (!documentHasId(order)) {
            throw new IllegalStateException("The document does not contain an _id");
        }

        return new BsonString(order.getId().toHexString());
    }
}

com.example.main.Main

Main class for the app. Here we register the codecs and create our MongoClient.

package com.example.main;

import com.example.model.Item;
import com.example.model.Order;
import com.example.mongo.ItemCodec;
import com.example.mongo.OrderCodec;
import com.mongodb.MongoClient;
import com.mongodb.MongoClientOptions;
import com.mongodb.ServerAddress;
import org.bson.Document;
import org.bson.codecs.Codec;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;

/**
 * Main class.
 */
public class Main {

    /**
     * Main function for the app.
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        CodecRegistry codecRegistry = MongoClient.getDefaultCodecRegistry();
        Codec<Document> documentCodec = codecRegistry.get(Document.class);
        Codec<Item> itemCodec = new ItemCodec(codecRegistry);
        Codec<Order> orderCodec = new OrderCodec(codecRegistry);
        codecRegistry = CodecRegistries.fromRegistries(
                                            MongoClient.getDefaultCodecRegistry(),
                                            CodecRegistries.fromCodecs(
                                                documentCodec,
                                                itemCodec,
                                                orderCodec
                                            )
                                        );

        MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();
        MongoClient mongo = new MongoClient(new ServerAddress("localhost", 27018), options);

        // Your code here.
    }
}

From there you can read/write orders and items to/from Mongo.

Sign up to request clarification or add additional context in comments.

2 Comments

I'm accepting your answer since I trust it and you put a lot of work into it, but I honestly haven't tried it and have no interest in trying since it's an old question, but thanks man!
This method contains far more manual conversion than simply writing the object as a Document manually. :(
7

The other answers are overly complicated. For the vast majority of the of the POJO classes you'll use, you do not need to write a codec at all. Just register your object:

// Don't you hate it when sample code does not include references?
import static org.bson.codecs.configuration.CodecRegistries.fromProviders;
import static org.bson.codecs.configuration.CodecRegistries.fromRegistries;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import com.mongodb.MongoClientSettings;

...
{
    CodecRegistry myRegistry = fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(),
                fromProviders(
                    PojoCodecProvider.builder()
                        .register(Item.class)
                        .build()
                    )
                ));

The above code makes a new registry by combining it with the default registry. This is important because the default registry covers a ton of Java objects (lists, maps, etc.)

What if you have a lot of model objects? No problem: register an entire package of objects...

{
    CodecRegistry myRegistry = fromRegistries(
                MongoClientSettings.getDefaultCodecRegistry(),
                fromProviders(
                    PojoCodecProvider.builder()
                        .register("com.funkytown.model.package")
                        .build()
                    )
                ));

So next, how do you use this registry? You have 3 options.

  1. Attach it to a MongoClient instance:
MongoClient mongoClient = new MongoClient("localhost", MongoClientOptions.builder().codecRegistry(myRegistry).build());

  1. Use it with a specific database:
myDatabase = mongoClient.getDatabase("FunkyTown").withCodecRegistry(myRegistry);
  1. Use it with a specific collection:
myCollection = mongoClient.getDatabase("FunkyTown").getCollection("Items").withCodecRegistry(myRegistry);

The MongoDB Documentation isn't the greatest, but in my experience the POJO functionality is fast and flexible.

Comments

1

A better approach will be to implement your own CodecProvider. This way you can manage all used codec implementation. Using your case:

Create MyCodecProvider

public class MyCodecProvider implements CodecProvider {

private final BsonTypeClassMap bsonTypeClassMap;

public MyCodecProvider(final BsonTypeClassMap bsonTypeClassMap) {
    this.bsonTypeClassMap = bsonTypeClassMap;
}

@Override
public <T> Codec<T> get(final Class<T> clazz, final CodecRegistry registry) {
    if (clazz == Document.class) {
        // construct DocumentCodec with a CodecRegistry and a BsonTypeClassMap
        return (Codec<T>) new org.bson.codecs.DocumentCodec(registry, bsonTypeClassMap);
    }
    else if (clazz == Order.class) {
        return (Codec<T>) new OrderCodec(registry);
    }
    else if (clazz == Item.class) {
        return (Codec<T>) new ItemCodec(registry);
    }

    return null;
  }
}

Now you may simple do this in your main class.

Map<BsonType, Class<?>> replacements = new HashMap<BsonType, Class<?>>();
    replacements.put(BsonType.DATE_TIME, Timestamp.class);

// replacements.put(BsonType.DATE_TIME, Timestamp.class); in case you have complex java type

    BsonTypeClassMap bsonTypeClassMap = new BsonTypeClassMap(replacements);


    MyCodecProvider provider = new MyCodecProvider(bsonTypeClassMap);
    CodecRegistry pojoCodecRegistry = fromRegistries(MongoClient.getDefaultCodecRegistry(),
            fromProviders(PojoCodecProvider.builder().automatic(true).build()));
    CodecRegistry codecRegistry = MongoClient.getDefaultCodecRegistry();
    Codec<Document> documentCodec = codecRegistry.get(Document.class);

    codecRegistry = CodecRegistries.fromRegistries(
            CodecRegistries.fromProviders(provider),
            CodecRegistries.fromRegistries(pojoCodecRegistry),
            CodecRegistries.fromCodecs(documentCodec));

    MongoClientOptions options = MongoClientOptions.builder().codecRegistry(codecRegistry).build();
    MongoClient mongo = new MongoClient(new ServerAddress("localhost", 27018), options);

Comments

0
@Bean
public MongoClientSettings mongoClientSettings() {
    MongoClientSettings mongoClientSettings = MongoClientSettings.builder()
            .codecRegistry(createCustomCodecRegistry()).build();
    return mongoClientSettings;
}

@Bean
public CodecRegistry createCustomCodecRegistry() {
    return CodecRegistries.fromRegistries(
            CodecRegistries.fromCodecs(
                    new UuidCodec(UuidRepresentation.STANDARD),
                    new ZonedDateTimeCodec()),
            MongoClientSettings.getDefaultCodecRegistry()
    );
}

Comments

0

I just found out that it has to do with me using Item[], apparently it's not possible with MongoDB to use arrays, that sucks. – vedi0boy Jan 12, 2017 at 17:01

This is great advice. I was working with MongoDB Quickstart and trying to get it to work w/ the Azure Emulator and kept crapping out on 'codec issues'. After a couple of days of googling/frustration I came upon this post while creating my codecs.

The Quick start has a couple of classes that define some variables as Arrays which, as you mentioned, can't be converted via the MongoDB Codec registery.

After I flipped those variables to Lists I was good to go.

Thanks for the advice :)

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.