Type exception when creating a custom user type

We are migrating from hibernate 5.x.x to hibernate 6.5.2. Our code base has custom user type which needs to be migrated/update. We have type mismatch issues while migrating these custom user type.

Below is our custom user type.

/**
 * A platform specific implementation of SpcfUniqueId.   
 */
  
public class SpcfUniqueIdImpl extends SpcfUniqueId
{    
	/**
	 * needed for serialization
	 */  
    private static final long serialVersionUID = -5416092023493031186L; 
      
    /**
     * the byte array to hold the value of this UUID
     */
    private byte[] mBytes = null;  
    
    /** 
     * Constructs a new SpcfUniqueId instance with an empty value  
     */
 	public SpcfUniqueIdImpl()  
 	{ 
 		this(false);
 	}
 	
 	/**
     * Constructs a new SpcfUniqueId instance with either empty or random value.
     * @param initializeToRandomValue Initialize with a pseudo-random initial value if true
     */
 	public SpcfUniqueIdImpl(boolean initializeToRandomValue)  
 	{   
 		String uniqueId = null;
 		if (initializeToRandomValue)
 		{
 			uniqueId = doGenerateRandomUniqueIdString();
 		}
 		else
 		{
 			uniqueId = EmptyGuid;
 		} 
 		
 		mBytes = toByteArray(uniqueId);
 	} 
 	
 	/**
     * Constructs a new SpcfUniqueId instance from the specified string.
     * The string must be a series of 32 hexadecimal digits in the standard
     * format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX or one of the acceptable
     * following formats: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, 
     * {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}, 
     * or (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX). Any non-hexadecimal 
     * digit character can be used as a separator, if consistent. 
     * @param s The string representation of the unique id value. 
     * @throws SpcfArgumentNullException - if the specified string is null
     * @throws SpcfIllegalArgumentException - if the specified string is empty or does 
     * not have the expected format
     */
 	public SpcfUniqueIdImpl(String s)
 	{ 
 		SpcfParamValidator.checkIsNotNull(s, "s"); 
 		
 		mBytes = toByteArray(validateFormat(s));
 	} 
 	
 	/**
     * Constructs a new SpcfUniqueId instance from the specified byte array.
     * The byte array must be 16 bytes long and match byte order for java.util.UUID
     * @param bytes The binary representation of the java.util.UUID. 
     * @throws SpcfArgumentNullException - if the specified byte array is null
     * @throws SpcfIllegalArgumentException - if the byte array is not the expected size.  
     */
 	public SpcfUniqueIdImpl(byte[] bytes)
 	{ 
 		SpcfParamValidator.checkIsNotNull(bytes, "bytes");
 		if( bytes.length != 16 )
 		{
 			throw new SpcfIllegalArgumentException( "bytes must be 16 bytes in length" );
 		}   
 		mBytes = bytes;
 	}  

 	/**
     * Constructs a new SpcfUniqueId instance from the specified java.util.UUID.
     * @param uid The java.util.UUID to encapsulate. 
     * @throws SpcfArgumentNullException - if the specified byte java.util.UUID is null
     */
    public SpcfUniqueIdImpl(java.util.UUID uid)
    {
        SpcfParamValidator.checkIsNotNull(uid, "uid");
        mBytes = toByteArray(uid.toString());
    } 
 
	/**
	 * @see com.intuit.spc.foundations.portability.SpcfUniqueId#equals(Object)
	 */
    @Override
    public boolean equals(Object o)
    { 
		if (o == null)
		{
			return false;
		}

		if (o instanceof SpcfUniqueIdImpl)
		{ 
			return java.util.Arrays.equals(mBytes, ((SpcfUniqueIdImpl)o).toByteArray());
		}
		return false;
	}
      
	/**
	 * @see com.intuit.spc.foundations.portability.SpcfUniqueId#hashCode()
	 */
    @Override
	public int hashCode()
	{
		int hash = 0;
        for (int i = 0; i < mBytes.length; i ++)
        	hash += mBytes[i] * 31 ^ mBytes.length - (i + 1); 
        return hash;
	}
	
