spring-data-couchbase: `MappingInstantiationException` for Abstract Classes using tasks ran on ForkJoinPool

Hi, our team has had a really tough problem we been dealing with for some time and been trying to get to root cause. We have an issue with a particular service where it begins failing with the following exception:

Exception: org.springframework.data.mapping.model.MappingInstantiationException

Message:

Failed to instantiate com.qvc.common.couchbase.model.cart.payment.AbstractPaymentMethod using constructor public com.qvc.common.couchbase.model.cart.payment.AbstractPaymentMethod() with arguments

Stacktrace:

org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator$MappingInstantiationExceptionEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:358)
org.springframework.data.mapping.model.ClassGeneratingEntityInstantiator.createInstance(ClassGeneratingEntityInstantiator.java:102)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:266)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:244)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readCollection(MappingCouchbaseConverter.java:773)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.readValue(MappingCouchbaseConverter.java:856)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.access$300(MappingCouchbaseConverter.java:86)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$CouchbasePropertyValueProvider.getPropertyValue(MappingCouchbaseConverter.java:972)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.getValueInternal(MappingCouchbaseConverter.java:309)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$1.doWithPersistentProperty(MappingCouchbaseConverter.java:277)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter$1.doWithPersistentProperty(MappingCouchbaseConverter.java:269)
org.springframework.data.mapping.model.BasicPersistentEntity.doWithProperties(BasicPersistentEntity.java:368)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:269)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:244)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:201)
org.springframework.data.couchbase.core.convert.MappingCouchbaseConverter.read(MappingCouchbaseConverter.java:86)
org.springframework.data.couchbase.core.CouchbaseTemplateSupport.decodeEntity(CouchbaseTemplateSupport.java:141)
org.springframework.data.couchbase.core.NonReactiveSupportWrapper.lambda$decodeEntity$1(NonReactiveSupportWrapper.java:45)
reactor.core.publisher.MonoSupplier.call(MonoSupplier.java:86)
reactor.core.publisher.MonoFlatMap$FlatMapMain.onNext(MonoFlatMap.java:139)
reactor.core.publisher.FluxHide$SuppressFuseableSubscriber.onNext(FluxHide.java:137)
org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:89)
reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
reactor.core.publisher.FluxDoFinally$DoFinallySubscriber.onNext(FluxDoFinally.java:113)
org.springframework.cloud.sleuth.instrument.reactor.ScopePassingSpanSubscriber.onNext(ScopePassingSpanSubscriber.java:89)
reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1839)
com.couchbase.client.core.Reactor$SilentMonoCompletionStage.lambda$subscribe$0(Reactor.java:183)
java.util.concurrent.CompletableFuture.uniWhenComplete(CompletableFuture.java:859)
java.util.concurrent.CompletableFuture$UniWhenComplete.tryFire(CompletableFuture.java:837)
java.util.concurrent.CompletableFuture.postComplete(CompletableFuture.java:506)
java.util.concurrent.CompletableFuture.complete(CompletableFuture.java:2073)
com.couchbase.client.core.msg.BaseRequest.succeed(BaseRequest.java:161)
com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decodeAndComplete(KeyValueMessageHandler.java:412)
com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.retryOrComplete(KeyValueMessageHandler.java:368)
com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.decode(KeyValueMessageHandler.java:351)
com.couchbase.client.core.io.netty.kv.KeyValueMessageHandler.channelRead(KeyValueMessageHandler.java:282)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
com.couchbase.client.core.io.netty.kv.MemcacheProtocolVerificationHandler.channelRead(MemcacheProtocolVerificationHandler.java:85)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:336)
com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:308)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
com.couchbase.client.core.deps.io.netty.handler.flush.FlushConsolidationHandler.channelRead(FlushConsolidationHandler.java:152)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
com.couchbase.client.core.deps.io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1373)
com.couchbase.client.core.deps.io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1236)
com.couchbase.client.core.deps.io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1285)
com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:519)
com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:458)
com.couchbase.client.core.deps.io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:280)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
com.couchbase.client.core.deps.io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
com.couchbase.client.core.deps.io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
com.couchbase.client.core.deps.io.netty.channel.epoll.AbstractEpollStreamChannel$EpollStreamUnsafe.epollInReady(AbstractEpollStreamChannel.java:800)
com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.processReady(EpollEventLoop.java:499)
com.couchbase.client.core.deps.io.netty.channel.epoll.EpollEventLoop.run(EpollEventLoop.java:397)
com.couchbase.client.core.deps.io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:997)
com.couchbase.client.core.deps.io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
com.couchbase.client.core.deps.io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
java.lang.Thread.run(Thread.java:829)

We have a standard @Document with a list of an Abstract object like so:

private List<AbstractPaymentMethod<?>> paymentMethods;

The definition of the abstract class is:

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public abstract class AbstractPaymentMethod<T>   {

	private String id;
	private T data;

}

An example of a class implementing the abstract class is:

public class CreditPaymentMethod extends AbstractPaymentMethod<Credit> {
}

