Skip to content

Commit 60a2744

Browse files
committed
refactor: modernize code for Java 21 and Spring Boot 4
- Convert test DTOs (Bar, Foo) to records - Use pattern matching in FriendlyId.equals() - Replace inner converter classes with lambdas and static imports - Improve UUID format detection with proper validation - Use text blocks and var keyword throughout - Simplify Application controllers to use record constructors
1 parent 990b68e commit 60a2744

9 files changed

Lines changed: 91 additions & 192 deletions

File tree

friendly-id-jackson-datatype/src/main/java/com/devskiller/friendly_id/jackson/FriendlyIdDeserializer.java

Lines changed: 27 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -25,31 +25,45 @@ private FriendlyIdDeserializer(boolean useFriendlyFormat) {
2525
}
2626

2727
@Override
28-
public UUID deserialize(JsonParser parser, DeserializationContext deserializationContext) {
29-
JsonToken token = parser.currentToken();
28+
public UUID deserialize(JsonParser parser, DeserializationContext ctxt) {
29+
var token = parser.currentToken();
3030
if (token == JsonToken.VALUE_STRING) {
31-
String string = parser.getString().trim();
31+
var value = parser.getString().trim();
3232
if (useFriendlyFormat) {
33-
if (looksLikeUuid(string)) {
34-
return UUID.fromString(string);
35-
} else {
36-
return FriendlyId.toUuid(string);
37-
}
33+
return parseAsUuidOrFriendlyId(value);
3834
} else {
39-
return UUID.fromString(string);
35+
return UUID.fromString(value);
4036
}
4137
}
42-
throw new IllegalStateException("This is not friendly id");
38+
throw ctxt.weirdStringException(parser.getString(), UUID.class, "Expected UUID string value");
4339
}
4440

45-
private boolean looksLikeUuid(String value) {
46-
return value.contains("-");
41+
/**
42+
* Attempts to parse the value as a standard UUID first, then falls back to FriendlyId format.
43+
* This approach is more robust than heuristic-based detection.
44+
*/
45+
private UUID parseAsUuidOrFriendlyId(String value) {
46+
if (isStandardUuidFormat(value)) {
47+
return UUID.fromString(value);
48+
}
49+
return FriendlyId.toUuid(value);
50+
}
51+
52+
/**
53+
* Checks if the string matches standard UUID format (36 chars with hyphens at positions 8, 13, 18, 23).
54+
*/
55+
private boolean isStandardUuidFormat(String value) {
56+
return value.length() == 36
57+
&& value.charAt(8) == '-'
58+
&& value.charAt(13) == '-'
59+
&& value.charAt(18) == '-'
60+
&& value.charAt(23) == '-';
4761
}
4862

4963
@Override
5064
public ValueDeserializer<?> createContextual(DeserializationContext ctxt, BeanProperty property) {
5165
if (property != null) {
52-
IdFormat annotation = property.getAnnotation(IdFormat.class);
66+
var annotation = property.getAnnotation(IdFormat.class);
5367
if (annotation != null && annotation.value() == FriendlyIdFormat.RAW) {
5468
return new FriendlyIdDeserializer(false);
5569
}

friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/Bar.java

Lines changed: 4 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -5,25 +5,7 @@
55
import com.devskiller.friendly_id.jackson.FriendlyIdFormat;
66
import com.devskiller.friendly_id.jackson.IdFormat;
77

8-
public class Bar {
9-
10-
@IdFormat(FriendlyIdFormat.RAW)
11-
private final UUID rawUuid;
12-
13-
private final UUID friendlyId;
14-
15-
public Bar(UUID rawUuid, UUID friendlyId) {
16-
this.rawUuid = rawUuid;
17-
this.friendlyId = friendlyId;
18-
}
19-
20-
public UUID getRawUuid() {
21-
return rawUuid;
22-
}
23-
24-
public UUID getFriendlyId() {
25-
return friendlyId;
26-
}
27-
28-
29-
}
8+
public record Bar(
9+
@IdFormat(FriendlyIdFormat.RAW) UUID rawUuid,
10+
UUID friendlyId
11+
) {}

friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/FieldWithoutFriendlyIdTest.java

Lines changed: 19 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -15,53 +15,50 @@ class FieldWithoutFriendlyIdTest {
1515

1616
@Test
1717
void shouldAllowToDoNotCodeUuidInDataObject() {
18-
Foo foo = new Foo();
19-
foo.setRawUuid(uuid);
20-
foo.setFriendlyId(uuid);
18+
var foo = new Foo(uuid, uuid);
2119

22-
String json = jsonMapper.writeValueAsString(foo);
20+
var json = jsonMapper.writeValueAsString(foo);
2321

2422
// JSON field order may vary, so check each field separately
2523
assertThat(json).contains("\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\"");
2624
assertThat(json).contains("\"friendlyId\":\"7Jsg6CPDscHawyJfE70b9x\"");
2725

28-
Foo cloned = jsonMapper.readValue(json, Foo.class);
29-
assertThat(cloned.getRawUuid()).isEqualTo(foo.getFriendlyId());
26+
var cloned = jsonMapper.readValue(json, Foo.class);
27+
assertThat(cloned.rawUuid()).isEqualTo(foo.friendlyId());
3028
}
3129

3230
@Test
3331
void shouldDeserializeUuidsInDataObject() {
34-
String json = "{\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\",\"friendlyId\":\"7Jsg6CPDscHawyJfE70b9x\"}";
32+
var json = """
33+
{"rawUuid":"f088ce5b-9279-4cc3-946a-c15ad740dd6d","friendlyId":"7Jsg6CPDscHawyJfE70b9x"}""";
3534

36-
Foo cloned = jsonMapper.readValue(json, Foo.class);
37-
assertThat(cloned.getRawUuid()).isEqualTo(uuid);
38-
assertThat(cloned.getFriendlyId()).isEqualTo(uuid);
35+
var cloned = jsonMapper.readValue(json, Foo.class);
36+
assertThat(cloned.rawUuid()).isEqualTo(uuid);
37+
assertThat(cloned.friendlyId()).isEqualTo(uuid);
3938
}
4039

41-
4240
@Test
4341
void shouldSerializeUuidsInValueObject() {
4442
jsonMapper = mapper();
4543

46-
Bar bar = new Bar(uuid, uuid);
44+
var bar = new Bar(uuid, uuid);
4745

48-
String json = jsonMapper.writeValueAsString(bar);
46+
var json = jsonMapper.writeValueAsString(bar);
4947

50-
assertThat(json).isEqualToIgnoringWhitespace(
51-
"{\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\",\"friendlyId\":\"7Jsg6CPDscHawyJfE70b9x\"}"
52-
);
48+
assertThat(json).isEqualToIgnoringWhitespace("""
49+
{"rawUuid":"f088ce5b-9279-4cc3-946a-c15ad740dd6d","friendlyId":"7Jsg6CPDscHawyJfE70b9x"}""");
5350
}
5451

5552
@Test
56-
void shouldDeserializeUuuidsValueObject() {
53+
void shouldDeserializeUuidsInValueObject() {
5754
jsonMapper = mapper();
5855

59-
String json = "{\"rawUuid\":\"f088ce5b-9279-4cc3-946a-c15ad740dd6d\",\"friendlyId\":\"7Jsg6CPDscHawyJfE70b9x\"}";
56+
var json = """
57+
{"rawUuid":"f088ce5b-9279-4cc3-946a-c15ad740dd6d","friendlyId":"7Jsg6CPDscHawyJfE70b9x"}""";
6058

61-
Bar deserialized = jsonMapper.readValue(json, Bar.class);
59+
var deserialized = jsonMapper.readValue(json, Bar.class);
6260

63-
assertThat(deserialized.getRawUuid()).isEqualTo(uuid);
64-
assertThat(deserialized.getFriendlyId()).isEqualTo(uuid);
61+
assertThat(deserialized.rawUuid()).isEqualTo(uuid);
62+
assertThat(deserialized.friendlyId()).isEqualTo(uuid);
6563
}
66-
6764
}

friendly-id-jackson-datatype/src/test/java/com/devskiller/friendly_id/spring/Foo.java

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,7 @@
55
import com.devskiller.friendly_id.jackson.FriendlyIdFormat;
66
import com.devskiller.friendly_id.jackson.IdFormat;
77

8-
public class Foo {
9-
10-
@IdFormat(FriendlyIdFormat.RAW)
11-
private UUID rawUuid;
12-
13-
private UUID friendlyId;
14-
15-
public UUID getRawUuid() {
16-
return rawUuid;
17-
}
18-
19-
public void setRawUuid(UUID rawUuid) {
20-
this.rawUuid = rawUuid;
21-
}
22-
23-
public UUID getFriendlyId() {
24-
return friendlyId;
25-
}
26-
27-
public void setFriendlyId(UUID friendlyId) {
28-
this.friendlyId = friendlyId;
29-
}
30-
}
8+
public record Foo(
9+
@IdFormat(FriendlyIdFormat.RAW) UUID rawUuid,
10+
UUID friendlyId
11+
) {}

friendly-id-samples/friendly-id-spring-boot-jpa-demo/src/main/java/com/devskiller/friendly_id/sample/jpa/FriendlyIdJpaDemoApplication.java

Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,14 @@ public static void main(String[] args) {
3939
@Bean
4040
public CommandLineRunner initData(ProductRepository repository) {
4141
return args -> {
42-
System.out.println("\n========================================");
43-
System.out.println("Initializing demo products...");
44-
System.out.println("========================================\n");
42+
System.out.println("""
4543
46-
Product laptop = new Product(
44+
========================================
45+
Initializing demo products...
46+
========================================
47+
""");
48+
49+
var laptop = new Product(
4750
"Laptop",
4851
"High-performance laptop for developers",
4952
new BigDecimal("1299.99"),
@@ -52,7 +55,7 @@ public CommandLineRunner initData(ProductRepository repository) {
5255
repository.save(laptop);
5356
System.out.println("Created product: Laptop with ID: " + laptop.getId());
5457

55-
Product mouse = new Product(
58+
var mouse = new Product(
5659
"Wireless Mouse",
5760
"Ergonomic wireless mouse",
5861
new BigDecimal("29.99"),
@@ -61,7 +64,7 @@ public CommandLineRunner initData(ProductRepository repository) {
6164
repository.save(mouse);
6265
System.out.println("Created product: Wireless Mouse with ID: " + mouse.getId());
6366

64-
Product keyboard = new Product(
67+
var keyboard = new Product(
6568
"Mechanical Keyboard",
6669
"RGB mechanical keyboard with Cherry MX switches",
6770
new BigDecimal("149.99"),
@@ -70,20 +73,23 @@ public CommandLineRunner initData(ProductRepository repository) {
7073
repository.save(keyboard);
7174
System.out.println("Created product: Mechanical Keyboard with ID: " + keyboard.getId());
7275

73-
System.out.println("\n========================================");
74-
System.out.println("Demo ready!");
75-
System.out.println("========================================");
76-
System.out.println("REST API: http://localhost:8080/api/products");
77-
System.out.println("H2 Console: http://localhost:8080/h2-console");
78-
System.out.println(" JDBC URL: jdbc:h2:mem:friendlyid_demo");
79-
System.out.println(" Username: sa");
80-
System.out.println(" Password: (empty)");
81-
System.out.println("========================================\n");
76+
System.out.println("""
77+
78+
========================================
79+
Demo ready!
80+
========================================
81+
REST API: http://localhost:8080/api/products
82+
H2 Console: http://localhost:8080/h2-console
83+
JDBC URL: jdbc:h2:mem:friendlyid_demo
84+
Username: sa
85+
Password: (empty)
86+
========================================
87+
88+
Try these commands:
89+
curl http://localhost:8080/api/products
90+
curl http://localhost:8080/api/products/%s
8291
83-
System.out.println("Try these commands:");
84-
System.out.println(" curl http://localhost:8080/api/products");
85-
System.out.println(" curl http://localhost:8080/api/products/" + laptop.getId());
86-
System.out.println("\n");
92+
""".formatted(laptop.getId()));
8793
};
8894
}
8995
}

friendly-id-samples/friendly-id-spring-boot-simple/src/main/java/com/devskiller/friendly_id/sample/simple/Application.java

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,6 @@ public static void main(String[] args) {
1818

1919
@GetMapping("/bars/{id}")
2020
Bar getBar(@PathVariable UUID id) {
21-
Bar bar = new Bar();
22-
bar.setId(id);
23-
return bar;
21+
return new Bar(id);
2422
}
25-
2623
}

friendly-id-samples/friendly-id-spring-boot-simple/src/main/java/com/devskiller/friendly_id/sample/simple/Bar.java

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -2,22 +2,4 @@
22

33
import java.util.UUID;
44

5-
public class Bar {
6-
7-
private UUID id;
8-
9-
public Bar() {
10-
}
11-
12-
public Bar(UUID id) {
13-
this.id = id;
14-
}
15-
16-
public UUID getId() {
17-
return id;
18-
}
19-
20-
public void setId(UUID id) {
21-
this.id = id;
22-
}
23-
}
5+
public record Bar(UUID id) {}

friendly-id-spring-boot/src/main/java/com/devskiller/friendly_id/spring/FriendlyIdConfiguration.java

Lines changed: 9 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,14 @@
66

77
import org.springframework.context.annotation.Bean;
88
import org.springframework.context.annotation.Configuration;
9-
import org.springframework.core.convert.converter.Converter;
109
import org.springframework.format.FormatterRegistry;
1110
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
1211

13-
import com.devskiller.friendly_id.FriendlyId;
1412
import com.devskiller.friendly_id.jackson.FriendlyIdModule;
1513

14+
import static com.devskiller.friendly_id.FriendlyId.toFriendlyId;
15+
import static com.devskiller.friendly_id.FriendlyId.toUuid;
16+
1617
/**
1718
* Configuration for FriendlyId integration with Spring MVC.
1819
* <p>
@@ -30,74 +31,16 @@ public class FriendlyIdConfiguration implements WebMvcConfigurer {
3031

3132
@Override
3233
public void addFormatters(FormatterRegistry registry) {
33-
registry.addConverter(new StringToUuidConverter());
34-
registry.addConverter(new UuidToStringConverter());
35-
registry.addConverter(new StringToFriendlyIdConverter());
36-
registry.addConverter(new FriendlyIdToStringConverter());
34+
registry.addConverter(String.class, UUID.class, id -> toUuid(id));
35+
registry.addConverter(UUID.class, String.class, id -> toFriendlyId(id));
36+
registry.addConverter(String.class, com.devskiller.friendly_id.type.FriendlyId.class,
37+
com.devskiller.friendly_id.type.FriendlyId::fromString);
38+
registry.addConverter(com.devskiller.friendly_id.type.FriendlyId.class, String.class,
39+
com.devskiller.friendly_id.type.FriendlyId::toString);
3740
}
3841

3942
@Bean
4043
public JacksonModule friendlyIdModule() {
4144
return new FriendlyIdModule();
4245
}
43-
44-
/**
45-
* Converter that converts FriendlyId strings to UUID.
46-
* <p>
47-
* This converter is automatically registered in Spring's conversion service
48-
* and allows path variables and request parameters to be automatically converted
49-
* from FriendlyId format to UUID.
50-
*/
51-
public static class StringToUuidConverter implements Converter<String, UUID> {
52-
53-
@Override
54-
public UUID convert(String id) {
55-
return FriendlyId.toUuid(id);
56-
}
57-
}
58-
59-
/**
60-
* Converter that converts UUID to FriendlyId strings.
61-
* <p>
62-
* This converter is automatically registered in Spring's conversion service
63-
* and allows UUIDs to be automatically converted to FriendlyId format
64-
* in responses and URL generation.
65-
*/
66-
public static class UuidToStringConverter implements Converter<UUID, String> {
67-
68-
@Override
69-
public String convert(UUID id) {
70-
return FriendlyId.toFriendlyId(id);
71-
}
72-
}
73-
74-
/**
75-
* Converter that converts FriendlyId strings to FriendlyId value objects.
76-
* <p>
77-
* This converter is automatically registered in Spring's conversion service
78-
* and allows path variables and request parameters to be automatically converted
79-
* from FriendlyId format to FriendlyId value object.
80-
*/
81-
public static class StringToFriendlyIdConverter implements Converter<String, com.devskiller.friendly_id.type.FriendlyId> {
82-
83-
@Override
84-
public com.devskiller.friendly_id.type.FriendlyId convert(String id) {
85-
return com.devskiller.friendly_id.type.FriendlyId.fromString(id);
86-
}
87-
}
88-
89-
/**
90-
* Converter that converts FriendlyId value objects to FriendlyId strings.
91-
* <p>
92-
* This converter is automatically registered in Spring's conversion service
93-
* and allows FriendlyId value objects to be automatically converted to FriendlyId format
94-
* in responses and URL generation.
95-
*/
96-
public static class FriendlyIdToStringConverter implements Converter<com.devskiller.friendly_id.type.FriendlyId, String> {
97-
98-
@Override
99-
public String convert(com.devskiller.friendly_id.type.FriendlyId id) {
100-
return id.toString();
101-
}
102-
}
10346
}

0 commit comments

Comments
 (0)