[JAVA-2951] NPE with getter without backing field Created: 29/Aug/18  Updated: 28/Oct/23  Resolved: 30/Aug/18

Status: Closed
Project: Java Driver
Component/s: POJO
Affects Version/s: 3.7.0, 3.7.1, 3.8.0, 3.8.1
Fix Version/s: 3.8.2

Type: Bug Priority: Major - P3
Reporter: Clécius J. Martinkoski Assignee: Ross Lawley
Resolution: Fixed Votes: 0
Labels: None
Remaining Estimate: Not Specified
Time Spent: Not Specified
Original Estimate: Not Specified

Backwards Compatibility: Fully Compatible

 Description   

ConventionSetPrivateFieldImpl throws a NPE when mapping a POJO with a getter method. This getter isn't related to a backing field, it is simply a business getter (like getTotal() of a sale).

The relevant part of stacktrace is:

 

java.lang.NullPointerException: nulljava.lang.NullPointerException: null at org.bson.codecs.pojo.ConventionSetPrivateFieldImpl.apply(ConventionSetPrivateFieldImpl.java:36) at org.bson.codecs.pojo.ClassModelBuilder.build(ClassModelBuilder.java:252) at org.bson.codecs.pojo.PojoCodecProvider.createClassModel(PojoCodecProvider.java:214) at org.bson.codecs.pojo.PojoCodecProvider.getPojoCodec(PojoCodecProvider.java:78) at org.bson.codecs.pojo.PojoCodecProvider.get(PojoCodecProvider.java:68) at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:43) at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:55) at org.bson.codecs.configuration.ProvidersCodecRegistry.get(ProvidersCodecRegistry.java:37) at com.mongodb.internal.operation.Operations.createFindOperation(Operations.java:144) at com.mongodb.internal.operation.Operations.findFirst(Operations.java:128) at com.mongodb.internal.operation.AsyncOperations.findFirst(AsyncOperations.java:81) at com.mongodb.async.client.FindIterableImpl.first(FindIterableImpl.java:197)

 

 

Debugging to discover the NPE source i'm got to this point of failure at line 36 of bson/src/main/org/bson/codecs/pojo/ConventionSetPrivateFieldImpl.java:

 

if (!propertyMetaData.isDeserializable() && isPrivate(propertyMetaData.getField().getModifiers()))

When the propertyMetaData has state:

setter=null
field=null
getter=not null

The return of isDeserializable is false, then after ! the other condition of && is tested, in this the propertyMetadaData.getField() that is null, is acessed without nullable check.

 

 



 Comments   
Comment by Githook User [ 30/Aug/18 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross.lawley@gmail.com', 'username': 'rozza'}

Message: Fix NPE error with ConventionSetPrivateField

JAVA-2951
Branch: 3.8.x
https://github.com/mongodb/mongo-java-driver/commit/84a32dc649d65624dd3edbe6600efb248e1e5066

Comment by Githook User [ 30/Aug/18 ]

Author:

{'name': 'Ross Lawley', 'email': 'ross.lawley@gmail.com', 'username': 'rozza'}

Message: Fix NPE error with ConventionSetPrivateField

JAVA-2951
Branch: master
https://github.com/mongodb/mongo-java-driver/commit/6fa5b4b1073a1df6d2a70d2fef360622bae6fbea

Comment by Ross Lawley [ 30/Aug/18 ]

Hi eprecise-clecius,

Thanks - seems my intellij was having an issue and wouldnt reload the class. I have a fix in review. 

Ross

Comment by Clécius J. Martinkoski [ 30/Aug/18 ]

Hi @ross.lawley,

I'm thinking in send a PR with this correction, do you see problems?

About the example, i've reached this bug in java and in kotlin as well, i'll send to you both POJO samples:

 

Java example:

import java.util.Collection;
import java.util.Optional;
import java.util.stream.Collectors;
import org.bson.codecs.pojo.annotations.BsonId;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.ToString;
@NoArgsConstructor(force = true)
@EqualsAndHashCode(of = "id")
@ToString
@Getter
public class PersonMdb implements MongoEntity {    private static final long serialVersionUID = 1L;    private @BsonId final String id;    private final String name;    private final Collection<ContactMdb> contacts;    private final Collection<AddressMdb> addresses;    public final Collection<ContactMdb> getAllMails() {
        return this.contacts.stream().filter(c -> ContactCategoryMdb.EMAIL.equals(c.getCategory())).collect(Collectors.toList());
    }    public Collection<ContactMdb> getAllPhones() {
        return this.contacts.stream()
                .filter(c -> ContactCategoryMdb.CELL.equals(c.getCategory()) || ContactCategoryMdb.PHONE.equals(c.getCategory()))
                .collect(Collectors.toList());
    }    public Optional<AddressMdb> getFirstMainAddress() {
        return this.addresses.stream().filter(AddressMdb::mainCategory).findFirst();
    }    public Optional<AddressMdb> getFirstMainAddressOrElseOther() {
        return Optional.ofNullable(this.getFirstMainAddress().orElse(this.addresses.stream().findFirst().orElse(null)));
    }}

The kotlin example:

@Entity("tickets")
data class Ticket(
        override val id: String = UUID.randomUUID().toString(),
        @Reference val session: Session,
        val number: Int = -1,
        val source: TicketSource = IndividualTicketSource()
) : ActivableMultitenantEntity {
 
    val creation = Instant.now()
 
    val callsHistory = TreeSet<TicketCallHistory>()
 
    override var isActive = true
 
    @TenantId
    override val tenantId: String = ""
 
    @Version
    private val version: Long = 0
 
    val code: String
        get() = this.source.asCode(this.number)
 
    val isCalled: Boolean
        get() = !this.callsHistory.isEmpty()
 
    val lastCall: TicketCallHistory?
        get() = this.callsHistory.first()
 
}

The problem is on getter that only execute some rule then return, without backing field. In first case our workaroud is removing 'get' prefix, but in kotlin this is generated by the compiler.

Comment by Ross Lawley [ 30/Aug/18 ]

Hi eprecise-clecius,

Could you provide an example POJO? - when testing this didn't NPE and I would like to add a regression test case.
Ross

Comment by Ross Lawley [ 30/Aug/18 ]

Thanks for reporting this eprecise-clecius.

Will look to fix in the next release.

 

 

Generated at Thu Feb 08 08:58:27 UTC 2024 using Jira 9.7.1#970001-sha1:2222b88b221c4928ef0de3161136cc90c8356a66.