Default4j
Default parameter values for Java via annotation processing
Install / Use
/learn @reugn/Default4jREADME
default4j
Default parameter values for Java via annotation processing.
Java doesn't natively support default parameter values like Scala, Kotlin, or Python. This library fills that gap by generating helper code at compile time.
Table of Contents
- Features
- Comparison with Other Libraries
- Installation
- Quick Start
- Usage Guide
- Supported Types
- Annotation Reference
- Compile-Time Validation
- How It Works
- Requirements
- Building from Source
- License
Features
- Default parameters for methods — call
greet()instead ofgreet("World", "Hello") - Named parameters — set only what you need:
.host("prod").timeout(30).call() - Constructor & record factories —
UserDefaults.create("Alice")with sensible defaults - Works with external types — generate defaults for third-party classes you can't modify
- Extensive compile-time validation — catch errors early with helpful messages and typo suggestions
- Compile-time only — no runtime dependencies, no reflection, just plain Java
Comparison with Other Libraries
Feature Comparison
| Feature | default4j | Lombok | Immutables | AutoValue | record-builder |
|---------|-----------|--------|------------|-----------|----------------|
| Primary Focus | Default values | Boilerplate reduction | Immutable objects | Value types | Record builders |
| Method Defaults | ✅ Full support | ❌ | ❌ | ❌ | ❌ |
| Constructor Defaults | ✅ Full support | ⚠️ Limited | ⚠️ Via builder | ⚠️ Via builder | ⚠️ Via builder |
| Named Parameters | ✅ Built-in | ❌ | ✅ Via builder | ✅ Via builder | ✅ Via builder |
| Record Support | ✅ Native | ⚠️ Partial | ✅ | ❌ | ✅ Native |
| External Types | ✅ @IncludeDefaults | ❌ | ❌ | ❌ | ✅ |
| Factory Methods | ✅ @DefaultFactory | ❌ | ❌ | ❌ | ❌ |
| Field References | ✅ @DefaultValue(field=...) | ❌ | ❌ | ❌ | ❌ |
Technical Comparison
| Aspect | default4j | Lombok | Immutables | AutoValue | record-builder | |--------|-----------|--------|------------|-----------|----------------| | Approach | Annotation Processing | Bytecode Modification | Annotation Processing | Annotation Processing | Annotation Processing | | Runtime Dependency | None | None | Optional | None | None | | IDE Plugin Required | No | Yes | No | No | No | | Compile-time Validation | ✅ Extensive | ⚠️ Limited | ✅ | ✅ | ✅ | | Debuggable Output | ✅ Plain Java | ⚠️ Complex | ✅ Plain Java | ✅ Plain Java | ✅ Plain Java | | Java Version | 17+ | 8+ | 8+ | 8+ | 16+ |
When to Use Each
| Library | Best For | |--------------------|------------------------------------------------------------------------------------------| | default4j | Adding default parameters to existing code, method defaults, Python/Kotlin-like defaults | | Lombok | Reducing boilerplate (getters, setters, equals, toString), quick prototyping | | Immutables | Complex immutable objects with many optional fields, serialization support | | AutoValue | Simple value types, Google ecosystem integration | | record-builder | Adding builders to records, withers for records |
Complementary Usage
default4j can work alongside other libraries:
// With Lombok - use Lombok for boilerplate, default4j for defaults
@Getter @Setter
public class Config {
@WithDefaults
public Config(@DefaultValue("localhost") String host) { ... }
}
// With record-builder - use record-builder for withers, default4j for factory defaults
@RecordBuilder // Generates withHost(), withPort() etc.
@WithDefaults // Generates ConfigDefaults.create() with defaults
public record Config(
@DefaultValue("localhost") String host,
@DefaultValue("8080") int port) {}
Key Differentiators
default4j is unique in providing:
- Method-level defaults — No other library supports default values for regular method parameters
- Unified syntax — Same
@DefaultValueworks for methods, constructors, and records - True default values — Unlike builders, you get actual default parameters (omit trailing args)
- Factory method defaults —
@DefaultFactoryfor computed/lazy default values - Field reference defaults —
@DefaultValue(field="CONSTANT")for static constants - External type defaults —
@IncludeDefaultsfor third-party classes you can't modify
When to Use Constructor Defaults
Java allows inline field initialization, but constructor defaults solve problems inline initialization can't:
| Use Case | Inline Defaults | default4j |
|----------|----------------|-----------|
| Records & Immutables | ❌ Not possible | ✅ Full support |
| Factory Methods | ❌ Manual boilerplate | ✅ Auto-generated |
| Third-Party Classes | ❌ Can't modify source | ✅ @IncludeDefaults |
| Computed Defaults | ❌ No method calls | ✅ @DefaultFactory |
| Builder Pattern | ❌ Manual implementation | ✅ named=true |
| Self-Documenting API | ❌ Defaults hidden in code | ✅ Visible in annotations |
Best for: Records, immutable classes, factory patterns, external types, computed/dynamic values.
When inline is simpler: Mutable classes with simple literal defaults that don't need factory methods.
Installation
Maven
<dependency>
<groupId>io.github.reugn</groupId>
<artifactId>default4j</artifactId>
<version>${version}</version>
</dependency>
Gradle
implementation 'io.github.reugn:default4j:${version}'
annotationProcessor 'io.github.reugn:default4j:${version}'
Quick Start
import io.github.reugn.default4j.annotation.*;
@WithDefaults
public class Config {
public Config(
@DefaultValue("localhost") String host,
@DefaultValue("8080") int port) {
// ...
}
}
// Usage - generated ConfigDefaults class:
Config c1 = ConfigDefaults.create(); // host="localhost", port=8080
Config c2 = ConfigDefaults.create("example.com"); // host="example.com", port=8080
Usage Guide
1. Method Defaults
Generate overloaded static methods that omit trailing parameters with defaults.
public class Greeter {
@WithDefaults
public String greet(
@DefaultValue("World") String name,
@DefaultValue("Hello") String greeting) {
return greeting + ", " + name + "!";
}
}
Generated usage:
Greeter g = new Greeter();
GreeterDefaults.greet(g); // "Hello, World!"
GreeterDefaults.greet(g, "Alice"); // "Hello, Alice!"
GreeterDefaults.greet(g, "Alice", "Hi"); // "Hi, Alice!"
2. Named Parameters
Use named = true to generate a fluent builder that allows skipping any parameter, not just trailing ones.
public class Database {
@WithDefaults(named = true)
public Connection connect(
@DefaultValue("localhost") String host,
@DefaultValue("5432") int port,
@DefaultValue("postgres") String user) {
return createConnection(host, port, user);
}
}
Generated usage:
Database db = new Database();
// Skip port, set only host and user
DatabaseDefaults.connect(db)
.host("prod.example.com")
.user("admin")
.call();
// Use all defaults
DatabaseDefaults.connect(db).call();
3. Constructor Defaults
Generate factory methods for constructors with default parameters.
public class User {
@WithDefaults
public User(
String name,
@DefaultValue("user@example.com") String email,
@DefaultValue("USER") String role) {
// ...
}
}
Generated usage:
User u1 = UserDefaults.create("Alice"); // Default email & role
User u2 = UserDefaults.create("Bob", "bob@test.com"); // Default role
User u3 = UserDefaults.create("Carol", "c@x.com", "ADMIN"); // All specified
Named mode for constructors:
public class User {
@WithDefaults(named = true)
public User(String name, @DefaultValue("USER") String role) {
// ...
}
}
// Skip to any parameter
User u = UserDefaults.create()
.name("Alice")
.build(); // role uses default
4. Class-Level Annotation
Apply @WithDefaults to a class to generate helpers for all constructors and methods that have @DefaultValue or
@DefaultFactory parameters.
@WithDefaults
public class Service {
// Constructor with defaults -> factory methods generated
public Service(
@DefaultValue("default") String name,
@DefaultValue("100") int value) {
// ...
}
// Method with defaults -> helper methods generated
public void process(@DefaultValue("INFO") String level) {
// ...
}
}
Generated usage:
Service s = ServiceDefaults.create(); // All constructor defaults
ServiceDefaults.process(s); // Method with default level
With options:
@WithDefaults(named = true, methodName = "builder")
public class AppConfig {
public AppConfig(
@DefaultValue("localhost") String ho
