Java Code Writing — Android Automotive
Expert-level best practices for writing production-quality Java on Android
Automotive / AOSP platforms, guided by the principles of Joshua Bloch
(Effective Java), Robert C. Martin (Clean Code), and the Android Code Style Guide.
Standards baseline: Java 11 (default) · Java 8 min · Java 17 where available.
When to Use This Skill
- Writing a new Java class, service, or module for Android Framework or App layers.
- Reviewing a Gerrit patch that touches
.java files.
- Refactoring legacy Java to modern idioms (streams, optionals, records).
- Designing an Android System Service, AIDL interface, or Car API integration.
- Generating Javadoc class / method documentation.
Prerequisites
- Java version confirmed: Java 11 (default for AOSP), Java 8 min, Java 17 if available.
- Target confirmed: Android Framework (
com.android.*), Android App, or System Service.
- Android SDK / AOSP build environment available.
- Kotlin is out of scope here — use the
kotlin-code-writing skill.
1. Package & File Organization
Goal: clear module boundaries; one concept per file; zero circular dependencies.
- One top-level public class per
.java file; file name must match the class name exactly.
- Package names: all lowercase, reverse-domain convention —
com.company.module.feature.
- Never use the default (unnamed) package.
- Group imports: Android / AOSP imports → third-party →
java.* / javax.*. Remove unused imports — no wildcards (import java.util.*).
- Keep classes focused: aim for < 300 lines. If a class grows beyond that, look for extraction opportunities.
- Use package-private visibility as the default for internal implementation classes — not everything needs to be
public.
package com.automotive.sensor;
import android.content.Context; // Android first
import android.util.Log;
import com.automotive.hal.IAdcDriver; // project
import java.util.Optional; // java.* last
2. Class Design & SOLID Principles
Goal: classes that are easy to understand, test, extend, and replace independently.
Single Responsibility (S)
- Each class has one reason to change — one job, one actor that owns it.
- Activity / Fragment: only UI logic. ViewModel: only presentation state. Repository: only data access. Never mix concerns.
- If the class name contains "And", "Manager", or "Helper" spanning multiple domains, split it.
Open / Closed (O)
- Extend behaviour via new implementations or injected strategies, not by modifying existing classes.
- Use interfaces and abstract classes as stable extension points — callers depend on the abstraction.
Liskov Substitution (L)
- A subclass must honour every contract of its superclass: don't throw unchecked exceptions the parent doesn't throw, don't weaken preconditions.
- If overriding breaks callers, prefer composition (
has-a) over inheritance.
Interface Segregation (I)
- Prefer narrow, role-specific interfaces over fat interfaces.
- A class implementing an interface should use every method it provides.
// GOOD — focused interfaces
interface Readable { float read(); }
interface Writable { void write(float value); }
// BAD — forces all implementors to provide unused methods
interface SensorGod { float read(); void write(float v); void calibrate(); void reset(); }
Dependency Inversion (D)
- High-level classes must not depend on concrete low-level classes — both depend on interfaces.
- Inject dependencies through the constructor — never instantiate concrete collaborators inside a class.
- In Android: use constructor injection (Hilt / Dagger) or manual injection in tests.
// GOOD — ThermalMonitor depends on the ISensor interface, not a concrete class
public final class ThermalMonitor {
private final ISensor mSensor;
public ThermalMonitor(@NonNull ISensor sensor) {
mSensor = Objects.requireNonNull(sensor, "sensor must not be null");
}
}
3. Immutability & Object Design
Prefer immutable objects. Mutability is the root cause of most concurrency bugs.
- Declare fields
final whenever possible — signals intent clearly and enables safe sharing.
- Make classes
final unless designed for inheritance. Document inheritance contracts explicitly with @NonNull/@Nullable and override invariants.
- Prefer value objects (immutable data carriers) over mutable beans.
- Builder pattern for objects with ≥ 3 optional parameters — avoid telescoping constructors.
- Override
equals(), hashCode(), and toString() for value objects — always together, never partially.
- Use Java 16+ records (
record Point(int x, int y) {}) for pure data carriers when the toolchain allows.
// GOOD — immutable value object
public final class Temperature {
private final float mDegC;
public Temperature(float degC) {
mDegC = degC;
}
public float getDegC() { return mDegC; }
@Override public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Temperature)) return false;
return Float.compare(((Temperature) o).mDegC, mDegC) == 0;
}
@Override public int hashCode() { return Float.hashCode(mDegC); }
@Override public String toString() { return "Temperature{" + mDegC + "°C}"; }
}
4. Null Safety
Every NullPointerException in production is a design failure.
- Annotate all method parameters, return types, and fields with
@NonNull or @Nullable (androidx annotation or javax.annotation). Be explicit — unannotated means unknown.
- Validate
@NonNull parameters at the top of every constructor and public method:
Objects.requireNonNull(param, "param must not be null");
- Prefer
Optional<T> as a return type when a method may legitimately return no value — never return null from a public API.
- Never pass
null intentionally — redesign the API or use Optional/overloading.
- Use
@Nullable + explicit null-check over silent null-swallowing patterns like if (x != null) scattered throughout business logic.
// GOOD — Optional as explicit "no value" return
public Optional<Float> tryReadDegC() {
if (!mIsInitialised) { return Optional.empty(); }
return Optional.of(convertAdcToTemp(mDriver.readChannel(mChannel)));
}
// BAD — null return forces every caller to guess whether null is possible
public Float readDegC() {
if (!mIsInitialised) { return null; } // callers won't know to check
return convertAdcToTemp(mDriver.readChannel(mChannel));
}
5. Modern Java Idioms (Java 8–17)
Prefer language features that remove boilerplate and eliminate bug classes.
var (Java 10) — use for local variables when the type is obvious from the right-hand side. Avoid when it obscures type intent.
- Lambdas & method references — replace single-method anonymous classes. Prefer method references (
ClassName::method) over equivalent lambdas.
- Streams — use for declarative data transformation pipelines. Avoid side effects inside
map/filter. Prefer collect(Collectors.toUnmodifiableList()) over mutable lists.
Optional<T> — return type for "maybe a value". Never wrap Optional in another Optional or use it as a field/parameter type.
switch expressions (Java 14) — prefer over switch statements; exhaustive, returns a value.
- Records (Java 16) — concise immutable data carriers replacing boilerplate POJOs.
instanceof pattern matching (Java 16) — if (obj instanceof String s) eliminates explicit cast.
- Sealed classes (Java 17) — constrain inheritance to a known set of subtypes.
Collections.unmodifiableList / List.of / Map.of — return unmodifiable views from getters; never expose mutable internal collections.
// Method reference over lambda
list.stream()
.filter(Objects::nonNull)
.map(String::toUpperCase)
.collect(Collectors.toUnmodifiableList());
// switch expression (Java 14)
String label = switch (state) {
case IDLE -> "Idle";
case ACTIVE -> "Active";
case FAULT -> "Fault";
};
// instanceof pattern matching (Java 16)
if (event instanceof TemperatureEvent te) {
process(te.getDegC());
}
6. Error Handling
Choose one strategy per module boundary. Never swallow exceptions silently.
| Context |
Strategy |
Rationale |
| Programming errors (preconditions) |
throw new IllegalArgumentException / IllegalStateException |
Fail fast; not recoverable by caller |
| Recoverable failures in business logic |
Checked exceptions or Optional<T> / result type |
Forces callers to handle explicitly |
| I/O, system, hardware failures |
Checked exceptions (IOException, custom domain exception) |
Explicit error propagation |
| Android callbacks / async paths |
Error callback / LiveData<Result<T>> |
Exception can't propagate across thread boundary cleanly |
- Never
catch (Exception e) { /* swallow */ } — at minimum log and rethrow or propagate.
- Never use exceptions for control flow (e.g., catching
NumberFormatException instead of calling isDigit).
- Create domain-specific exception types for different failure categories —
SensorException, HalException.
- Always log at the point of first catch with full context (
Log.e(TAG, "readTemp failed: channel=" + mChannel, e)).
- In Android: avoid throwing exceptions across Binder calls — use
ServiceSpecificException or return status codes via AIDL.
// GOOD — domain exception with context
public float readDegC() throws SensorException {
if (!mIsInitialised) {
throw new IllegalStateException("TemperatureSensor not initialised");
}
try {
return mDriver.readChannel(mChannel);
} catch (HalException e) {
throw new SensorException("Failed to read ADC channel " + mChannel, e);
}
}
7. Concurrency & Thread Safety
Android has a strict main-thread rule. Design for it from the start.
- Document the thread-safety contract of every class and method with
@MainThread, @WorkerThread, @AnyThread, @GuardedBy("mLock") annotations (androidx).
- Never do I/O, blocking calls, or long computation on the main/UI thread — use
ExecutorService, HandlerThread, or coroutines.
- Protect shared mutable state with
synchronized blocks or java.util.concurrent.locks.ReentrantLock. Prefer the latter for tryLock / fairness needs.
- Use
java.util.concurrent atomic types (AtomicBoolean, AtomicInteger) for simple shared flags — cheaper than synchronized.
- Prefer immutable data + message passing over shared mutable state. In Android: use
Handler / Looper for thread-confined state, LiveData / Flow for observable state across threads.
- Use
volatile only for single-variable visibility guarantees — it is not a substitute for synchronisation of compound actions.
- Use
ConcurrentHashMap, CopyOnWriteArrayList for shared collections instead of manual synchronization.
- Use
CountDownLatch, Semaphore, BlockingQueue from java.util.concurrent for cross-thread coordination — not wait/notify directly.
@GuardedBy("mLock")
private float mLatestDegC;
private final Object mLock = new Object();
/** @thread_safety Safe for concurrent readers and a single writer. */
@AnyThread
public float getLatestDegC() {
synchronized (mLock) { return mLatestDegC; }
}
@WorkerThread
public void update(float degC) {
synchronized (mLock) { mLatestDegC = degC; }
}
8. Design Patterns
Pick the simplest pattern that solves the problem. Over-engineering is a defect.
Creational
| Pattern |
When to use |
Java idiom |
| Factory Method |
Decouple creation from use; subclasses decide the concrete type |
Static create() / newInstance() returning interface type |
| Abstract Factory |
Families of platform-specific objects (sensor suite per SoC vendor) |
Interface with multiple make*() factory methods |
| Builder |
Object with ≥ 3 optional / validated parameters |
Inner static Builder class; build() validates and returns the product |
| Singleton |
Process-wide resource (logger, config). Prefer DI; use sparingly |
enum singleton or private static final instance + getInstance() |
Structural
| Pattern |
When to use |
Java idiom |
| Adapter |
Wrap an incompatible legacy or C HAL API behind a Java interface |
Class implementing IFoo, delegating to legacy handle |
| Facade |
Single clean entry point to a complex subsystem (e.g., CarSensorManager) |
One class delegating internally; hides subsystem classes |
| Decorator |
Add cross-cutting behaviours (logging, retry, caching) without subclassing |
Implements same interface, wraps delegate, augments calls |
| Proxy |
Lazy init, access control, remoting (Binder stub is a proxy) |
Implements same interface, intercepts calls |
Behavioural
| Pattern |
When to use |
Java idiom |
| Strategy |
Swap algorithm at runtime without touching the host class |
Interface injected via constructor; @FunctionalInterface for single-method |
| Observer |
Decouple an event source from N listeners (event bus, LiveData) |
interface Listener { void onEvent(E e); } + addListener / removeListener |
| State |
Object's behaviour changes entirely based on internal state |
Enum states + switch, or IState interface with polymorphic dispatch |
| Command |
Encapsulate an operation (queue, undo, delayed execution) |
Runnable / Callable / @FunctionalInterface Command |
| Template Method |
Fixed algorithm skeleton; steps customised by subclass |
Abstract class with final public method + protected abstract hook methods |
// Builder — avoids telescoping constructors
public final class SensorConfig {
private final int mChannel;
private final long mTimeoutMs;
private final String mTag;
private SensorConfig(Builder b) {
mChannel = b.mChannel;
mTimeoutMs = b.mTimeoutMs;
mTag = b.mTag;
}
public static final class Builder {
private int mChannel;
private long mTimeoutMs = 100L;
private String mTag = "Sensor";
public Builder channel(int channel) { mChannel = channel; return this; }
public Builder timeoutMs(long ms) { mTimeoutMs = ms; return this; }
public Builder tag(@NonNull String tag) { mTag = tag; return this; }
public SensorConfig build() { return new SensorConfig(this); }
}
}
// Strategy via @FunctionalInterface
@FunctionalInterface
public interface SensorFilter { float apply(float raw); }
SensorFilter lowPass = raw -> raw * 0.9f + mPrev * 0.1f;
9. Android-Specific Best Practices
Android has its own lifecycle, IPC model, and resource constraints on top of Java.
- Context leaks: never store
Activity context in a static field or in a long-lived object. Use Application context for non-UI resources.
WeakReference for callbacks and listeners that might outlive the component. Always unregister in onDestroy / onStop.
- Binder / AIDL: methods called on the Binder thread, not the main thread. Never assume otherwise. Use
@NonNull/@Nullable in .aidl files.
- Lifecycle-awareness: prefer
LifecycleObserver / DefaultLifecycleObserver over manual onStart/onStop bookkeeping.
- Resources: release
Cursor, ParcelFileDescriptor, MediaPlayer, and similar in finally or try-with-resources.
- Permissions: check at runtime before use — never assume a permission is still granted after the initial check.
Log tag: private static final String TAG = TemperatureSensor.class.getSimpleName(); — max 23 chars (Android limit).
Handler / HandlerThread: always call quit() or quitSafely() when the component is destroyed to prevent leaks.
// try-with-resources for Android resources
try (Cursor cursor = db.query(table, columns, null, null, null, null, null)) {
while (cursor.moveToNext()) {
processRow(cursor);
}
}
// Lifecycle-aware component
public class SensorObserver implements DefaultLifecycleObserver {
@Override public void onStart(@NonNull LifecycleOwner owner) { register(); }
@Override public void onStop(@NonNull LifecycleOwner owner) { unregister(); }
}
10. Documentation (Javadoc)
If the intent is not obvious from the code alone, document it before writing the code.
- Every public class needs a
/** @brief summary */ block stating its purpose and context.
- Every public method: summary sentence,
@param, @return, @throws for each checked and commonly thrown unchecked exception.
@NonNull / @Nullable on every parameter and return type — treat it as part of the API contract.
@GuardedBy, @MainThread, @WorkerThread, @AnyThread on methods and fields with threading constraints.
@hide in AOSP/framework code to exclude from public SDK Javadoc while keeping internal docs.
{@link ClassName#method} to cross-reference related APIs.
/**
* Reads the current temperature from the NTC thermistor on the configured ADC channel.
*
* @return Temperature in degrees Celsius, or {@link Optional#empty()} on hardware error.
*
* @throws IllegalStateException if {@link #init()} has not been called successfully.
* @thread_safety Safe for concurrent callers after successful {@link #init()}.
*/
@WorkerThread
@NonNull
public Optional<Float> readDegC() { /* … */ }
Step-by-Step Workflows
Step 1: Set up the file structure
Use the correct package declaration; name the file after the public class.
Step 2: Design the class
Apply immutability where possible; annotate with @NonNull / @Nullable consistently.
Step 3: Apply the patterns from this skill
Follow sections 1–10 below in order: naming → null safety → modern Java → error handling, etc.
Step 4: Run Android Lint
Fix all Error severity issues; investigate and address Warning issues.
Step 5: Write unit tests
Use JUnit 4/5 with Mockito; test every public method with at least a happy-path test.
Pre-Commit Review Checklist
Before pushing to Gerrit, verify:
Package & file
Class design
Null safety
Modern Java
Error handling
Concurrency
Android-specific
Documentation
Examples
See companion files:
Troubleshooting
| Symptom |
Likely cause |
Fix |
NullPointerException in production |
Missing null check on input or return value |
Add @NonNull/@Nullable and requireNonNull guards |
NetworkOnMainThreadException |
Blocking I/O on UI thread |
Move to ExecutorService or WorkerThread |
ConcurrentModificationException |
Iterating while modifying collection |
Copy-on-iterate or use ConcurrentHashMap |
Binder exception: DeadObjectException |
Service crashed; client still calling |
Catch RemoteException, reconnect via ServiceConnection |
| Memory leak in Activity |
Static reference to Activity context |
Use WeakReference or ApplicationContext; unregister listeners |
IllegalStateException: Fragment not attached |
Callback fires after onDetach |
Guard fragment callbacks with isAdded() check |
References
1---2name: lang-java-code-writing3description: Use when writing, reviewing, or refactoring Java code (*.java) in an Android Automotive context (IVI, HUD, RSE on Android / AOSP). Covers Modern Java 8–17 idioms, SOLID principles, OOP design, immutability, null safety, clean error handling, concurrency, design patterns, Javadoc documentation, and a full pre-commit review checklist. Applies to Android Framework, Android App, and Android System Service development.4---56# Java Code Writing — Android Automotive78Expert-level best practices for writing production-quality Java on Android9Automotive / AOSP platforms, guided by the principles of Joshua Bloch10(Effective Java), Robert C. Martin (Clean Code), and the Android Code Style Guide.1112Standards baseline: **Java 11** (default) · **Java 8** min · **Java 17** where available.1314---1516## When to Use This Skill1718- Writing a new Java class, service, or module for Android Framework or App layers.19- Reviewing a Gerrit patch that touches `.java` files.20- Refactoring legacy Java to modern idioms (streams, optionals, records).21- Designing an Android System Service, AIDL interface, or Car API integration.22- Generating Javadoc class / method documentation.2324---2526## Prerequisites2728- Java version confirmed: **Java 11** (default for AOSP), Java 8 min, Java 17 if available.29- Target confirmed: Android Framework (`com.android.*`), Android App, or System Service.30- Android SDK / AOSP build environment available.31- Kotlin is out of scope here — use the `kotlin-code-writing` skill.3233---3435## 1. Package & File Organization3637> Goal: clear module boundaries; one concept per file; zero circular dependencies.3839- One **top-level public class** per `.java` file; file name must match the class name exactly.40- Package names: all lowercase, reverse-domain convention — `com.company.module.feature`.41- Never use the default (unnamed) package.42- Group imports: Android / AOSP imports → third-party → `java.*` / `javax.*`. Remove unused imports — no wildcards (`import java.util.*`).43- Keep classes focused: aim for < 300 lines. If a class grows beyond that, look for extraction opportunities.44- Use package-private visibility as the default for internal implementation classes — not everything needs to be `public`.4546```java47package com.automotive.sensor;4849import android.content.Context; // Android first50import android.util.Log;5152import com.automotive.hal.IAdcDriver; // project5354import java.util.Optional; // java.* last55```5657---5859## 2. Class Design & SOLID Principles6061> Goal: classes that are easy to understand, test, extend, and replace independently.6263### Single Responsibility (S)64- Each class has **one reason to change** — one job, one actor that owns it.65- Activity / Fragment: only UI logic. ViewModel: only presentation state. Repository: only data access. Never mix concerns.66- If the class name contains "And", "Manager", or "Helper" spanning multiple domains, split it.6768### Open / Closed (O)69- Extend behaviour via **new implementations or injected strategies**, not by modifying existing classes.70- Use interfaces and abstract classes as stable extension points — callers depend on the abstraction.7172### Liskov Substitution (L)73- A subclass must honour **every contract** of its superclass: don't throw unchecked exceptions the parent doesn't throw, don't weaken preconditions.74- If overriding breaks callers, prefer composition (`has-a`) over inheritance.7576### Interface Segregation (I)77- Prefer **narrow, role-specific interfaces** over fat interfaces.78- A class implementing an interface should use every method it provides.7980```java81// GOOD — focused interfaces82interface Readable { float read(); }83interface Writable { void write(float value); }8485// BAD — forces all implementors to provide unused methods86interface SensorGod { float read(); void write(float v); void calibrate(); void reset(); }87```8889### Dependency Inversion (D)90- High-level classes must not depend on concrete low-level classes — both depend on **interfaces**.91- Inject dependencies through the **constructor** — never instantiate concrete collaborators inside a class.92- In Android: use constructor injection (Hilt / Dagger) or manual injection in tests.9394```java95// GOOD — ThermalMonitor depends on the ISensor interface, not a concrete class96public final class ThermalMonitor {97 private final ISensor mSensor;9899 public ThermalMonitor(@NonNull ISensor sensor) {100 mSensor = Objects.requireNonNull(sensor, "sensor must not be null");101 }102}103```104105---106107## 3. Immutability & Object Design108109> Prefer immutable objects. Mutability is the root cause of most concurrency bugs.110111- Declare fields `final` whenever possible — signals intent clearly and enables safe sharing.112- Make classes `final` unless designed for inheritance. Document inheritance contracts explicitly with `@NonNull`/`@Nullable` and override invariants.113- Prefer **value objects** (immutable data carriers) over mutable beans.114- Builder pattern for objects with ≥ 3 optional parameters — avoid telescoping constructors.115- Override `equals()`, `hashCode()`, and `toString()` for value objects — always together, never partially.116- Use Java 16+ **records** (`record Point(int x, int y) {}`) for pure data carriers when the toolchain allows.117118```java119// GOOD — immutable value object120public final class Temperature {121 private final float mDegC;122123 public Temperature(float degC) {124 mDegC = degC;125 }126127 public float getDegC() { return mDegC; }128129 @Override public boolean equals(Object o) {130 if (this == o) return true;131 if (!(o instanceof Temperature)) return false;132 return Float.compare(((Temperature) o).mDegC, mDegC) == 0;133 }134135 @Override public int hashCode() { return Float.hashCode(mDegC); }136137 @Override public String toString() { return "Temperature{" + mDegC + "°C}"; }138}139```140141---142143## 4. Null Safety144145> Every `NullPointerException` in production is a design failure.146147- Annotate all method parameters, return types, and fields with `@NonNull` or `@Nullable` (androidx annotation or `javax.annotation`). Be explicit — unannotated means unknown.148- Validate `@NonNull` parameters at the **top of every constructor and public method**:149 `Objects.requireNonNull(param, "param must not be null");`150- Prefer `Optional<T>` as a **return type** when a method may legitimately return no value — never return `null` from a public API.151- Never pass `null` intentionally — redesign the API or use `Optional`/overloading.152- Use `@Nullable` + explicit null-check over silent null-swallowing patterns like `if (x != null)` scattered throughout business logic.153154```java155// GOOD — Optional as explicit "no value" return156public Optional<Float> tryReadDegC() {157 if (!mIsInitialised) { return Optional.empty(); }158 return Optional.of(convertAdcToTemp(mDriver.readChannel(mChannel)));159}160161// BAD — null return forces every caller to guess whether null is possible162public Float readDegC() {163 if (!mIsInitialised) { return null; } // callers won't know to check164 return convertAdcToTemp(mDriver.readChannel(mChannel));165}166```167168---169170## 5. Modern Java Idioms (Java 8–17)171172> Prefer language features that remove boilerplate and eliminate bug classes.173174- **`var` (Java 10)** — use for local variables when the type is obvious from the right-hand side. Avoid when it obscures type intent.175- **Lambdas & method references** — replace single-method anonymous classes. Prefer method references (`ClassName::method`) over equivalent lambdas.176- **Streams** — use for declarative data transformation pipelines. Avoid side effects inside `map`/`filter`. Prefer `collect(Collectors.toUnmodifiableList())` over mutable lists.177- **`Optional<T>`** — return type for "maybe a value". Never wrap `Optional` in another `Optional` or use it as a field/parameter type.178- **`switch` expressions (Java 14)** — prefer over `switch` statements; exhaustive, returns a value.179- **Records (Java 16)** — concise immutable data carriers replacing boilerplate POJOs.180- **`instanceof` pattern matching (Java 16)** — `if (obj instanceof String s)` eliminates explicit cast.181- **Sealed classes (Java 17)** — constrain inheritance to a known set of subtypes.182- **`Collections.unmodifiableList` / `List.of` / `Map.of`** — return unmodifiable views from getters; never expose mutable internal collections.183184```java185// Method reference over lambda186list.stream()187 .filter(Objects::nonNull)188 .map(String::toUpperCase)189 .collect(Collectors.toUnmodifiableList());190191// switch expression (Java 14)192String label = switch (state) {193 case IDLE -> "Idle";194 case ACTIVE -> "Active";195 case FAULT -> "Fault";196};197198// instanceof pattern matching (Java 16)199if (event instanceof TemperatureEvent te) {200 process(te.getDegC());201}202```203204---205206## 6. Error Handling207208> Choose one strategy per module boundary. Never swallow exceptions silently.209210| Context | Strategy | Rationale |211|---|---|---|212| Programming errors (preconditions) | `throw new IllegalArgumentException` / `IllegalStateException` | Fail fast; not recoverable by caller |213| Recoverable failures in business logic | **Checked exceptions** or `Optional<T>` / result type | Forces callers to handle explicitly |214| I/O, system, hardware failures | **Checked exceptions** (`IOException`, custom domain exception) | Explicit error propagation |215| Android callbacks / async paths | **Error callback / `LiveData<Result<T>>`** | Exception can't propagate across thread boundary cleanly |216217- Never `catch (Exception e) { /* swallow */ }` — at minimum log and rethrow or propagate.218- Never use exceptions for control flow (e.g., catching `NumberFormatException` instead of calling `isDigit`).219- Create **domain-specific exception types** for different failure categories — `SensorException`, `HalException`.220- Always log at the point of first catch with full context (`Log.e(TAG, "readTemp failed: channel=" + mChannel, e)`).221- In Android: avoid throwing exceptions across Binder calls — use `ServiceSpecificException` or return status codes via AIDL.222223```java224// GOOD — domain exception with context225public float readDegC() throws SensorException {226 if (!mIsInitialised) {227 throw new IllegalStateException("TemperatureSensor not initialised");228 }229 try {230 return mDriver.readChannel(mChannel);231 } catch (HalException e) {232 throw new SensorException("Failed to read ADC channel " + mChannel, e);233 }234}235```236237---238239## 7. Concurrency & Thread Safety240241> Android has a strict main-thread rule. Design for it from the start.242243- Document the thread-safety contract of every class and method with `@MainThread`, `@WorkerThread`, `@AnyThread`, `@GuardedBy("mLock")` annotations (androidx).244- **Never** do I/O, blocking calls, or long computation on the **main/UI thread** — use `ExecutorService`, `HandlerThread`, or coroutines.245- Protect shared mutable state with `synchronized` blocks or `java.util.concurrent.locks.ReentrantLock`. Prefer the latter for tryLock / fairness needs.246- Use `java.util.concurrent` atomic types (`AtomicBoolean`, `AtomicInteger`) for simple shared flags — cheaper than `synchronized`.247- Prefer **immutable data + message passing** over shared mutable state. In Android: use `Handler` / `Looper` for thread-confined state, `LiveData` / `Flow` for observable state across threads.248- Use `volatile` only for single-variable visibility guarantees — it is **not** a substitute for synchronisation of compound actions.249- Use `ConcurrentHashMap`, `CopyOnWriteArrayList` for shared collections instead of manual synchronization.250- Use `CountDownLatch`, `Semaphore`, `BlockingQueue` from `java.util.concurrent` for cross-thread coordination — not `wait`/`notify` directly.251252```java253@GuardedBy("mLock")254private float mLatestDegC;255private final Object mLock = new Object();256257/** @thread_safety Safe for concurrent readers and a single writer. */258@AnyThread259public float getLatestDegC() {260 synchronized (mLock) { return mLatestDegC; }261}262263@WorkerThread264public void update(float degC) {265 synchronized (mLock) { mLatestDegC = degC; }266}267```268269---270271## 8. Design Patterns272273> Pick the simplest pattern that solves the problem. Over-engineering is a defect.274275### Creational276277| Pattern | When to use | Java idiom |278|---|---|---|279| **Factory Method** | Decouple creation from use; subclasses decide the concrete type | Static `create()` / `newInstance()` returning interface type |280| **Abstract Factory** | Families of platform-specific objects (sensor suite per SoC vendor) | Interface with multiple `make*()` factory methods |281| **Builder** | Object with ≥ 3 optional / validated parameters | Inner static `Builder` class; `build()` validates and returns the product |282| **Singleton** | Process-wide resource (logger, config). Prefer DI; use sparingly | `enum` singleton or `private static final` instance + `getInstance()` |283284### Structural285286| Pattern | When to use | Java idiom |287|---|---|---|288| **Adapter** | Wrap an incompatible legacy or C HAL API behind a Java interface | Class implementing `IFoo`, delegating to legacy handle |289| **Facade** | Single clean entry point to a complex subsystem (e.g., `CarSensorManager`) | One class delegating internally; hides subsystem classes |290| **Decorator** | Add cross-cutting behaviours (logging, retry, caching) without subclassing | Implements same interface, wraps delegate, augments calls |291| **Proxy** | Lazy init, access control, remoting (Binder stub is a proxy) | Implements same interface, intercepts calls |292293### Behavioural294295| Pattern | When to use | Java idiom |296|---|---|---|297| **Strategy** | Swap algorithm at runtime without touching the host class | Interface injected via constructor; `@FunctionalInterface` for single-method |298| **Observer** | Decouple an event source from N listeners (event bus, LiveData) | `interface Listener { void onEvent(E e); }` + `addListener` / `removeListener` |299| **State** | Object's behaviour changes entirely based on internal state | Enum states + switch, or `IState` interface with polymorphic dispatch |300| **Command** | Encapsulate an operation (queue, undo, delayed execution) | `Runnable` / `Callable` / `@FunctionalInterface Command` |301| **Template Method** | Fixed algorithm skeleton; steps customised by subclass | Abstract class with `final` public method + `protected abstract` hook methods |302303```java304// Builder — avoids telescoping constructors305public final class SensorConfig {306 private final int mChannel;307 private final long mTimeoutMs;308 private final String mTag;309310 private SensorConfig(Builder b) {311 mChannel = b.mChannel;312 mTimeoutMs = b.mTimeoutMs;313 mTag = b.mTag;314 }315316 public static final class Builder {317 private int mChannel;318 private long mTimeoutMs = 100L;319 private String mTag = "Sensor";320321 public Builder channel(int channel) { mChannel = channel; return this; }322 public Builder timeoutMs(long ms) { mTimeoutMs = ms; return this; }323 public Builder tag(@NonNull String tag) { mTag = tag; return this; }324 public SensorConfig build() { return new SensorConfig(this); }325 }326}327328// Strategy via @FunctionalInterface329@FunctionalInterface330public interface SensorFilter { float apply(float raw); }331332SensorFilter lowPass = raw -> raw * 0.9f + mPrev * 0.1f;333```334335---336337## 9. Android-Specific Best Practices338339> Android has its own lifecycle, IPC model, and resource constraints on top of Java.340341- **Context leaks**: never store `Activity` context in a `static` field or in a long-lived object. Use `Application` context for non-UI resources.342- **`WeakReference`** for callbacks and listeners that might outlive the component. Always unregister in `onDestroy` / `onStop`.343- **Binder / AIDL**: methods called on the Binder thread, not the main thread. Never assume otherwise. Use `@NonNull`/`@Nullable` in `.aidl` files.344- **Lifecycle-awareness**: prefer `LifecycleObserver` / `DefaultLifecycleObserver` over manual `onStart`/`onStop` bookkeeping.345- **Resources**: release `Cursor`, `ParcelFileDescriptor`, `MediaPlayer`, and similar in `finally` or `try-with-resources`.346- **Permissions**: check at runtime before use — never assume a permission is still granted after the initial check.347- **`Log` tag**: `private static final String TAG = TemperatureSensor.class.getSimpleName();` — max 23 chars (Android limit).348- **`Handler` / `HandlerThread`**: always call `quit()` or `quitSafely()` when the component is destroyed to prevent leaks.349350```java351// try-with-resources for Android resources352try (Cursor cursor = db.query(table, columns, null, null, null, null, null)) {353 while (cursor.moveToNext()) {354 processRow(cursor);355 }356}357358// Lifecycle-aware component359public class SensorObserver implements DefaultLifecycleObserver {360 @Override public void onStart(@NonNull LifecycleOwner owner) { register(); }361 @Override public void onStop(@NonNull LifecycleOwner owner) { unregister(); }362}363```364365---366367## 10. Documentation (Javadoc)368369> If the intent is not obvious from the code alone, document it before writing the code.370371- Every public class needs a `/** @brief summary */` block stating its purpose and context.372- Every public method: summary sentence, `@param`, `@return`, `@throws` for each checked and commonly thrown unchecked exception.373- `@NonNull` / `@Nullable` on every parameter and return type — treat it as part of the API contract.374- `@GuardedBy`, `@MainThread`, `@WorkerThread`, `@AnyThread` on methods and fields with threading constraints.375- `@hide` in AOSP/framework code to exclude from public SDK Javadoc while keeping internal docs.376- `{@link ClassName#method}` to cross-reference related APIs.377378```java379/**380 * Reads the current temperature from the NTC thermistor on the configured ADC channel.381 *382 * @return Temperature in degrees Celsius, or {@link Optional#empty()} on hardware error.383 *384 * @throws IllegalStateException if {@link #init()} has not been called successfully.385 * @thread_safety Safe for concurrent callers after successful {@link #init()}.386 */387@WorkerThread388@NonNull389public Optional<Float> readDegC() { /* … */ }390```391392---393394## Step-by-Step Workflows395396### Step 1: Set up the file structure397Use the correct package declaration; name the file after the public class.398399### Step 2: Design the class400Apply immutability where possible; annotate with `@NonNull` / `@Nullable` consistently.401402### Step 3: Apply the patterns from this skill403Follow sections 1–10 below in order: naming → null safety → modern Java → error handling, etc.404405### Step 4: Run Android Lint406Fix all **Error** severity issues; investigate and address **Warning** issues.407408### Step 5: Write unit tests409Use JUnit 4/5 with Mockito; test every public method with at least a happy-path test.410411412## Pre-Commit Review Checklist413414Before pushing to Gerrit, verify:415416**Package & file**417- [ ] One top-level public class per `.java` file; file name matches class name418- [ ] Package name is lowercase, reverse-domain; no default package419- [ ] No wildcard imports; no unused imports420421**Class design**422- [ ] Each class has a single, clearly stated responsibility423- [ ] Constructors validate `@NonNull` parameters with `Objects.requireNonNull`424- [ ] Immutable fields declared `final`; value objects override `equals`/`hashCode`/`toString`425426**Null safety**427- [ ] All public parameters and return types annotated `@NonNull` or `@Nullable`428- [ ] No public method returns `null` — use `Optional<T>` or throw429- [ ] No silent null-swallowing; null checks have explicit consequences430431**Modern Java**432- [ ] No anonymous inner classes where a lambda / method reference suffices433- [ ] `List.of` / `Map.of` / `Collections.unmodifiableList` for collections returned from getters434- [ ] `Optional<T>` used as return type, not as parameter or field type435436**Error handling**437- [ ] No empty `catch` blocks — at minimum log + rethrow438- [ ] Exceptions carry full context (message + cause)439- [ ] No exceptions across Binder calls — use `ServiceSpecificException` or status codes440441**Concurrency**442- [ ] Thread-safety contract documented (`@MainThread`, `@WorkerThread`, `@GuardedBy`)443- [ ] No I/O or blocking calls on the main thread444- [ ] Shared mutable state protected by `synchronized` or `java.util.concurrent`445446**Android-specific**447- [ ] No `Activity` context stored in static fields or long-lived objects448- [ ] All `Cursor` / `ParcelFileDescriptor` / closeable resources in `try-with-resources`449- [ ] Listeners / callbacks unregistered in the matching lifecycle callback450451**Documentation**452- [ ] Every public class has a Javadoc summary453- [ ] Every public method has `@param`, `@return`, `@throws`454455---456457## Examples458459See companion files:460- [GoodTemperatureSensor.java](./examples/GoodTemperatureSensor.java) — SOLID, null-safe, Javadoc, Optional return461- [BadSensor.java](./examples/BadSensor.java) — common anti-patterns annotated line by line462463---464465## Troubleshooting466467| Symptom | Likely cause | Fix |468|---|---|---|469| `NullPointerException` in production | Missing null check on input or return value | Add `@NonNull`/`@Nullable` and `requireNonNull` guards |470| `NetworkOnMainThreadException` | Blocking I/O on UI thread | Move to `ExecutorService` or `WorkerThread` |471| `ConcurrentModificationException` | Iterating while modifying collection | Copy-on-iterate or use `ConcurrentHashMap` |472| Binder exception: `DeadObjectException` | Service crashed; client still calling | Catch `RemoteException`, reconnect via `ServiceConnection` |473| Memory leak in Activity | Static reference to Activity context | Use `WeakReference` or `ApplicationContext`; unregister listeners |474| `IllegalStateException: Fragment not attached` | Callback fires after `onDetach` | Guard fragment callbacks with `isAdded()` check |475476---477478## References479480- [Effective Java (3rd Ed.)](https://www.oreilly.com/library/view/effective-java-3rd/9780134686097/) — Joshua Bloch481- [Android Code Style Guide](https://source.android.com/docs/setup/contribute/code-style)482- [Android Architecture Guide](https://developer.android.com/topic/architecture)483- [Java Concurrency in Practice](https://jcip.net/) — Goetz et al.484- Internal skill: [kotlin-code-writing](../kotlin-code-writing/SKILL.md) — Kotlin equivalent for the same platforms