Chapter 4. Java Tools

The original role of programming languages is that of a communication medium between a human and a computer. Today, the life span of software has increased, and programming teams have grown in size. As programmers need to communicate about software, computer code has also become an important human communication medium.

Gilles Dubochet

In this quote, Gilles Dubochet introduces “distributed cognition” as he analyzes the role of programming languages for human communication. Programming languages are often studied in the abstract without considering a number of rather obvious contextual issues:

  • Programming has never been a completely isolated activity. Many recent projects involve large, distributed teams.
  • Computer languages are an important medium for human communication among programmers themselves.
  • Programmers use literary terms to describe code quality. There is a stated or implied expectation that code be readable by peers.
  • “Code comprehension” is improved with denser code for programmers who share common ground.
  • Programmers cannot hold all system requirements of large systems in their own (human) memory.
  • Human-readable documentation (when available) tends to get out of sync with the system described.
  • Documentation does not always capture every edge case handled by a piece of software. Knowledge resides in the code itself.

This chapter points out the variety of languages that can be run on the Java platform. This allows the possibility of choosing the best language for a particular task or group of people. This chapter also describes Maven as a tool that promotes a variety of established best practices related to programming with groups of people.

Java Language

Although Java is a mature, stable, and well-known language, there are challenges with its usage in certain instances because of its design as a class-based, statically-typed, object-oriented language. It is helpful to acknowledge areas where there might be a mismatch between technologies and to consider alternatives up front that might alleviate the disjunction altogether.

Developers are familiar with the challenge of integrating an object-oriented language (like Java) with a relational database system in all but the most trivial of cases. Although some of the tension can be mitigated by altering usage practices, there is always a layer of mapping involved due to fundamental philosophical differences between the object-oriented and relational model.

This is not unique to Java; such a tension also exists between JSON and REST. REST requires links as defined in the HATEOAS Uniform Interface constraint. JSON, as a subset of the JavaScript language, has no such construct. Neither implementation is inherently wrong, but they have different origins that result in incongruities when they are integrated.

A similar tension exists between object-oriented systems and REST. In an object-oriented system, you take actions on an object itself. In REST, a resource is manipulated through its representation. This additional level of abstraction allows resources to be manipulated through a common interface not available by default in an object system.

Java developers have devised specific libraries that can be used to address most translation or integration activities related to interfacing with other technologies. In addition, a component of the Java platform itself that introduces the possibility of using an entirely different language is the Java Virtual Machine (or JVM).

Java Virtual Machine (JVM)

Java is translated into bytecode, which is then executed on the JVM (see Figure 4-1). Because Java was designed to be executable cross-platform, JVMs have been developed and refined for a wide range of operating systems, from high-powered servers to embedded device implementations.

Java compilation process
Figure 4-1. Java compilation process

The relative independence of the JVM from the Java language itself led to the development of other languages that can run on the JVM, including Ruby (JRuby, introduced in the previous chapter), Python (Jython), Groovy, Clojure, and JavaScript. The JVM, once seen only as the part of the language installation that enabled cross-platform development, now serves as an interface for other languages’ implementations (see Figure 4-2).

JVM Scripting Interface and Static Typed Language Support

The languages shown in Figure 4-2 are used in the project later in this chapter and elsewhere in this book. They were selected among the many JVM languages supported because they are relatively well-known and are available through the scripting interface defined in JSR 223: Scripting for the Java Platform. The scripting interface provides a common mechanism for the integration of supported languages in a single project. Several of these languages are also affected by later work for JSR 292: Supporting Dynamically Typed Languages on the Java Platform and so are now driving development and enhancements to aspects of the JVM itself.

JVM language compilation process
Figure 4-2. JVM language compilation process

The availability of relatively terse and powerful scripting languages provides an appealing alternative when creating APIs to run on the JVM. The project later in this chapter will demonstrate a variety of the server-side options for manipulating JSON in JVM languages.

Java Tools

Java is a mature language with many standard options for IDEs, including Eclipse, NetBeans, and IntelliJ. These IDEs contain development tools and plug-ins that take the drudgery out of writing code, debugging, profiling, and a myriad of other programming chores. The IDE is front and center of the individual developer’s work, and mastering is an indispensible skill for modern Java development. IDEs provide integration with other systems that are a significant part of team projects, version control, and build automation.

