DEV Community

chien hsin Yang
chien hsin Yang

Posted on

Propify: Type-Safe Configuration and Internationalization for Java Applications

In the world of Java application development, managing configuration and internationalization (i18n) has always been a challenge. Developers often resort to string-based lookups that are error-prone and lack compile-time safety. What if there was a better way? Enter Propify β€” a powerful, lightweight Java annotation processor that eliminates configuration errors by generating type-safe classes from your configuration files and internationalization bundles.

Say goodbye to "stringly-typed" keys and runtime errors! Access every configuration value and message through strongly-typed Java methods, catching invalid accesses at compile time instead of runtime.

The Problem with Traditional Configuration

Traditional approaches to configuration in Java applications often involve:

  1. String-based lookups: config.getString("database.url") - prone to typos and no IDE assistance
  2. Manual type conversion: Integer.parseInt(config.getString("server.port")) - tedious and error-prone
  3. No compile-time validation: Errors only discovered at runtime
  4. Verbose boilerplate code: Creating POJOs to represent configuration structures

Similarly, internationalization typically relies on string keys that can't be validated until runtime:

// Traditional approach - error-prone  
String message = ResourceBundle.getBundle("messages").getString("greeting");  
String formatted = MessageFormat.format(message, userName);
Enter fullscreen mode Exit fullscreen mode

Introducing Propify

Propify is a lightweight Java annotation processor that automatically generates type-safe classes for both configuration files (YAML, properties, INI) and internationalization bundles. It provides a clean, intuitive API that catches errors at compile time rather than runtime.

Key Features

  • πŸ”’ Type-Safe Configuration: Access properties through generated Java methods
  • 🌐 Type-Safe Internationalization: Strongly-typed resource bundles with ICU4J formatting
  • πŸ› οΈ Compile-Time Validation: Catch errors during build, not at runtime
  • πŸ“š Nested Properties Support: Hierarchical configuration with dot notation
  • πŸ”„ Custom Lookups: Inject dynamic values from environment variables or custom sources
  • ⚑ Zero Runtime Overhead: All code generated at compile time

How Propify Works

Propify uses Java's annotation processing system to generate code during compilation. Here's the basic workflow:

  1. Annotate an interface or class with @Propify or @I18n
  2. Compile your code β€” Propify reads your configuration files and generates type-safe classes
  3. Use the generated classes in your application code

The magic happens at compile time β€” Propify scans your codebase for annotations, reads the referenced configuration files, and generates Java classes that provide type-safe access to your configuration and messages.

Getting Started with Propify

Installation

Maven

Add dependency and annotation processor:

<dependencies>
  <dependency>
    <groupId>com.vgerbot</groupId>
    <artifactId>propify</artifactId>
    <version>2.0.0</version>
  </dependency>
</dependencies>
<build>
  <plugins>
    <plugin>
      <artifactId>maven-compiler-plugin</artifactId>
      <version>3.8.1</version>
      <configuration>
        <source>1.8</source>
        <target>1.8</target>
        <annotationProcessorPaths>
          <path>
            <groupId>com.vgerbot</groupId>
            <artifactId>propify</artifactId>
            <version>2.0.0</version>
          </path>
        </annotationProcessorPaths>
      </configuration>
    </plugin>
  </plugins>
</build>
Enter fullscreen mode Exit fullscreen mode

Gradle (β‰₯4.6)

dependencies {
  implementation 'com.vgerbot:propify:2.0.0'
  annotationProcessor 'com.vgerbot:propify:2.0.0'
}
Enter fullscreen mode Exit fullscreen mode

Gradle (<4.6)

plugins {
  id 'net.ltgt.apt' version '0.21'
}
dependencies {
  compile 'com.vgerbot:propify:2.0.0'
  apt     'com.vgerbot:propify:2.0.0'
}
Enter fullscreen mode Exit fullscreen mode

Type-Safe Configuration in 3 Steps

1. Create a configuration file (YAML, properties, or INI):

# application.yml
server:
  host: localhost
  port: 8080
database:
  url: jdbc:mysql://localhost:3306/mydb
  username: root
  password: secret
Enter fullscreen mode Exit fullscreen mode

2. Annotate an interface:

@Propify(location = "application.yml")
public interface AppConfig {}
Enter fullscreen mode Exit fullscreen mode

3. Use the generated class:

// Type-safe configuration access
AppConfigPropify config = new AppConfigPropify();
String host = config.getServer().getHost();
int port = config.getServer().getPort();
String dbUrl = config.getDatabase().getUrl();
Enter fullscreen mode Exit fullscreen mode

That's it! No more string-based lookups, no more type conversion, and no more runtime errors due to typos.

Type-Safe Internationalization

Propify also makes internationalization a breeze:

1. Create message files:

# messages.properties (default)
welcome=Welcome
greeting=Hello, {name}!

# messages_zh_CN.properties
welcome=欒迎
greeting=δ½ ε₯½, {name}!
Enter fullscreen mode Exit fullscreen mode

2. Annotate a class:

@I18n(baseName = "messages", defaultLocale = "en")
public class Messages {}
Enter fullscreen mode Exit fullscreen mode

3. Access messages:

// Type-safe message formatting
String welcome = MessageResource.getDefault().welcome();
String greeting = MessageResource.getDefault().greeting("John");

// Locale-specific messages
String chineseGreeting = MessageResource.get(Locale.CHINESE).greeting("εΌ δΈ‰");
Enter fullscreen mode Exit fullscreen mode

Advanced Features

Custom Lookups for Dynamic Values

Propify supports variable interpolation in configuration files:

app:
  tempDir: "{env:TEMP_DIR}"
  secretKey: "{vault:db-secret}"
Enter fullscreen mode Exit fullscreen mode

You can implement custom lookup providers:

class VaultLookup implements PropifyLookup {
    @Override
    public String getPrefix() {
        return "vault";
    }

    @Override
    public Object lookup(String key) {
        // Retrieve secret from vault
        return VaultClient.getSecret(key);
    }
}

@Propify(
  location = "application.yml",
  lookups = { VaultLookup.class }
)
public interface AppConfig {}
Enter fullscreen mode Exit fullscreen mode

Support for Multiple Configuration Formats

Propify supports various configuration formats:

  • YAML: For hierarchical configuration
  • Properties: Traditional Java properties files
  • INI: For simple configuration needs

The format is automatically detected from the file extension, or you can specify it explicitly:

@Propify(
  location = "config.custom",
  mediaType = "application/x-java-properties"
)
public interface CustomConfig {}
Enter fullscreen mode Exit fullscreen mode

Practical Examples

Here are some practical examples of how to use Propify in common scenarios:

Nested Configuration

# application.yml
server:
  http:
    port: 8080
  https:
    port: 8443
    keystore: /path/to/keystore
Enter fullscreen mode Exit fullscreen mode
@Propify(location = "application.yml")
public interface ServerConfig {}

// Usage
ServerConfigPropify config = new ServerConfigPropify();
int httpPort = config.getServer().getHttp().getPort();  // 8080
String keystore = config.getServer().getHttps().getKeystore();  // /path/to/keystore
Enter fullscreen mode Exit fullscreen mode

Environment-Specific Configuration

# app.yml
database:
  url: "jdbc:mysql://{env:DB_HOST}:{env:DB_PORT}/{env:DB_NAME}"
Enter fullscreen mode Exit fullscreen mode
@Propify(
  location = "app.yml",
  lookups = { EnvironmentLookup.class }
)
public interface AppConfig {}
Enter fullscreen mode Exit fullscreen mode

Benefits for Enterprise Applications

For enterprise applications, Propify offers several key benefits:

  1. Reduced Bugs: Catch configuration errors at compile time
  2. Improved Developer Experience: IDE auto-completion and refactoring support
  3. Better Maintainability: Type-safe access makes code more readable and maintainable
  4. Enhanced Security: No more hardcoded secrets with custom lookup providers
  5. Simplified Internationalization: Type-safe message formatting with ICU support

Getting Help

If you encounter any issues or have questions about using Propify:

  • GitHub Issues: Submit a new issue on the GitHub repository
  • Documentation: Check the Wiki for detailed documentation
  • Examples: Browse the examples directory for sample projects

Conclusion

Propify brings type safety to configuration and internationalization in Java applications, eliminating a whole class of runtime errors and improving developer productivity. By leveraging the power of Java's annotation processing system, it provides these benefits with zero runtime overhead.

Whether you're building a small application or a large enterprise system, Propify can help you manage configuration and internationalization in a type-safe, maintainable way.

Ready to try Propify? Check out the GitHub repository and start enjoying type-safe configuration today!

Acknowledgments

Propify builds on several excellent open-source libraries:

Top comments (0)