    /** 
     * @see com.intuit.spc.foundations.portability.SpcfUniqueId#toString()
     */
    @Override
    public String toString()
    {
 		long mostSig = 0;
 		for (int i = 0; i < 8; i++) {
 			mostSig = (mostSig << 8) | (mBytes[i] & 0xff);
 		}

 		long leastSig = 0;
 		for (int i = 8; i < 16; i++) {
 			leastSig = (leastSig << 8) | (mBytes[i] & 0xff);
 		}
 		java.util.UUID uid = new UUID(mostSig, leastSig); 
 		return uid.toString();
    } 
    
    /** 
     * @see com.intuit.spc.foundations.portability.SpcfUniqueId#getStandardFormatString()
     */
    @Override
    public String getStandardFormatString()
    {   
    	return toString();
    }  
    
    /**
     * @see com.intuit.spc.foundations.portability.SpcfUniqueId#doGenerateRandomUniqueIdString()
     */
    @Override
	protected String doGenerateRandomUniqueIdString()
	{  
		java.util.UUID uid = java.util.UUID.randomUUID();
		return uid.toString(); 
	} 
	 
	 /**
     * Get the byte array in standard network byte order.
     * @param standardizedString the UUID string in standarized order
     * @return the UUID in a byte array in standard network byte order
     */ 
   private byte[] toByteArray(String standardizedString)
   {
   		byte[] byteArray = new byte[16];
   		String id = standardizedString;
   		for (int i = 0, j = 0; i < 36; ++j)  
   		{  
   			// Need to bypass hyphens:  
   			switch (i)  
   			{  
   			case 8 :  
   			case 13 :  
   			case 18 :  
   			case 23 :  
   				++i;  
   			}  
   			char c = id.charAt(i);  

   			if (c >= '0' && c <= '9')  
   			{  
   				byteArray[j] = (byte) ((c - '0') << 4);  
   			}  
   			else if (c >= 'a' && c <= 'f')  
   			{  
   				byteArray[j] = (byte) ((c - 'a' + 10) << 4);  
   			}  
   			c = id.charAt(++i);  

   			if (c >= '0' && c <= '9')  
   			{  
   				byteArray[j] |= (byte) (c - '0');  
   			}  
   			else if (c >= 'a' && c <= 'f')  
   			{  
   				byteArray[j] |= (byte) (c - 'a' + 10);  
   			}   
   			++i;  
   		} 
   		return byteArray; 
   }

	 /**
      * Get the byte array in standard network byte order.
      * @return the UUID in a byte array in standard network byte order
      */ 
    public byte[] toByteArray()
    {
    	return mBytes; 
    }

    /**
     *  Get the equivalent platform specific java.util.UUID
     *  @return the java.util.UUID representation of this value
     */ 
    public java.util.UUID getSpecific()
    {
    	//return java.util.UUID.fromString(getStandardizedString()); 
    	return java.util.UUID.fromString(toString()); 
    } 
    
    /**
     * Validates a string to see if its in accepted format.  Returns standard 
     * format if it is, else throws SpcfIllegalArgumentException. 
     * The string must be a series of 32 hexadecimal digits in the standard
     * format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX or one of the acceptable
     * following formats: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX, 
     * {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX}, 
     * or (XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX). Any non-hexadecimal 
     * digit character can be used as a separator, if consistent. 
     * @param s The string representation of the unique id value.  
     * @return the UUID string in standard format
     * @throws SpcfIllegalArgumentException - if the specified string is empty or does 
     * not have the expected format
     */
    protected String validateFormat(String s)
    {  
    	// valid lengths are 32, 36, 34, 38
    	// valid brackets are {} and () 
    	
    	int original_length = s.length(); 
    	String tempId = s.toLowerCase();  
    	
    	if (original_length == 0)
    	{ 
    		throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "cannot be zero length.");
        }
    	
