Android API Guidelines

This document is intended to be a general guide for developers to use to adhere to the general principles that the API Council enforces in API reviews.

In addition to understanding and following these guidelines when writing APIs, developers should run the API Lint tool, which encodes many of these rules in checks that it runs against APIs.

Think of this as the guide to the rules that are obeyed by that Lint tool, plus guidelines and general advice on rules that cannot be easily codified into that tool.

API Lint tool

API Lint is integrated into the Metalava static analysis tool and runs as part of the platform build. You can run it manually from an AOSP checkout with:

# Run lint on public API
$ make api-stubs-docs-api-lint

# Run lint on system API
$ make system-api-stubs-docs-api-lint

API rules

One of the difficulties in concrete rules is applying them to a platform that was developed without strict guidelines from the beginning, so some of the existing APIs may not adhere. In some cases, the right choice might be to go with what is consistent with APIs in the same general area of the code, rather than in the ideal rules laid out herein.

The rules are a work in progress and will be added to in the future as other patterns emerge from future API reviews.

API Basics

This category pertains to the core aspects of an Android API.

All APIs must be implemented

Irrespective of an API's audience (Public, @SystemApi, etc), all API surfaces must be implemented when merged or exposed as API. Merging API stubs with implementation to come at a later date would be a violation of this guideline.

Unimplemented API surfaces have multiple issues:

  • There is no guarantee that a proper or complete surface has been exposed. Until an API is tested or used by clients, there is no way to verify a client has the appropriate APIs to be able to use the feature.
  • APIs without implementation cannot be tested in Developer Previews.
  • APIs without implementation cannot be tested in CTS.
  • By definition, APIs without implementation are not IC complete.

All APIs must be tested

This is in line with CTS requirements and implementation expectation.

Testing API surfaces provides a base guarantee that the API surface is usable and we've exposed all the necessary aspects. Testing for existence is not sufficient; the API functionality itself must be tested.

You should be able to answer the question of “How will a developer test your API?”

All APIs must be documented

Documentation is a key part of API usability. While the syntax of an API surface may seem obvious, any new clients will not understand the semantics, behavior, or context behind the API.

Coding style [S]

This category pertains to the general coding style that developers should use, especially in the public API.

Follow standard Java coding conventions, except where noted

There are Android coding conventions posted for external contributors here:

https://source.android.com/source/code-style.html

Overall, we tend to follow standard Java coding conventions.

Please also review the Kotlin-Java interop guide for best practices related to writing Kotlin-friendly APIs in Java. Some of these guidelines are reflected in the recommendations on this site; however, the link may contain slightly newer information that has not yet been propagated.

Acronyms should not be capitalized in method names

For example: method name should be runCtsTests and not runCTSTests.

Names shouldn't end with Impl

This exposes implementation details, avoid that.

Classes [CL]

These are rules about classes, interfaces, and inheritance.

Inherit new public classes from the appropriate base class

Inheritance exposes API in your subclass which may not be appropriate. For example, a new public subclass of FrameLayout will look like a FrameLayout (plus the new functionality/API). If that inherited API is not appropriate for your use case, inherit from something further up the tree (for example, ViewGroup or even View, instead of FrameLayout).

If you are tempted to override methods from the base class to throw @UnsupportedOperationException, reconsider which base class you are using.

Use the base collections classes

Whether taking a collection as an argument or returning it as a value, always prefer the base class over the specific implementation (e.g. return List<Foo> rather than ArrayList<Foo>).

Use a base class that expresses appropriate constraints for the API. For example, an API whose collection must be ordered should use List and an API whose collection must consist of unique elements should use Set.

In Kotlin, prefer immutable collections. See Collection mutability for more details.

Abstract classes versus interfaces

Java 8 adds support for default interface methods, which allows API designers to add methods to interfaces while maintaining binary compatibility. Platform code and all Jetpack libraries should target Java 8 or later.

In cases where the default implementation is stateless, API designers should prefer interfaces over abstract classes -- that is, default interface methods can be implemented as calls to other interface methods.

In cases where a constructor or internal state is required by the default implementation, abstract classes must be used.

In both cases, API designers may choose to leave a single method abstract to simplify usage as a lambda.

public interface AnimationEndCallback {
  // Always called, must be implemented.
  public void onFinished(Animation anim);
  // Optional callbacks.
  public default void onStopped(Animation anim) { }
  public default void onCanceled(Animation anim) { }
}

Class names should reflect what they extend

For example, classes which extend Service should be named FooService for clarity.

public class IntentHelper extends Service {}
public class IntentService extends Service {}

Generic suffixes (Helper, Util, etc.)

Avoid using generic class name suffixes like Helper and Util for collections of utility methods. Instead, put the methods directly in the associated classes or into Kotlin extension functions.

In cases where methods are bridging multiple classes, give the containing class a meaningful name that explains what it does.

In very limited cases, using the Helper suffix may be appropriate:

  • Used for composition of default behavior
  • May involve delegation of existing behavior to new classes
  • May require persisted state
  • Typically involves View

For example, if backporting tooltips requires persisting state associated with a View and calling several methods on the View to install the backport, TooltipHelper would be an acceptable class name.

Do not use CompletableFuture or Future

java.util.concurrent.CompletableFuture has a large API surface that permits arbitrary mutation of the future's value and has error-prone defaults .

Conversely, java.util.concurrent.Future is missing non-blocking listening, making it hard to use with asynchronous code.

In platform code, prefer a combination of a completion callback, Executor, and if the API supports cancellation CancellationSignal.

public interface LoadFooCallback {
  void onSuccess(Foo result);
  void onFailure(Throwable t);
}

public void asyncLoadFoo(android.os.CancellationSignal cancellationSignal,
    Executor callbackExecutor, LoadFooCallback callback);

In libraries and apps, prefer Guava's ListenableFuture.

public com.google.common.util.concurrent.ListenableFuture<Foo> asyncLoadFoo();

If you are targeting Kotlin, prefer suspend functions.

suspend fun asyncLoadFoo(): Foo

Do not use Optional

While Optional can have advantages in some API surfaces it is inconsistent with the existing Android API surface area. @Nullable and @NonNull provide tooling assistance for null safety and Kotlin enforces nullability contracts at the compiler level, making Optional unnecessary API cruft.

Use private constructors for non-instantiable classes

