JShepherd is an annotation-based configuration management library for Java that supports modern hierarchical formats (YAML, JSON, TOML) with automatic format detection based on file extensions. It intelligently merges configuration changes β adding new fields and removing obsolete ones without overwriting user-modified values.
- π― Automatic Format Detection β File extension (
.yaml,.json,.toml) determines the persistence format - π Annotation-Driven β Declarative configuration with
@Key,@Comment,@Section - π Smart Config Merging β Automatically adds new keys and removes obsolete ones without losing user-modified values
- β
Post-Load Validation β
@PostInjectmethods are called after loading, enabling validation or derived field computation - πΎ Live Reload & Persistence β Call
config.reload()orconfig.save()at any time - π Documentation Generation β Auto-generated
.mddocs for formats without comment support (JSON) - π§ Type-Safe API β Compile-time checked
save()andreload()via self-referential generics (ConfigurablePojo<T>) - π§© Modular β Include only the format modules you need
JShepherd is available on Maven Central. Check the badge above for the latest version.
<dependencies>
<!-- Core module (required) -->
<dependency>
<groupId>de.bsommerfeld.jshepherd</groupId>
<artifactId>core</artifactId>
<version>4.0.1</version>
</dependency>
<!-- Format-specific modules (include only what you need) -->
<dependency>
<groupId>de.bsommerfeld.jshepherd</groupId>
<artifactId>yaml</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>de.bsommerfeld.jshepherd</groupId>
<artifactId>json</artifactId>
<version>4.0.1</version>
</dependency>
<dependency>
<groupId>de.bsommerfeld.jshepherd</groupId>
<artifactId>toml</artifactId>
<version>4.0.1</version>
</dependency>
</dependencies>dependencies {
// Core module (required)
implementation 'de.bsommerfeld.jshepherd:core:4.0.1'
// Format-specific modules (include only what you need)
implementation 'de.bsommerfeld.jshepherd:yaml:4.0.1'
implementation 'de.bsommerfeld.jshepherd:json:4.0.1'
implementation 'de.bsommerfeld.jshepherd:toml:4.0.1'
}Extend ConfigurablePojo<YourClassName> β the self-reference enables type-safe save() and reload():
@Comment("Server Configuration")
public class ServerConfig extends ConfigurablePojo<ServerConfig> {
public enum Environment { DEV, STAGING, PROD }
@Key("host")
@Comment("Server hostname")
private String host = "localhost";
@Key("port")
@Comment("Server port number")
private int port = 8080;
@Key("environment")
@Comment("Deployment environment")
private Environment environment = Environment.DEV;
@Key("allowed-origins")
@Comment("CORS allowed origins")
private List<String> allowedOrigins = List.of("http://localhost:3000");
@Key("feature-flags")
@Comment("Feature toggles")
private Map<String, Boolean> featureFlags = Map.of("darkMode", true, "betaFeatures", false);
@Comment("Database connection settings")
@Section("database")
private DatabaseSettings database = new DatabaseSettings();
@Comment("Cache tuning")
@Section("cache")
private CacheSettings cache = new CacheSettings();
public ServerConfig() {}
@PostInject
private void validate() {
if (port < 0 || port > 65535) {
throw new IllegalArgumentException("Port must be between 0 and 65535");
}
}
// Getters and setters...
}
// Section POJOs don't extend ConfigurablePojo
public class DatabaseSettings {
@Key("url")
@Comment("JDBC connection URL")
private String url = "jdbc:postgresql://localhost/mydb";
@Key("pool-size")
@Comment("Connection pool size")
private int poolSize = 10;
}
public class CacheSettings {
@Key("max-entries")
@Comment("Maximum cache entries")
private int maxEntries = 1000;
@Key("ttl-seconds")
@Comment("Time-to-live in seconds")
private int ttlSeconds = 300;
}Path configFile = Paths.get("config.yaml"); // or .json, .toml
// Load configuration (creates file with defaults if it doesn't exist)
ServerConfig config = ConfigurationLoader.from(configFile)
.withComments()
.load(ServerConfig::new);
System.out.println("Host: " + config.getHost());
// Modify and persist
config.setPort(9090);
config.save();
// Pick up external file changes at runtime
config.reload();YAML (config.yaml):
# Server Configuration
# Server hostname
host: localhost
# Server port number
port: 8080
# Deployment environment
environment: DEV
# CORS allowed origins
allowed-origins:
- http://localhost:3000
# Feature toggles
feature-flags:
darkMode: true
betaFeatures: false
# Database connection settings
database:
# JDBC connection URL
url: jdbc:postgresql://localhost/mydb
# Connection pool size
pool-size: 10
# Cache tuning
cache:
# Maximum cache entries
max-entries: 1000
# Time-to-live in seconds
ttl-seconds: 300TOML (config.toml):
# Server Configuration
# Server hostname
host = "localhost"
# Server port number
port = 8080
# Deployment environment
environment = "DEV"
# CORS allowed origins
allowed-origins = ["http://localhost:3000"]
# Feature toggles
[feature-flags]
darkMode = true
betaFeatures = false
# Database connection settings
[database]
# JDBC connection URL
url = "jdbc:postgresql://localhost/mydb"
# Connection pool size
pool-size = 10
# Cache tuning
[cache]
# Maximum cache entries
max-entries = 1000
# Time-to-live in seconds
ttl-seconds = 300JSON (config.json):
{
"host": "localhost",
"port": 8080,
"environment": "DEV",
"allowed-origins": ["http://localhost:3000"],
"feature-flags": {
"darkMode": true,
"betaFeatures": false
},
"database": {
"url": "jdbc:postgresql://localhost/mydb",
"pool-size": 10
},
"cache": {
"max-entries": 1000,
"ttl-seconds": 300
}
}Note: JSON does not support comments. When
withComments()is enabled, aconfig-documentation.mdfile is generated alongside the JSON file.
| Format | Extensions | Comments Support | Notes |
|---|---|---|---|
| YAML | .yaml, .yml |
β Inline comments | Full native support |
| TOML | .toml |
β Inline comments | Full native support + sections |
| JSON | .json |
β No native support | Generates .md documentation |
| Annotation | Target | Purpose |
|---|---|---|
@Key("name") |
Field | Custom key name in config file |
@Comment("text") |
Type, Field | Adds comments (header or inline) |
@Section("name") |
Field | Nested POJO as config section (all formats) |
@PostInject |
Method | Invoked after loading β use for validation or derived state |
JShepherd's key differentiator is intelligent configuration merging. When your Java class evolves, the library automatically synchronizes the config file without losing user modifications.
Your application is deployed with v1.0. A user has customized port to 9090. Now you release v1.1 β removing legacy-mode and adding max-connections.
| π Config File (User's v1.0) | β Java Class (v1.1) |
|---|---|
# config.yaml
host: localhost
port: 9090 # User modified!
legacy-mode: true # Obsolete in v1.1 |
public class ServerConfig ... {
@Key("host")
String host = "localhost";
@Key("port")
int port = 8080;
// legacy-mode removed in v1.1
@Key("max-connections") // NEW
int maxConnections = 100;
} |
After initial load + config.save():
# config.yaml β automatically merged
host: localhost
port: 9090 # β
User value PRESERVED
max-connections: 100 # β
New field with default
# legacy-mode: gone # β
Obsolete key REMOVED| Behavior | Description |
|---|---|
| Preserve User Values | port: 9090 stays β the user's setting is never overwritten |
| Add New Fields | max-connections is injected with its Java default value |
| Remove Obsolete Keys | legacy-mode is dropped β no orphaned keys clutter the file |
How it works: On
load(), existing file values are merged into the Java object. Onsave(), only fields defined in the current Java class are written back β obsolete keys are simply not serialized.