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:
-
String-based lookups:
config.getString("database.url")
- prone to typos and no IDE assistance -
Manual type conversion:
Integer.parseInt(config.getString("server.port"))
- tedious and error-prone - No compile-time validation: Errors only discovered at runtime
- 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);
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:
-
Annotate an interface or class with
@Propify
or@I18n
- Compile your code β Propify reads your configuration files and generates type-safe classes
- 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>
Gradle (β₯4.6)
dependencies {
implementation 'com.vgerbot:propify:2.0.0'
annotationProcessor 'com.vgerbot:propify:2.0.0'
}
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'
}
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
2. Annotate an interface:
@Propify(location = "application.yml")
public interface AppConfig {}
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();
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}οΌ
2. Annotate a class:
@I18n(baseName = "messages", defaultLocale = "en")
public class Messages {}
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("εΌ δΈ");
Advanced Features
Custom Lookups for Dynamic Values
Propify supports variable interpolation in configuration files:
app:
tempDir: "{env:TEMP_DIR}"
secretKey: "{vault:db-secret}"
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 {}
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 {}
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
@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
Environment-Specific Configuration
# app.yml
database:
url: "jdbc:mysql://{env:DB_HOST}:{env:DB_PORT}/{env:DB_NAME}"
@Propify(
location = "app.yml",
lookups = { EnvironmentLookup.class }
)
public interface AppConfig {}
Benefits for Enterprise Applications
For enterprise applications, Propify offers several key benefits:
- Reduced Bugs: Catch configuration errors at compile time
- Improved Developer Experience: IDE auto-completion and refactoring support
- Better Maintainability: Type-safe access makes code more readable and maintainable
- Enhanced Security: No more hardcoded secrets with custom lookup providers
- 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:
- JavaPoet β Java source file generation
- Jackson YAML β YAML parsing
- Apache Commons Configuration β Configuration management
- ICU4J β Internationalization support
Top comments (0)