private boolean canValidateProp(final Class<T> configSuperType, final Prop prop) {
        // Only want to validate config objects
        final Class<?> valueClass = prop.getValueClass();

        final boolean isIncludedInValidation;

        if (prop.getValueFromConfigObject() == null) {
            isIncludedInValidation = false;
        } else if (configSuperType.isAssignableFrom(valueClass)) {
            // One of our config classes
            isIncludedInValidation = true;
        } else if (Collection.class.isAssignableFrom(valueClass)) {
            // e.g. List<?>, as in forwardStreamConfig.forwardDestinations
            final List<Type> genericTypes = PropertyUtil.getGenericTypes(prop.getValueType());
            // TODO what to do about maps? Hopefully we won't have maps containing config objects
            if (genericTypes.size() == 1) {
                final Class<?> genericTypeClass = PropertyUtil.getDataType(genericTypes.get(0));
                if (configSuperType.isAssignableFrom(genericTypeClass)) {
                    // e.g. List<XxxxConfig>, Set<XxxxxConfig>, etc.
                    isIncludedInValidation = true;
                } else {
                    // e.g. List<String>
                    isIncludedInValidation = false;
                }
            } else {
                isIncludedInValidation = false;
            }
        } else {
            isIncludedInValidation = false;
        }

        if (LOGGER.isDebugEnabled() && valueClass.getName().startsWith("stroom")) {
            LOGGER.debug(() -> LogUtil.message(
                    "Testing class {}, isIncluded {}",
                    valueClass.getSimpleName(),
                    isIncludedInValidation));
        }
        return isIncludedInValidation;
    }
Walks the config object tree validating each branch regardless of whether they have @Valid
annotations.
stroom-util/src/main/java/stroom/util/config/ConfigValidator.java