Tips for Java Developers

Page last updated:

Pivotal Application Service (PAS) can deploy a number of different JVM-based artifact types. For a more detailed explanation of what it supports, see Additional Documentation in the Cloud Foundry Java Buildpack repository on GitHub.

Java Buildpack

For information about using, configuring, and extending the PAS Java buildpack, see the Cloud Foundry Java Buildpack repository on GitHub.

Design

The Java buildpack is designed to convert artifacts that run on the JVM into executable apps. It does this by identifying one of the supported artifact types (Grails, Groovy, Java, Play Framework, Spring Boot, and Servlet) and downloading all additional dependencies needed to run. It also analyzes the collection of services bound to the app and downloads any dependencies related to those services.

As an example, pushing a WAR file that is bound to a PostgreSQL database and New Relic for performance monitoring results in the following:

Initialized empty Git repository in /tmp/buildpacks/java-buildpack/.git/
--> Java Buildpack source: https://github.com/cloudfoundry/java-buildpack#0928916a2dd78e9faf9469c558046eef09f60e5d
--> Downloading Open Jdk JRE 1.7.0_51 from
      http://.../openjdk/lucid/x86_64/openjdk-1.7.0_51.tar.gz (0.0s)
        Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.9s)
--> Downloading New Relic Agent 3.4.1 from
      http://.../new-relic/new-relic-3.4.1.jar (0.4s)
--> Downloading Postgresql JDBC 9.3.1100 from
      http://.../postgresql-jdbc/postgresql-jdbc-9.3.1100.jar (0.0s)
--> Downloading Spring Auto Reconfiguration 0.8.7 from
      http://.../auto-reconfiguration/auto-reconfiguration-0.8.7.jar (0.0s)
        Modifying /WEB-INF/web.xml for Auto Reconfiguration
--> Downloading Tomcat 7.0.50 from
      http://.../tomcat/tomcat-7.0.50.tar.gz (0.0s)
        Expanding Tomcat to .java-buildpack/tomcat (0.1s)
--> Downloading Buildpack Tomcat Support 1.1.1 from
      http://.../tomcat-buildpack-support/tomcat-buildpack-support-1.1.1.jar (0.1s)
--> Uploading droplet (57M)

Configuration

In most cases, the buildpack should work without any configuration. If you are new to PAS, Pivotal recommends that you make your first attempts without modifying the buildpack configuration. If the buildpack requires some configuration, use a fork of the buildpack. For more information, see Configuration and Extension in the Cloud Foundry Java Buildpack repository on GitHub.

Java Client Library

The Cloud Foundry Client Library provides a Java API for interacting with a PAS instance. This library, cloudfoundry-client-lib, is used by the Cloud Foundry Maven plugin, the Cloud Foundry Gradle plugin, and other Java-based tools.

For information about using this library, see Java Cloud Foundry Library.

Grails

Grails packages apps into WAR files for deployment into a Servlet container. To build the WAR file and deploy it, run:

grails prod war
cf push YOUR-APP -p target/YOUR-APP-VERSION.war

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the WAR file you want to build and deploy.

Groovy

PAS supports Groovy apps based on both Ratpack and a simple collection of files.

Ratpack

Ratpack packages apps into two different styles. PAS supports the distZip style. To build the ZIP file and deploy it, run:

gradle distZip
cf push YOUR-APP -p build/distributions/YOUR-ZIP-FILE.zip

Where:

  • YOUR-APP is the name of your app.
  • YOUR-ZIP-FILE is the name of the ZIP file you want to build and deploy.

For more information, see the Ratpack website.

Raw Groovy

You can run Groovy apps that are made up of a single entry point and any supporting files without any other work. To deploy them, run:

cf push YOUR-APP

Where YOUR-APP is the name of your app.

For more information, see Groovy Container in the Cloud Foundry Java Buildpack repository on GitHub.

Java Main

Java apps with a main() method can be run provided that they are packaged as self-executable JARs. For more information, see Java Main Container in the Cloud Foundry Java Buildpack repository on GitHub.

Note: If your app is not web-enabled, you must suppress route creation to avoid a failed to start accepting connections error. To suppress route creation, add no-route: true to the app manifest or use the --no-route flag with the cf push command.

For more information about the no-route attribute, see Deploying with App Manifests.

Maven

A Maven build can create a self-executable JAR. To build and deploy the JAR, run:

mvn package
cf push YOUR-APP -p target/YOUR-APP-VERSION.jar

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the JAR you want to build and deploy.

Gradle

A Gradle build can create a self-executable JAR. To build and deploy the JAR, run:

gradle build
cf push YOUR-APP -p build/libs/YOUR-APP-VERSION.jar

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the JAR you want to build and deploy.

Play Framework

The Play Framework packages apps into two different styles. PAS supports both the staged and dist styles. To build the dist style and deploy it, run:

play dist
cf push YOUR-APP -p target/universal/YOUR-APP-VERSION.zip

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the dist style ZIP you want to build and deploy.

For more information, see the Play Framework website.

Spring Boot CLI

Spring Boot can run apps comprised entirely of POGOs. To deploy them, run:

spring grab *.groovy
cf push YOUR-APP

Where YOUR-APP is the name of your app.

For more information, see Spring Boot on the Spring website and Spring Boot CLI Container in the Cloud Foundry Java Buildpack repository on GitHub.

Servlet

Java apps can be packaged as Servlet apps.

Maven

A Maven build can create a Servlet WAR. To build and deploy the WAR, run:

mvn package
cf push YOUR-APP -p target/YOUR-APP-VERSION.war

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the WAR you want to build and deploy.

Gradle

A Gradle build can create a Servlet WAR. To build and deploy the WAR, run:

gradle build
cf push YOUR-APP -p build/libs/YOUR-APP-VERSION.war

Where:

  • YOUR-APP is the name of your app.
  • YOUR-APP-VERSION is the name of the WAR you want to build and deploy.

Binding to Services

For more information about binding apps to services, see:

Java and Grails Best Practices

Provide a JDBC Driver

The Java buildpack does not bundle a JDBC driver with your app. If you want your app to access a SQL RDBMS, include the appropriate driver in your app.

Allocate Sufficient Memory

If you do not allocate sufficient memory to a Java app when you deploy it, it may fail to start, or PAS may terminate it. You must allocate enough memory to allow for:

  • Java heap
  • Metaspace, if using Java 8
  • PermGen, if using Java 7 or earlier
  • Stack size per Thread
  • JVM overhead

The config/open_jdk_jre.yml file of the Java buildpack contains default memory size and weighting settings for the JRE. For an explanation of JRE memory sizes and weightings and how the Java buildpack calculates and allocates memory to the JRE for your app, see Open JDK JRE in the Cloud Foundry Java Buildpack on GitHub.

To configure memory-related JRE options for your app, either create a custom buildpack and specify this buildpack in your deployment manifest, or override the default memory settings of your buildpack as described in Configuration and Extension with the properties listed in the Open JDK JRE README in the Cloud Foundry Java Buildpack on GitHub. For more information about configuring custom buildpacks and manifests, see Custom Buildpacks and Deploying with App Manifests.

To see memory utilization when your app is running, run:

cf app YOUR-APP

Where YOUR-APP is the name of your app.

Troubleshoot Out of Memory

A Java app may crash because of insufficient memory on the Garden container or the JVM on which it runs. The sections below provide guidance for help diagnosing and resolving such issues.

JVM

  • Error: java.lang.OutOfMemoryError. For example:

    $ cf logs YOUR-APP --recent
    2016-06-20T09:18:51.00+0100 [APP/0] OUT java.lang.OutOfMemoryError: Metaspace
    Where YOUR-APP is the name of your app.

  • Cause: If the JVM cannot garbage-collect enough space to ensure the allocation of a data-structure, it fails with java.lang.OutOfMemoryError. In the example above, JVM has an under-sized metaspace. You may see failures in other memory pools, such as heap.

  • Solution: Configure the JVM correctly for your app. For more information, see Allocate Sufficient Memory.

Garden Container