@Getter
@Setter
public class Credit  {
	private String id;
	private String type;
	private String description;
	private BigDecimal amount;
}

We can’t quite determine when exactly it happen but some of the versions that we’re on are:

  • Spring Boot 2.7.6
  • spring data commons 2.7.6
  • spring data couchbase 4.4.6

When app is initially deployed it runs fine for a while and then suddenly it begins throwing this error complaining specifically about our abstract class. Previous to these versions, we have been running fine up to 5 years so we’re trying to determine if there was any change of behavior in the spring data commons area where we have to configure abstract classes differently. Any help would be greatly appreciated.

We are not using converters (@ReadingConverter / @WritingConverter) for this but simply just have an overall @Document spring data couchbase entity class and use the standard PagingAndSortingRepository.

I imagine more data will be needed to help here but wanted to get convo started.

About this issue

  • Original URL
  • State: closed
  • Created a year ago
  • Comments: 26 (2 by maintainers)

Commits related to this issue

Most upvoted comments

The fix is going to be 4.4.11 and 5.0.5

Release date for those versions is April 14

The error message “Failed to instantiate com.qvc.common.couchbase.model.cart.payment.AbstractPaymentMethod” indicates that DefaultTypeMapper.readType() could not determine the concrete type (documentsTargetType) based on the ‘source’ and was therefore trying to use the abstract type (basicType) which was used to define the repository. The issue looks more like an issue determining what the documentsTargetType is (or determining that it isMoreConcreteCustomType than the abstract type). Which points to the (sub)document not having a _class property, or the code not finding it because it is using a different @TypeAlias than _class, or a custom TypeMapper (instead of the DefaultTypeMapper). (Or the aforementioned bug of not projecting _class, but that should be fixed in 4.4.8). The error message indicates it is trying to instantiate the wrong class - not any issue with instantiation of the correct class.

  public <T> TypeInformation<? extends T> readType(S source, TypeInformation<T> basicType) {
    Assert.notNull(source, "Source must not be null");
    Assert.notNull(basicType, "Basic type must not be null");
    Class<?> documentsTargetType = this.getDefaultedTypeToBeUsed(source);
    if (documentsTargetType == null) {
      return basicType;
    } else {
      Class<T> rawType = basicType.getType();
      boolean isMoreConcreteCustomType = rawType == null || rawType.isAssignableFrom(documentsTargetType) && !rawType.equals(documentsTargetType);
      if (!isMoreConcreteCustomType) {
        return basicType;
      } else {
        TypeInformation<?> targetType = TypeInformation.of(documentsTargetType);
        return basicType.specialize(targetType);
      }
    }
  }

The fix is going to be 4.4.11 and 5.0.5 @mikereiche I’m guessing you no longer need the stack trace you asked for? Let me know if needed.

And thanks for helping and getting a fix in!

Thanks for mentioning CompletableFuture.runAsync(…). This method runs typically on the ForkJoin pool that isn’t associated with a contextual class loader but rather uses the initial application class loader. Depending on your setup, the app class loader sees only the outer Spring Boot JAR and not the packaged inner jars.

The issue can be fixed by providing the bean class loader to CouchbaseTypeMapper. DefaultTypeMapper that serves as base class for Couchbase’s TypeMapper is BeanClassLoaderAware accepting ClassLoader. The easiest fix would be setting the class loader via MappingCouchbaseConverter.setApplicationContext(…), see MongoDB’s usage for reference.

If you set a break-point at line 231 in MappingCouchbaseConverter - you should see that type=com.example.demo.AbstractPaymentMethod<Object> and the content of source contains _class CouchbaseDocument{id=null, exp=0, content={_class=com.example.demo.CreditPaymentMethod, data=CouchbaseDocument{id=null, exp=0, content={amount=10, description=Walmart_Purchase, id=visa_id, type=visa_type}}, id=123}}

resulting in typeToUse=com.example.demo.CreditPaymentMethod

protected <R> R read(final TypeInformation<R> type, final CouchbaseDocument source, final Object parent) {
		if (source == null) {
			return null;
		}

231:		TypeInformation<? extends R> typeToUse = typeMapper.readType(source, type);
		Class<? extends R> rawType = typeToUse.getType();

		if (conversions.hasCustomReadTarget(source.getClass(), rawType)) {
			return conversionService.convert(source, rawType);
		}

		if (typeToUse.isMap()) {
			return (R) readMap(typeToUse, source, parent);
		}

		CouchbasePersistentEntity<R> entity = (CouchbasePersistentEntity<R>) mappingContext
				.getRequiredPersistentEntity(typeToUse);
		return read(entity, source, parent);
	}

@cabbonizio - I’ll investigate your issue. There was an issue around the _class not being leveraged that was causing an issue for abstract entities - #1315 (and its duplicate #1364). But these were fixed before 4.4.6. (Meanwhile, can you please use the latest 4.x which is 4.4.9 so we are both looking using the same codebase? Thanks. Edit: 4.4.8 from spring-boot 2.7.9 is fine).