How to Convert Properties to Collections (List, Set, and Map) in Owner
Owner automatically converts comma-separated string properties into Java Collections like List and Set through its type-conversion engine in Converters.java, while Map types require a custom Converter implementation.
The Owner configuration library by matteobaccan/owner maps plain String properties to strongly-typed Java objects through a sophisticated conversion system. When you need to convert properties to collections such as List<String> or Set<Integer>, the framework handles tokenization, type coercion, and instantiation automatically via the classes in org.aeonbits.owner.
Converting Properties to List and Set Collections
Owner detects Collection return types and routes them through the COLLECTION converter defined in org.aeonbits.owner.Converters.
The COLLECTION Converter Logic
When a method returns a type that implements java.util.Collection, the converter executes a four-step process:
- Type Detection: The converter first verifies
Collection.class.isAssignableFrom(targetType)(lines 66-70 inConverters.java) to confirm the return type is a collection interface or implementation. - Tokenization: The raw property string is split using the tokenizer defined for the method (defaults to comma) according to the
@Separatorannotation. - Array Conversion: The resulting tokens are converted to an array of the target element type using the
ARRAYconverter. - Collection Wrapping: The array is wrapped via
Arrays.asList()and then used to populate an instantiated concrete collection type.
This logic is implemented between lines 71 and 76, where the framework handles the transition from String tokens to typed collections.
Generic Type Resolution
For parameterized types like List<String> or Set<Integer>, Owner extracts the generic argument from the method's return type to determine the element conversion target. If the method is raw (declared without generics), the framework falls back to String.class as the element type (lines 84-90). This extraction occurs in the type resolution phase before conversion begins.
Concrete Collection Instantiation
The instantiateCollection method in Converters.java handles the creation of the actual collection instance:
private <T> Collection<T> instantiateCollection(Class<? extends T> targetType) {
if (targetType.isInterface())
return instantiateCollectionFromInterface(targetType);
return instantiateCollectionFromClass(targetType);
}
If the return type is an interface such as List, Set, or SortedSet, Owner supplies a sensible default implementation—typically ArrayList for List and LinkedHashSet for Set (lines 94-115). When the return type is a concrete class, the framework attempts to invoke its no-argument constructor (lines 99-103).
Practical Examples
Define a configuration interface with collection return types:
import org.aeonbits.owner.Config;
import org.aeonbits.owner.ConfigFactory;
import java.util.List;
import java.util.Set;
public interface AppConfig extends Config {
@Separator(",")
@DefaultValue("a,b,c")
List<String> names();
@Separator(";")
@DefaultValue("1;2;3")
Set<Integer> ids();
}
Create and use the configuration:
AppConfig cfg = ConfigFactory.create(AppConfig.class);
List<String> names = cfg.names(); // Returns ["a", "b", "c"]
Set<Integer> ids = cfg.ids(); // Returns [1, 2, 3]
Working with Arrays of Collections
Owner supports arrays of collections through the same conversion pipeline. When a method returns List<String>[], the COLLECTION converter processes each array element individually, applying the standard list conversion logic to every component.
public interface ArrayConfig extends Config {
@Separator("|")
@DefaultValue("a|b|c;d|e|f")
List<String>[] rows();
}
The outer array is handled by the standard array converter, while each inner list goes through the collection instantiation process described above.
Converting Properties to Map Types
Unlike List and Set, Owner does not provide a generic Map converter because key/value types require arbitrary parsing logic that cannot be inferred from property strings alone. You must implement a custom Converter and register it with @ConverterClass.
Implementing a Map Converter
Create a converter class that parses your specific map format:
import org.aeonbits.owner.Converter;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.Map;
public class MapPropertyConverter implements Converter<Map<String, String>> {
public Map<String, String> convert(Method method, String input) {
Map<String, String> result = new LinkedHashMap<>();
for (String pair : input.split(",")) {
String[] kv = pair.split(":");
result.put(kv[0].trim(), kv[1].trim());
}
return result;
}
}
Apply the converter to your configuration method:
public interface MapConfig extends Config {
@Separator(";")
@DefaultValue("host:localhost,port:8080;host:example.com,port:9090")
@ConverterClass(MapPropertyConverter.class)
Map<String, String>[] servers();
}
Owner treats the returned Map array like any other collection, with the COLLECTION converter managing the outer array structure while your custom converter handles the individual Map instances.
Using the Collections Utility for Testing
Owner ships with org.aeonbits.owner.util.Collections, a helper class for creating immutable collection literals in test code or default value construction. These methods wrap Arrays.asList and LinkedHashSet for convenience (lines 91-96).
import static org.aeonbits.owner.util.Collections.*;
@Test
public void testDefaults() {
assertEquals(list("foo", "bar"), cfg.names());
assertEquals(set(1, 2, 3), cfg.ids());
assertEquals(map(entry("k", "v")), cfg.singleEntry());
}
The utility provides list(), set(), map(), and entry() methods that correspond to the standard Java collections used internally by the framework.
Summary
- Automatic Collection Conversion: Owner converts comma-separated properties to
List,Set, and other Collection types automatically when methods return these interfaces. - Type Safety: The framework extracts generic type parameters in
Converters.javato ensure elements are converted from Strings to the correct target types (e.g.,Integer,Boolean). - Interface Defaults: When returning collection interfaces, Owner instantiates sensible defaults like
ArrayListforListandLinkedHashSetforSetviainstantiateCollectionFromInterface. - Custom Map Support: Map types require custom
Converterimplementations using@ConverterClassbecause key/value parsing logic varies by use case. - Testing Utilities: The
Collectionsutility class inorg.aeonbits.owner.util.Collectionsprovides convenient literal constructors for unit testing configuration defaults.
Frequently Asked Questions
How does Owner determine which collection implementation to use?
When a method returns a collection interface such as List or Set, Owner invokes instantiateCollectionFromInterface in Converters.java (lines 94-115) to select a default implementation. If the return type is a concrete class with a no-argument constructor, Owner instantiates that class directly via reflection (lines 99-103).
Can I use a custom delimiter for collection properties?
Yes. Apply the @Separator annotation to your configuration method to define a custom tokenizer. For example, @Separator(";") splits properties on semicolons instead of commas. The COLLECTION converter passes this separator to the tokenizer before converting individual tokens to the target element type.
Why doesn't Owner provide a built-in Map converter?
Map structures require explicit knowledge of key-value parsing rules that vary by application—some use colons, others use equals signs, and some require nested delimiters. Because this logic cannot be generically inferred from plain strings, Owner requires you to implement Converter<Map<K,V>> and annotate the method with @ConverterClass to supply your parsing logic.
How do I handle nested collections or arrays of maps?
Define methods that return arrays of collections, such as Map<String,String>[] or List<String>[]. Owner first applies your custom converter (for Maps) or the standard collection converter to each element, then assembles the results into an array. The outer array conversion is handled by the standard array conversion logic in the type-conversion engine.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →