Support for newer Java language APIs

Murat Yener
Android Developers
Published in
6 min readJul 14, 2020

--

Imagine developing an app where you need to deal with time calculations. After some googling, you’ll most likely end up finding great samples with the java.time package! However, after releasing your app, suddenly you get thousands of crashes and complaints from users on API < 26!

Want to know the reason? Watch the video or read the blog below.

The reason is that the devices which encountered the issues simply didn’t ship with the necessary classes, as at the time those classes were not part of Android.

With each new release of Android additional java.* APIs are added from OpenJDK to Android. In Android 11, we added support for a number of APIs from newer OpenJDK releases all the way up to version 13, including additions to List, Set, Map, and the new java.time API. While we continuously add new Java APIs to each platform release, we also want to make these APIs available for older devices.

What if I told you that when you use Android Gradle Plugin 4.0.0 and newer, you can now use several hundred APIs from newer OpenJDK versions and your app will work on any Android device? Some of these newer Java APIs in Android 11 are supported through backporting while other APIs are also available via desugaring on older devices where the Android platform does not have the APIs on the runtime.

If you are wondering how that is even possible, let’s talk about desugaring.

D8/R8 Desugaring

The Android Gradle plugin 4.0 provides built-in support for using certain Java language APIs and third-party libraries that use them. Previously, these APIs were only supported on newer Android versions, but with AGP 4.0 they are now supported on nearly all Android versions.

This support is possible with a trick called desugaring, which is performed by D8/R8. When you build an app using these new Java APIs, the Java compiler first converts Java source code into Java bytecode. Then the toolchain implements the new APIs by performing bytecode transformations on your app including any 3rd-party libraries your app uses, and converts them into dex code while adding the necessary Java 8 runtime code as a separate dex library for devices that are missing those runtime classes.

This process is called desugaring and it enables a set of Java 8 APIs to work on all existing devices, except parallel, which is supported from API level 21.

Using newer Java APIs

To start using the newer Java APIs, update the Android Gradle plugin to 4.0 or higher and add the following configuration in your module’s build.gradle file.

  • Set coreLibraryDesugaringEnabled flag.
  • Set Java source and target compatibility to Java 8.
  • Add coreLibraryDesugaring as a dependency.
  • Enable multidex. To support these language APIs, the plugin compiles a separate DEX file that contains an implementation of the missing APIs and includes it in your app

Now that you’ve learned about and enabled Java 8+ desugaring, let’s take a look at the new APIs.

java.time

The old java.util Time API had some drawbacks. java.util.Date and java.util.Calendar classes are mutable, which can cause concurrency issues. Also, the API is not very consistent and easy to use. To give a simple example, in a Date object, day starts from 1 and month starts from 0, which can be confusing.

Date date = new Date(2, 3, 1); //Tue Apr 01 00:00:00 PST 1902

With OpenJDK version 8, a new Java Time API, java.time package, is added to Java. The new java.time is based on the popular library JodaTime. All core java.time classes are immutable and don’t have setter methods, so they don’t introduce any concurrency issues. Plus the new java.time API makes working with timezones much easier. Let’s take a look at the new date and time classes.

First, if you do not need timezone data on Date and Time objects, java.time has two new classes, LocalDate and LocalTime. These two classes represent date and time relative to the user, such as an alarm clock or a timer, without worrying about timezones.

If your use case requires timezones, you can use ZonedDateTime or OffsetDateTime classes. Let’s start with ZonedDateTime. ZonedDateTime is an immutable representation of DateTime that holds a LocalDateTime, a ZoneId, and the resolved ZoneOffset.

Time zones can be set either in abbreviated or long text form of the zone using the ZoneId class.

ZonedDateTime is particularly useful when you need to store date and time without relying on the context of a specific device or app. ZonedDateTime can be resolved to any timezone at any point in time.

You can use ZoneOffsets to calculate the difference between the current timezone and Greenwich/UTC.

OffsetDateTime is similar to ZonedDateTime but keeps the offset from Greenwich/UTC instead of ZoneId.

To summarize the differences between ZonedDateTime and OffsetDateTime, ZonedDateTime is great for displaying timezone-sensitive datetime data. OffsetDateTime is best for storing datetime data to a database and other use cases where the data needs to be serialized.

The new java.time API has two new classes, Period and Duration, to define a date range or length of time, respectively.

java.util.stream

Streams allow you to perform functional-style operations on collections. Streams do not store data or modify the underlying data structure and offer much better readability.

Streams have a large number of built-in intermediate and terminal operations. Intermediate operations are lazy and always return a new stream. Terminal operations are eager and once invoked, they complete the traversal of the data source and the stream can no longer be used. Since intermediate operations are not evaluated until terminal operation is invoked, streams may perform better when working on large data sources.

To use streams, you can call the stream() method on a collection or use Stream.of() and pass your data source or use the Stream.Builder().

other java.util APIs

There are new additions to the Map, Collection and Comparator interfaces under java.util package.

Writing a comparator is not hard but requires lots of boilerplate code where you can easily make mistakes. In Java 8, you can use a simple chaining style code to write comparators. With the help of method handles and lambdas, a comparator in Java 8 looks as simple as this.

Another interesting addition to the java.util package is the Optional class and its primitive counterparts OptionalInt, OptionalLong and OptionalDouble. Optional helps you work with real types instead of null references. Optional can represent null with “the absence of the value”. You can use the utility methods to handle values as ‘available’ or ‘not available’ instead of checking null values.

The Optional class has a variety of utility methods such as orElse(), orElseThrow(), filter() and more which help you handle null checks and handle null cases easily.

java.util.concurrent

Java 8+ also introduces new methods on AtomicInteger, AtomicLong and AtomicReference in the java.util.concurrent.atomic package. Another update in java.util.concurrent package is bug fixes to the ConcurrentHashMap, a thread-safe alternative to HashMap. ConcurrentHashMap has always been part of Android. However, on API levels 21 and 22, the implementation had a bug. A new fixed implementation is part of the desugared library, and with library desugaring that implementation will be used in your app in place of the one in the platform which might have a bug.

If you are not familiar with ConcurrentHashMap, this class allows any number of threads to perform get operations, but for update and insert operations, the performing thread must lock the particular segment where the data is modified or inserted. Although any thread can perform get operations, this lock may block get operations that try to access the data in the locked segment. Due to synchronization, ConcurrentHashMap can perform slower than a HashMap.

Please note in Android Studio 4.0 using these library desugared types in instrumented tests is not supported.

Upgrade your projects to make full use of all newer Java language APIs. Streams, Optionals and the new time APIs help you write less code and introduce fewer errors while using modern language APIs. Make sure to take a look at the full list of supported Java 8+ APIs.

Content and code samples on this page are subject to the licenses described in the Content License. Java is a registered trademark of Oracle and/or its affiliates.

--

--