Classes that can only be created by Builders, classes containing only constants or static methods, or otherwise non-instantiable classes should include at least one private constructor to prevent instantiation via the default no-arg constructor.

public final class Log {
  // Not instantiable.
  private Log() {
  }
}

Singleton classes should use getInstance() method

Singleton classes must have private constructors and only be acquired through static getInstance() methods.

Classes that release resources should implement AutoCloseable

Classes that release resources through close, release, destroy or similar methods should implement java.lang.AutoCloseable to allow developers to automatically clean up these resources when using a try-with-resources block.

Fields [F]

These rules are about public fields on classes.

Do not expose raw fields

Java classes should not expose fields directly. Fields should be private and accessible only via public getters and setters regardless of whether these fields are final or non-final.

Rare exceptions include simple data structures where there will never be a need to enhance the functionality of specifying or retrieving a field. In such cases, the fields should be named using standard variable naming conventions, ex. Point.x and Point.y.

Kotlin classes may expose properties.

Exposed fields should be marked final

Raw fields are strongly discouraged (@see Do not expose raw fields). But in the rare situation where a field is exposed as a public field, mark that field final.

Internal fields should not be exposed

Do not reference internal field names in public API.

public int mFlags;

Use public instead of protected

@see Use public instead of protected

Constants [C]

These are rules about public constants.

Flag constants should be non-overlapping int or long values

“Flags” implies bits that can be combined into some union value. If this is not the case, do not call the variable/constant flag.

public static final int FLAG_SOMETHING = 2;
public static final int FLAG_SOMETHING = 3;
public static final int FLAG_PRIVATE = 1 << 2;
public static final int FLAG_PRESENTATION = 1 << 3;

See @IntDef for bitmask flags for more information on defining public flag constants.

static final constants should use all-cap, underscore-separated naming convention

All words in the constant should be capitalized and multiple words should be separated by _. For example:

public static final int fooThing = 5
public static final int FOO_THING = 5

Use standard prefixes for constants

Many of the constants used in Android are for standard things, such as flags, keys, and actions. These constants should have standard prefixes to make them more identifiable as these things.

For example, intent extras should start with EXTRA_. Intent actions should start with ACTION_. Constants used with Context.bindService() should start with BIND_.

Naming and scoping of key constants

String constants values should be consistent with the constant name itself, and should generally be scoped to the package or domain. For example:

public static final String FOO_THING = foo

is neither named consistently nor appropriately scoped. Instead, consider:

public static final String FOO_THING = android.fooservice.FOO_THING

Prefixes of android in scoped string constants are reserved for the Android Open Source Project.

Intent actions and extras should be namespaced using the package name they are defined within.

package android.foo.bar {

public static final String ACTION_BAZ = android.foo.bar.action.BAZ

public static final String EXTRA_BAZ = android.foo.bar.extra.BAZ

}

Use public instead of protected

@see Use public instead of protected

Use consistent prefixes

Related constants should all start with the same prefix. For example, for a set of constants to use with flag values:

public static final int SOME_VALUE = 0x01;

public static final int SOME_OTHER_VALUE = 0x10;

public static final int SOME_THIRD_VALUE = 0x100;
public static final int FLAG_SOME_VALUE = 0x01;

public static final int FLAG_SOME_OTHER_VALUE = 0x10;

public static final int FLAG_SOME_THIRD_VALUE = 0x100;

@see Use standard prefixes for constants

Use consistent resource names

Public identifiers, attributes, and values must be named using the camelCase naming convention, e.g. @id/accessibilityActionPageUp or @attr/textAppearance, similar to public fields in Java.

In some cases, a public identifier or attribute may include a common prefix separated by an underscore:

  • Platform config values such as @string/config_recentsComponentName in config.xml
  • Layout-specific view attributes such as @attr/layout_marginStart in attrs.xml

Public themes and styles must follow the hierarchical PascalCase naming convention, e.g. @style/Theme.Material.Light.DarkActionBar or @style/Widget.Material.SearchView.ActionBar, similar to nested classes in Java.

Layout and drawable resources should not be exposed as public APIs. If they must be exposed, however, then public layouts and drawables must be named using the under_score naming convention, e.g. layout/simple_list_item_1.xml or drawable/title_bar_tall.xml.

When min/max constants could change, make them dynamic

If a MIN_FOO or MAX_FOO constant could change in the future, consider making them dynamic methods instead.

CameraManager.MAX_CAMERAS
CameraManager.getMaxCameras()

Consider forward-compatibility for callbacks

Constants defined in future API versions are not known to apps that target older APIs. For this reason, constants delivered to apps should take into consideration that app’s target API version and map newer constants to a consistent value. Consider the following scenario:

Hypothetical SDK source:

// Added in API level 22
public static final int STATUS_SUCCESS = 1;
public static final int STATUS_FAILURE = 2;
// Added in API level 23
public static final int STATUS_FAILURE_RETRY = 3;
// Added in API level 26
public static final int STATUS_FAILURE_ABORT = 4;

Hypothetical app with targetSdkVersion="22":

if (result == STATUS_FAILURE) {
  // Oh no!
} else {
  // Success!
}

In this case, the app was designed within the constraints of API level 22 and made a (somewhat) reasonable assumption that there were only two possible states. If the app receives the newly-added STATUS_FAILURE_RETRY, however, it will interpret this as success.

Methods that return constants can safely handle cases like this by constraining their output to match the API level targeted by the app:

private int mapResultForTargetSdk(Context context, int result) {
  int targetSdkVersion = context.getApplicationInfo().targetSdkVersion;
  if (targetSdkVersion < 26) {
    if (result == STATUS_FAILURE_ABORT) {
      return STATUS_FAILURE;
    }
    if (targetSdkVersion < 23) {
      if (result == STATUS_FAILURE_RETRY) {
        return STATUS_FAILURE;
      }
    }
  }
  return result;
}

It’s unreasonable to expect developers and their published applications to be clairvoyant. If you define an API with an UNKNOWN or UNSPECIFIED constant that looks like a catch-all, developers will assume that the published constants when they wrote their app are exhaustive. If you’re unwilling to set this expectation, reconsider whether a catch-all constant is a good idea for your API.

Consider also that libraries cannot specify their own targetSdkVersion separate from the app, and that handling targetSdkVersion behavior changes from library code is exceedingly complicated and error-prone.

Integer or String constant?

Use integer constants if the namespace for values is not extensible outside of your package. Use string constants if the namespace is shared or can be extended by code outside of your package.