Note: The solutions in this section require configuring the memory calculator, which is a sub-project of the Java buildpack that calculates suitable memory settings for Java apps when you push them. For more information, see the java-buildpack-memory-calculator repository on GitHub. If you have questions about the memory calculator, you can ask them in the #java-buildpack channel of the Cloud Foundry Slack organization.

  • Error: The Garden container terminates the Java process with the out of memory event. For example:

    $ cf events YOUR-APP
    time                          event         actor         description
    2016-06-20T09:18:51.00+0100   app.crash     app-name      index: 0, reason: CRASHED, exit_description: out of memory, exit_status: 255
    
    Where YOUR-APP is the name of your app.

    This error appears when the JVM allocates more OS-level memory than the quota requested by the app, such as through the manifest.

  • Cause 1 - Insufficient native memory: This error commonly means that the JVM requires more native memory. In the scope of the Java buildpack and the memory calculator, the term native means the memory required for the JVM to work, along with forms of memory not covered in the other classifications of the memory calculator. This includes the memory footprint of OS-level threads, direct NIO buffers, code cache, program counters, and others.

  • Solution 1: Determine how much native memory a Java app needs by measuring it with realistic workloads and fine-tuning it accordingly. You can then configure the Java buildpack using the native setting of the memory calculator, as in the example below:

    ---
    applications:
    - name: YOUR-APP
      memory: 1G
      env:
        JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: {memory_sizes: {native: 150m}}]'
    

    Where YOUR-APP is the name of your app.

    This example shows that 150m of the overall available 1G is reserved for anything that is not heap, metaspace, or permgen. In less common cases, this may come from companion processes started by the JVM, such as the Process API.

    For more information about measuring how much native memory a Java app needs, see Native Memory Tracking in the Java documentation. For more information about configuring the Java buildpack using the native setting, see OpenJDK JRE in the Cloud Foundry Java Buildpack on GitHub. For more information about the Process API, see Class Process in the Java documentation.

  • Cause 2 - High thread count: Java threads in the JVM can cause memory errors at the Garden level. When an app is under heavy load, it uses a high number of threads and consumes a significant amount of memory.

  • Solution 2: Set the reserved memory for stack traces to the correct value for your app.

    You can use the stack setting of the memory calculator to configure the amount of space the JVM reserves for each Java thread. You must multiply this value by the number of threads your app requires. Specify the number of threads in the stack_threads setting of the memory calculator. For example, if you estimate the max thread count for an app at 800 and the amount of memory needed to represent the deepest stacktrace of a Java thread is 512KB, configure the memory calculator as follows:

    ---
    applications:
    - name: YOUR-APP
      memory: 1G
      env:
        JBP_CONFIG_OPEN_JDK_JRE: '[memory_calculator: {stack_threads: 800, memory_sizes: {stack: 512k}}]'
    

    Where YOUR-APP is the name of your app.

    In this example, the overall memory amount reserved by the JVM for representing the stacks of Java threads is 800 * 512k = 400m.

    The correct settings for stack and stack_threads depend on your app code, including the libraries it uses. Your app may technically have no upper limit, such as in the case of cavalier usage of CachedThreadPool executors. However, you still must calculate the depth of the thread stacks and the amount of space the JVM should reserve for each of them. For more information, see Executors.newCachedThreadPool() considered harmful on the Bizo website and the newCachedThreadPool section of the Class Executors topic in the Java documentation.

Troubleshoot Failed Upload

If your app fails to upload when you push it to PAS, it may be for one of the following reasons:

  • WAR is too large: An upload may fail due to the size of the WAR file. PAS testing indicates WAR files as large as 250 MB upload successfully. If a WAR file larger than that fails to upload, it may be a result of the file size.

  • Connection issues: App uploads can fail if you have a slow Internet connection, or if you upload from a location that is very remote from the target PAS instance. If an app upload takes a long time, your authorization token can expire before the upload completes. A workaround is to copy the WAR to a server that is closer to the PAS instance, and then push it from there.

  • Out-of-date Cloud Foundry Command-Line Interface (cf CLI) client: Upload of a large WAR is faster and hence less likely to fail if you are using a recent version of the cf CLI. If you are using an older version of the cf CLI client to upload a large WAR, and having problems, try updating to the latest version of the cf CLI.

  • Incorrect WAR targeting: By default, cf push uploads everything in the current directory. For a Java app, cf push with no option flags uploads source code and other unnecessary files, in addition to the WAR. When you push a Java app, specify the path to the WAR by running:

    cf push YOUR-APP -p PATH/TO/WAR-FILE
    

    Where:

    • YOUR-APP is the name of your app.
    • PATH/TO/WAR-FILE is the path to the WAR. You can determine whether or not the path was specified for a previously pushed app by examining the app deployment manifest, manifest.yml. If the path attribute specifies the current directory, the manifest includes a line like:

      path: .
      

      To re-push just the WAR, either:

      • Delete manifest.yml and run cf push again, specifying the location of the WAR using the -p flag.
      • Edit the path argument in manifest.yml to point to the WAR, and re-push the app.

