# Codebase Structure Optimization for Java

This guide covers class loading, module system, startup optimization, lazy initialization patterns, and package structure analysis for Java projects. For cross-domain interaction patterns (structure changes that affect CPU/memory/concurrency), see the deep agent prompt.

## Class Loading

### How the JVM loads classes

Classes are loaded lazily -- a class is loaded only when it is first actively used. "Active use" means:

- Creating an instance (`new MyClass()`)
- Accessing a static field (except compile-time constants)
- Calling a static method
- Reflecting on the class (`Class.forName("MyClass")`)
- Initializing a subclass (triggers parent class initialization first)

**Not active use** (does not trigger loading):
- Declaring a variable of that type (`MyClass x;` -- no load)
- Accessing a compile-time constant static field (`static final int X = 42` -- inlined by javac)
- Array type reference (`MyClass[] arr` -- array class is created, but `MyClass` is not loaded)

### Class loading phases

| Phase | What happens | Cost |
|-------|-------------|------|
| **Loading** | Read `.class` bytes, create `Class` object | I/O (disk or network) |
| **Linking: Verify** | Bytecode verification (type safety, stack consistency) | CPU (can be disabled with `-Xverify:none` -- not recommended in production) |
| **Linking: Prepare** | Allocate memory for static fields, set to default values | Memory |
| **Linking: Resolve** | Resolve symbolic references to direct references | CPU + may trigger loading of referenced classes |
| **Initialization** | Run `<clinit>` (static initializer blocks + static field initializers) | Arbitrary (depends on initializer code) |

### Static initializer timing and cost

The `<clinit>` method runs exactly once, when the class is first initialized. It includes all `static { ... }` blocks and `static` field initializer expressions, concatenated in source order.

```java
public class Config {
    // These all contribute to <clinit>:
    static final Map<String, String> DEFAULTS = loadDefaults();  // may read files
    static final Pattern REGEX = Pattern.compile("complex|regex");  // regex compilation
    static {
        System.loadLibrary("native-lib");  // JNI library loading
    }
}
```

**Dangers of heavy static initializers**:
- They run under the class initialization lock -- other threads that reference this class block until initialization completes
- Circular initialization dependencies can cause deadlocks
- Errors in `<clinit>` throw `ExceptionInInitializerError` and mark the class as unusable
- They contribute to startup time even if the initialized data isn't needed immediately

## Static Initializer Optimization

### Initialization-on-demand holder pattern

Defers initialization until the holder class is first accessed:

```java
// BEFORE: heavy initialization at class load time
public class ConfigService {
    private static final ExpensiveResource RESOURCE = createExpensiveResource();
    // RESOURCE is created when ConfigService is first loaded, even if RESOURCE is never used
}

// AFTER: holder pattern -- defers until first access
public class ConfigService {
    private static class ResourceHolder {
        static final ExpensiveResource INSTANCE = createExpensiveResource();
    }

    public static ExpensiveResource getResource() {
        return ResourceHolder.INSTANCE;  // ResourceHolder loaded (and INSTANCE created) here
    }
}
```

**Why this works**: The JVM guarantees that `ResourceHolder` is not loaded until `getResource()` is called. The class initialization lock ensures thread safety without explicit synchronization.

### Lazy singletons with Suppliers.memoize (Guava)

```java
private static final Supplier<ExpensiveResource> RESOURCE =
    Suppliers.memoize(() -> createExpensiveResource());

public static ExpensiveResource getResource() {
    return RESOURCE.get();  // created on first call, cached thereafter
}
```

### Double-checked locking (manual)

```java
private static volatile ExpensiveResource resource;

public static ExpensiveResource getResource() {
    ExpensiveResource local = resource;
    if (local == null) {
        synchronized (ConfigService.class) {
            local = resource;
            if (local == null) {
                resource = local = createExpensiveResource();
            }
        }
    }
    return local;
}
```

**The `volatile` keyword is mandatory.** Without it, the JVM may reorder the write to `resource` before the constructor completes, allowing another thread to see a partially-constructed object.

## JPMS (Java Platform Module System)

### Module descriptor (`module-info.java`)

```java
module com.example.app {
    requires java.net.http;          // dependency on another module
    requires transitive com.example.core;  // transitive: consumers of this module also get core

    exports com.example.app.api;     // packages accessible to other modules
    exports com.example.app.spi to com.example.plugin;  // qualified export

    opens com.example.app.model to com.fasterxml.jackson.databind;  // reflection access
    provides com.example.spi.Plugin with com.example.app.DefaultPlugin;
    uses com.example.spi.Plugin;
}
```