Methods [M]

These are rules about various specifics in methods, around parameters, method names, return types, and access specifiers.

Time

Prefer java.time.* types where possible

java.time.Duration, java.time.Instant and many other java.time.* types are available on all platform versions through desugaring and should be preferred when expressing time in API parameters or return values.

Libraries targeting SDK < 26 must not use java.time.* until the AAR format supports advertising core library desugaring requirements (see b/203113147).

Prefer exposing only variants of an API that accept or return java.time.Duration or java.time.Instant and omit primitive variants with the same functionality unless the API domain is one where object allocation in intended usage patterns would have a prohibitive performance impact.

Methods expressing durations should be named duration

If a time value expresses the duration of time involved, name the parameter “duration”, not “time”.

ValueAnimator.setTime(java.time.Duration);
ValueAnimator.setDuration(java.time.Duration);

Exceptions:

“timeout” is appropriate when the duration specifically applies to a timeout value.

“time” with a type of java.time.Instant is appropriate when referring to a specific point in time, not a duration.

Methods expressing durations or time as a primitive should be named with their time unit, and use long

Methods accepting or returning durations as a primitive should suffix the method name with the associated time units (e.g. Millis, Nanos, Seconds) to reserve the undecorated name for use with java.time.Duration. See Time.

Methods should also be annotated apporiately with their unit and time base:

  • @CurrentTimeMillisLong: Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z.
  • @CurrentTimeSecondsLong: Value is a non-negative timestamp measured as the number of seconds since 1970-01-01T00:00:00Z.
  • @DurationMillisLong: Value is a non-negative duration in milliseconds.
  • @ElapsedRealtimeLong: Value is a non-negative timestamp in the SystemClock.elapsedRealtime() time base.
  • @UptimeMillisLong: Value is a non-negative timestamp in theSystemClock.uptimeMillis() time base.

Primitive time parameters or return values should use long, not int.

ValueAnimator.setDuration(@DurationMillisLong long);
ValueAnimator.setDurationNanos(long);

Methods expressing units of time should prefer non-abbreviated shorthand for unit names

public void setIntervalNs(long intervalNs);

public void setTimeoutUs(long timeoutUs);
public void setIntervalNanos(long intervalNanos);

public void setTimeoutMicros(long timeoutMicros);

Annotate long time arguments

The platform includes several annotations to provide stronger typing for long-type time units:

  • @CurrentTimeMillisLong: Value is a non-negative timestamp measured as the number of milliseconds since 1970-01-01T00:00:00Z, e.g. in the System.currentTimeMillis() time base.
  • @CurrentTimeSecondsLong: Value is a non-negative timestamp measured as the number of seconds since 1970-01-01T00:00:00Z.
  • @DurationMillisLong: Value is a non-negative duration in milliseconds.
  • @ElapsedRealtimeLong: Value is a non-negative timestamp in the SystemClock#elapsedRealtime() time base.
  • @UptimeMillisLong: Value is a non-negative timestamp in the SystemClock#uptimeMillis() time base.

Units of measurement

For all methods expressing a unit of measurement other than time, prefer CamelCased SI unit prefixes.

public  long[] getFrequenciesKhz();

public  float getStreamVolumeDb();

Put optional parameters at end in overloads

If you have overloads of a method with optional parameters, keep those parameters at the end and keep consistent ordering with the other parameters:

public int doFoo(boolean flag);

public int doFoo(int id, boolean flag);
public int doFoo(boolean flag);

public int doFoo(boolean flag, int id);

When adding overloads for optional arguments, the behavior of the simpler methods should behave in exactly the same way as if default arguments had been provided to the more elaborate methods.

Corollary: Don’t overload methods other than to add optional arguments or to accept different types of arguments if the method is polymorphic. If the overloaded method does something fundamentally different, then give it a new name.

Note: The guideline on placement of single abstract method parameters (ex. Runnable, listeners) overrides this guideline. In cases where a developer could reasonably expected to write the body of a SAM class as a lambda, the SAM class parameter should be placed last.

Do not remove default parameter values

If a method has shipped with a parameter with a default value, removal of the default value is a source incompatible change.

The most distinctive and identifying method parameters should be first

If you have a method with multiple parameters, put the most relevant ones first. Parameters that specify flags and other options are less important than those that describe the object that is being acted upon. If there is a completion callback, put it last.

public void openFile(int flags, String name);

public void openFileAsync(OnFileOpenedListener listener, String name, int flags);

public void setFlags(int mask, int flags);
public void openFile(String name, int flags);

public void openFileAsync(String name, int flags, OnFileOpenedListener listener);

public void setFlags(int flags, int mask);

@see Put optional parameters at end in overloads

Builders

The Builder pattern is recommended for creating complex Java objects, and is commonly used in Android for cases where:

  • The resulting object's properties should be immutable
  • There are a large number of required properties, e.g. many constructor arguments
  • There is a complex relationship between properties at construction time, e.g. a verification step is required. Note that this level of complexity often indicates problems with the API's usability.

In most cases, Kotlin classes should prefer constructors with default arguments over Builders.

Builder classes must return the builder

Builder classes must enable method chaining by returning the Builder object (e.g. this) from every method except build(). Additional built objects should be passed as arguments -- do not return a different object’s builder. For example:

public static class Builder {
  public void setDuration(long);
  public void setFrequency(int);
  public DtmfConfigBuilder addDtmfConfig();
  public Tone build();
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}

Builder classes must be created through a constructor

To ensure consistent Builder creation through Android API surface, all the builders must be created through a constructor and not a static creator method.

public class Tone {
  public static Builder builder();
  public static class Builder {
  }
}
public class Tone {
  public static class Builder {
    public Builder();
  }
}

All arguments to builder constructors must be required (e.g. @NonNull)

Optional, e.g. @Nullable, arguments should be moved to setter methods. The Builder constructor should throw an NullPointerException (consider using Objects.requireNonNull) if any required arguments are not specified.

Builder class should be a final static inner class of their built type

For the sake of logical organization within a package, builder classes should typically be exposed as final inner classes of their built types, ex. Tone.Builder rather than ToneBuilder.

Builders may include a constructor to create a new instance from an existing instance

Builders may include a copy constructor to create a new builder instance from an existing builder or built object. They should not provide alternative methods for creating builder instances from existing builders or build objects.