    	if (original_length > 38 || original_length < 32 )
    	{ 
    		throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "is not the correct size");
        } 
    	
    	if (s.charAt(0) == '{')
    	{
    		if (s.charAt(original_length - 1) != '}')	
    		{ 
    			throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
			}  
    		tempId = tempId.substring(1, original_length - 1);
    	}
    	
    	if (s.charAt(0) == '(')
    	{
    		if (s.charAt(original_length - 1) != ')')	
    		{ 
    			throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
			} 
    		tempId = tempId.substring(1, original_length - 1);
    	}
    	
    	  
    	// at this point, length must be either 32 (no separators) or 36 to be valid 
    	if (tempId.length() == 36)
    	{
    		 //remove separators and check consistency 
    		char separator;
    		separator = tempId.charAt(8);
    		boolean separatorsNotConsistent = false; 
    			 
			if (java.lang.Character.isLetterOrDigit(separator))
			{  
				//separator cannot be a hex digit
				throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
    		}  
			// make sure separator is consistent
			if (tempId.charAt(13) != separator) { separatorsNotConsistent = true;}
			if (tempId.charAt(18) != separator) { separatorsNotConsistent = true;}
			if (tempId.charAt(23) != separator) { separatorsNotConsistent = true;}
			
			if (separatorsNotConsistent)
			{ 
				throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
			}   
			
			StringBuilder sb = new StringBuilder(); 
	    	sb.append(tempId.substring(0, 8));  
			sb.append(tempId.substring(9, 13));  
			sb.append(tempId.substring(14, 18));  
			sb.append(tempId.substring(19, 23));  
			sb.append(tempId.substring(24));    
			 
			//remove separators and check consistency  
			tempId = sb.toString();
    	} 
		
    	// at this point separators are 
    	if (tempId.length() != 32) 
    	{ 
    		throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "does not have the correct format.");
    	}   
    	 
    	//validate hex digits
    	String hexDigits = "0123456789abcdef";  
    	char c;
    	for (int i = 0; i < tempId.length(); i++) 
 		{ 
    		c = tempId.charAt(i); 
			if (hexDigits.indexOf(c) < 0) 
			{   
				throw new SpcfIllegalArgumentException("Unique ID: " + s + " " + "has invalid hexadecimal digits.");
			}  
 		} 
    	 
    	// add standard separators
    	StringBuilder sbFinalId = new StringBuilder();  
    	sbFinalId.append(tempId.substring(0, 8)); 
    	sbFinalId.append("-");
    	sbFinalId.append(tempId.substring(8, 12)); 
    	sbFinalId.append("-");
    	sbFinalId.append(tempId.substring(12, 16)); 
    	sbFinalId.append("-");
    	sbFinalId.append(tempId.substring(16, 20)); 
    	sbFinalId.append("-");
    	sbFinalId.append(tempId.substring(20));    
		return sbFinalId.toString();
    }    
    
} 

User type implementation.

/**
 * This class implements the UserType to persist SpcfUniqueId to the database.
 */
public final class SpcfUniqueIdUserType implements UserType<SpcfUniqueId>, Serializable
{
    private static final long serialVersionUID = -5421900602922035504L;
    /**
     * logger for logging SQL binding variable with the SQL. Use log4j logger
     * so we respect the same log level and log to where Hibernate logs.
     */
    private static final SpcfLogger sHibernateLogger = Application.getLogger("org.hibernate.type");

    /**
     * Types returned by sql-types method.
     */
    private static final int[] sSqlTypes = new int[] { Types.VARCHAR };

    /**
     * @see org.hibernate.usertype.UserType#sqlTypes()
     */
    public final int[] sqlTypes()
    {
        return sSqlTypes;
    }

    /**
     * @see org.hibernate.usertype.UserType#equals(Object, Object)
     */
    public final boolean equals(SpcfUniqueId x, SpcfUniqueId y)
    {
        return (x == null) ? (y == null) : x.equals(y);
    }

    /**
     * @see org.hibernate.usertype.UserType#hashCode(Object)
     */
    public final int hashCode(SpcfUniqueId obj)
    {
       return obj.hashCode();
    }

    /**
     * @see org.hibernate.usertype.UserType#isMutable()
     */
    public final boolean isMutable()
    {
        return true;
    }

    @Override
    public int getSqlType() {
        return 0;
    }

    /**
     * @see org.hibernate.usertype.UserType#returnedClass()
     */
    @SuppressWarnings("unchecked")
    public final Class returnedClass()
    {
        return SpcfUniqueId.class;
    }