### Performance implications

- **Strong encapsulation**: Non-exported packages are inaccessible -- reduces accidental coupling, enables aggressive JIT optimization (JIT can prove more about types in encapsulated packages)
- **Reliable configuration**: Missing dependencies are detected at startup (fail-fast) instead of at first use (NoClassDefFoundError at runtime)
- **Split packages prohibited**: A package cannot span multiple modules -- eliminates a class of classloader ordering bugs

### Migration strategy

1. Run `jdeps --multi-release 21 -s app.jar` to analyze dependencies
2. Add `module-info.java` to each module with `requires` for direct dependencies
3. Use `--add-opens` and `--add-reads` flags for libraries that need deep reflection (Jackson, Hibernate)
4. Use `opens` directives instead of `--add-opens` flags for long-term cleanliness

## Circular Dependency Detection

### jdeps tool

```bash
# Analyze module dependencies
jdeps --module-path mods -s -m com.example.app

# Generate dot graph for visualization
jdeps --module-path mods --dot-output /tmp/deps -m com.example.app

# Analyze a JAR for package-level dependencies
jdeps -verbose:package app.jar

# Find cycles
jdeps -verbose:class app.jar 2>&1 | grep "cycle"
```

### Identifying cycles in package structure

Cycles indicate tangled responsibilities. Common patterns:

| Cycle pattern | Cause | Fix |
|--------------|-------|-----|
| `service` <-> `model` | Service creates model objects, model calls back to service | Extract interface in model, service implements it |
| `api` <-> `impl` | API references implementation types | API should define interfaces only; inject implementations |
| `core` <-> `util` | Utility functions need domain types | Move domain-dependent utilities into core, keep util generic |

### Breaking cycles

1. **Extract interface**: Move the shared contract to a third package that both depend on
2. **Dependency inversion**: Depend on abstractions, not concretions. The higher-level module defines the interface; the lower-level module implements it.
3. **Event-based decoupling**: Replace direct calls with events/listeners. The publisher doesn't know about the subscriber.
4. **Merge**: If two packages are tightly coupled in both directions, they may be one logical unit -- merge them.

## Startup Optimization

### Class Data Sharing (CDS)

CDS pre-loads class metadata into a shared archive that is memory-mapped at startup, avoiding repeated class loading and verification.

```bash
# Step 1: Create a class list (run with representative workload)
java -Xshare:off -XX:DumpLoadedClassList=classes.lst -jar app.jar

# Step 2: Create the CDS archive
java -Xshare:dump -XX:SharedClassListFile=classes.lst -XX:SharedArchiveFile=app-cds.jsa -jar app.jar

# Step 3: Use the archive at startup
java -Xshare:on -XX:SharedArchiveFile=app-cds.jsa -jar app.jar
```

**Impact**: 20-40% startup time reduction for typical applications. Larger improvements for applications that load many classes.

### AppCDS (JDK 10+)

Application Class Data Sharing extends CDS to include application classes (not just JDK classes):

```bash
# JDK 13+: automatic archive creation
java -XX:ArchiveClassesAtExit=app-cds.jsa -jar app.jar
java -XX:SharedArchiveFile=app-cds.jsa -jar app.jar
```

### GraalVM native-image

Ahead-of-time compilation eliminates JVM startup entirely:

```bash
native-image -jar app.jar
./app  # starts in milliseconds
```

**Trade-offs**:
- Startup: ~10-50 ms (vs ~500 ms - 5 s for JVM)
- Peak throughput: Lower than JVM (no JIT profile-guided optimization at runtime)
- Memory: Lower baseline (no JIT compiler, no interpreter)
- Build time: Minutes to tens of minutes
- Reflection: Must be configured explicitly (`reflect-config.json`)

**Best for**: CLI tools, serverless functions, microservices with strict startup requirements.

## ServiceLoader

Java's built-in service provider interface (SPI) mechanism.

### Performance considerations

```java
// EAGER: loads and instantiates ALL providers
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
for (Plugin p : loader) {  // instantiates each provider
    if (p.supports(format)) {
        return p;
    }
}

// LAZY (JDK 9+): load metadata without instantiation
ServiceLoader<Plugin> loader = ServiceLoader.load(Plugin.class);
Plugin plugin = loader.stream()
    .filter(p -> p.type().getAnnotation(FormatSupport.class).value().equals(format))
    .findFirst()
    .map(ServiceLoader.Provider::get)  // instantiate only the matching provider
    .orElseThrow();
```

### Module system integration