There are many different tools available for building projects (Apache Ant is a long-time favorite in the Java community). Maven goes significantly beyond defining a simple build process and also coordinates a range of widely accepted software development tasks. Gradle is a relative newcomer based on Groovy. Both Ant and Maven are XML-based, while Gradle is a full-fledged Domain Specific Language (DSL).

Beyond a certain critical complexity, there needs to be additional infrastructure to help guide the development process. The specifics of such an infrastructure vary somewhat from project to project, but by following well-understood and established programming practices, it is possible to decrease ramp-up time and maintain control of a project with minimal impact on programmer productivity. Simply put, such an infrastructure allows a project team to scale in ways that would otherwise be impossible.

Build Tools

One of the most important decisions an architect or lead developer makes is what assets to initially include in a project. This decision impacts subsequent development when the project stabilizes but becomes more difficult to change. The choice of a build system has immense ramifications for later activities. There are a number of factors that go into this choice.

The flexibility provided by the syntax of a programming tool is a significant factor in its usefulness. Martin Fowler has contrasted make (which has its own custom syntax) with ant (which uses an XML-based syntax) and rake (which uses the Ruby programming language) to call attention to the value of having a full-fledged programming language available to build scripts. A factor that has been of perhaps greater interest in recent years is the availablity of dependency management in a build tool. Without dependency management of some sort, a developer is in the unhappy situation of having to identify, locate, download, and install dependent modules and related resources (sometimes with limited documentation). The automatic management of specified versions of modules from online code repositories is immensely valuable to individual developers and benefits the general stability of a project. See Table 4-1.

Table 4-1. Comparison of build tools
Custom DSLXML DSLProgramming language Dependency management  

make

Y

N

-

N

ant

N

Y

-

N

rake

N

N

Ruby

N

maven

N

Y

-

Y

gradle

N

N

Groovy

Y

SBT

N

N

Scala

Y

This is a bit of a simplification because build tools are incredibly extensible. Dependency management add-ons and support for other programming languages can be added to expand the capabilities of those that do not provide these natively. For example, Ant supports dependency management using Ivy, and a Groovy-based version of the project named Gant also exists.

The list is representive of widely used build tools but is not exhaustive. Since 1977, Make has influenced most other build tools to some degree (including Rake). Rake itself has inspired several Java-oriented projects (including Raven and Apache buildr).

Dependency management is indisputably an excellent feature for practically any project of any size. Having a full-fledged programming language provides a good deal of flexibility. Ruby has been a popular choice but has been superceded by Groovy in many cases because of Groovy’s greater similarity to Java (as noted earlier, a valid Groovy file is generally a valid Java file). And so Gradle is emerging as a popular choice that includes dependency management support of a full-fledged scripting language in a syntax that is succinct and immediately accessible to Java developers. Similarly, Scala users have Scala Build Tool (SBT), which is roughly analogous to Gradle (build tool for Groovy) in its design and purpose. A notable use of the tool outside of Scala is the Play Framework, which supports both Scala and Java.

Though there are many options, and flexibility is often advantageous, there are significant benefits to using a tool that is “opinionated” and effectively makes a number of up-front decisions based on best practices. This approach is best represented by Maven.

Maven (the word in Yiddish means “accumulator of knowledge”) organizes a project and defines its software development workflow. It is described as a “software project management and comprehension tool.” It simplifies the build process and provides a uniform system for setting up a project (by allowing the specification of project dependencies in a declarative fashion). Its reporting and documentation features serve to produce and centralize all project-specific technical artifacts.

Maven has a number of notable characteristics:

  • It promotes convention over configuration (while remaining extensible and customizable).
  • It presents a common interface across projects.
  • It provides a well-defined build life cycle.
  • It defines a common project configuration through the project object model (pom.xml).

For more details on Maven, see Maven: The Definitive Guide (O’Reilly), which is also available free online.

Benefits of Maven

Maven’s value is immediately evident even in minimal Java projects, as it allows JARs to be identified declaratively. As part of the build process, Maven locates these JARs in online repositories along with all dependencies and downloads them to their proper location in a local repository. It takes care of specifying the required CLASSPATH entries so all of the basic setup tasks that are needed to initially build a project are taken care of in a single command. This is sufficient enough to consider using Maven for even the simplest of projects, but its benefits don’t end there.

