Ensuring if an object is Assignable from a class type
private Object ensureType(Class&lt;?&gt; expectedClass, Object value) {

Your names could be slightly better, given that this function does not only ensure something, but also converts it.
private Object convert(Class&lt;?&gt; targetClass, Object value) {


    throw new ClassCastException(
            &quot;argument &quot; + value + &quot; of type &quot; + valueClass + &quot; cannot be converted to &quot; + expectedClass);

Given that the function converts, I find the ClassCastException inappropriate. An IllegalStateException or IllegalArgumentException might be better fitting.

What you're doing here is complex, and there is not really an easier way to do it either. You have n classes that you want to map to m classes, so the complexity will always be there in one way or another. The best thing you can do is make it more readable. For example with extracting the converting parts into functions:
    // ...
    
    if (targetClass.isAssignableFrom(String.class)) {
        return convertToString(value);
    }

    if (targetClass.isAssignableFrom(Date.class)) {
        return convertToDate(value);
    }
    
    if (targetClass.isAssignableFrom(Integer.class)) {
        return convertToInteger(value);
    }
    
    // ...

That is as readable as it gets, in my opinion.
If you like classes, you can always extract the conversion into a single class for each mapping, and then iterate over that:
public interface Converter {
    public Class&lt;?&gt; getTargetType();
    public Object convert(Object value);
}

// ...

List&lt;Converter&gt; converters = new ArrayList&lt;&gt;();
converters.add(new StringConverter());
converters.add(new DateConverter());
converters.add(new IntegerConverter());
// ...

// ...

for (Converter converter : converters) {
    if (targetClass.isAssignableFrom(converter.getTargetType())) {
        return converter.convert(value);
    }
} 

This, however, has the downside to split everything over many classes. A possible remedy to that might be to use single functions in the same class, like this:
HashMap&lt;Class&lt;?&gt;, Function&lt;Object, Object&gt;&gt; converters = new HashMap&lt;&gt;();
converters.put(String.class, this::convertToString);
converters.put(Date.class, this::convertToDate);
converters.put(Integer.class, this::convertToInteger);
// ...

// ...

for (Entry&lt;Class&lt;?&gt;, Function&lt;Object, Object&gt;&gt; converterEntry : converters.entrySet()) {
    if (targetClass.isAssignableFrom(converterEntry.getKey())) {
        return converterEntry.getValue().apply(value);
    }
} 

That might be one of the easier readable and maintainable variants.
There is another, completely insane option, actually, which I feel to need to point out. You can use the above setup, with a method for every conversion, and reflection to build this list dynamically. Something along these lines:
private Integer convertToInteger(Object value);
private Date convertToDate(Object value);
private String convertToString(Object value);

// Pseudo-Code follows:

for (Method method : getClass().getDeclaredMethods()) {
    if (method.getName().startsWith(&quot;convert&quot;)) {
        if (targetValue matches method return type
                &amp;&amp; value matches first method parameter) {
            return method.invoke(this, value);
        }
    }
}

But that should be an absolute last resort, as it is not quite easy to maintain without knowing what you've got at your hands there.