```java
// module-info.java (provider module)
module com.example.json {
    provides com.example.spi.FormatPlugin with com.example.json.JsonPlugin;
}

// module-info.java (consumer module)
module com.example.app {
    uses com.example.spi.FormatPlugin;
}
```

Module-based service loading is faster than classpath-based because the module system knows exactly which modules provide which services -- no classpath scanning needed.

## Framework Startup Optimization

### Spring Boot

| Technique | Impact | How |
|-----------|--------|-----|
| **Narrow component scan** | 10-30% startup reduction | `@ComponentScan(basePackages = "com.example.app")` instead of root package |
| **Lazy initialization** | 30-50% startup reduction (delays first-request latency) | `spring.main.lazy-initialization=true` in `application.properties` |
| **AOT compilation** (Spring 6+) | 40-60% startup reduction | `mvn spring-boot:process-aot && mvn spring-boot:build-image` |
| **Virtual threads** (Spring Boot 3.2+) | Better throughput under I/O load | `spring.threads.virtual.enabled=true` |
| **Conditional beans** | Avoid loading unused beans | `@ConditionalOnProperty`, `@ConditionalOnClass` |

### Quarkus

Quarkus performs build-time optimization by default:
- Classpath scanning, annotation processing, and proxy generation happen at build time
- CDI beans are discovered at build time (no runtime scanning)
- Native image support built-in (`./mvnw package -Pnative`)

### Micronaut

Similar to Quarkus -- compile-time dependency injection via annotation processing. No runtime reflection for DI.

## Package Structure

### DDD-style bounded contexts vs layer-based

**Layer-based** (traditional):
```
com.example.app/
  controller/
  service/
  repository/
  model/
  util/
```

**Bounded context / feature-based**:
```
com.example.app/
  order/
    OrderController.java
    OrderService.java
    OrderRepository.java
    Order.java
  user/
    UserController.java
    UserService.java
    UserRepository.java
    User.java
  shared/
    BaseEntity.java
```

**Performance implications**: Bounded context structure has better locality -- all files related to a feature are co-located. This reduces accidental coupling because cross-feature dependencies are visible as cross-package imports.

### Reducing package coupling

Use jdeps to measure coupling:

```bash
# Package-level dependency analysis
jdeps -verbose:package -filter:none app.jar

# Count inter-package dependencies
jdeps -verbose:package app.jar 2>&1 | grep " -> " | wc -l
```

**Signals of poor structure**:
- A single package imported by >50% of other packages (God package -- often `util` or `common`)
- Bidirectional dependencies between packages (cycle)
- A feature change requires modifying files in 5+ packages (shotgun surgery)
- A package with only 1-2 files that are always used together with another package's files (merge candidate)

## Profiling for Structure Issues

### Class loading profiling

```bash
# Log all class loading
java -verbose:class -jar app.jar 2>&1 | head -100

# JFR class load events
jcmd <pid> JFR.start name=classload settings=default duration=30s filename=/tmp/classload.jfr
```

Key JFR events:
- `jdk.ClassLoad`: Every class load with timestamp, classloader, and duration
- `jdk.ClassDefine`: Class definition (subset of ClassLoad)
- Look for class load bursts at startup -- these indicate initialization chains

### Static initializer profiling

Add timing to suspect static initializers:

```java
public class HeavyConfig {
    static {
        long start = System.nanoTime();
        // ... initialization code ...
        System.out.printf("HeavyConfig <clinit>: %d ms%n",
            (System.nanoTime() - start) / 1_000_000);
    }
}
```

Or use JFR `jdk.ClassLoad` event durations -- long class load times often indicate heavy `<clinit>`.

## Pitfalls

- **Don't load eagerly what can be lazy**: Heavy static initializers in classes that may not be used waste startup time. Use holder patterns or `Supplier.memoize`.
- **Don't scan the classpath at startup**: Component scanning, annotation scanning, and reflection-based DI all contribute to startup time. Narrow the scan scope or use build-time processing (Quarkus, Micronaut).
- **Don't ignore module boundaries**: Even without JPMS `module-info.java`, respect logical module boundaries in package structure. Tangled packages produce tangled code.
- **Don't create circular initialization dependencies**: Class A's static initializer references class B, whose static initializer references class A. This can deadlock if two threads trigger initialization simultaneously.
- **CDS archives are JDK-version-specific**: An archive created with JDK 21.0.1 may not work with JDK 21.0.2. Regenerate after JDK updates.
- **Native image reflection configuration**: GraalVM native-image does not support arbitrary reflection. All reflectively-accessed classes must be declared in `reflect-config.json` or via `@RegisterForReflection`. Missing configuration causes `ClassNotFoundException` at runtime.