As a project expands or is formalized, assets such as unit tests, generated documentation, and build reports can be run, assembled, and subsequently stored in standard locations. Having standard project structures and conventions makes it far easier to onboard new developers. This has advantages for a range of projects from those done in open source development to others in large organizations where developers are moved from project to project as staffing requirements change.

Well-structured projects are well-suited for sophisticated development and deployment options. Maven-built projects can be easily integrated into a continuous integration (CI) process. And with its references to the version control system, release management is done in a controlled and standardized manner using tags with incremental version numbers. In fact, if a system includes a robust set of tests run regularly on a CI server and the production environment includes sufficent monitoring and alerts, it is possible to shift from managing specific releases to continuous deployment of software on a much more frequent basis. The proper initial organization of a project provides immediate benefit to developers and saves effort all the way through to production deployment.

Functionality of Maven

Maven itself is fairly simple and provides little functionality out of the box. Through the use of plug-ins, it can execute a wide range of standard build tasks and can be extended to incorporate any others you can imagine. A few concrete examples:

  • Standard unit testing via JUnit and code coverage reports are included as standard features when generating many new projects (from Maven project templates called archetypes).
  • A project website (mvn site) can be created to document the purpose and status of your project. A Maven site can be created using the default wiki-like format called Almost Plain Text (APT) or other supported option. This site provides a central location for project information, contacts, reports, and resources.
  • An embedded application server can be run with minimal configuration using a simple command (mvn jetty:run) for use during development. This server can also be run as part of the build itself to support unit tests. Other plug-ins are available to deploy web applications to your application server of choice.
  • Although rooted in the Java development community, Maven plug-ins have been written to support development in other languages. JavaScript plug-ins provide code organization and unit testing. Minification and code quality tools are also readily available.

Maven is often described as “opinionated software.” Understood negatively, this suggests that it is difficult to adapt to certain types of projects. However, its opiniated character promotes a defined software development life cycle and best practices worth considering even when using another build system. It replaces reliance on individuals to remember and manually enact details of proper build processes and project management with standardized configuration that can be heavily augmented with a wide range of plug-ins. It promotes unified practices within a project or even across an organization. Rather than using a wide-open system that puts the onus on the developer to remember, Maven’s “opinions” can be leveraged so that the average developer does not even think about peripheral issues and will simply do the right thing because many wrong options require too much additional effort.

The assets initially included in a project profoundly impact subsequent development. This is true not only of the components that comprise the project itself, but also the choice of development tools and build system. The decision to include Maven or any other built tool has immense ramifications for later activities. The purpose for including Maven in the project presented later in this chapter is to demonstrate how simple it is to set up an absolutely minimal configuration and show its value for declarative management of modules. Once a project is already being built on Maven, it is relatively easy to integrate additional plug-ins and features.

Version Control

If you are reading this book, you likely do not need to be convinced of the value of using version control. Most development shops need to maintain control of their software assets and use version control systems to this end. If treated as a mere file-system backup, many of their greatest benefits are missed. These include viewing historic changes of code over time, comparing various versions, establishing defined releases of code (tags), creating branches to facilitate parallel development of integration of code, and creating patches.

Version control systems (VCS) are fundamental for enabling groups of developers to effectively work together on large-scale projects. They greatly ease conflict resolution that results when several developers need to change the same file. Support for existing projects is greatly eased through the use of VCS because there is an audit history of who changed what, and when. If useful comments are included on commit or there is integration with issue tracking systems, there is also an indication of why a given change was made.

Individual developers benefit from VCS because they can confidently experiment, knowing that they can revert to a previous “known-good” version as needed. If a project takes an extended period to complete, VCS history can make visible progress that has been made over time and provide helpful reminder of why changes were made.

Maven projects manage source code using Git or Subversion.

Unit Testing

It is easy to incorporate unit tests into a project using Maven/Junit (for Java), node/Karma (for JavaScript) or other testing framework using virtually any other programming language. There are also Java frameworks that support other types of testing. JBehave can be used for Behavior-Driven Development (BDD) and is available through a Maven plug-in. It is also possible to test using browser automation through the Selenium plug-in.