Debug Java Apps on PAS

Because of the way that PAS deploys your apps and isolates them, it is not possible to connect to your app with the remote Java debugger. Instead, instruct the app to connect to the Java debugger on your local machine.

To set up remote debugging when using BOSH Lite or a PAS installation:

  1. Open your project in Eclipse.

  2. Right-click on your project, go to Debug as and pick Debug Configurations.

  3. Create a new Remote Java Application.

  4. Make sure your project is selected, pick Standard (Socket Listen) from the Connection Type drop down and set a port. Make sure this port is open if you are running a firewall.

  5. Click Debug.

The debugger should now be running. If you switch to the Debug perspective, you should see your app listed in the Debug panel, and it should say Waiting for vm to connect at port.

Next, to push your app to PAS and instruct PAS to connect to the debugger running on your local machine:

  1. Edit your manifest.yml file. Set the instances count to 1. If you set this greater than one, multiple apps try to connect to your debugger.

  2. Also in manifest.yml, add an env block and create a variable named JAVA_OPTS. For more information about the env block, see Deploying with App Manifests.

  3. Add the remote debugger configuration to the JAVA_OPTS variable: -agentlib:jdwp=transport=dt_socket,address=YOUR-IP-ADDRESS:YOUR-PORT.

  4. Save the manifest.yml file.

  5. Run:

    cf push
    

Upon completion, you should see that your app has started and is now connected to the debugger running in your IDE. You can now add breakpoints and interrogate the app just as you would if it were running locally.

Slow Starting Java or Grails Apps

Some Java and Grails apps do not start quickly, and the health check for an app can fail if an app starts too slowly.

The current Java buildpack implementation sets the Tomcat bindOnInit property to false. This prevents Tomcat from listening for HTTP requests until an app has fully deployed.

If your app does not start quickly, the health check may fail because it checks the health of the app before the app can accept requests. By default, the health check fails after a timeout threshold of 60 seconds.

To resolve this issue, run cf push with the -t TIMEOUT-THRESHOLD option to increase the timeout threshold. Run:

$ cf push YOUR-APP -t TIMEOUT-THRESHOLD

Where:

  • YOUR-APP is the name of your app.
  • TIMEOUT-THRESHOLD is the number of seconds to which you want to increase the timeout threshold.

Note: The timeout threshold cannot exceed 180 seconds. Specifying a timeout threshold greater than 180 seconds results in the following error: Server error, status code: 400, error code: 100001, message: The app is invalid: health_check_timeout maximum_exceeded

Extension

The Java buildpack is also designed to be easily extended. It creates abstractions for three types of components (containers, frameworks, and JREs) in order to allow users to easily add functionality. In addition to these abstractions, there are a number of utility classes for simplifying typical buildpack behaviors.

As an example, the New Relic framework looks like the below:

class NewRelicAgent < JavaBuildpack::Component::VersionedDependencyComponent

  # @macro base_component_compile
  def compile
    FileUtils.mkdir_p logs_dir

    download_jar
    @droplet.copy_resources
  end

  # @macro base_component_release
  def release
    @droplet.java_opts
    .add_javaagent(@droplet.sandbox + jar_name)
    .add_system_property('newrelic.home', @droplet.sandbox)
    .add_system_property('newrelic.config.license_key', license_key)
    .add_system_property('newrelic.config.app_name', "'#{application_name}'")
    .add_system_property('newrelic.config.log_file_path', logs_dir)
  end

  protected

  # @macro versioned_dependency_component_supports
  def supports?
    @application.services.one_service? FILTER, 'licenseKey'
  end

  private

  FILTER = /newrelic/.freeze

  def application_name
    @application.details['application_name']
  end

  def license_key
    @application.services.find_service(FILTER)['credentials']['licenseKey']
  end

  def logs_dir
    @droplet.sandbox + 'logs'
  end

end

For more information, see Design, Extending, and Configuration and Extension in the Cloud Foundry Java Buildpack repository on GitHub.

Environment Variables

You can access environments variable programmatically.

For example, you can obtain VCAP_SERVICES by running:

System.getenv("VCAP_SERVICES");

For more information, see PAS Environment Variables.