public class Tone {
  public static class Builder {
    public Builder clone();
  }

  public Builder toBuilder();
}
public class Tone {
  public static class Builder {
    public Builder(Builder original);
    public Builder(Tone original);
  }
}
Builder setters should take @Nullable arguments if the builder has copy constructor

Resetting is essential if a new instance of a builder may be created from an existing instance. If no copy constructor is available, then the builder may have either @Nullable or @NonNullable arguments.

public static class Builder {
  public Builder(Builder original);
  public Builder setObjectValue(@Nullable Object value);
}

Builder setters may be provided for mutable properties where setters are available on the build class

If your class has mutable properties and needs a Builder class, first ask yourself whether your class should actually have mutable properties.

Next, if you're certain that you need mutable properties, decide which of the following scenarios works better for your expected use case:

  1. The built object should be immediately usable, thus setters should be provided for all relevant properties whether mutable or immutable.

    map.put(key, new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .setUsefulMutableProperty(usefulValue)
        .build());
    
  2. Some additional calls may need to be made before the built object can be useful, thus setters should not be provided for mutable properties.

    Value v = new Value.Builder(requiredValue)
        .setImmutableProperty(immutableValue)
        .build();
    v.setUsefulMutableProperty(usefulValue)
    Result r = v.performSomeAction();
    Key k = callSomeMethod(r);
    map.put(k, v);
    

Don't mix the two scenarios.

Value v = new Value.Builder(requiredValue)
    .setImmutableProperty(immutableValue)
    .setUsefulMutableProperty(usefulValue)
    .build();
Result r = v.performSomeAction();
Key k = callSomeMethod(r);
map.put(k, v);

Builder should not have getters

Getter should be on the built object, not the builder.

Builder setters must have corresponding getters on the built class

public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }
}
public class Tone {
  public static class Builder {
    public Builder setDuration(long);
    public Builder setFrequency(int);
    public Builder addDtmfConfig(DtmfConfig);
    public Tone build();
  }

  public long getDuration();
  public int getFrequency();
  public @NonNull List<DtmfConfig> getDtmfConfigs();
}

Builder classes are expected to declare a build() method

Builder method naming

Builder methods names should use setFoo() / addFoo() / clearFoo() style.

Do not expose internal locks

Methods in the public API should not use the synchronized keyword. This keyword causes your object/class to be used as the lock, and since it’s exposed to others, you may encounter unexpected side effects if other code outside your class starts using it for locking purposes.

Instead, perform any required locking against an internal, private object.

public synchronized void doThing() { ... }
private Object mThingLock = new Object();

public void doThing() {
  synchronized (mThingLock) {
    ...
  }
}

Use is prefix for boolean accessor methods

This is the standard naming convention for boolean methods and fields in Java. Generally, boolean method and variable names should be written as questions that are answered by the return value.

Java boolean accessor methods should follow a set/is naming scheme and fields should prefer is, as in:

// Visibility is a direct property. The object "is" visible:
void setVisible(boolean visible);
boolean isVisible();

// Factory reset protection is an indirect property.
void setFactoryResetProtectionEnabled(boolean enabled);
boolean isFactoryResetProtectionEnabled();

final boolean isAvailable;

Using set/is for Java accessor methods or is for Java fields will allow them to be used as properties from Kotlin:

obj.isVisible = true
obj.isFactoryResetProtectionEnabled = false
if (!obj.isAvailable) return

Properties and accessor methods should generally use positive naming, e.g. Enabled rather than Disabled. Using negative terminology inverts the meaning of true and false and makes it more difficult to reason about behavior.

// Passing false here is a double-negative.
void setFactoryResetProtectionDisabled(boolean disabled);

In cases where the boolean describes inclusion or ownership of a property, you may use has rather than is; however, this will not work with Kotlin property syntax:

// Transient state is an indirect property used to track state
// related to the object. The object is not transient; rather,
// the object "has" transient state associated with it:
void setHasTransientState(boolean hasTransientState);
boolean hasTransientState();

Some alternative prefixes that may be more suitable include can and should:

// "Can" describes a behavior that the object may provide,
// and here is more concise than setRecordingEnabled or
// setRecordingAllowed. The object "can" record:
void setCanRecord(boolean canRecord);
boolean canRecord();

// "Should" describes a hint or property that is not strictly
// enforced, and here is more explicit than setFitWidthEnabled.
// The object "should" fit width:
void setShouldFitWidth(boolean shouldFitWidth);
boolean shouldFitWidth();

Methods that toggle behaviors or features may use the is prefix and Enabled suffix:

// "Enabled" describes the availability of a property, and is
// more appropriate here than "can use" or "should use" the
// property:
void setWiFiRoamingSettingEnabled(boolean enabled)
boolean isWiFiRoamingSettingEnabled()

Generally, method names should be written as questions that are answered by the return value.

Kotlin property methods

For a class property var foo: Foo Kotlin will autogenerate get/set methods using a simple rule: prepend get and uppercase the first character for the getter, and prepend set and uppercase the first character for the setter. The above declaration will produce methods named public Foo getFoo() and public void setFoo(Foo foo), respectively.

If the property is of type Boolean an additional rule applies in name generation: if the property name begins with is, then get is not prepended for the getter method name, the property name itself is used as the getter. Therefore, prefer naming Boolean properties with an is prefix in order to follow the naming guideline above:

var isVisible: Boolean

If your property is one of the aforementioned exceptions and begins with an appropriate prefix, use the @get:JvmName annotation on the property to manually specify the appropriate name:

@get:JvmName("hasTransientState")
var hasTransientState: Boolean

@get:JvmName("canRecord")
var canRecord: Boolean

@get:JvmName("shouldFitWidth")
var shouldFitWidth: Boolean

Bitmask accessors

See Use @IntDef for bitmask flags for API guidelines regarding defining bitmask flags.

Setters

Two setter methods should be provided: one that takes a full bitstring and overwrites all existing flags and another that takes a custom bitmask to allow more flexibility.