One challenge encountered in ongoing unit testing is isolating code from dependence on external systems. This can be overcome by creating an object to replace or mimic real objects in a testing context. There are actually a number of different kinds of objects that can be used. Martin Fowler highlights dummies, fakes, stubs, and mocks. Testers vary as to how much mock objects should be used in testing, but projects like Mockito and JMock make it much easier to make tests that are otherwise difficult or impossible to create.

The applicability of a given testing approach varies based on the project, customer, development team, and available resources. Regardless of the choice, Maven makes the inclusion of a unit test framework easy. And as pointed out earlier, a comprehensive set of tests makes possible a range of development and deployment options that are simply not feasible without this sort of validation and coverage.

JSON Java Libraries

There are a number of Java Libraries that can process (parse or generate) JSON. Because the fundamental structure in Java is a Java object, libraries written for JSON in Java tend to rely on mappings between JSON and Java objects and provide methods to serialize and deserialize objects from JSON. Jackson and Gson are two popular libraries that can convert Java objects into their JSON representation and vice versa.

Projects

These projects provide an absolutely minimal Maven pom to show its value for declarative management of modules. Once a project is already being built on Maven, it is relatively easy to integrate additional plug-ins and features.

Each project can be built using the following command from the root directory for the project:

mvn clean install

An Internet connection must be available to allow Maven to locate and download the modules required by each project. The first time you run this command for a project, it checks your local repository (which is in <your OS user’s home directory>/.m2/repository by default). If the project does not exist there, it will find it in a Maven repository online and download it so that it is available for subsequent use. Running a mvn clean command results in resources in your project being removed but does not affect the modules have been downloaded to your local repository.

Java with JSON

The java_json project demonstrates basic usage of Maven along with the Jackson and JSON Java APIs. The pom.xml needed to include both JSON and Jackson as dependencies in a project is as follows:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>JSON_Java</groupId>
    <artifactId>JSON_Java</artifactId>
    <packaging>jar</packaging>
    <version>1.0</version>

    <dependencies>
        <dependency>
            <groupId>com.google.code.gson</groupId>
            <artifactId>gson</artifactId>
            <version>2.2.3</version>
            <scope>compile</scope>
        </dependency>
        <dependency>
            <groupId>org.codehaus.jackson</groupId>
            <artifactId>jackson-mapper-asl</artifactId>
            <version>1.9.12</version>
        </dependency>
    </dependencies>
    <build>
      <plugins>
              <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.2.1</version>
              </plugin>
            </plugins>
    </build>
</project>

This pom specifies the project’s identity (or coordinates: groupId, artifactId, and version) and mode of packaging (JAR). Specific versions of Jackson and JSON are identified by version, and a plug-in that facilitates the execution of main methods is included. This is an extremely simple pom.xml that shows how easily Maven can be used to manage JAR dependencies. This should be less intimidating if you are a Maven beginner, and Maven pros can expand on what is provided to add unit tests, documentation, reports, and all of your favorite bells and whistles.

The Java code included is comprised of three classes. A plain old Java object (POJO) is defined which will be used for serializing and deserializing the JSON:

package com.saternos.json;

public class MyPojo {
  private String thing1;
  private String thing2;

  public MyPojo(){
    System.out.println("*** Constructor MyPojo() called");
  }

  public String getThing1() {
    return thing1;
  }

  public void setThing1(String thing1) {
    this.thing1 = thing1;
  }

  public String getThing2() {
    return thing2;
  }

  public void setThing2(String thing2) {
    this.thing2 = thing2;
  }

  @Override
  public boolean equals(Object o) {
    if (this == o) return true;
    if (o == null || getClass() != o.getClass()) return false;

    MyPojo myPojo = (MyPojo) o;

    if (thing1 != null ? !thing1.equals(myPojo.thing1) : myPojo.thing1 != null)
        return false;
    if (thing2 != null ? !thing2.equals(myPojo.thing2) : myPojo.thing2 != null)
        return false;

    return true;
  }

  @Override
  public int hashCode() {
    int result = thing1 != null ? thing1.hashCode() : 0;
    result = 31 * result + (thing2 != null ? thing2.hashCode() : 0);
    return result;
  }
}

