Multiclass APIs

If a single API is particularly complex, you might want to implement it from multiple Java classes. To make different classes part of the same API, you must:

For example, the following two classes are both part of the tictactoe API:

@Api(name = "tictactoe", version = "v1")
class TicTacToeA {  }

@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

The API configuration is specified through the @Api annotation properties. However, for multiple classes in the same API, the @Api requirements extend beyond simply having the same name and version strings in the @Api annotation for each class. In fact, your backend API will not work if there are any differences in the API configurations specified in the classes' @Api properties. Any difference in the @Api properties for classes in a multiclass API result in an "ambiguous" API configuration, which will not work in Cloud Endpoints Frameworks for App Engine.

There are several ways to create a unambiguous multiclass API:

  • Manually ensure that all classes in a single API have the exact same @Api annotation properties.
  • Use annotation inheritance through Java inheritance. In this inheritance, all classes in a single API inherit the same API configuration from a common @Api-annotated base class.
  • Use annotation inheritance through the @ApiReference annotation on all classes in a single API to have them reference the same API configuration from a common @Api-annotated class.

Using @ApiClass for properties that can differ between classes

To use this feature, you need the following import:

import com.google.api.server.spi.config.ApiClass;

While all properties in the @Api annotation must match for all classes in an API, you can additionally use the @ApiClass annotation to provides properties that don't need to be exactly the same between classes. For example:

// API methods implemented in this class allow only "clientIdA".
@Api(name = "tictactoe", version = "v1")
@ApiClass(clientIds = { "clientIdA" })
class TicTacToeA {  }

// API methods implemented in this class provide unauthenticated access.
@Api(name = "tictactoe", version = "v1")
class TicTacToeB {  }

where TicTacToeA limits access by using a whitelist of client IDs containing the allowed client ID, and TicTacToeB doesn't limit access.

All properties provided by the @ApiClass annotation have an equivalent property in the @Api annotation. Note that the @Api equivalent property acts as the API-wide default. If there is an API-wide default for that same property, specified in @Api, the class-specific @ApiClass property overrides the API-wide default.

The following examples illustrate the overriding of @Api properties by the class-specific @ApiClass equivalents:

// For this class "boards" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "boards")
class TicTacToeBoards {  }

// For this class "scores" overrides "games".
@Api(name = "tictactoe", version = "v1", resource = "games")
@ApiClass(resource = "scores")
class TicTacToeScores {  }

// For this class, the API-wide default "games" is used as the resource.
@Api(name = "tictactoe", version = "v1", resource = "games")
class TicTacToeGames {  }

Annotation inheritance

The @Api and @ApiClass annotation properties can be inherited from from other classes, and individual properties can be overridden either through Java inheritance or @ApiReference inheritance

Using Java inheritance

A class that extends another class with @Api or @ApiClass annotations behaves as if annotated with the same properties. For example:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA and TicTacToeB both behave as if they have the same @Api annotation as
// TicTacToeBase
class TicTacToeA extends TicTacToeBase {  }
class TicTacToeB extends TicTacToeBase {  }

Annotations are only inherited through Java subclassing, not through interface implementation. For example:

@Api(name = "tictactoe", version = "v1")
interface TicTacToeBase {  }
// Does *not* behave as if annotated.
class TicTacToeA implements TicTacToeBase {  }

As a result, there is no support for any sort of multiple inheritance of frameworks annotations.

Inheritance works for @ApiClass too:

@ApiClass(resource = "boards")
class BoardsBase {  }

// TicTacToeBoards behaves as if annotated with the @ApiClass from BoardsBase.
// Thus, the "resource" property will be "boards".
@Api(name = "tictactoe", version = "v1", resource = "scores")
class TicTacToeBoards extends BoardsBase {  }

where TicTacToeBoards inherits the resource property value boards from BoardsBase, thus overriding the resource property setting (scores) in its @Api annotation. Remember that if any class has specified the resource property in the @Api annotation, all of the classes need to specify that same setting in the @Api annotation; this inheritance technique lets you override that @Api property.

Using @ApiReference inheritance

To use this feature, you need the following import:

import com.google.api.server.spi.config.ApiReference;

The @ApiReference annotation provides an alternate way to specify annotation inheritance. A class that uses @ApiReference to specify another class with @Api or @ApiClass annotations behaves as if annotated with the same properties. For example:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {  }

// TicTacToeA behaves as if it has the same @Api annotation as TicTacToeBase
@ApiReference(TicTacToeBase.class)
class TicTacToeA {  }

If both Java inheritance and the @ApiReference are used, the annotations inherit through the @ApiReference annotation only. @Api and @ApiClass annotations on the class inherited through Java inheritance are ignored. For example:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBaseA {  }
@Api(name = "tictactoe", version = "v2")
class TicTacToeBaseB {  }