/**
 * Sets the state of all scroll indicators.
 * <p>
 * See {@link #setScrollIndicators(int, int)} for usage information.
 *
 * @param indicators a bitmask of indicators that should be enabled, or
 *                   {@code 0} to disable all indicators
 * @see #setScrollIndicators(int, int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators);

/**
 * Sets the state of the scroll indicators specified by the mask. To change
 * all scroll indicators at once, see {@link #setScrollIndicators(int)}.
 * <p>
 * When a scroll indicator is enabled, it will be displayed if the view
 * can scroll in the direction of the indicator.
 * <p>
 * Multiple indicator types may be enabled or disabled by passing the
 * logical OR of the desired types. If multiple types are specified, they
 * will all be set to the same enabled state.
 * <p>
 * For example, to enable the top scroll indicator:
 * {@code setScrollIndicators(SCROLL_INDICATOR_TOP, SCROLL_INDICATOR_TOP)}
 * <p>
 * To disable the top scroll indicator:
 * {@code setScrollIndicators(0, SCROLL_INDICATOR_TOP)}
 *
 * @param indicators a bitmask of values to set; may be a single flag,
 *                   the logical OR of multiple flags, or 0 to clear
 * @param mask a bitmask indicating which indicator flags to modify
 * @see #setScrollIndicators(int)
 * @see #getScrollIndicators()
 */
public void setScrollIndicators(@ScrollIndicators int indicators, @ScrollIndicators int mask);

Getters

One getter should be provided to obtain the full bitmask.

/**
 * Returns a bitmask representing the enabled scroll indicators.
 * <p>
 * For example, if the top and left scroll indicators are enabled and all
 * other indicators are disabled, the return value will be
 * {@code View.SCROLL_INDICATOR_TOP | View.SCROLL_INDICATOR_LEFT}.
 * <p>
 * To check whether the bottom scroll indicator is enabled, use the value
 * of {@code (getScrollIndicators() & View.SCROLL_INDICATOR_BOTTOM) != 0}.
 *
 * @return a bitmask representing the enabled scroll indicators
 */
@ScrollIndicators
public int getScrollIndicators();

Use public instead of protected

Always prefer public to protected in public API. Protected access ends up being painful in the long run, because implementers have to override to implement the functionality in cases where external access would have been just as good.

Remember that protected visibility does not prevent developers from calling an API -- it only makes it slightly more obnoxious.

Implement neither or both of equals() and hashCode()

If you override one, you must override the other.

Implement toString() for data classes

Data classes are encouraged to override toString(), to help developers debug their code.

Document whether the output is for program behavior or debugging

Decide whether you want program behavior to rely on your implementation or not. For example, UUID.toString() and File.toString() document their specific format for programs to use. If you are exposing information for debugging only, like Intent, simply inherit docs from the superclass.

Do not include extra information

All the information available from toString() should also be available through the public API of the object. Otherwise, you are encouraging developers to parse and rely on your toString() output, which will prevent future changes. A good practice is to implement toString() using only the object's public API.

Discourage reliance on debug output

While it's impossible to prevent developers from depending on debug output, including the System.identityHashCode of your object in its toString() output will make it very unlikely that two different objects will have equal toString() output.

@Override
public String toString() {
  return getClass().getSimpleName() + "@" + Integer.toHexString(System.identityHashCode(this)) + " {mFoo=" + mFoo + "}";
}

This can effectively discourage developers from writing test assertions like assertThat(a.toString()).isEqualTo(b.toString()) on your objects.

Use createFoo when returning newly created objects

Use the prefix create, not get or new, for methods that will create return values, e.g. by constructing new objects.

When the method will create an object to return, make that clear in the method name.

public FooThing getFooThing() {
  return new FooThing();
}
public FooThing createFooThing() {
  return new FooThing();
}

Methods accepting File objects should also accept streams

Data storage locations on Android are not always files on disk. For example, content passed across user boundaries is represented as content:// Uris. To enable processing of various data sources, APIs which accept File objects should also accept InputStream and/or OutputStream.

public void setDataSource(File file)
public void setDataSource(InputStream stream)

Take and return raw primitives instead of boxed versions

If you need to communicate missing or null values, consider using -1, Integer.MAX_VALUE, or Integer.MIN_VALUE.

public java.lang.Integer getLength()
public void setLength(java.lang.Integer)
public int getLength()
public void setLength(int value)

Avoiding class equivalents of primitive types avoids the memory overhead of these classes, method access to values, and, more importantly, autoboxing that comes from casting between primitive and object types. Avoiding these behaviors saves on memory and on temporary allocations that can lead to expensive and more frequent garbage collections.

Use annotations to clarify valid parameter and return values

Developer annotations were added to help clarify allowable values in various situations. This makes it easier for tools to help developers when they supply incorrect values (for example, passing an arbitrary int when the framework requires one of a specific set of constant values). Use any and all of the following annotations when appropriate:

Nullability

Explicit nullabilty annotations are required for Java APIs, but the concept of nullability is part of the Kotlin language and nullability annotations should never be used in Kotlin APIs.

@Nullable: Indicates that a given return value, parameter, or field can be null:

@Nullable
public String getName()

public void setName(@Nullable String name)

@NonNull: Indicates that a given return value, parameter, or field cannot be null. Marking things as @Nullable is relatively new to Android, so most of Android's API methods are not consistently documented. Therefore we have a tri-state of “unknown, @Nullable, @NonNull” which is why @NonNull is part of the API guidelines.:

@NonNull
public String getName()

public void setName(@NonNull String name)

Existing “not really nullable” methods: Existing methods in the API without a declared @Nullable annotation may be annotated @Nullable if the method can return null under specific, obvious circumstances (e.g. findViewById()). Companion @NotNull requireFoo() methods that throw IllegalArgumentException should be added for developers who do not want to null check.

Resources

Resource identifiers: Integer parameters that denote ids for specific resources should be annotated with the appropriate resource-type definition. There is an annotation for every type of resource, such as @StringRes, @ColorRes, and @AnimRes, in addition to the catch-all @AnyRes. For example:

public void setTitle(@StringRes int resId)

@IntDef for constant sets

Magic constants: String and int parameters that are meant to receive one of a finite set of possible values denoted by public constants should be annotated appropriately with @StringDef or @IntDef. These annotations allow you to create a new annotation that you can use that works like a typedef for allowable parameters. For example:

/** @hide */
@IntDef(prefix = {“NAVIGATION_MODE_”}, value = {
  NAVIGATION_MODE_STANDARD,
  NAVIGATION_MODE_LIST,
  NAVIGATION_MODE_TABS
})
@Retention(RetentionPolicy.SOURCE)
public @interface NavigationMode {}

public static final int NAVIGATION_MODE_STANDARD = 0;
public static final int NAVIGATION_MODE_LIST = 1;
public static final int NAVIGATION_MODE_TABS = 2;

