diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java
index c7bf287643e3..3786fc3acad9 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessor.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2012-2022 the original author or authors.
+ * Copyright 2012-2023 the original author or authors.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -24,9 +24,10 @@
 import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
 import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
+import org.springframework.boot.context.properties.bind.BindMethod;
+import org.springframework.boot.context.properties.bind.Bindable;
 import org.springframework.boot.context.properties.bind.BindableRuntimeHintsRegistrar;
 import org.springframework.util.ClassUtils;
-import org.springframework.util.CollectionUtils;
 
 /**
  * {@link BeanFactoryInitializationAotProcessor} that contributes runtime hints for
@@ -43,36 +44,38 @@ class ConfigurationPropertiesBeanFactoryInitializationAotProcessor implements Be
 	public ConfigurationPropertiesReflectionHintsContribution processAheadOfTime(
 			ConfigurableListableBeanFactory beanFactory) {
 		String[] beanNames = beanFactory.getBeanNamesForAnnotation(ConfigurationProperties.class);
-		List<Class<?>> types = new ArrayList<>();
+		List<Bindable<?>> bindables = new ArrayList<>();
 		for (String beanName : beanNames) {
 			Class<?> beanType = beanFactory.getType(beanName, false);
 			if (beanType != null) {
-				types.add(ClassUtils.getUserClass(beanType));
+				BindMethod bindMethod = beanFactory.containsBeanDefinition(beanName)
+						? (BindMethod) beanFactory.getBeanDefinition(beanName).getAttribute(BindMethod.class.getName())
+						: null;
+				bindables.add(Bindable.of(ClassUtils.getUserClass(beanType))
+					.withBindMethod((bindMethod != null) ? bindMethod : BindMethod.JAVA_BEAN));
 			}
 		}
-		if (!CollectionUtils.isEmpty(types)) {
-			return new ConfigurationPropertiesReflectionHintsContribution(types);
-		}
-		return null;
+		return (!bindables.isEmpty()) ? new ConfigurationPropertiesReflectionHintsContribution(bindables) : null;
 	}
 
 	static final class ConfigurationPropertiesReflectionHintsContribution
 			implements BeanFactoryInitializationAotContribution {
 
-		private final Iterable<Class<?>> types;
+		private final List<Bindable<?>> bindables;
 
-		private ConfigurationPropertiesReflectionHintsContribution(Iterable<Class<?>> types) {
-			this.types = types;
+		private ConfigurationPropertiesReflectionHintsContribution(List<Bindable<?>> bindables) {
+			this.bindables = bindables;
 		}
 
 		@Override
 		public void applyTo(GenerationContext generationContext,
 				BeanFactoryInitializationCode beanFactoryInitializationCode) {
-			BindableRuntimeHintsRegistrar.forTypes(this.types).registerHints(generationContext.getRuntimeHints());
+			BindableRuntimeHintsRegistrar.forBindables(this.bindables)
+				.registerHints(generationContext.getRuntimeHints());
 		}
 
-		Iterable<Class<?>> getTypes() {
-			return this.types;
+		Iterable<Bindable<?>> getBindables() {
+			return this.bindables;
 		}
 
 	}
diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java
index 0ed6fecc295c..ebb002a15163 100644
--- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java
+++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrar.java
@@ -24,6 +24,7 @@
 import java.util.HashSet;
 import java.util.Map;
 import java.util.Set;
+import java.util.stream.Stream;
 import java.util.stream.StreamSupport;
 
 import kotlin.jvm.JvmClassMappingKt;
@@ -51,8 +52,8 @@
  * {@link RuntimeHintsRegistrar} that can be used to register {@link ReflectionHints} for
  * {@link Bindable} types, discovering any nested type it may expose through a property.
  * <p>
- * This class can be used as a base-class, or instantiated using the {@code forTypes}
- * factory methods.
+ * This class can be used as a base-class, or instantiated using the {@code forTypes} and
+ * {@code forBindables} factory methods.
  *
  * @author Andy Wilkinson
  * @author Moritz Halbritter
@@ -62,14 +63,23 @@
  */
 public class BindableRuntimeHintsRegistrar implements RuntimeHintsRegistrar {
 
-	private final Class<?>[] types;
+	private final Bindable<?>[] bindables;
 
 	/**
 	 * Create a new {@link BindableRuntimeHintsRegistrar} for the specified types.
 	 * @param types the types to process
 	 */
 	protected BindableRuntimeHintsRegistrar(Class<?>... types) {
-		this.types = types;
+		this(Stream.of(types).map(Bindable::of).toArray(Bindable[]::new));
+	}
+
+	/**
+	 * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
+	 * @param bindables the bindables to process
+	 * @since 3.0.8
+	 */
+	protected BindableRuntimeHintsRegistrar(Bindable<?>... bindables) {
+		this.bindables = bindables;
 	}
 
 	@Override
@@ -83,8 +93,8 @@ public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
 	 */
 	public void registerHints(RuntimeHints hints) {
 		Set<Class<?>> compiledWithoutParameters = new HashSet<>();
-		for (Class<?> type : this.types) {
-			new Processor(type, compiledWithoutParameters).process(hints.reflection());
+		for (Bindable<?> bindable : this.bindables) {
+			new Processor(bindable, compiledWithoutParameters).process(hints.reflection());
 		}
 		if (!compiledWithoutParameters.isEmpty()) {
 			throw new MissingParametersCompilerArgumentException(compiledWithoutParameters);
@@ -110,6 +120,27 @@ public static BindableRuntimeHintsRegistrar forTypes(Class<?>... types) {
 		return new BindableRuntimeHintsRegistrar(types);
 	}
 
+	/**
+	 * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
+	 * @param bindables the bindables to process
+	 * @return a new {@link BindableRuntimeHintsRegistrar} instance
+	 * @since 3.0.8
+	 */
+	public static BindableRuntimeHintsRegistrar forBindables(Iterable<Bindable<?>> bindables) {
+		Assert.notNull(bindables, "Bindables must not be null");
+		return forBindables(StreamSupport.stream(bindables.spliterator(), false).toArray(Bindable[]::new));
+	}
+
+	/**
+	 * Create a new {@link BindableRuntimeHintsRegistrar} for the specified bindables.
+	 * @param bindables the bindables to process
+	 * @return a new {@link BindableRuntimeHintsRegistrar} instance
+	 * @since 3.0.8
+	 */
+	public static BindableRuntimeHintsRegistrar forBindables(Bindable<?>... bindables) {
+		return new BindableRuntimeHintsRegistrar(bindables);
+	}
+
 	/**
 	 * Processor used to register the hints.
 	 */
@@ -136,15 +167,17 @@ private final class Processor {
 
 		private final Set<Class<?>> compiledWithoutParameters;
 
-		Processor(Class<?> type, Set<Class<?>> compiledWithoutParameters) {
-			this(type, false, new HashSet<>(), compiledWithoutParameters);
+		Processor(Bindable<?> bindable, Set<Class<?>> compiledWithoutParameters) {
+			this(bindable, false, new HashSet<>(), compiledWithoutParameters);
 		}
 
-		private Processor(Class<?> type, boolean nestedType, Set<Class<?>> seen,
+		private Processor(Bindable<?> bindable, boolean nestedType, Set<Class<?>> seen,
 				Set<Class<?>> compiledWithoutParameters) {
-			this.type = type;
-			this.bindConstructor = BindConstructorProvider.DEFAULT.getBindConstructor(Bindable.of(type), nestedType);
-			this.bean = JavaBeanBinder.BeanProperties.of(Bindable.of(type));
+			this.type = bindable.getType().getRawClass();
+			this.bindConstructor = (bindable.getBindMethod() != BindMethod.JAVA_BEAN)
+					? BindConstructorProvider.DEFAULT.getBindConstructor(bindable.getType().resolve(), nestedType)
+					: null;
+			this.bean = JavaBeanBinder.BeanProperties.of(bindable);
 			this.seen = seen;
 			this.compiledWithoutParameters = compiledWithoutParameters;
 		}
@@ -235,7 +268,7 @@ else if (isNestedType(propertyName, propertyClass)) {
 		}
 
 		private void processNested(Class<?> type, ReflectionHints hints) {
-			new Processor(type, true, this.seen, this.compiledWithoutParameters).process(hints);
+			new Processor(Bindable.of(type), true, this.seen, this.compiledWithoutParameters).process(hints);
 		}
 
 		private Class<?> getComponentClass(ResolvableType type) {
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java
index 1b45fc8e5d24..984cfaf4542d 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests.java
@@ -18,22 +18,26 @@
 
 import java.util.stream.Stream;
 
+import org.assertj.core.api.AbstractAssert;
+import org.assertj.core.api.AssertProvider;
+import org.assertj.core.error.BasicErrorMessageFactory;
 import org.junit.jupiter.api.Test;
 
-import org.springframework.aot.generate.GenerationContext;
 import org.springframework.aot.hint.TypeHint;
 import org.springframework.aot.hint.TypeReference;
 import org.springframework.aot.test.generate.TestGenerationContext;
 import org.springframework.beans.factory.aot.AotServices;
 import org.springframework.beans.factory.aot.BeanFactoryInitializationAotProcessor;
-import org.springframework.beans.factory.aot.BeanFactoryInitializationCode;
 import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
 import org.springframework.beans.factory.support.DefaultListableBeanFactory;
-import org.springframework.beans.factory.support.RootBeanDefinition;
 import org.springframework.boot.context.properties.ConfigurationPropertiesBeanFactoryInitializationAotProcessor.ConfigurationPropertiesReflectionHintsContribution;
+import org.springframework.boot.context.properties.bind.BindMethod;
+import org.springframework.boot.context.properties.bind.Bindable;
+import org.springframework.context.annotation.AnnotationConfigApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
 
 import static org.assertj.core.api.Assertions.assertThat;
-import static org.mockito.Mockito.mock;
 
 /**
  * Tests for {@link ConfigurationPropertiesBeanFactoryInitializationAotProcessor}.
@@ -41,6 +45,7 @@
  * @author Stephane Nicoll
  * @author Moritz Halbritter
  * @author Sebastien Deleuze
+ * @author Andy Wilkinson
  */
 class ConfigurationPropertiesBeanFactoryInitializationAotProcessorTests {
 
@@ -58,44 +63,192 @@ void processNoMatchesReturnsNullContribution() {
 	}
 
 	@Test
-	void processManuallyRegisteredSingleton() {
+	void manuallyRegisteredSingletonBindsAsJavaBean() {
 		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
 		beanFactory.registerSingleton("test", new SampleProperties());
 		ConfigurationPropertiesReflectionHintsContribution contribution = process(beanFactory);
-		assertThat(contribution.getTypes()).containsExactly(SampleProperties.class);
+		assertThat(singleBindable(contribution)).hasBindMethod(BindMethod.JAVA_BEAN).hasType(SampleProperties.class);
 		assertThat(typeHints(contribution).map(TypeHint::getType))
 			.containsExactly(TypeReference.of(SampleProperties.class));
 	}
 
 	@Test
-	void processDefinedBean() {
-		ConfigurationPropertiesReflectionHintsContribution contribution = process(SampleProperties.class);
-		assertThat(contribution.getTypes()).containsExactly(SampleProperties.class);
+	void javaBeanConfigurationPropertiesBindAsJavaBean() {
+		ConfigurationPropertiesReflectionHintsContribution contribution = process(EnableJavaBeanProperties.class);
+		assertThat(singleBindable(contribution)).hasBindMethod(BindMethod.JAVA_BEAN).hasType(JavaBeanProperties.class);
 		assertThat(typeHints(contribution).map(TypeHint::getType))
-			.containsExactly(TypeReference.of(SampleProperties.class));
+			.containsExactly(TypeReference.of(JavaBeanProperties.class));
+	}
+
+	@Test
+	void constructorBindingConfigurationPropertiesBindAsValueObject() {
+		ConfigurationPropertiesReflectionHintsContribution contribution = process(
+				EnableConstructorBindingProperties.class);
+		assertThat(singleBindable(contribution)).hasBindMethod(BindMethod.VALUE_OBJECT)
+			.hasType(ConstructorBindingProperties.class);
+		assertThat(typeHints(contribution).map(TypeHint::getType))
+			.containsExactly(TypeReference.of(ConstructorBindingProperties.class));
 	}
 
-	Stream<TypeHint> typeHints(ConfigurationPropertiesReflectionHintsContribution contribution) {
-		GenerationContext generationContext = new TestGenerationContext();
-		contribution.applyTo(generationContext, mock(BeanFactoryInitializationCode.class));
+	@Test
+	void possibleConstructorBindingPropertiesDefinedThroughBeanMethodBindAsJavaBean() {
+		ConfigurationPropertiesReflectionHintsContribution contribution = process(
+				PossibleConstructorBindingPropertiesBeanMethodConfiguration.class);
+		assertThat(singleBindable(contribution)).hasBindMethod(BindMethod.JAVA_BEAN)
+			.hasType(PossibleConstructorBindingProperties.class);
+		assertThat(typeHints(contribution).map(TypeHint::getType))
+			.containsExactly(TypeReference.of(PossibleConstructorBindingProperties.class));
+	}
+
+	@Test
+	void possibleConstructorBindingPropertiesDefinedThroughEnabledAnnotationBindAsValueObject() {
+		ConfigurationPropertiesReflectionHintsContribution contribution = process(
+				EnablePossibleConstructorBindingProperties.class);
+		assertThat(singleBindable(contribution)).hasBindMethod(BindMethod.VALUE_OBJECT)
+			.hasType(PossibleConstructorBindingProperties.class);
+		assertThat(typeHints(contribution).map(TypeHint::getType))
+			.containsExactly(TypeReference.of(PossibleConstructorBindingProperties.class));
+	}
+
+	private Stream<TypeHint> typeHints(ConfigurationPropertiesReflectionHintsContribution contribution) {
+		TestGenerationContext generationContext = new TestGenerationContext();
+		contribution.applyTo(generationContext, null);
 		return generationContext.getRuntimeHints().reflection().typeHints();
 	}
 
-	private ConfigurationPropertiesReflectionHintsContribution process(Class<?>... types) {
-		DefaultListableBeanFactory beanFactory = new DefaultListableBeanFactory();
-		for (Class<?> type : types) {
-			beanFactory.registerBeanDefinition(type.getName(), new RootBeanDefinition(type));
+	private ConfigurationPropertiesReflectionHintsContribution process(Class<?> config) {
+		try (AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(config)) {
+			return process(context.getBeanFactory());
 		}
-		return process(beanFactory);
 	}
 
 	private ConfigurationPropertiesReflectionHintsContribution process(ConfigurableListableBeanFactory beanFactory) {
 		return this.processor.processAheadOfTime(beanFactory);
 	}
 
+	private BindableAssertProvider singleBindable(ConfigurationPropertiesReflectionHintsContribution contribution) {
+		assertThat(contribution.getBindables()).hasSize(1);
+		return new BindableAssertProvider(contribution.getBindables().iterator().next());
+	}
+
 	@ConfigurationProperties("test")
 	static class SampleProperties {
 
 	}
 
+	@EnableConfigurationProperties(JavaBeanProperties.class)
+	static class EnableJavaBeanProperties {
+
+	}
+
+	@ConfigurationProperties("java-bean")
+	static class JavaBeanProperties {
+
+		private String value;
+
+		String getValue() {
+			return this.value;
+		}
+
+		void setValue(String value) {
+			this.value = value;
+		}
+
+	}
+
+	@EnableConfigurationProperties(ConstructorBindingProperties.class)
+	static class EnableConstructorBindingProperties {
+
+	}
+
+	@ConfigurationProperties("constructor-binding")
+	static class ConstructorBindingProperties {
+
+		private final String value;
+
+		ConstructorBindingProperties(String value) {
+			this.value = value;
+		}
+
+		String getValue() {
+			return this.value;
+		}
+
+	}
+
+	@Configuration(proxyBeanMethods = false)
+	static class PossibleConstructorBindingPropertiesBeanMethodConfiguration {
+
+		@Bean
+		@ConfigurationProperties(prefix = "bean-method")
+		PossibleConstructorBindingProperties possibleConstructorBindingProperties() {
+			return new PossibleConstructorBindingProperties("alpha");
+		}
+
+	}
+
+	@EnableConfigurationProperties(PossibleConstructorBindingProperties.class)
+	static class EnablePossibleConstructorBindingProperties {
+
+	}
+
+	@ConfigurationProperties("possible-constructor-binding")
+	static class PossibleConstructorBindingProperties {
+
+		private String value;
+
+		PossibleConstructorBindingProperties(String arg) {
+
+		}
+
+		String getValue() {
+			return this.value;
+		}
+
+		void setValue(String value) {
+			this.value = value;
+		}
+
+	}
+
+	static class BindableAssertProvider implements AssertProvider<BindableAssert> {
+
+		private final Bindable<?> bindable;
+
+		BindableAssertProvider(Bindable<?> bindable) {
+			this.bindable = bindable;
+		}
+
+		@Override
+		public BindableAssert assertThat() {
+			return new BindableAssert(this.bindable);
+		}
+
+	}
+
+	static class BindableAssert extends AbstractAssert<BindableAssert, Bindable<?>> {
+
+		BindableAssert(Bindable<?> bindable) {
+			super(bindable, BindableAssert.class);
+		}
+
+		BindableAssert hasBindMethod(BindMethod bindMethod) {
+			if (this.actual.getBindMethod() != bindMethod) {
+				throwAssertionError(
+						new BasicErrorMessageFactory("Expected %s to have bind method %s but bind method was %s",
+								this.actual, bindMethod, this.actual.getBindMethod()));
+			}
+			return this;
+		}
+
+		BindableAssert hasType(Class<?> type) {
+			if (!type.equals(this.actual.getType().resolve())) {
+				throwAssertionError(new BasicErrorMessageFactory("Expected %s to have type %s but type was %s",
+						this.actual, type, this.actual.getType().resolve()));
+			}
+			return this;
+		}
+
+	}
+
 }
diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java
index d0aed438f293..e42cba584075 100644
--- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java
+++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/context/properties/bind/BindableRuntimeHintsRegistrarTests.java
@@ -75,7 +75,7 @@ void registerHintsWithIterable() {
 	@Test
 	void registerHintsWhenNoClasses() {
 		RuntimeHints runtimeHints = new RuntimeHints();
-		BindableRuntimeHintsRegistrar registrar = new BindableRuntimeHintsRegistrar();
+		BindableRuntimeHintsRegistrar registrar = new BindableRuntimeHintsRegistrar(new Class<?>[0]);
 		registrar.registerHints(runtimeHints);
 		assertThat(runtimeHints.reflection().typeHints()).isEmpty();
 	}