Creating @Embedded objects with list of embedded objects

Hi,
I am using Java 21, Hibernate 7.0.6.Final and spring-boot-starter-data-jpa 3.5.3.

As a general info:
The explained structure is defined by an old application which cannot be changed.
In the past the stored procedures using this structure were called from Java using oracle.sql.STRUCT and oracle.sql.ARRAY. We are now trying to add some features but have to use the existing complex stored procedure but using Java 21.

Description:
We have a custom Oracle Data Type OT_COUNTRY, which contains a list of partners.
This list of partners is wrapped with another custom Oracle Datatype TT_PARTNERS and this type contains a list of
OT_PARTNER.

create or replace TYPE  "OT_COUNTRY"  AS  object  (
    COUNTRY_CODE        VARCHAR2(2),
    COUNTRY_NAME            VARCHAR2(2000),
    AMOUNT  NUMBER,
    PARTNERS  TT_PARTNERS
  );
create or replace TYPE  "OT_PARTNER" as object (
  code NVARCHAR2(20),
  name NVARCHAR2(200),
  phone_number NVARCHAR2(200)
);
create or replace TYPE  "TT_PARTNERS" as TABLE OF OT_PARTNER;

The Java classes are all defined as @Embeddable and used inside the other class as @Embedded.

@Embeddable
....
@Struct(name = "OT_COUNTRY", attributes = { "countryCode", "countryName", "amount", "partners" })
public class OTCountry implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String countryCode;
    private String countryName;
    private Long amount;
    @Embedded
    private TTPartner partners;
}
@Embeddable
...
@Struct(name = "TT_PARTNER", attributes = { "listOfPartners" })
public class TTPartner implements Serializable {
    private static final long serialVersionUID = -6155092651111036183L;
    @Embedded
    @ElementCollection(targetClass = OTPartner.class)
    private List<OTPartner> listOfPartners;
}
@Embeddable
...
@Struct(name = "OT_PARTNER", attributes = { "code", "name", "phoneNumber" })
public class OTPartner implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String code;
    private String name;
    private String phoneNumber;
}

The class OTCountry is added to a dummy Hibernate Entity class to let Hibernate create the metadata for these @Embeddables.
This structure is needed for calling some of the existing complex Oracle stored procedures.
When trying to deploy the war-File I always receive the following exception:

org.hibernate.AssertionFailure: Collection property holder does not have a class name

This message comes directly from org.hibernate.boot.model.internal method.CollectionPropertyHolder.getClassName()

	@Override
	public String getClassName() {
		throw new AssertionFailure( "Collection property holder does not have a class name" );
	}

Is this structure not supported by Hibernate? Does it mean I have to continue working directly with JPA?
Or am I doing something wrong? Is there another way?

Maybe you can create a Jira for this problem? Essentially, I seem to be missing a case in the validation logic that lets @ElementCollection slip through, even though it makes no sense.

The solution is to just remove @Embedded and @ElementCollection from listOfPartners.

Hi,
I followed your suggestion and initialized the list (new ArrayList<>()) but now I receive this error message:
java.sql.SQLException: ORA-17074: Invalid name pattern: OT_PARTNERARRAY
Do you have any further ideas?

Please share the full stack trace and mapping.

Hi
thanks for your help.

The StackTrace:

17-Jul-2025 18:32:34.740 SEVERE [https-openssl-nio-943-exec-118] org.apache.catalina.core.StandardWrapperValve.invoke Servlet.service() for servlet [rest] in context with path [/fxcd-country] threw exception [Request processing failed: org.springframework.orm.jpa.JpaSystemException: Couldn't create a java.sql.Array] with root cause
	java.sql.SQLException: ORA-17074: Invalid name pattern: OT_PARTNERARRAY
https://docs.oracle.com/error-help/db/ora-17074/
		at oracle.jdbc.oracore.OracleTypeADT.initMetadata12(OracleTypeADT.java:647)
		at oracle.jdbc.oracore.OracleTypeADT.initMetadata(OracleTypeADT.java:582)
		at oracle.jdbc.oracore.OracleTypeADT.init(OracleTypeADT.java:505)
		at oracle.sql.ArrayDescriptor.initPickler(ArrayDescriptor.java:1728)
		at oracle.sql.ArrayDescriptor.<init>(ArrayDescriptor.java:394)
		at oracle.sql.ArrayDescriptor.<init>(ArrayDescriptor.java:370)
		at oracle.sql.ArrayDescriptor.createDescriptor(ArrayDescriptor.java:222)
		at oracle.sql.ArrayDescriptor.createDescriptor(ArrayDescriptor.java:104)
		at oracle.jdbc.driver.PhysicalConnection.createARRAY(PhysicalConnection.java:7844)
		at oracle.jdbc.driver.PhysicalConnection.createOracleArray(PhysicalConnection.java:7865)
		at org.hibernate.dialect.type.OracleArrayJdbcType$1.getBindValue(OracleArrayJdbcType.java:120)
		at org.hibernate.dialect.type.OracleArrayJdbcType$1.getBindValue(OracleArrayJdbcType.java:76)
		at org.hibernate.type.descriptor.jdbc.StructHelper.injectJdbcValue(StructHelper.java:303)
		at org.hibernate.type.descriptor.jdbc.StructHelper.injectJdbcValues(StructHelper.java:173)
		at org.hibernate.type.descriptor.jdbc.StructHelper.getJdbcValues(StructHelper.java:129)
		at org.hibernate.type.descriptor.jdbc.StructJdbcType.createJdbcValue(StructJdbcType.java:132)
		at org.hibernate.dialect.type.OracleBaseStructJdbcType$1.getBindValue(OracleBaseStructJdbcType.java:61)
		at org.hibernate.type.descriptor.jdbc.StructHelper.injectJdbcValue(StructHelper.java:269)
		at org.hibernate.type.descriptor.jdbc.StructHelper.injectJdbcValues(StructHelper.java:173)
		at org.hibernate.type.descriptor.jdbc.StructHelper.getJdbcValues(StructHelper.java:129)
		at org.hibernate.type.descriptor.jdbc.StructJdbcType.createJdbcValue(StructJdbcType.java:132)
		at org.hibernate.dialect.type.OracleBaseStructJdbcType$1.doBind(OracleBaseStructJdbcType.java:50)
		at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:59)
		at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:111)
		at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:82)
		at org.hibernate.procedure.internal.ProcedureCallImpl.buildOutputs(ProcedureCallImpl.java:663)
		at org.hibernate.procedure.internal.ProcedureCallImpl.getOutputs(ProcedureCallImpl.java:567)
		at org.hibernate.procedure.internal.ProcedureCallImpl.outputs(ProcedureCallImpl.java:818)
		at org.hibernate.procedure.internal.ProcedureCallImpl.execute(ProcedureCallImpl.java:802)
		at xxxxx.data.repositories.CountrySpRepository.searchCountry(CountrySpRepository.java:114)
		at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
		at java.base/java.lang.reflect.Method.invoke(Method.java:580)
		at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
		at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:138)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
		at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
		at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
		at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:223)
		at jdk.proxy12/jdk.proxy12.$Proxy750.searchCountry(Unknown Source)
		at xxxxx.country.service.CountryDataService.search(CountryDataService.java:62)
		at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
		at java.base/java.lang.reflect.Method.invoke(Method.java:580)
		at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:355)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:196)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
		at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:769)
		at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:379)
		at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
		at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:184)
		at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:769)
		at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:721)
		at xxxxx.country.service.CountryDataService$$SpringCGLIB$$0.search(<generated>)
		at xxxxx.country.rest.DataAccessController.searchCountry(DataAccessController.java:80)
		at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
		at java.base/java.lang.reflect.Method.invoke(Method.java:580)
		at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:255)
		at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:188)
		at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:118)
		at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:926)
		at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:831)
		at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
		at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1089)
		at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:979)
		at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1014)
		at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:903)
		at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:564)
		at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:885)
		at jakarta.servlet.http.HttpServlet.service(HttpServlet.java:658)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:195)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
		at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:51)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
		at xxxxx.filter.cors.MORACorsResponseFilter.doFilter(MORACorsResponseFilter.java:60)
		at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:164)
		at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:140)
		at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:167)
		at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:90)
		at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:597)
		at org.apache.catalina.valves.rewrite.RewriteValve.invoke(RewriteValve.java:291)
		at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
		at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:115)
		at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:93)
		at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:663)
		at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:74)
		at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:344)
		at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:389)
		at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:63)
		at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
		at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1741)
		at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
		at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1190)
		at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
		at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
		at java.base/java.lang.Thread.run(Thread.java:1583)

The changed mapping:

@Embeddable
.....
@Struct(name = "TT_PARTNER", attributes = { "listOfPartners" })
public class TTPartner implements Serializable {
    private static final long serialVersionUID = -6155092651111036183L;
    private List<OTPartner> listOfPartners;
}
@Embeddable
...
@Struct(name = "OT_PARTNER", attributes = { "code", "name", "phoneNumber" })
public class OTPartner implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String code;
    private String name;
    private String phoneNumber;
}
@Embeddable
..
@Struct(name = "OT_COUNTRY", attributes = { "countryCode", "countryName", "amount", "partners" })
public class OTCountry implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String countryCode;
    private String countryName;
    private Long amount;
    @Embedded
    private TTPartner partners;
}

Regards

