Using Datastore with Java for the App Engine standard environment

This part of the Bookshelf tutorial for Java shows how to create, read, update, and delete structured data in Datastore.

This page is part of a multipage tutorial. To start from the beginning and read the setup instructions, go to Java Bookshelf app.

Configuring settings

The pom.xml file holds configuration information for all parts of the Bookshelf tutorial. For this part of the tutorial, you don't have to add any configuration information. The only requirement is that the bookshelf.storageType property is set to datastore, which is already done for you.

Running the app on your local machine

To run the app locally:

  1. In the getting-started-java/bookshelf-standard/2-structured-data directory, enter the following command to start a local web server:

    mvn -Plocal clean appengine:devserver
  2. In your web browser, go to http://localhost:8080.

Now you can browse the app's web pages to add, edit, and delete books.

Deploying the app to the App Engine standard environment

To deploy to the App Engine standard environment:

  1. To create a Datastore index that is uploaded to the Bookshelf app, create at least one book, and then click My Books. Note, building the app using the clean command deletes that local index so do this on the build of the app that you are going to deploy.
  2. In the getting-started-java/bookshelf-standard/2-structured-data directory, enter the following command to deploy the app:
    mvn appengine:deploy -Dappengine.appId=[YOUR-PROJECT-ID] -Dappengine.version=[YOUR-VERSION]
    Replace [YOUR-PROJECT-ID] with your project ID and [YOUR-VERSION] with your version, for example, 1, or 2, or some other string value you want to use.
  3. In your web browser, enter the following address:
    Replace [YOUR-PROJECT-ID] with your project ID.

After you update your app, you can redeploy the updated version by entering the same command as when you first deployed the app as well as specifying the same project ID and version. This overwrites the currently deployed app. If you specify a different version string in the updated command line, the new deployment creates a new version of your app and promotes it to be the currently serving version.

You can reduce costs by deleting the non-serving versions of your app.

To delete an app version:

  1. In the Cloud Console, go to the Versions page for App Engine.

    Go to the Versions page

  2. Select the checkbox for the non-default app version you want to delete.
  3. Click Delete to delete the app version.

For complete information about cleaning up billable resources, see the Cleaning up section in the final step of this tutorial.

App structure

The following diagram shows the app's components and how they fit together.

Bookshelf app deployment process and structure

Understanding the code

This section walks you through the app's code and explains how it works.

Interact with Datastore

The app uses an authorized Datastore service object to interact with Datastore. This object exchanges credentials for a token to access the Datastore APIs. The app also creates a KeyFactory object of type Book. The book is the only type of data that this app stores; in general, however, you can store as many types of data as you like. Datastore supports a rich assortment of data types.

private DatastoreService datastore;
private static final String BOOK_KIND = "Book2";

public DatastoreDao() {
  datastore = DatastoreServiceFactory.getDatastoreService(); // Authorized Datastore service

Create books

To save a book, the app stores an entity in Datastore. An entity has a key and a collection of name/value pairs. The createBook method creates a new entity of BOOK type and sets user-supplied values for author, description, publish date, and title. The key is assigned to the entity when the entity is added to Datastore.

public Long createBook(Book book) {
  Entity incBookEntity = new Entity(BOOK_KIND);  // Key will be assigned once written
  incBookEntity.setProperty(Book.AUTHOR, book.getAuthor());
  incBookEntity.setProperty(Book.DESCRIPTION, book.getDescription());
  incBookEntity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());
  incBookEntity.setProperty(Book.TITLE, book.getTitle());

  Key bookKey = datastore.put(incBookEntity); // Save the Entity
  return bookKey.getId();                     // The ID of the Key

Read from Datastore

The readBook method takes a book's ID and converts it to a key. Then it passes the key to the getProperty method, which returns an Entity object.

public Book readBook(Long bookId) {
  try {
    Entity bookEntity = datastore.get(KeyFactory.createKey(BOOK_KIND, bookId));
    return entityToBook(bookEntity);
  } catch (EntityNotFoundException e) {
    return null;

The entityToBook method converts the entity returned from Datastore to a Book object by using getProperty methods for each type, similar to the way setProperty is used during entity creation. Property names are String values.

public Book entityToBook(Entity entity) {
  return new Book.Builder()                                     // Convert to Book form
      .author((String) entity.getProperty(Book.AUTHOR))
      .description((String) entity.getProperty(Book.DESCRIPTION))
      .publishedDate((String) entity.getProperty(Book.PUBLISHED_DATE))
      .title((String) entity.getProperty(Book.TITLE))

Update books

Update requires calling the put method with an Entity.

public void updateBook(Book book) {
  Key key = KeyFactory.createKey(BOOK_KIND, book.getId());  // From a book, create a Key
  Entity entity = new Entity(key);         // Convert Book to an Entity
  entity.setProperty(Book.AUTHOR, book.getAuthor());
  entity.setProperty(Book.DESCRIPTION, book.getDescription());
  entity.setProperty(Book.PUBLISHED_DATE, book.getPublishedDate());
  entity.setProperty(Book.TITLE, book.getTitle());

  datastore.put(entity);                   // Update the Entity

Delete books

Deleting with Datastore requires calling the delete method with a Key.

public void deleteBook(Long bookId) {
  Key key = KeyFactory.createKey(BOOK_KIND, bookId);        // Create the Key
  datastore.delete(key);                      // Delete the Entity

List books

The listBooks method returns a list of books, 10 at a time, along with a URL safe Cursor.

public Result<Book> listBooks(String startCursorString) {
  FetchOptions fetchOptions = FetchOptions.Builder.withLimit(10); // Only show 10 at a time
  if (startCursorString != null && !startCursorString.equals("")) {
    fetchOptions.startCursor(Cursor.fromWebSafeString(startCursorString)); // Where we left off
  Query query = new Query(BOOK_KIND) // We only care about Books
      .addSort(Book.TITLE, SortDirection.ASCENDING); // Use default Index "title"
  PreparedQuery preparedQuery = datastore.prepare(query);
  QueryResultIterator<Entity> results = preparedQuery.asQueryResultIterator(fetchOptions);

  List<Book> resultBooks = entitiesToBooks(results);     // Retrieve and convert Entities
  Cursor cursor = results.getCursor();              // Where to start next time
  if (cursor != null && resultBooks.size() == 10) {         // Are we paging? Save Cursor
    String cursorString = cursor.toWebSafeString();               // Cursors are WebSafe
    return new Result<>(resultBooks, cursorString);
  } else {
    return new Result<>(resultBooks);


Datastore creates an index for each of your properties by default, although you can change this behavior. Some queries need more than a single property in the index to complete, such as Query by user and ordered by Title query above. You can create indexes to support such queries manually or by using the development server to create indexes.

Was this page helpful? Let us know how we did:

Send feedback about...