// TicTacToe will behave as if annotated the same as TicTacToeBaseA, not TicTacToeBaseB.
// The value of the "version" property will be "v1".
@ApiReference(TicTacToeBaseA.class)
class TicTacToe extends TicTacToeBaseB {  }

Overriding inherited configuration

Whether inheriting configuration by using Java inheritance or @ApiReference, you can override the inherited configuration by using a new @Api or @ApiClass annotation. Only configuration properties specified in the new annotation are overridden. Properties that are unspecified are still inherited. For example:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@Api(name = "checkers")
class Checkers extends TicTacToe {  }

Overriding inheritance works for @ApiClass too:

@Api(name = "tictactoe", version = "v1")
@ApiClass(resource = "boards", clientIds = { "c1" })
class Boards {  }

// Scores will behave as if annotated with resource = "scores" and clientIds = { "c1" }
@ApiClass(resource = "scores")
class Scores {  }

Overriding also works when inheriting through @ApiReference:

@Api(name = "tictactoe", version = "v2")
class TicTacToe {  }

// Checkers will behave as if annotated with name = "checkers" and version = "v2"
@ApiReference(TicTacToe.class)
@Api(name = "checkers")
class Checkers {  }

Inheriting @ApiMethod annotations

The @ApiMethod annotation can be inherited from overridden methods. For example:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST")
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with the @ApiMethod from TicTacToeBase.setGame.
  // Thus the "httpMethod" property will be "POST".
  @Override
  public Game setGame(Game game) {  }
}

Similarly to @Api and @ApiClass annotation inheritance, if multiple methods overriding each other have @ApiMethod annotations, individual properties can be overridden. For example:

class TicTacToeBase {
  @ApiMethod(httpMethod = "POST", clientIds = { "c1" })
  public Game setGame(Game game) {  }
}
@Api(name = "tictactoe", version = "v1")
class TicTacToe extends TicTacToeBase {
  // setGame behaves as if annotated with httpMethod = "GET" and clientIds = { "c1"}.
  @ApiMethod(httpMethod = "GET")
  @Override
  public Game setGame(Game game) {  }
}

There is no @ApiReference annotation or equivalent for methods, so @ApiMethod is always inherited through Java inheritance, not through @ApiReference.

Inheritance and precedence rules

To synopsize the preceding discussion, the follow table shows the inheritance rules and order of precedence.

Annotation/inheritance Rule
@Api Must be identical for all classes.
@ApiClass Specified for a class to override @Api properties.
Java inheritance Class inherits @Api and @ApiClass of base class.
@ApiReference Class inherits @Api and @ApiClass of referenced class.
Using @ApiReference on a class (Java) inheriting from a base class Class inherits the @Api and @ApiClass of referenced class, not from the base class.

Common use cases for annotation inheritance

The following are examples of the typical use cases for inheritance:

For API versioning:

@Api(name = "tictactoe", version = "v1")
class TicTacToeV1 {  }
@Api(version = "v2")
class TicTacToeV2 extends TicTacToeV1 {  }

For multiclass APIs:

@Api(name = "tictactoe", version = "v1")
class TicTacToeBase {}
@ApiClass(resource = "boards")
class TicTacToeBoards extends TicTacToeBase {  }
@ApiClass(resource = "scores")
class TicTacToeScores extends TicTacToeBase {  }

For testing different versions of the same API:

@Api(name = "tictactoe", version = "v1")
class TicTacToe {
  protected Foo someMethod() {
    // Do something real;
  }

  public Foo getFoo() {  }
}


@Api(version="v1test")
class TicTacToeTest extends TicTacToe {
  protected Foo someMethod() {
    // Stub out real action;
  }
}

where someMethod might return pre-determined responses, avoid calls with side-effects, skip a network or datastore request, and so forth.

Adding the classes to web.xml

After annotating your classes, you must add them to your web.xml file. The following example shows a single class:

<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee
         http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd"
         version="3.1">
    <!-- Wrap the backend with Endpoints Frameworks v2. -->
    <servlet>
        <servlet-name>EndpointsServlet</servlet-name>
        <servlet-class>com.google.api.server.spi.EndpointsServlet</servlet-class>
        <init-param>
            <param-name>services</param-name>
            <param-value>com.example.skeleton.MyApi</param-value>
        </init-param>
    </servlet>
    <!-- Route API method requests to the backend. -->
    <servlet-mapping>
        <servlet-name>EndpointsServlet</servlet-name>
        <url-pattern>/_ah/api/*</url-pattern>
    </servlet-mapping>
</web-app>

To add multiple classes:

  1. Replace <param-value>com.example.skeleton.MyApi</param-value> with your own API class name.

  2. Add each class inside this same <param-value> field separated by a comma, for example:

    <param-value>com.example-company.example-api.Hello,com.example-company.example-api.Goodbye</param-value>