The mapping looks wrong to me. Try this instead:

@Embeddable
...
@Struct(name = "OT_PARTNER", attributes = { "code", "name", "phoneNumber" })
public class OTPartner implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String code;
    private String name;
    private String phoneNumber;
}
@Embeddable
..
@Struct(name = "OT_COUNTRY", attributes = { "countryCode", "countryName", "amount", "partners" })
public class OTCountry implements Serializable {
    private static final long serialVersionUID = -6577203874286836224L;
    private String countryCode;
    private String countryName;
    private Long amount;
    @Column(columnDefinition = "TT_PARTNER")
    private List<OTPartner> listOfPartners;
}

Unfortunately this change leads to a deployment exception:

18-Jul-2025 08:40:00.583 SEVERE [Catalina-utility-2] org.apache.catalina.core.StandardContext.listenerStart Exception sending context initialized event to listener instance of class [org.springframework.web.context.ContextLoaderListener]
	org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'xxxxEntityManagerFactory' defined in xxxx.config.PersistenceConfig: Unsupported aggregate SQL type: 2003
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1806)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:600)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
		at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:337)
		at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
		at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:335)
		at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:205)
		at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:954)
		at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:625)
		at org.springframework.web.context.ContextLoader.configureAndRefreshWebApplicationContext(ContextLoader.java:394)
		at org.springframework.web.context.ContextLoader.initWebApplicationContext(ContextLoader.java:274)
		at org.springframework.web.context.ContextLoaderListener.contextInitialized(ContextLoaderListener.java:102)
		at org.apache.catalina.core.StandardContext.listenerStart(StandardContext.java:3997)
		at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:4424)
		at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:171)
		at org.apache.catalina.core.ContainerBase.addChildInternal(ContainerBase.java:599)
		at org.apache.catalina.core.ContainerBase.addChild(ContainerBase.java:571)
		at org.apache.catalina.core.StandardHost.addChild(StandardHost.java:654)
		at org.apache.catalina.startup.HostConfig.deployWAR(HostConfig.java:969)
		at org.apache.catalina.startup.HostConfig$DeployWar.run(HostConfig.java:1911)
		at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
		at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:317)
		at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
		at java.base/java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:123)
		at org.apache.catalina.startup.HostConfig.deployWARs(HostConfig.java:771)
		at org.apache.catalina.startup.HostConfig.deployApps(HostConfig.java:423)
		at org.apache.catalina.startup.HostConfig.check(HostConfig.java:1678)
		at org.apache.catalina.startup.HostConfig.lifecycleEvent(HostConfig.java:299)
		at org.apache.catalina.util.LifecycleBase.fireLifecycleEvent(LifecycleBase.java:114)
		at org.apache.catalina.core.ContainerBase.backgroundProcess(ContainerBase.java:973)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1172)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.processChildren(ContainerBase.java:1176)
		at org.apache.catalina.core.ContainerBase$ContainerBackgroundProcessor.run(ContainerBase.java:1154)
		at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:572)
		at java.base/java.util.concurrent.FutureTask.runAndReset(FutureTask.java:358)
		at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:305)
		at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1144)
		at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:642)
		at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
		at java.base/java.lang.Thread.run(Thread.java:1583)
	Caused by: java.lang.IllegalArgumentException: Unsupported aggregate SQL type: 2003
		at org.hibernate.dialect.aggregate.OracleAggregateSupport.aggregateComponentAssignmentExpression(OracleAggregateSupport.java:390)
		at org.hibernate.dialect.aggregate.AggregateSupport.aggregateComponentAssignmentExpression(AggregateSupport.java:111)
		at org.hibernate.boot.model.internal.AggregateComponentSecondPass.doSecondPass(AggregateComponentSecondPass.java:148)
		at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1812)
		at org.hibernate.boot.internal.InFlightMetadataCollectorImpl.processSecondPasses(InFlightMetadataCollectorImpl.java:1781)
		at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.coordinateProcessors(MetadataBuildingProcess.java:346)
		at org.hibernate.boot.model.process.spi.MetadataBuildingProcess.complete(MetadataBuildingProcess.java:215)
		at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.metadata(EntityManagerFactoryBuilderImpl.java:1412)
		at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.populateSessionFactoryBuilder(EntityManagerFactoryBuilderImpl.java:1493)
		at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:1475)
		at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:75)
		at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:390)
		at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:409)
		at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:396)
		at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:366)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1853)
		at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1802)
		... 39 more

Any other idea?
Our problem is that the existing stored procedures and oracle data types cannot be changed,in this project. These changes will be part of a later migration project.

Regards

This could be a bug. Please try to create a reproducer with our test case template and if you are able to reproduce the issue, create a bug ticket in our issue tracker and attach that reproducer.