diff --git a/modules/typed-ids-spring-convert/build.gradle.kts b/modules/typed-ids-spring-convert/build.gradle.kts
new file mode 100644
index 0000000..447a7bb
--- /dev/null
+++ b/modules/typed-ids-spring-convert/build.gradle.kts
@@ -0,0 +1,19 @@
+plugins {
+ id("framefork.java-public")
+}
+
+dependencies {
+ api(project(":typed-ids"))
+
+ compileOnly("org.springframework:spring-core:6.0.0")
+ compileOnly(libs.jetbrains.annotations)
+
+ compileOnly(libs.autoService.annotations)
+ annotationProcessor(libs.autoService.processor)
+
+ testImplementation(project(":typed-ids-testing"))
+ testImplementation("org.springframework:spring-core:6.0.0")
+ testRuntimeOnly("org.junit.platform:junit-platform-launcher")
+}
+
+project.description = "TypeIds Spring Framework converters"
\ No newline at end of file
diff --git a/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactory.java b/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactory.java
new file mode 100644
index 0000000..3db0cd5
--- /dev/null
+++ b/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactory.java
@@ -0,0 +1,70 @@
+package org.framefork.typedIds.spring.convert;
+
+import org.framefork.typedIds.bigint.ObjectBigIntId;
+import org.framefork.typedIds.bigint.ObjectBigIntIdTypeUtils;
+import org.framefork.typedIds.common.ReflectionHacks;
+import org.jetbrains.annotations.NotNull;
+import org.jspecify.annotations.Nullable;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+
+import java.lang.invoke.MethodHandle;
+
+/**
+ * Generic converter factory that converts {@link Number} to any {@link ObjectBigIntId} subtype.
+ *
+ *
This factory eliminates the need for individual converter classes for each Id type.
+ * It works by using {@link MethodHandle} to invoke the private constructor that all
+ * {@link ObjectBigIntId} subtypes have.
+ *
+ *
To use this converter, register it with Spring's conversion service:
+ *
{@code
+ * @Configuration
+ * public class ConversionServiceConfiguration {
+ * @Bean
+ * public ConversionService conversionService() {
+ * DefaultConversionService service = new DefaultConversionService();
+ * service.addConverterFactory(new NumberToObjectBigIntIdConverterFactory());
+ * return service;
+ * }
+ * }
+ * }
+ */
+public class NumberToObjectBigIntIdConverterFactory implements ConverterFactory> {
+
+ @Override
+ public > @NotNull Converter getConverter(final @NotNull Class targetType) {
+ return new NumberToObjectBigIntIdConverter<>(targetType);
+ }
+
+ private static final class NumberToObjectBigIntIdConverter>
+ implements Converter {
+
+ private final Class targetType;
+
+ private NumberToObjectBigIntIdConverter(final Class targetType) {
+ this.targetType = targetType;
+ }
+
+ @Override
+ public @Nullable T convert(final @Nullable Number source) {
+ if (source == null) {
+ return null;
+ }
+
+ try {
+ final var constructor = ReflectionHacks.getConstructor(targetType, long.class);
+ @SuppressWarnings("unchecked")
+ var result = (T) ObjectBigIntIdTypeUtils.wrapBigIntToIdentifier(source.longValue(), constructor);
+ return result;
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Cannot convert Number to " + targetType.getName() +
+ ". Ensure it extends ObjectBigIntId and has a private constructor taking a long parameter.",
+ e
+ );
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactory.java b/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactory.java
new file mode 100644
index 0000000..1077a09
--- /dev/null
+++ b/modules/typed-ids-spring-convert/src/main/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactory.java
@@ -0,0 +1,72 @@
+package org.framefork.typedIds.spring.convert;
+
+import org.framefork.typedIds.common.ReflectionHacks;
+import org.framefork.typedIds.uuid.ObjectUuid;
+import org.framefork.typedIds.uuid.ObjectUuidTypeUtils;
+import org.jetbrains.annotations.NotNull;
+import org.jspecify.annotations.Nullable;
+import org.springframework.core.convert.converter.Converter;
+import org.springframework.core.convert.converter.ConverterFactory;
+
+import java.lang.invoke.MethodHandle;
+import java.util.UUID;
+
+/**
+ * Generic converter factory that converts {@link String} to any {@link ObjectUuid} subtype.
+ *
+ * This factory eliminates the need for individual converter classes for each Id type.
+ * It works by using {@link MethodHandle} to invoke the private constructor that all
+ * {@link ObjectUuid} subtypes have.
+ *
+ *
To use this converter, register it with Spring's conversion service:
+ *
{@code
+ * @Configuration
+ * public class ConversionServiceConfiguration {
+ * @Bean
+ * public ConversionService conversionService() {
+ * DefaultConversionService service = new DefaultConversionService();
+ * service.addConverterFactory(new StringToObjectUuidConverterFactory());
+ * return service;
+ * }
+ * }
+ * }
+ */
+public class StringToObjectUuidConverterFactory implements ConverterFactory> {
+
+ @Override
+ public > @NotNull Converter getConverter(final @NotNull Class targetType) {
+ return new StringToObjectUuidConverter<>(targetType);
+ }
+
+ private static final class StringToObjectUuidConverter>
+ implements Converter {
+
+ private final Class targetType;
+
+ private StringToObjectUuidConverter(final Class targetType) {
+ this.targetType = targetType;
+ }
+
+ @Override
+ public @Nullable T convert(final @Nullable String source) {
+ if (source == null || source.isEmpty()) {
+ return null;
+ }
+
+ try {
+ final UUID uuid = UUID.fromString(source);
+ final var constructor = ReflectionHacks.getConstructor(targetType, UUID.class);
+ @SuppressWarnings("unchecked")
+ var result = (T) ObjectUuidTypeUtils.wrapUuidToIdentifier(uuid, constructor);
+ return result;
+ } catch (final IllegalArgumentException e) {
+ throw new IllegalArgumentException(
+ "Cannot convert String to " + targetType.getName() +
+ ". Ensure it extends ObjectUuid and has a private constructor taking a UUID parameter.",
+ e
+ );
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactoryTest.java b/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactoryTest.java
new file mode 100644
index 0000000..7e6d0cf
--- /dev/null
+++ b/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/NumberToObjectBigIntIdConverterFactoryTest.java
@@ -0,0 +1,90 @@
+package org.framefork.typedIds.spring.convert;
+
+import org.framefork.typedIds.bigint.ObjectBigIntId;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.convert.converter.Converter;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class NumberToObjectBigIntIdConverterFactoryTest {
+
+ private NumberToObjectBigIntIdConverterFactory factory;
+
+ @BeforeEach
+ void setUp() {
+ factory = new NumberToObjectBigIntIdConverterFactory();
+ }
+
+ @Test
+ void shouldConvertLongToObjectBigIntId() {
+ // Given
+ Converter converter = factory.getConverter(TestBigIntId.class);
+ long value = 12345L;
+
+ // When
+ TestBigIntId result = converter.convert(value);
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result.toLong()).isEqualTo(value);
+ }
+
+ @Test
+ void shouldConvertIntegerToObjectBigIntId() {
+ // Given
+ Converter converter = factory.getConverter(TestBigIntId.class);
+ int value = 42;
+
+ // When
+ TestBigIntId result = converter.convert(value);
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result.toLong()).isEqualTo(value);
+ }
+
+ @Test
+ void shouldReturnNullForNullInput() {
+ // Given
+ Converter converter = factory.getConverter(TestBigIntId.class);
+
+ // When
+ TestBigIntId result = converter.convert(null);
+
+ // Then
+ assertThat(result).isNull();
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidType() {
+ // Given
+ Converter converter = factory.getConverter(InvalidBigIntId.class);
+
+ // When / Then
+ assertThatThrownBy(() -> converter.convert(123L))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Cannot convert Number to");
+ }
+
+ // Test ID class
+ public static final class TestBigIntId extends ObjectBigIntId {
+ private TestBigIntId(long inner) {
+ super(inner);
+ }
+
+ public static TestBigIntId from(long value) {
+ return ObjectBigIntId.fromLong(TestBigIntId::new, value);
+ }
+ }
+
+ // Invalid test class (no proper constructor)
+ public static final class InvalidBigIntId extends ObjectBigIntId {
+ // This class intentionally has no long constructor to test error handling
+ @SuppressWarnings({"UnusedMethod", "UnusedVariable"})
+ private InvalidBigIntId(String invalid) {
+ super(0);
+ }
+ }
+}
\ No newline at end of file
diff --git a/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactoryTest.java b/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactoryTest.java
new file mode 100644
index 0000000..78fc535
--- /dev/null
+++ b/modules/typed-ids-spring-convert/src/test/java/org/framefork/typedIds/spring/convert/StringToObjectUuidConverterFactoryTest.java
@@ -0,0 +1,105 @@
+package org.framefork.typedIds.spring.convert;
+
+import org.framefork.typedIds.uuid.ObjectUuid;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.core.convert.converter.Converter;
+
+import java.util.UUID;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+class StringToObjectUuidConverterFactoryTest {
+
+ private StringToObjectUuidConverterFactory factory;
+
+ @BeforeEach
+ void setUp() {
+ factory = new StringToObjectUuidConverterFactory();
+ }
+
+ @Test
+ void shouldConvertStringToObjectUuid() {
+ // Given
+ Converter converter = factory.getConverter(TestUuid.class);
+ UUID uuid = UUID.fromString("550e8400-e29b-41d4-a716-446655440000");
+ String uuidString = uuid.toString();
+
+ // When
+ TestUuid result = converter.convert(uuidString);
+
+ // Then
+ assertThat(result).isNotNull();
+ assertThat(result.toNativeUuid()).isEqualTo(uuid);
+ }
+
+ @Test
+ void shouldReturnNullForNullInput() {
+ // Given
+ Converter converter = factory.getConverter(TestUuid.class);
+
+ // When
+ TestUuid result = converter.convert(null);
+
+ // Then
+ assertThat(result).isNull();
+ }
+
+ @Test
+ void shouldReturnNullForEmptyString() {
+ // Given
+ Converter converter = factory.getConverter(TestUuid.class);
+
+ // When
+ TestUuid result = converter.convert("");
+
+ // Then
+ assertThat(result).isNull();
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidUuidString() {
+ // Given
+ Converter converter = factory.getConverter(TestUuid.class);
+
+ // When / Then
+ assertThatThrownBy(() -> converter.convert("not-a-uuid"))
+ .isInstanceOf(IllegalArgumentException.class);
+ }
+
+ @Test
+ void shouldThrowExceptionForInvalidType() {
+ // Given
+ Converter converter = factory.getConverter(InvalidUuid.class);
+
+ // When / Then
+ assertThatThrownBy(() -> converter.convert("550e8400-e29b-41d4-a716-446655440000"))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessageContaining("Cannot convert String to");
+ }
+
+ // Test UUID class - using UUID v4 for testing
+ public static final class TestUuid extends ObjectUuid {
+ private TestUuid(UUID inner) {
+ super(inner);
+ }
+
+ public static TestUuid from(UUID value) {
+ return ObjectUuid.fromUuid(TestUuid::new, value);
+ }
+
+ public static TestUuid from(String value) {
+ return ObjectUuid.fromString(TestUuid::new, value);
+ }
+ }
+
+ // Invalid test class (no proper constructor)
+ public static final class InvalidUuid extends ObjectUuid {
+ // This class intentionally has no UUID constructor to test error handling
+ @SuppressWarnings({"UnusedMethod", "UnusedVariable"})
+ private InvalidUuid(String invalid) {
+ super(UUID.fromString("550e8400-e29b-41d4-a716-446655440000"));
+ }
+ }
+}
\ No newline at end of file