    /**
     * @see org.hibernate.usertype.UserType#deepCopy(Object)
     */
    public final SpcfUniqueId deepCopy(SpcfUniqueId value)
    {
        if (value == null)
        {
            return null;
        }
//        else if (value instanceof String)
//        {
//            return SpcfTypeConversionUtilities.convertStringToUniqueId((String)value);
//        }
        else if (value instanceof SpcfUniqueId)
        {
            return value;
        }
        else
        {
            throw new HibernateException("Unsupported value type: " + value.getClass());
        }
    }

    /**
     * @see org.hibernate.usertype.UserType#nullSafeGet(ResultSet, String[], SharedSessionContractImplementor, Object)
     */
    @Override
    public final SpcfUniqueId nullSafeGet(ResultSet rs, int position, SharedSessionContractImplementor sharedSessionContractImplementor, Object owner) throws SQLException
    {
        String uniqueId = rs.getString(position);

        return SpcfTypeConversionUtilities.convertStringToUniqueId(uniqueId);

    }

    /**
     * @see org.hibernate.usertype.UserType#nullSafeSet(PreparedStatement, Object, int, SharedSessionContractImplementor)
     */
    @Override
    public final void nullSafeSet(PreparedStatement st, SpcfUniqueId value, int index, SharedSessionContractImplementor sharedSessionContractImplementor) throws SQLException
    {

        Object localValue = value;

        // convert from SpcfUniqueId to String
        if (value instanceof SpcfUniqueId)
        {
            localValue = SpcfTypeConversionUtilities.convertUniqueIdToString((SpcfUniqueId)value);
        }
        // If value is null or BigDecimal then simply save the value
        if (sHibernateLogger.isDebugEnabled())
        {
            sHibernateLogger.debug(this.getClass().getSimpleName() + " - binding '"
                    + value + "' to parameter: " + index);
        }

        st.setString(index, (String)localValue);

    }

    /**
     * @see org.hibernate.usertype.UserType#assemble(Serializable, Object)
     */
    public final SpcfUniqueId assemble(Serializable cached, Object owner) throws HibernateException
    {
        return null;
    }

    /**
     * @see org.hibernate.usertype.UserType#disassemble(Object)
     */
    public final Serializable disassemble(SpcfUniqueId value) throws HibernateException
    {
        return null;
    }

    /**
     * @see org.hibernate.usertype.UserType#replace(Object, Object, Object)
     */
    public final SpcfUniqueId replace(SpcfUniqueId original, Object target, Object owner) throws HibernateException
    {
        return deepCopy(original);
    }
}

Stack trace:

java.lang.ArrayStoreException: com.intuit.spc.foundations.portabilitySpecific.SpcfUniqueIdImpl

	at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:272)
	at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:33)
	at org.hibernate.type.descriptor.jdbc.ArrayJdbcType$1.getArray(ArrayJdbcType.java:136)
	at org.hibernate.type.descriptor.jdbc.ArrayJdbcType$1.doBind(ArrayJdbcType.java:101)
	at org.hibernate.type.descriptor.jdbc.BasicBinder.bind(BasicBinder.java:61)
	at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:130)
	at org.hibernate.sql.exec.internal.AbstractJdbcParameter.bindParameterValue(AbstractJdbcParameter.java:101)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.bindParameters(DeferredResultSetAccess.java:203)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.executeQuery(DeferredResultSetAccess.java:233)
	at org.hibernate.sql.results.jdbc.internal.DeferredResultSetAccess.getResultSet(DeferredResultSetAccess.java:167)
	at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.advanceNext(JdbcValuesResultSetImpl.java:265)
	at org.hibernate.sql.results.jdbc.internal.JdbcValuesResultSetImpl.processNext(JdbcValuesResultSetImpl.java:145)
	at org.hibernate.sql.results.jdbc.internal.AbstractJdbcValues.next(AbstractJdbcValues.java:19)
	at org.hibernate.sql.results.internal.RowProcessingStateStandardImpl.next(RowProcessingStateStandardImpl.java:67)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:182)
	at org.hibernate.sql.results.spi.ListResultsConsumer.consume(ListResultsConsumer.java:33)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.doExecuteQuery(JdbcSelectExecutorStandardImpl.java:211)
	at org.hibernate.sql.exec.internal.JdbcSelectExecutorStandardImpl.executeQuery(JdbcSelectExecutorStandardImpl.java:83)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:76)
	at org.hibernate.sql.exec.spi.JdbcSelectExecutor.list(JdbcSelectExecutor.java:65)
	at org.hibernate.loader.ast.internal.CollectionBatchLoaderArrayParam.initializeKeys(CollectionBatchLoaderArrayParam.java:204)
	at org.hibernate.loader.ast.internal.AbstractCollectionBatchLoader.load(AbstractCollectionBatchLoader.java:94)
	at org.hibernate.loader.ast.internal.CollectionBatchLoaderArrayParam.load(CollectionBatchLoaderArrayParam.java:118)
	at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:703)
	at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:67)
	at com.intuit.sbd.payroll.psp.hibernate.PSPInitializeCollectionEventListener.onInitializeCollection(PSPInitializeCollectionEventListener.java:44)
	at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:127)
	at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1720)
	at org.hibernate.collection.spi.AbstractPersistentCollection.lambda$initialize$3(AbstractPersistentCollection.java:615)
	at org.hibernate.collection.spi.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:264)
	at org.hibernate.collection.spi.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:613)
	at org.hibernate.collection.spi.AbstractPersistentCollection.read(AbstractPersistentCollection.java:136)