@NavigationMode
public int getNavigationMode();
public void setNavigationMode(@NavigationMode int mode);

Notice that the constants must be defined in the class that will use them, not in a subclass or interface.

@IntDef for bitmask flags

The annotation can also specify that the constants are flags, and can be combined with & and I:

/** @hide */
@IntDef(flag = true, prefix = { FLAG_ }, value = {
  FLAG_USE_LOGO,
  FLAG_SHOW_HOME,
  FLAG_HOME_AS_UP,
});
@Retention(RetentionPolicy.SOURCE)
public @interface DisplayOptions {}

@StringDef for string constant sets

There is also the @StringDef annotation, which is exactly like @IntDef above, but for String constants. You can include multiple “prefix” values which are used to automatically emit documentation for all values.

@see Support Annotations (Ignore the “support” part of this document, and don’t use it in the includes; the framework has internal annotations that you should use directly. The article is intended for external developers that would be using the support library version of annotations instead).

@SdkConstant for SDK constants

@SdkConstant Annotate public fields when they are one of these SdkConstant values: ACTIVITY_INTENT_ACTION, BROADCAST_INTENT_ACTION, SERVICE_ACTION, INTENT_CATEGORY, FEATURE.

@SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION)
public static final String ACTION_CALL = "android.intent.action.CALL";

Annotate all non-primitive Java arguments and return types with nullability

Java APIs that do not have default nullability states interact poorly with Kotlin and static analysis tooling, unless explicitly annotated with @NonNull or @Nullable.

Additionally, annotating your method parameters will automatically generate documentation in the form “This value may be null.” unless “null” is explicitly used elsewhere in the parameter doc.

Methods are recommended to perform input validation for @NonNull parameters via Objects.requireNonNull() and throw a NullPointerException when the parameters are null.

Ensure overrides have compatible nullability

To ensure API compatibility, the nullability of overrides should be compatible with the current nullability of the parent. The table below represents the compatibility expectations. Plainly, overrides should only be as restrictive or more restrictive than the element they override.

typeparentchild
return typeunannotatedunannotated | non-null
return typenullablenullable | non-null
return typenon-nullnon-null
fun argumentunannotatedunannotated | nullable
fun argumentnullablenullable
fun argumentnon-nullnullable | non-null

Prefer non-Nullable (e.g. @NonNull) arguments where possible

When methods are overloaded, prefer that all arguments are non-null.

public void startActivity(@NonNull Component component) { ... }
public void startActivity(@NonNull Component component, @NonNull Bundle options) { ... }

This rule applies to overloaded property setters as well. The primary argument should be non-null and clearing the property should be implemented as a separate method. This prevents “nonsense” calls where the developer must set trailing parameters even though they are not required.

public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode)
public void setTitleItem(@Nullable IconCompat icon, @ImageMode mode, boolean isLoading)

// Nonsense call to clear property
setTitleItem(null, MODE_RAW, false);
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode)
public void setTitleItem(@NonNull IconCompat icon, @ImageMode mode, boolean isLoading)
public void clearTitleItem()

Prefer non-Nullable (e.g. @NonNull) return types for containers

For container types -- Bundles, Collections, etc. -- return an empty (and immutable, where applicable) container. In cases where null would be used to distinguish availability of a container, consider providing a separate boolean method.

@NonNull
public Bundle getExtras() { ... }

Note: Intent.getExtras() returns a @Nullable Bundle and specifies a case where it returns null, but this was a mistake that should be avoided in future APIs.

Nullability annotations for get/set pairs must agree

Get/set method pairs for a single logical property should always agree in their nullability annotations. Failing to follow this guideline will defeat Kotlin's property syntax, and adding disagreeing nullability annotations to existing property methods is therefore a source-breaking change for Kotlin users.

@NonNull
public Bundle getExtras() { ... }
public void setExtras(@NonNull Bundle bundle) { ... }

Return value in failure / error conditions

All APIs should permit applications to react to errors. Returning false, -1, null, or other catch-all values of “something went wrong” do not tell a developer enough about the failure to set user expectations or accurately track reliability of their app in the field. When designing an API, imagine that you are building an application. If you encounter an error, does the API give you enough information to surface it to the user or react appropriately?

  1. It‘s fine (and encouraged) to include detailed information in an exception message, but developers shouldn’t have to parse it to handle the error appropriately. Verbose error codes or other information should be exposed as methods.
  2. Make sure your chosen error handling option gives you the flexibility to introduce new error types in the future. For @IntDef, that means including an OTHER or UNKNOWN value - when returning a new code, you can check the caller‘s targetSdkVersion to avoid returning an error code the application doesn’t know about. For exceptions, have a common superclass that your exceptions implement, so that any code that handles that type will also catch and handle subtypes.
  3. It should be difficult or impossible for a developer to accidentally ignore an error -- if your error is communicated by returning a value, annotate your method with @CheckResult.

Prefer throwing a ? extends RuntimeException when a failure or error condition is reached due to something that the developer did wrong, for example ignoring constraints on input parameters or failing to check observable state.

Setter or action (ex. perform) methods may return an integer status code if the action may fail as a result of asynchronously-updated state or conditions outside the developer’s control.

Status codes should be defined on the containing class as public static final fields, prefixed with ERROR_, and enumerated in an @hide @IntDef annotation.

Method names should always begin with the verb, not the subject

The name of the method should always begin with the verb (e.g. get, create, reload, etc.), not the object you’re acting on.

public void tableReload() {
  mTable.reload();
}
public void reloadTable() {
  mTable.reload();
}

Prefer Collection<T> types over arrays as return or parameter type

Generically-typed collection interfaces provide several advantages over arrays, including stronger API guarantees around uniqueness and ordering, support for generics, and a number of developer-friendly convenience methods.

Exception for primitives

If the elements are primitives, do prefer arrays instead, in order to avoid the cost of auto-boxing. See Take and return raw primitives instead of boxed versions

Exception for Kotlin

Kotlin arrays are invariant and the Kotlin language provides ample utility APIs around arrays, so arrays are on-par with List and Collection for Kotlin APIs intended to be accessed from Kotlin.

Prefer @NonNull collections

Always prefer @NonNull for collection objects. When returning an empty collection, use the appropriate Collections.empty method to return a low-cost, correctly-typed, and immutable collection object.

