0
# Type Conversion
1
2
Spring Boot's type conversion system extends Spring's core ConversionService with additional converters for common data types including durations, data sizes, periods, and collections. It provides formatted conversion with annotations and support for property binding.
3
4
## Capabilities
5
6
### Application Conversion Service
7
8
Enhanced conversion service that includes all Spring Boot converters and formatters.
9
10
```java { .api }
11
/**
12
* A specialization of DefaultFormattingConversionService configured by default with
13
* converters and formatters appropriate for most Spring Boot applications
14
*/
15
public class ApplicationConversionService extends DefaultFormattingConversionService {
16
17
private static volatile ApplicationConversionService sharedInstance;
18
19
/**
20
* Return a shared default application ConversionService instance, lazily
21
* building it once needed
22
* @return the shared ApplicationConversionService instance (never null)
23
*/
24
public static ConversionService getSharedInstance() {
25
ApplicationConversionService sharedInstance = ApplicationConversionService.sharedInstance;
26
if (sharedInstance == null) {
27
synchronized (ApplicationConversionService.class) {
28
sharedInstance = ApplicationConversionService.sharedInstance;
29
if (sharedInstance == null) {
30
sharedInstance = new ApplicationConversionService();
31
ApplicationConversionService.sharedInstance = sharedInstance;
32
}
33
}
34
}
35
return sharedInstance;
36
}
37
38
/**
39
* Configure the given FormatterRegistry with formatters and converters appropriate
40
* for most Spring Boot applications
41
* @param registry the registry of converters to add to (must not be null)
42
*/
43
public static void configure(FormatterRegistry registry) {
44
DefaultConversionService.addDefaultConverters(registry);
45
DefaultFormattingConversionService.addDefaultFormatters(registry);
46
addApplicationFormatters(registry);
47
addApplicationConverters(registry);
48
}
49
50
/**
51
* Add converters useful for most Spring Boot applications
52
* @param registry the registry of converters to add to (must not be null)
53
*/
54
public static void addApplicationConverters(ConverterRegistry registry) {
55
addDelimitedStringConverters(registry);
56
registry.addConverter(new StringToDurationConverter());
57
registry.addConverter(new DurationToStringConverter());
58
registry.addConverter(new NumberToDurationConverter());
59
registry.addConverter(new DurationToNumberConverter());
60
registry.addConverter(new StringToPeriodConverter());
61
registry.addConverter(new PeriodToStringConverter());
62
registry.addConverter(new NumberToPeriodConverter());
63
registry.addConverter(new StringToDataSizeConverter());
64
registry.addConverter(new NumberToDataSizeConverter());
65
registry.addConverter(new StringToFileConverter());
66
registry.addConverter(new InputStreamSourceToByteArrayConverter());
67
}
68
69
/**
70
* Add formatters useful for most Spring Boot applications
71
* @param registry the service to register default formatters with
72
*/
73
public static void addApplicationFormatters(FormatterRegistry registry) {
74
registry.addFormatter(new CharArrayFormatter());
75
registry.addFormatter(new InetAddressFormatter());
76
registry.addFormatter(new IsoOffsetFormatter());
77
}
78
79
private static void addDelimitedStringConverters(ConverterRegistry registry) {
80
ConversionService service = (ConversionService) registry;
81
registry.addConverter(new ArrayToDelimitedStringConverter(service));
82
registry.addConverter(new CollectionToDelimitedStringConverter(service));
83
registry.addConverter(new DelimitedStringToArrayConverter(service));
84
registry.addConverter(new DelimitedStringToCollectionConverter(service));
85
}
86
}
87
```
88
89
**Usage Examples:**
90
91
```java
92
import org.springframework.context.annotation.Configuration;
93
import org.springframework.context.annotation.Bean;
94
import org.springframework.context.annotation.Primary;
95
import org.springframework.core.convert.ConversionService;
96
97
@Configuration
98
public class ConversionConfiguration {
99
100
@Bean
101
@Primary
102
public ConversionService conversionService() {
103
return ApplicationConversionService.getSharedInstance();
104
}
105
}
106
107
@Service
108
public class DataConverter {
109
110
@Autowired
111
private ConversionService conversionService;
112
113
public void demonstrateConversions() {
114
// Duration conversions
115
Duration duration = conversionService.convert("30s", Duration.class);
116
String durationStr = conversionService.convert(Duration.ofMinutes(5), String.class);
117
118
// Data size conversions
119
DataSize size = conversionService.convert("10MB", DataSize.class);
120
String sizeStr = conversionService.convert(DataSize.ofGigabytes(2), String.class);
121
122
// Period conversions
123
Period period = conversionService.convert("P1Y2M3D", Period.class);
124
125
// Collection conversions with delimiters
126
List<String> list = conversionService.convert("a,b,c",
127
TypeDescriptor.valueOf(String.class),
128
TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class)));
129
130
System.out.println("Duration: " + duration);
131
System.out.println("Duration string: " + durationStr);
132
System.out.println("Data size: " + size);
133
System.out.println("List: " + list);
134
}
135
}
136
```
137
138
### Duration Support
139
140
Duration conversion with multiple format styles and units.
141
142
```java { .api }
143
/**
144
* Format annotation for Duration conversion
145
*/
146
@Target({ElementType.FIELD, ElementType.PARAMETER})
147
@Retention(RetentionPolicy.RUNTIME)
148
@Documented
149
public @interface DurationFormat {
150
151
/**
152
* The duration format style to use
153
* @return the duration style
154
*/
155
DurationStyle value();
156
157
/**
158
* The duration unit to use when the format style is SIMPLE
159
* @return the duration unit
160
*/
161
ChronoUnit unit() default ChronoUnit.MILLIS;
162
}
163
164
/**
165
* Duration format styles
166
*/
167
public enum DurationStyle implements TemporalUnitStyleFormatter {
168
169
/**
170
* Simple formatting, for example '1s'
171
*/
172
SIMPLE("^([+\\-]?\\d+)([a-zA-Z]{0,2})$") {
173
@Override
174
public Duration parse(String value, ChronoUnit unit) {
175
try {
176
Matcher matcher = getPattern().matcher(value);
177
Assert.state(matcher.matches(), "Does not match simple duration pattern");
178
String suffix = matcher.group(2);
179
return (StringUtils.hasLength(suffix) ? Unit.fromSuffix(suffix) : Unit.fromChronoUnit(unit))
180
.parse(matcher.group(1));
181
}
182
catch (Exception ex) {
183
throw new IllegalArgumentException("'" + value + "' is not a valid simple duration", ex);
184
}
185
}
186
187
@Override
188
public String print(Duration value, ChronoUnit unit) {
189
return Unit.fromChronoUnit(unit).print(value);
190
}
191
},
192
193
/**
194
* ISO-8601 formatting
195
*/
196
ISO8601("^[+\\-]?P.*$") {
197
@Override
198
public Duration parse(String value, ChronoUnit unit) {
199
try {
200
return Duration.parse(value);
201
}
202
catch (Exception ex) {
203
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 duration", ex);
204
}
205
}
206
207
@Override
208
public String print(Duration value, ChronoUnit unit) {
209
return value.toString();
210
}
211
};
212
213
/**
214
* Parse the given value to a duration
215
* @param value the value to parse
216
* @return a duration
217
*/
218
public Duration parse(String value) {
219
return parse(value, null);
220
}
221
222
/**
223
* Parse the given value to a duration using the specified unit when the value
224
* doesn't contain a suffix
225
* @param value the value to parse
226
* @param unit the duration unit to use when there is no suffix
227
* @return a duration
228
*/
229
public abstract Duration parse(String value, ChronoUnit unit);
230
231
/**
232
* Print the specified duration
233
* @param value the value to print
234
* @param unit the value to use for printing when the original unit is not known
235
* @return the printed result
236
*/
237
public abstract String print(Duration value, ChronoUnit unit);
238
239
/**
240
* Detect the style from the given source value
241
* @param value the source value
242
* @return the duration style
243
*/
244
public static DurationStyle detect(String value) {
245
Assert.notNull(value, "Value must not be null");
246
for (DurationStyle candidate : values()) {
247
if (candidate.matches(value)) {
248
return candidate;
249
}
250
}
251
throw new IllegalArgumentException("'" + value + "' is not a valid duration");
252
}
253
}
254
```
255
256
**Usage Examples:**
257
258
```java
259
@ConfigurationProperties("app.timeout")
260
public class TimeoutProperties {
261
262
@DurationFormat(DurationStyle.SIMPLE)
263
private Duration connectionTimeout = Duration.ofSeconds(30);
264
265
@DurationFormat(value = DurationStyle.SIMPLE, unit = ChronoUnit.MINUTES)
266
private Duration sessionTimeout = Duration.ofMinutes(30);
267
268
@DurationFormat(DurationStyle.ISO8601)
269
private Duration maxWaitTime = Duration.parse("PT1M30S");
270
271
// Getters and setters
272
public Duration getConnectionTimeout() { return connectionTimeout; }
273
public void setConnectionTimeout(Duration connectionTimeout) {
274
this.connectionTimeout = connectionTimeout;
275
}
276
277
public Duration getSessionTimeout() { return sessionTimeout; }
278
public void setSessionTimeout(Duration sessionTimeout) {
279
this.sessionTimeout = sessionTimeout;
280
}
281
282
public Duration getMaxWaitTime() { return maxWaitTime; }
283
public void setMaxWaitTime(Duration maxWaitTime) {
284
this.maxWaitTime = maxWaitTime;
285
}
286
}
287
288
// Properties file:
289
// app.timeout.connection-timeout=5s
290
// app.timeout.session-timeout=30
291
// app.timeout.max-wait-time=PT2M30S
292
293
@Service
294
public class DurationService {
295
296
public void demonstrateDurationParsing() {
297
// Simple format parsing
298
Duration duration1 = DurationStyle.SIMPLE.parse("30s");
299
Duration duration2 = DurationStyle.SIMPLE.parse("5", ChronoUnit.MINUTES);
300
Duration duration3 = DurationStyle.SIMPLE.parse("2h");
301
302
// ISO-8601 format parsing
303
Duration duration4 = DurationStyle.ISO8601.parse("PT30M");
304
Duration duration5 = DurationStyle.ISO8601.parse("P1DT2H30M");
305
306
// Auto-detection
307
Duration duration6 = DurationStyle.detect("45s").parse("45s");
308
Duration duration7 = DurationStyle.detect("PT1H").parse("PT1H");
309
310
System.out.println("30s = " + duration1);
311
System.out.println("5 minutes = " + duration2);
312
System.out.println("2h = " + duration3);
313
System.out.println("PT30M = " + duration4);
314
System.out.println("P1DT2H30M = " + duration5);
315
}
316
}
317
```
318
319
### Data Size Support
320
321
Data size conversion with standard units (bytes, KB, MB, GB, TB).
322
323
```java { .api }
324
/**
325
* A data size, such as '12MB'
326
*/
327
public final class DataSize implements Comparable<DataSize>, Serializable {
328
329
/**
330
* Bytes per Kilobyte
331
*/
332
public static final long BYTES_PER_KB = 1024;
333
334
/**
335
* Bytes per Megabyte
336
*/
337
public static final long BYTES_PER_MB = BYTES_PER_KB * 1024;
338
339
/**
340
* Bytes per Gigabyte
341
*/
342
public static final long BYTES_PER_GB = BYTES_PER_MB * 1024;
343
344
/**
345
* Bytes per Terabyte
346
*/
347
public static final long BYTES_PER_TB = BYTES_PER_GB * 1024;
348
349
/**
350
* Obtain a DataSize representing the specified number of bytes
351
* @param bytes the number of bytes, positive or negative
352
* @return a DataSize
353
*/
354
public static DataSize ofBytes(long bytes) {
355
return new DataSize(bytes);
356
}
357
358
/**
359
* Obtain a DataSize representing the specified number of kilobytes
360
* @param kilobytes the number of kilobytes, positive or negative
361
* @return a DataSize
362
*/
363
public static DataSize ofKilobytes(long kilobytes) {
364
return new DataSize(Math.multiplyExact(kilobytes, BYTES_PER_KB));
365
}
366
367
/**
368
* Obtain a DataSize representing the specified number of megabytes
369
* @param megabytes the number of megabytes, positive or negative
370
* @return a DataSize
371
*/
372
public static DataSize ofMegabytes(long megabytes) {
373
return new DataSize(Math.multiplyExact(megabytes, BYTES_PER_MB));
374
}
375
376
/**
377
* Obtain a DataSize representing the specified number of gigabytes
378
* @param gigabytes the number of gigabytes, positive or negative
379
* @return a DataSize
380
*/
381
public static DataSize ofGigabytes(long gigabytes) {
382
return new DataSize(Math.multiplyExact(gigabytes, BYTES_PER_GB));
383
}
384
385
/**
386
* Obtain a DataSize representing the specified number of terabytes
387
* @param terabytes the number of terabytes, positive or negative
388
* @return a DataSize
389
*/
390
public static DataSize ofTerabytes(long terabytes) {
391
return new DataSize(Math.multiplyExact(terabytes, BYTES_PER_TB));
392
}
393
394
/**
395
* Obtain a DataSize from a text string such as {@code 12MB}
396
* @param text the text to parse
397
* @return the parsed DataSize
398
*/
399
public static DataSize parse(CharSequence text) {
400
return parse(text, null);
401
}
402
403
/**
404
* Obtain a DataSize from a text string such as {@code 12MB}
405
* @param text the text to parse
406
* @param defaultUnit the default DataUnit if none is specified
407
* @return the parsed DataSize
408
*/
409
public static DataSize parse(CharSequence text, DataUnit defaultUnit) {
410
Assert.notNull(text, "Text must not be null");
411
try {
412
Matcher matcher = PATTERN.matcher(text);
413
Assert.state(matcher.matches(), "Does not match data size pattern");
414
DataUnit unit = determineDataUnit(matcher.group(2), defaultUnit);
415
long amount = Long.parseLong(matcher.group(1));
416
return DataSize.of(amount, unit);
417
}
418
catch (Exception ex) {
419
throw new IllegalArgumentException("'" + text + "' is not a valid data size", ex);
420
}
421
}
422
423
/**
424
* Return the number of bytes in this instance
425
* @return the number of bytes
426
*/
427
public long toBytes() {
428
return this.bytes;
429
}
430
431
/**
432
* Return the number of kilobytes in this instance
433
* @return the number of kilobytes
434
*/
435
public long toKilobytes() {
436
return this.bytes / BYTES_PER_KB;
437
}
438
439
/**
440
* Return the number of megabytes in this instance
441
* @return the number of megabytes
442
*/
443
public long toMegabytes() {
444
return this.bytes / BYTES_PER_MB;
445
}
446
447
/**
448
* Return the number of gigabytes in this instance
449
* @return the number of gigabytes
450
*/
451
public long toGigabytes() {
452
return this.bytes / BYTES_PER_GB;
453
}
454
455
/**
456
* Return the number of terabytes in this instance
457
* @return the number of terabytes
458
*/
459
public long toTerabytes() {
460
return this.bytes / BYTES_PER_TB;
461
}
462
}
463
464
/**
465
* A standard set of DataSize units
466
*/
467
public enum DataUnit {
468
469
/**
470
* Bytes
471
*/
472
BYTES("B", DataSize.ofBytes(1)),
473
474
/**
475
* Kilobytes
476
*/
477
KILOBYTES("KB", DataSize.ofKilobytes(1)),
478
479
/**
480
* Megabytes
481
*/
482
MEGABYTES("MB", DataSize.ofMegabytes(1)),
483
484
/**
485
* Gigabytes
486
*/
487
GIGABYTES("GB", DataSize.ofGigabytes(1)),
488
489
/**
490
* Terabytes
491
*/
492
TERABYTES("TB", DataSize.ofTerabytes(1));
493
494
private final String suffix;
495
private final DataSize size;
496
497
DataUnit(String suffix, DataSize size) {
498
this.suffix = suffix;
499
this.size = size;
500
}
501
502
DataSize size() {
503
return this.size;
504
}
505
506
/**
507
* Return the DataUnit matching the specified suffix
508
* @param suffix the suffix to match
509
* @return the matching DataUnit
510
*/
511
public static DataUnit fromSuffix(String suffix) {
512
for (DataUnit candidate : values()) {
513
if (candidate.suffix.equalsIgnoreCase(suffix)) {
514
return candidate;
515
}
516
}
517
throw new IllegalArgumentException("Unknown data unit suffix '" + suffix + "'");
518
}
519
}
520
```
521
522
**Usage Examples:**
523
524
```java
525
@ConfigurationProperties("app.storage")
526
public class StorageProperties {
527
528
private DataSize maxFileSize = DataSize.ofMegabytes(10);
529
private DataSize totalQuota = DataSize.ofGigabytes(100);
530
private DataSize cacheSize = DataSize.ofMegabytes(256);
531
532
// Getters and setters
533
public DataSize getMaxFileSize() { return maxFileSize; }
534
public void setMaxFileSize(DataSize maxFileSize) { this.maxFileSize = maxFileSize; }
535
536
public DataSize getTotalQuota() { return totalQuota; }
537
public void setTotalQuota(DataSize totalQuota) { this.totalQuota = totalQuota; }
538
539
public DataSize getCacheSize() { return cacheSize; }
540
public void setCacheSize(DataSize cacheSize) { this.cacheSize = cacheSize; }
541
}
542
543
// Properties file:
544
// app.storage.max-file-size=50MB
545
// app.storage.total-quota=2GB
546
// app.storage.cache-size=512MB
547
548
@Service
549
public class DataSizeService {
550
551
public void demonstrateDataSizeParsing() {
552
// Parsing different formats
553
DataSize size1 = DataSize.parse("10MB");
554
DataSize size2 = DataSize.parse("2GB");
555
DataSize size3 = DataSize.parse("1024KB");
556
DataSize size4 = DataSize.parse("500"); // Bytes by default
557
DataSize size5 = DataSize.parse("500", DataUnit.KILOBYTES);
558
559
// Creating sizes programmatically
560
DataSize size6 = DataSize.ofBytes(1024);
561
DataSize size7 = DataSize.ofMegabytes(50);
562
DataSize size8 = DataSize.ofGigabytes(2);
563
564
// Converting between units
565
System.out.println("10MB in bytes: " + size1.toBytes());
566
System.out.println("2GB in MB: " + size2.toMegabytes());
567
System.out.println("1024KB in MB: " + size3.toMegabytes());
568
569
// Comparisons
570
System.out.println("10MB > 5MB: " + (size1.compareTo(DataSize.ofMegabytes(5)) > 0));
571
}
572
}
573
```
574
575
### Period Support
576
577
Period conversion for date-based amounts of time.
578
579
```java { .api }
580
/**
581
* Format annotation for Period conversion
582
*/
583
@Target({ElementType.FIELD, ElementType.PARAMETER})
584
@Retention(RetentionPolicy.RUNTIME)
585
@Documented
586
public @interface PeriodFormat {
587
588
/**
589
* The period format style to use
590
* @return the period style
591
*/
592
PeriodStyle value();
593
}
594
595
/**
596
* Period format styles
597
*/
598
public enum PeriodStyle {
599
600
/**
601
* Simple formatting, for example '1y2m3d'
602
*/
603
SIMPLE("^([+\\-]?\\d+)([ymdw])$") {
604
@Override
605
public Period parse(String value, ChronoUnit unit) {
606
try {
607
if (value.isEmpty()) {
608
return Period.ZERO;
609
}
610
Matcher matcher = getPattern().matcher(value.toLowerCase());
611
Period period = Period.ZERO;
612
while (matcher.find()) {
613
String number = matcher.group(1);
614
String suffix = matcher.group(2);
615
Unit valueUnit = Unit.fromSuffix(suffix);
616
period = period.plus(valueUnit.parse(number));
617
}
618
return period;
619
}
620
catch (Exception ex) {
621
throw new IllegalArgumentException("'" + value + "' is not a valid simple period", ex);
622
}
623
}
624
625
@Override
626
public String print(Period value, ChronoUnit unit) {
627
if (value.isZero()) {
628
return "0d";
629
}
630
StringBuilder result = new StringBuilder();
631
if (value.getYears() != 0) {
632
result.append(value.getYears()).append("y");
633
}
634
if (value.getMonths() != 0) {
635
result.append(value.getMonths()).append("m");
636
}
637
if (value.getDays() != 0) {
638
result.append(value.getDays()).append("d");
639
}
640
return result.toString();
641
}
642
},
643
644
/**
645
* ISO-8601 formatting
646
*/
647
ISO8601("^[+\\-]?P.*$") {
648
@Override
649
public Period parse(String value, ChronoUnit unit) {
650
try {
651
return Period.parse(value);
652
}
653
catch (Exception ex) {
654
throw new IllegalArgumentException("'" + value + "' is not a valid ISO-8601 period", ex);
655
}
656
}
657
658
@Override
659
public String print(Period value, ChronoUnit unit) {
660
return value.toString();
661
}
662
};
663
664
/**
665
* Parse the given value to a period
666
* @param value the value to parse
667
* @return a period
668
*/
669
public Period parse(String value) {
670
return parse(value, null);
671
}
672
673
/**
674
* Parse the given value to a period
675
* @param value the value to parse
676
* @param unit the period unit (may be null)
677
* @return a period
678
*/
679
public abstract Period parse(String value, ChronoUnit unit);
680
681
/**
682
* Print the specified period
683
* @param value the value to print
684
* @param unit the value to use for printing when the original unit is not known
685
* @return the printed result
686
*/
687
public abstract String print(Period value, ChronoUnit unit);
688
689
/**
690
* Detect the style from the given source value
691
* @param value the source value
692
* @return the period style
693
*/
694
public static PeriodStyle detect(String value) {
695
Assert.notNull(value, "Value must not be null");
696
for (PeriodStyle candidate : values()) {
697
if (candidate.matches(value)) {
698
return candidate;
699
}
700
}
701
throw new IllegalArgumentException("'" + value + "' is not a valid period");
702
}
703
}
704
```
705
706
**Usage Examples:**
707
708
```java
709
@ConfigurationProperties("app.schedule")
710
public class ScheduleProperties {
711
712
@PeriodFormat(PeriodStyle.SIMPLE)
713
private Period retentionPeriod = Period.ofDays(30);
714
715
@PeriodFormat(PeriodStyle.ISO8601)
716
private Period archivePeriod = Period.parse("P1Y");
717
718
@PeriodFormat(PeriodStyle.SIMPLE)
719
private Period cleanupInterval = Period.ofWeeks(2);
720
721
// Getters and setters
722
public Period getRetentionPeriod() { return retentionPeriod; }
723
public void setRetentionPeriod(Period retentionPeriod) {
724
this.retentionPeriod = retentionPeriod;
725
}
726
727
public Period getArchivePeriod() { return archivePeriod; }
728
public void setArchivePeriod(Period archivePeriod) {
729
this.archivePeriod = archivePeriod;
730
}
731
732
public Period getCleanupInterval() { return cleanupInterval; }
733
public void setCleanupInterval(Period cleanupInterval) {
734
this.cleanupInterval = cleanupInterval;
735
}
736
}
737
738
// Properties file:
739
// app.schedule.retention-period=90d
740
// app.schedule.archive-period=P2Y
741
// app.schedule.cleanup-interval=1m
742
```
743
744
### Delimited String Conversion
745
746
Collection conversion with custom delimiters.
747
748
```java { .api }
749
/**
750
* Annotation that can be used to change the delimiter used when converting from a
751
* String
752
*/
753
@Target({ElementType.FIELD, ElementType.PARAMETER})
754
@Retention(RetentionPolicy.RUNTIME)
755
@Documented
756
public @interface Delimiter {
757
758
/**
759
* The delimiter to use or an empty string if the delimiter should be inherited
760
* from the field type
761
* @return the delimiter
762
*/
763
String value();
764
}
765
```
766
767
**Usage Examples:**
768
769
```java
770
@ConfigurationProperties("app.lists")
771
public class ListProperties {
772
773
// Default comma delimiter: "apple,banana,cherry"
774
private List<String> fruits;
775
776
// Custom pipe delimiter: "red|green|blue"
777
@Delimiter("|")
778
private List<String> colors;
779
780
// Semicolon delimiter: "admin;user;guest"
781
@Delimiter(";")
782
private Set<String> roles;
783
784
// Space delimiter: "tag1 tag2 tag3"
785
@Delimiter(" ")
786
private String[] tags;
787
788
// Custom delimiter with numbers: "1:2:3:4"
789
@Delimiter(":")
790
private List<Integer> numbers;
791
792
// Getters and setters
793
public List<String> getFruits() { return fruits; }
794
public void setFruits(List<String> fruits) { this.fruits = fruits; }
795
796
public List<String> getColors() { return colors; }
797
public void setColors(List<String> colors) { this.colors = colors; }
798
799
public Set<String> getRoles() { return roles; }
800
public void setRoles(Set<String> roles) { this.roles = roles; }
801
802
public String[] getTags() { return tags; }
803
public void setTags(String[] tags) { this.tags = tags; }
804
805
public List<Integer> getNumbers() { return numbers; }
806
public void setNumbers(List<Integer> numbers) { this.numbers = numbers; }
807
}
808
809
// Properties file:
810
// app.lists.fruits=apple,banana,cherry,orange
811
// app.lists.colors=red|green|blue|yellow
812
// app.lists.roles=admin;user;guest;moderator
813
// app.lists.tags=spring boot java microservices
814
// app.lists.numbers=10:20:30:40:50
815
816
@Service
817
public class DelimiterService {
818
819
@Autowired
820
private ConversionService conversionService;
821
822
public void demonstrateDelimiterConversion() {
823
TypeDescriptor stringType = TypeDescriptor.valueOf(String.class);
824
TypeDescriptor listType = TypeDescriptor.collection(List.class, TypeDescriptor.valueOf(String.class));
825
826
// Convert delimited string to list
827
List<String> list = (List<String>) conversionService.convert(
828
"a,b,c,d", stringType, listType);
829
830
// Convert list back to delimited string
831
String delimited = (String) conversionService.convert(
832
Arrays.asList("x", "y", "z"), listType, stringType);
833
834
System.out.println("List from string: " + list);
835
System.out.println("String from list: " + delimited);
836
}
837
}
838
```