Analysis:
Getting java.lang.ArrayStoreException: at org.hibernate.type.descriptor.java.ArrayJavaType.unwrap(ArrayJavaType.java:272)

final Class<?> preferredJavaTypeClass = type.getComponentType();
			final Object[] unwrapped = (Object[]) Array.newInstance( preferredJavaTypeClass, value.length );
			for ( int i = 0; i < value.length; i++ ) {
				unwrapped[i] = getElementJavaType().unwrap( value[i], preferredJavaTypeClass, options );
			}

Unwrapped array was of string type where as unwrapped value was of SpcfUniqueId type, on further digging we identified the preferred java type was string instead of SpcfUniqueId

classname ArrayJdbcType
Preferred java type is computed as follows:

private java.sql.Array getArray(X value, WrapperOptions options) throws SQLException {
				final TypeConfiguration typeConfiguration = options.getSessionFactory().getTypeConfiguration();
				final JdbcType elementJdbcType = ( (ArrayJdbcType) getJdbcType() ).getElementJdbcType();
				final JdbcType underlyingJdbcType = typeConfiguration.getJdbcTypeRegistry()
						.getDescriptor( elementJdbcType.getDefaultSqlTypeCode() );
				final Class<?> preferredJavaTypeClass = elementJdbcType.getPreferredJavaTypeClass( options );
				final Class<?> elementJdbcJavaTypeClass;
				if ( preferredJavaTypeClass == null ) {
					elementJdbcJavaTypeClass = underlyingJdbcType.getJdbcRecommendedJavaTypeMapping(
							null,
							null,
							typeConfiguration
					).getJavaTypeClass();
				}
				else {
					elementJdbcJavaTypeClass = preferredJavaTypeClass;
				}
				//noinspection unchecked
				final Class<Object[]> arrayClass = (Class<Object[]>)
						Array.newInstance( elementJdbcJavaTypeClass, 0 ).getClass();
				final Object[] objects = getJavaType().unwrap( value, arrayClass, options );

				final SharedSessionContractImplementor session = options.getSession();
				final String typeName = getElementTypeName( elementJdbcType, session );
				return session.getJdbcCoordinator().getLogicalConnection().getPhysicalConnection()
						.createArrayOf( typeName, objects );
			}

elementJdbcType was of type UserTypeSqlTypeAdapter and we were getting null.
UserTypeSqlTypeAdapter extends JdbcType and does not override getPreferredJavaTypeClass() and returns null instead of correct type.

As per our understanding UserTypeSqlTypeAdapter should override getPreferredJavaType as follows:

public Class getPreferredJavaTypeClass() {
		return userType.returnedClass();
	}

Is our understanding correct, can you please help us ?