Where type annotations are supported, always prefer @NonNull for collection elements.

Collection mutability

Kotlin APIs should prefer immutable (e.g. not Mutable) return types for collections by default unless the API contract specifically requires a mutable return type.

Java APIs, however, should prefer mutable return types by default since the Android platform‘s implementation of Java APIs does not yet provide a convenient implementation of immutable collections. The exception to this rule is Collections.empty return types, which are immutable. In cases where mutability could be exploited by clients -- on purpose or by mistake -- to break the API’s intended usage pattern, Java APIs should return a shallow copy of the collection.

@Nullable
public PermissionInfo[] getGrantedPermissions() {
  return mPermissions;
}
@NonNull
public Set<PermissionInfo> getGrantedPermissions() {
  if (mPermissions == null) {
    return Collections.emptySet();
  }
  return new ArraySet<>(mPermissions);
}

Explicitly mutable return types

APIs that return collections should ideally not modify the returned collection object after returning. If the returned collection must change or be reused in some way, for example, an adapted view of a mutable data set, the precise behavior of when the contents of a previously returned collection can change must be documented and/or appeal to an appropriate established convention. For example:

/**
 * Returns a view of this object as a list of [Item]s.
 */
fun MyObject.asList(): List<Item> = MyObjectListWrapper(this)

The Kotlin .asFoo() convention is described below and permits the collection returned by .asList() to change if the original collection changes.

Use of vararg parameter type

Both Kotlin and Java APIs are encouraged to use vararg in cases where the developer would be likely to create an array at the call site for the sole purpose of passing multiple, related parameters of the same type.

public void setFeatures(Feature[] features) { ... }

// Developer code
setFeatures(new Feature[]{Features.A, Features.B, Features.C});
public void setFeatures(Feature... features) { ... }

// Developer code
setFeatures(Features.A, Features.B, Features.C);

Defensive copies

Both Java and Kotlin implementations of vararg parameters compile to the same array-backed bytecode and as a result may be called from Java code with a mutable array. API designers are strongly encouraged to create a defensive shallow copy of the array parameter in cases where it will be persisted to a field or anonymous inner class.

public void setValues(SomeObject... values) {
   this.values = Arrays.copyOf(values, values.length);
}

Note that creating a defensive copy does not provide any protection against concurrent modification between the initial method call and the creation of the copy, not does it protect aginst mutation of the objects contained in the array.

Provide correct semantics with collection type parameters / returned types

List<Foo> is default option, but consider other types to provide additional meaning:

  • Use Set<Foo>, if your API is indifferent to the order of elements and it doesn’t allow duplicates or duplicates are meaningless.

  • Collection<Foo>, if your API is indifferent to the order and allows duplicates.

Note: Remember that Java Collections are mutable by default, so consider defensive copying for your return and parameter types. Another option for the return type is Collection.unmodifiable*.

Kotlin conversion functions

Kotlin frequently uses .toFoo() and .asFoo() to obtain an object of a different type from an existing object where Foo is the name of the conversion's return type. This is consistent with the familiar JDK Object.toString(). Kotlin takes this further by using it for primitive conversions such as 25.toFloat().

The distinction between conversions named .toFoo() and .asFoo() is significant:

Use .toFoo() when creating a new, independent object {.numbered}

Like .toString(), a “to” conversion returns a new, independent object. If the original object is modified later, the new object will not reflect those changes. Similarly, if the new object is modified later, the old object will not reflect those changes.

fun Foo.toBundle(): Bundle = Bundle().apply {
    putInt(FOO_VALUE_KEY, value)
}

Use .asFoo() when creating a dependent wrapper, decorated object, or cast {.numbered}

Casting in Kotlin is performed using the as keyword. It reflects a change in interface but not a change in identity. When used as a prefix in an extension function, .asFoo() decorates the receiver. A mutation in the original receiver object will be reflected in the object returned by asFoo(). A mutation in the new Foo object may be reflected in the original object.

fun <T> Flow<T>.asLiveData(): LiveData<T> = liveData {
    collect {
        emit(it)
    }
}

Conversion functions should be written as extensions {.numbered}

Writing conversion functions outside of both the receiver and the result class definitions reduces coupling between types. An ideal conversion needs only public API access to the original object. This proves by example that a developer can write analogous conversions to their own preferred types as well.

Throw appropriate specific exceptions

Methods must not throw generic exceptions such as java.lang.Exception or java.lang.Throwable, instead an approriate specific exception has to be used like java.lang.NullPointerException to allow developers to handle exceptions without being overly broad.

Methods that take no parameters should throw java.lang.IllegalStateException instead of java.lang.IllegalArgumentException or java.lang.NullPointerException.

Listeners and Callbacks

These are the rules around the classes and methods used for listener/callback mechanisms.

Callback class names should be singular

Use MyObjectCallback instead of MyObjectCallbacks.

Callback method names should be of the format on<Something>

onFooEvent signifies that FooEvent is happening and that the callback should act in response.

Past vs. present tense should describe timing behavior

Callback methods regarding events should be named to indicate whether the event has already happened or is in the process of happening.

For example, if the method is called after a click action has been performed:

public void onClicked()

However, if the method is responsible for performing the click action:

public boolean onClick()

Registering/unregistering callbacks

When a listener or callback can be added or removed from an object, the associated methods should be named add/remove OR register/unregister. Be consistent with the existing convention used by the class or by other classes in the same package. When no such precedent exists, prefer add/remove.

Methods involving registering or unregistering callbacks should specify the whole name of the callback type.

public void addFooCallback(@NonNull FooCallback callback);
public void removeFooCallback(@NonNull FooCallback callback);
public void registerFooCallback(@NonNull FooCallback callback);
public void unregisterFooCallback(@NonNull FooCallback callback);

Avoid getters for callbacks

Do not add getFooCallback() methods. This is a tempting escape hatch for cases where developers may want to chain an existing callback together with their own replacement, but it is brittle and makes the current state difficult to reason about for component developers. For example,

  • Developer A calls setFooCallback(a)
  • Developer B calls setFooCallback(new B(getFooCallback()))
  • Developer A wishes to remove its callback a and has no way to do so without knowledge of B’s type, and B having been built to allow such modifications of its wrapped callback.

Accept Executors to control callback dispatch

