# How to Create Custom Converter Implementations for Property Types in Owner

> Implement the org.aeonbits.owner.Converter interface to create custom converter implementations for property types in the Owner library. Learn how to bind your deserialization logic.

- Repository: [Matteo Baccan/owner](https://github.com/matteobaccan/owner)
- Tags: how-to-guide
- Published: 2026-03-07

---

**To create custom Converter implementations for property types in the Owner library, implement the `org.aeonbits.owner.Converter<T>` interface with a stateless class and annotate your configuration method with `@ConverterClass` to bind your deserialization logic.**

The Owner library (matteobaccan/owner) automates the mapping between property files and Java interfaces using built-in type converters. When your application requires complex domain objects or non-standard string formats that exceed default capabilities, custom Converter implementations for property types provide a clean extension point for bespoke transformation logic.

## The Converter Interface and @ConverterClass Annotation

Owner defines the conversion contract through two core components located in the source tree.

### The Converter Functional Interface

The `Converter<T>` interface in [`owner/src/main/java/org/aeonbits/owner/Converter.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Converter.java) declares the single method that handles all custom transformations:

```java
T convert(Method method, String input);

```

Your implementation receives the **Method** object representing the invoked configuration interface method (allowing metadata inspection) and the **raw property value** as a String. The method must return the converted object of type `T`, or `null` if conversion fails.

### The @ConverterClass Annotation

Defined in [`owner/src/main/java/org/aeonbits/owner/Config.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Config.java) (lines 45-55), the `@ConverterClass` annotation binds a specific converter implementation to a configuration interface method. The annotation accepts a Class object that must satisfy three constraints:

- **Public** visibility
- **Non-abstract** concrete implementation
- Accessible **no-argument constructor**

When applied to methods returning arrays or collections, Owner applies the converter to each individual element.

## Converter Lifecycle and Stateless Design

Understanding the instantiation model is critical for correct implementation. Owner creates a **new converter instance for every property access** using `converterClass.newInstance()`. This design enforces thread safety but prohibits stateful converters.

Because each invocation receives a fresh instance, you must **avoid mutable instance fields** or expensive initialization routines inside the converter. If your conversion requires heavy resources (such as `DateTimeFormatter` instances or compiled regex patterns), declare them as `static final` constants or use static utility methods.

## Practical Code Examples

The Owner test suite and examples module demonstrate several real-world converter patterns.

### Converting Host:Port Strings to Server Objects

The `ServerConverter` implementation in [`owner/src/test/java/org/aeonbits/owner/typeconversion/ConverterClassTest.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/test/java/org/aeonbits/owner/typeconversion/ConverterClassTest.java) (lines 57-66) parses server connection strings:

```java
public static class ServerConverter implements Converter<Server> {
    @Override
    public Server convert(Method method, String text) {
        String[] parts = text.split(":", -1);
        String name = parts[0];
        int port = (parts.length >= 2) ? Integer.parseInt(parts[1]) : 80;
        return new Server(name, port);
    }
}

```

Bind this converter to your configuration interface:

```java
interface MyConfig extends Config {
    @DefaultValue("foobar.com:8080")
    @ConverterClass(ServerConverter.class)
    Server server();
}

```

### Parsing Key-Value Pairs into Map Structures

For complex property formats, the `MapPropertyConverter` in [`owner/src/test/java/org/aeonbits/owner/examples/MapPropertyExample.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/test/java/org/aeonbits/owner/examples/MapPropertyExample.java) (lines 32-44) converts comma-separated key-value entries into a LinkedHashMap:

```java
public static class MapPropertyConverter implements Converter<Map<String,String>> {
    @Override
    public Map<String,String> convert(Method method, String input) {
        Map<String,String> map = new LinkedHashMap<>();
        for (String entry : input.split(",", -1)) {
            String[] kv = entry.split(":", -1);
            map.put(kv[0].trim(), kv[1].trim());
        }
        return map;
    }
}

```

Usage with array returns:

```java
interface MyConfig extends Config {
    @Separator(";")
    @DefaultValue(
        "name : Dante Alighieri, book : Divine Comedy;" +
        "name : Alessandro Manzoni, book : The Betrothed")
    @ConverterClass(MapPropertyConverter.class)
    Map<String,String>[] authors();
}

```

### Integrating Java 8 Date/Time Types

While Owner lacks built-in JSR-310 support, a custom converter bridges the gap:

```java
public class LocalDateConverter implements Converter<LocalDate> {
    private static final DateTimeFormatter FMT = DateTimeFormatter.ISO_LOCAL_DATE;

    @Override
    public LocalDate convert(Method method, String input) {
        return LocalDate.parse(input, FMT);
    }
}

```

Configuration usage:

```java
interface MyConfig extends Config {
    @DefaultValue("2023-10-01")
    @ConverterClass(LocalDateConverter.class)
    LocalDate startDate();
}

```

### Overriding Built-in Conversions

Custom converters take precedence over default handlers. The `OverridesIntegerConversion` test case in [`ConverterClassTest.java`](https://github.com/matteobaccan/owner/blob/main/ConverterClassTest.java) (lines 93-97) demonstrates replacing standard Integer parsing:

```java
public static class OverridesIntegerConversion implements Converter<Integer> {
    @Override
    public Integer convert(Method method, String input) {
        return 42;  // Ignores input, returns constant
    }
}

```

```java
interface MyConfig extends Config {
    @DefaultValue("10")
    @ConverterClass(OverridesIntegerConversion.class)
    int overridden();
}

```

## Key Source Files for Reference

Review these canonical source files to understand the complete converter architecture:

- **[`owner/src/main/java/org/aeonbits/owner/Converter.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Converter.java)** — Defines the `Converter<T>` functional interface contract.
- **[`owner/src/main/java/org/aeonbits/owner/Config.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Config.java)** — Contains the `@ConverterClass` annotation definition (lines 45-55) and related metadata annotations.
- **[`owner/src/test/java/org/aeonbits/owner/typeconversion/ConverterClassTest.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/test/java/org/aeonbits/owner/typeconversion/ConverterClassTest.java)** — Comprehensive unit tests demonstrating Server conversion, built-in overrides, and error handling scenarios.
- **[`owner/src/test/java/org/aeonbits/owner/examples/MapPropertyExample.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/test/java/org/aeonbits/owner/examples/MapPropertyExample.java)** — Working example of Map-based property conversion with array return types.

## Summary

- Implement `org.aeonbits.owner.Converter<T>` to define custom transformation logic from String to any Java type.
- Annotate configuration methods with `@ConverterClass(YourConverter.class)` to bind the implementation; the converter must be public, concrete, and have a no-arg constructor.
- Design converters as **stateless** objects because Owner instantiates a new instance for every property access via reflection.
- Use the **Method** parameter in `convert()` to inspect configuration method metadata when needed.
- Place expensive resources (formatters, patterns) in `static final` fields rather than instance variables to avoid initialization overhead.

## Frequently Asked Questions

### What are the exact requirements for a custom Converter class?

Your converter class must be **public**, **non-abstract**, and possess an accessible **no-argument constructor**. It must implement the `Converter<T>` interface from [`owner/src/main/java/org/aeonbits/owner/Converter.java`](https://github.com/matteobaccan/owner/blob/main/owner/src/main/java/org/aeonbits/owner/Converter.java). Owner instantiates the class using `newInstance()` for each conversion, so the implementation cannot rely on constructor injection or instance state.

### Can I use stateful converters with cached resources?

No. Because Owner creates a **new converter instance for every method invocation**, instance fields would reset each time. For expensive immutable resources like `DateTimeFormatter` or compiled regular expressions, declare them as `static final` constants within the converter class. This provides shared access across all instantiations while maintaining thread safety.

### How do I apply custom converters to collections or arrays?

When you annotate a method returning an array or collection with `@ConverterClass`, Owner automatically applies your converter to **each element individually** rather than to the entire collection as a single string. Ensure your converter handles the element type (not the collection type), and use `@Separator` to define how Owner splits the raw property value into individual element strings before conversion.

### Can custom converters override Owner's built-in type handling?

Yes. The `@ConverterClass` annotation takes precedence over default converters. Even if Owner provides a standard converter for types like `Integer`, `Boolean`, or `URI`, annotating the method with your custom implementation completely replaces the built-in behavior. This is demonstrated in [`ConverterClassTest.java`](https://github.com/matteobaccan/owner/blob/main/ConverterClassTest.java) (lines 93-97) where an integer method always returns 42 regardless of the property value.