Please share the entity class that owns the collection and the entity class for the element of the collection. Also, please update to ORM 6.6

We are getting the issue while parsing the bind parameters for the query itself and not during the resultSet mapping. The query is hibernate generated query for lazy loading the related entity. The entity being loaded has composite primary key with both attributes of type SpcfUniqueId. That is the reason we have array of values in bind params. The attribute type for the bind param in this case is being taken up as ArrayJavaType with child element as SpcfUniqueId.

As already mentioned, on the jdbc side, the attribute type is ArrayJdbcType with element as UserTypeSqlTypeAdapter and UserTypeSqlTypeAdapter always returns null as preferredJavaType and recommended java type is computed which is always String for Varchar column type. But the composite primary key elements are both of type SpcfUniqueId on the Java side.

Look, I can’t help you unless I see the entity mappings, so if you’re not willing to share that and think you have 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.

Entity class

@Entity
@Table(name = "PSP_COMP_PMTTEMPLATE_PMTMETHOD")
@Access(AccessType.PROPERTY)
@AttributeOverride(name = "Id", column = @Column(name = "PMT_TEMPLATE_PAYMENTMETHOD_SEQ"))
public class PaymentTemplatePaymentMethod   {
    private SpcfUniqueId mId = SpcfUniqueId.generateRandomUniqueId();

    void setId(SpcfUniqueId uniqueId) {
        mId = uniqueId;
    }

    @Id
    @Type(com.intuit.sbd.payroll.psp.hibernate.SpcfUniqueIdUserType.class)
    public SpcfUniqueId getId() {
        if (mId == null) {
            mId = SpcfUniqueId.generateRandomUniqueId();
        }

        return mId;
    }

    void setPaymentMethodRequirementSet(Set<PaymentMethodRequirement> pPaymentMethodRequirement)
    {
    	mPaymentMethodRequirementSet =  new Set<PaymentMethodRequirement>(pPaymentMethodRequirement);
    }

    @OneToMany(cascade =  CascadeType.MERGE   )
    @JoinColumn(name = "PMT_TEMPLATE_PMT_METHOD_FK", nullable=false)
    @BatchSize(size =100)
    @OptimisticLock(excluded = false)
    @AttributeAccessor(value="property")
    @SuppressWarnings("unchecked")
    Set<PaymentMethodRequirement> getPaymentMethodRequirementSet()
    {
    	return mPaymentMethodRequirementSet;
    }

    // equals and hashcode

}

Collection class

@Entity
@Access(AccessType.PROPERTY)
@Table(name = "PSP_PAYMENT_METHOD_REQUIREMENT")
@Inheritance(strategy = InheritanceType.JOINED)
@AttributeOverride(name = "Id", column = @Column(name = "PAYMENT_METHOD_REQUIREMENT_SEQ"))
public class PaymentMethodRequirement 
{


    @Version
    @Column(name = "VERSION", columnDefinition = "bigint default -1", nullable = false)
    @AttributeAccessor(value="com.intuit.sbd.payroll.psp.hibernate.PSPMixedAccessor")
    public Long getVersion() {
        if(Objects.isNull(super.getVersion()) || super.getVersion().equals(-1L)){
            return null;
        }
        return super.getVersion();
    }

    private SpcfUniqueId mId = SpcfUniqueId.generateRandomUniqueId();

    void setId(SpcfUniqueId uniqueId) {
        mId = uniqueId;
    }

    @Id
    @Type(com.intuit.sbd.payroll.psp.hibernate.SpcfUniqueIdUserType.class)
    public SpcfUniqueId getId() {
        if (mId == null) {
            mId = SpcfUniqueId.generateRandomUniqueId();
        }

        return mId;
    }

    // equals and hashcode
}

Below line is giving error

Set<PaymentMethodRequirement> requirement = paymentTemplatePaymentMethod.getPaymentMethodRequirementSet()

Please implement the getValueConverter() method in SpcfUniqueIdUserType to allow the conversion between SpcfUniqueId and String. This should hopefully allow the array based batch loading code to work properly.

As a last resort, you could also disable array support for this by extending your Dialect of choice and overriding the useArrayForMultiValuedParameters() method to always return false.