When registering callbacks that have no explicit threading expectations (pretty much anywhere outside the UI toolkit), it is strongly encouraged to include an Executor parameter as part of registration to allow the developer to specify the thread upon which the callbacks will be invoked.

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Note: Developers must provide a valid Executor. The new @CallbackExecutor annotation will add automatic documentation to tell developers about common default options. Also note that the callback argument is required to be last to enable idiomatic usage from Kotlin.

As an exception to our usual guidelines about optional parameters, it is ok to provide an overload omitting the Executor even though it is not the final argument in the parameter list. If the Executor is not provided, the callback should be invoked on the main thread using Looper.getMainLooper() and this should be documented on the associated overloaded method.

/**
 * ...
 * Note that the callback will be executed on the main thread using
 * {@link Looper.getMainLooper()}. To specify the execution thread, use
 * {@link registerFooCallback(Executor, FooCallback)}.
 * ...
 */
public void registerFooCallback(
    @NonNull FooCallback callback)

public void registerFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

Executor implementation gotchas: Note that the following is a valid executor!

public class SynchronousExecutor implements Executor {
    @Override
    public void execute(Runnable r) {
        r.run();
    }
}

This means that when implementing APIs that take this form, your incoming binder object implementation on the app process side must call Binder.clearCallingIdentity() before invoking the app’s callback on the app-supplied Executor. This way any application code that uses Binder identity (e.g. Binder.getCallingUid()) for permission checks correctly attributes the code running to the application and not to the system process calling into the app. If users of your API want the UID / PID information of the caller then this should be an explicit part of your API surface, rather than implicit based on where the Executor they supplied ran.

The above should be supported by your API. In performance-critical cases apps may need to run code either immediately or synchronously with feedback from your API. Accepting an Executor permits this. Defensively creating an additional HandlerThread or similar to trampoline from defeats this desirable use case.

If an app is going to run expensive code somewhere in their own process, let them. The workarounds that app developers will find to overcome your restrictions will be much harder to support in the long term.

Exception for single callback: when the nature of the events being reported calls for only supporting a single callback instance, use the following style:

public void setFooCallback(
    @NonNull @CallbackExecutor Executor executor,
    @NonNull FooCallback callback)

public void clearFooCallback()

Symmetry in Registration

If there is a way to add or register something, there should also be a way to remove/unregister it. The method

registerThing(Thing)

should have a matching

unregisterThing(Thing)

Multiple-method callback objects

Now that all the APIs can assume Java 8‘s default interface methods, multiple-method callbacks can always be an interface. (Previously, this guideline asked to use abstract class instead in situations where jave 8 wasn’t supported.)

public interface MostlyOptionalCallback {
  void onImportantAction();
  default void onOptionalInformation() {
    // Empty stub, this method is optional.
  }
}

Prefer functional interfaces over creating new single abstract method (SAM) types

API level 24 added the java.util.function.* (reference docs) types, which offer generic SAM interfaces such as Consumer<T> that are suitable for use as callback lambdas. In many cases, creating new SAM interfaces provides little value in terms of type safety or communicating intent while unnecessarily expanding the Android API surface area.

See an example of platform-defined functional interfaces -- Runnable, Supplier, and Consumer -- in the CTS theme test’s ConditionCheck class and associated usage.

Consider using these generic interfaces, rather than creating new ones:

  • Runnable (nilary function to void)
  • Supplier<R> (nilary function to R)
  • Consumer<T> (unary function T to void)
  • Function<T,R> (unary function from T to R)
  • Predicate<T> (unary function T to boolean)
  • many more available in reference docs

Placement of SAM parameters

SAM parameters should be placed last to enable idiomatic usage from Kotlin, even if the method is being overloaded with additional parameters.

public void schedule(Runnable runnable)

public void schedule(int delay, Runnable runnable)

Docs [D]

This section has been moved to a separate guide.

Android Framework-specific rules [FW]

This section has been moved to a separate guide.

Services

Handling Intents in system-bound developer services

Services that are intended to be extended by the developer and bound by the system, for example abstract services like NotificationListenerService, may respond to an Intent action from the system. Such services should meet the following criteria:

  1. Define a SERVICE_INTERFACE string constant on the class containing the fully-qualified class name of the service. This constant must be annotated with @SdkConstant(SdkConstant.SdkConstantType.SERVICE_ACTION).
  2. Document on the class that a developer must add an <intent-filter> to their AndroidManifest.xml in order to receive Intents from the platform.
  3. Strongly consider adding a system-level permission to prevent rogue apps from sending Intents to developer services.

Kotlin-Java interop

See the official Android Kotlin-Java interop guide for a full list of guidelines. Select guidelines have been copied to this guide to improve discoverability.

Companion objects

Kotlin uses companion object to expose static members. In some cases, these will show up from Java on an inner class named Companion rather than on the containing class.

To maximize compatibility with Java, annotate companion objects' non-const fields with @JvmField and public functions with @JvmStatic to expose them directly on the containing class.

companion object {
  @JvmField val BIG_INTEGER_ONE = BigInteger.ONE
  @JvmStatic fun fromPointF(pointf: PointF) {
    /* ... */
  }
}

Evolution of Android platform APIs

This section has been moved to a separate guide.

XML Schemas

If an XML schema serves as a stable interface between components, that schema must be explicitly specified and must evolve in a backward-compatible manner, similar to other Android APIs. For example, the structure of XML elements and attributes must be preserved similar to how methods and variables are maintained on other Android API surfaces.

Deprecation best-practices

If you'd like to deprecate an XML element or attribute, you can add the xs:annotation marker, but you must continue to support any existing XML files by following the typical @SystemApi evolution lifecycle.

<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

Element types must be preserved

Schemas support the sequence element, choice element and all elements as child elements of complexType element. However, these child elements differ in the number and order of their child elements, so modifying an existing type would be an incompatible change.

If you want to modify an existing type, the best-practice is to deprecate the old type and introduce a new type to replace it.

<!-- Original "sequence" value -->
<xs:element name="foo">
    <xs:complexType>
        <xs:sequence>
            <xs:element name="name" type="xs:string">
                <xs:annotation name="Deprecated"/>
            </xs:element>
        </xs:sequence>
    </xs:complexType>
</xs:element>

<!-- New "choice" value -->
<xs:element name="fooChoice">
    <xs:complexType>
        <xs:choice>
            <xs:element name="name" type="xs:string"/>
        </xs:choice>
    </xs:complexType>
</xs:element>