The code required to instantiate a POJO, export its JSON representation, and parse the JSON to create another POJO in the DemoJSON class is as follows:

    MyPojo pojo = new MyPojo();
    //... code to populate pojo can be found in the project source code

    Gson gson = new Gson();
    String json = gson.toJson(pojo);

    MyPojo pojo2  = gson.fromJson(json, MyPojo.class);

DemoJackson follows the same pattern using object and method names particular to the Jackson API:

    MyPojo pojo = new MyPojo();
    //... code to populate pojo can be found in the project source code

    ObjectMapper mapper = new ObjectMapper();
    String json = mapper.writeValueAsString(pojo);

    MyPojo pojo2  = mapper.readValue(json, MyPojo.class);

These Java classes can be executed using the following commands:

$ mvn exec:java -Dexec.mainClass=com.saternos.json.DemoJackson

$ mvn exec:java -Dexec.mainClass=com.saternos.json.DemoGSON

JVM Scripting Languages with JSON

The preceding Java-based examples convert JSON to Java objects, which makes sense for Java but not for other languages. JSON is based on a few simple data structures (JavaScript arrays and objects). These correspond to arrays (or lists) and hashes (or dictionaries, tables, or hash maps) in other languages. Many libraries make a simple translation between a JSON object and the appropriate native data structures.

The jvm_json project shows how a JSON document can be read and a field extracted and outputted using Clojure, JavaScript, Jython, and Groovy. Figure 4-3 provides a visual representation highlighting how the ScriptEngineManager class is used to call the correct engine based on the file extension.

JVM JavaScript Engines

The examples in this book use the Rhino JavaScript engine developed by Mozilla (which gets its name from the O’Reilly book on the subject). A new Java JavaScript engine dubbed Nashorn is being developed by Oracle Corporation and is scheduled for release with Java 8.

The pom.xml in this project includes the relevant dependencies for the programming languages being used. In addition, two plug-ins are configured:

  1. The Exec Maven plug-in allows Java classes and other executables to be run at the command line.
  2. The Maven Shade plug-in is one of a number of plug-ins available that can be used to package JARs. The Shade plug-in is unique in its ability to package projects in JARs that otherwise have issues with conflicting class names. This is a somewhat common occurence when working with modules that implement the Java scripting interface mentioned earlier.

The pom also includes references to repositories that contain referenced modules.

The main Java class (Jvms.java) opens a script file specified as an argument to the program and executes it using the appropriate scripting engine (based on the file extension).

Script engine manager role
Figure 4-3. Script engine manager role

Jvms.java has a simple main method that iterates through the arguments passed to it at the command line. For each argument, the program tries to verify the existence of a file with that name. If one is not found, it appends src/main/resources/scripts as the path to the filename. This is the directory where the script files are stored. Note that exception handling and other details are omitted in the interest of presenting the aspects of the program relevant to the current discussion. In this case, if a file is not found, the exception is simply passed out of the main method (which is defined to throw Exception). The extension is identified to be the portion of the file name from the first dot to the end of the filename. Finally, a new ScriptEngineManager is instantiated, and a script engine is retrieved by extension. The File is read from the file system and evaluated by the script engine associated with the file extension:

public static void main( String[] args ) throws Exception
{
  for (String fileName: args){

    if (!fileName.trim().equals(""))
    {
      File file=new java.io.File(fileName);

      if (!file.exists())
        fileName ="src/main/resources/scripts/"+fileName;

      String ext = fileName.substring(
        fileName.indexOf(".") + 1,
        fileName.length()
      );

      new ScriptEngineManager().getEngineByExtension(ext).eval(
        new java.io.FileReader(fileName)
      );

    }
  }
}

The program can be executed from Maven, and one or more of the scripts can be passed in as arguments:

mvn -q \
exec:java -Dexec.args="testJson.clj testJson.js testJson.groovy testJson.py"

If you prefer, you can execute the JAR file directly without using Maven by using an included script:

jvms.sh testJson.clj testJson.js testJson.py testJson.groovy

The Python (Jython) example is terse and straightforward. The use of indentation in lieu of blocks with begin and end tokens makes it a great example that emphasizes the functionality in question in an almost pseudocode form without additional overhead or distractions. Python is a very regular language and has less ambiguities due to its consistency of design:

import json

print('*** JSON Jython ***')

for item in json.loads(open("data/test.json").read()):
    print item['title']

A library for processing JSON is imported, loaded from the file system, and the object is iterated through so each title will print. The Groovy example follows nearly the same pattern. Groovy is based heavily on Java (a valid Java file is usually a valid Groovy file as well). It introduces a number of idioms that are more compact and concise than pure Java. This makes it a popular language to introduce to Java developers who can fall back on their Java knowledge while they learn Groovy distinctives:

import groovy.json.JsonSlurper

println "*** JSON Groovy ***"

def json = new File("data/test.json").text

new JsonSlurper().parseText(json).each { println it.title }

The Clojure example follows the same basic pattern, although you might notice a certain plethora of parentheses. Clojure is a dialect of LISP that is a stark contrast to the C-like syntax of the other examples:

(require '[clojure.data.json :as json])

(println "*** JSON Clojure ***")

(def recs (json/read-str (slurp "data/test.json")))

(doseq [x recs] (println (x "title")))

The JavaScript version is a bit different. There is no need to import (we are using eval() in this example just for illustration purposes). If you recall, JavaScript does not have built-in I/O functionality. So this example uses a bit of a hack to make a call to a static Java method to read in file (using Java) and convert the file contents (available from Java as a Java string) to a JavaScript string:

println('*** JSON Javascript ***')

// Call a Java static method to read and convert the file to a JavaScript string
var str = String(com.saternos.app.Jvms.readFile("data/test.json"));

var o = eval(str);

for (var i=0; i < o.length; i++){
    println(o[i].title);
}

Although this might be considered a less pure implementation, it demonstrates the ease of integration between languages on the JVM.

Conclusion

People learn a new programming language or supporting utility like Maven for many reasons. The most common one is that it is needed for a specific project. Many Ruby developers started using the language when developing Rails applications or while using Chef or Puppet for system administration. Scientists have gravitated toward Python due to its specialized libraries relevant to their work as well as its disciplined design and high performance.

Studies suggest that natural languages shape thinking. This is highlighted in an article that appeared in the Wall Street Journal. The article describes how language can have a profound effect on how an individual sees and thinks about the world. The article states:

Some findings on how language can affect thinking.

  • Russian speakers, who have more words for light and dark blues, are better able to visually discriminate shades of blue.
  • Some indigenous tribes say north, south, east and west, rather than left and right, and as a consequence have great spatial orientation.
  • The Piraha, whose language eschews number words in favor of terms like few and many, are not able to keep track of exact quantities.
  • In one study, Spanish and Japanese speakers couldn’t remember the agents of accidental events as adeptly as English speakers could. Why? In Spanish and Japanese, the agent of causality is dropped: “The vase broke itself,” rather than “John broke the vase.”

Languages that are particularly expressive in a given area make an individual more attuned to that area. The same is true of programming languages. In this view, learning a new programming language is not an end unto itself, and is not merely done to complete a specific project. It helps you see the world differently. It improves your ability to solve problems in general. Most programmers who learn Clojure (or other LISP dialect) do not do so because of a specific project. Their intention is to improve their ability to conceptualize and solve problems. LISP dialects are well known for being among the most simple, expressive, powerful, and flexible languages available (or perhaps even possible). But the same is true to a lesser extent when learning other programming languages. Each one has features and a community that can differ widely from others. But many of these differences are not absolute, and a programmer can grow by simply learning other languages and tools, even if they are not of immediate use.

This chapter presented several JVM languages and Maven from the viewpoint of how they affect development done in community with other developers. Certain languages might facilitate good communication among developers. They might serve to encapsulate requirements in a way that will make the system easier to reverse-engineer and support in the future. Maven can be used to structure project resources and development processes in a way that has been conducive to many successful projects involving teams of developers. Although reading a book is a relatively solitary activity, it is certain that a significant amount of your work as a programmer will be done with others. JVM languages and Maven provide suitable features for projects that require extensive interaction with other developers over extended periods of time.

Get Client-Server Web Apps with JavaScript and Java now with the O’Reilly learning platform.

O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.