-
Notifications
You must be signed in to change notification settings - Fork 118
/
Copy pathjava-tips.html.md.erb
541 lines (358 loc) · 25.3 KB
/
java-tips.html.md.erb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
---
title: Using Cloud Foundry Java buildpack
owner: Java
---
You can use the Java buildpack with Cloud Foundry.
You can deploy a number of different JVM-based artifact types.
For a more detailed explanation of what the Cloud Foundry Java Builpack supports, see [Additional Documentation](https://github.com/cloudfoundry/java-buildpack#additional-documentation) in the repository on GitHub.
## <a id='buildpack'></a> Java buildpack
For information about using, configuring, and extending the Cloud Foundry Java buildpack, see the [Cloud Foundry Java Buildpack](https://github.com/cloudfoundry/java-buildpack) repository on GitHub.
## <a id='design'></a> Java buildpack design
The Java buildpack can 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.
For example, pushing a WAR file that is bound to a PostgreSQL database and New Relic for performance monitoring shows output like this:
<pre class="terminal">
-----> Java Buildpack v4.50 | https://github.com/cloudfoundry/java-buildpack#e1d6dbc
-----> Downloading Jvmkill Agent 1.16.0_RELEASE from https://java-buildpack.cloudfoundry.org/jvmkill/bionic/x86_64/jvmkill-1.16.0-RELEASE.so (0.1s)
-----> Downloading Open Jdk JRE 1.8.0_342 from https://java-buildpack.cloudfoundry.org/openjdk/bionic/x86_64/bellsoft-jre8u342%2B7-linux-amd64.tar.gz (0.6s)
Expanding Open Jdk JRE to .java-buildpack/open_jdk_jre (1.3s)
JVM DNS caching deactivated in lieu of BOSH DNS caching
-----> Downloading Open JDK Like Memory Calculator 3.13.0_RELEASE from https://java-buildpack.cloudfoundry.org/memory-calculator/bionic/x86_64/memory-calculator-3.13.0-RELEASE.tar.gz (0.1s)
Loaded Classes: 21014, Threads: 250
-----> Downloading Client Certificate Mapper 1.11.0_RELEASE from https://java-buildpack.cloudfoundry.org/client-certificate-mapper/client-certificate-mapper-1.11.0-RELEASE.jar (0.0s)
-----> Downloading Container Security Provider 1.19.0_RELEASE from https://java-buildpack.cloudfoundry.org/container-security-provider/container-security-provider-1.19.0-RELEASE.jar (0.1s)
Exit status 0
</pre>
## <a id='configuration'></a> Configuration
In most cases, the buildpack can work without any configuration. If you are new to Cloud Foundry, <%= vars.recommended_by %> recommends that you make your first attempts without modifying the buildpack configuration. The buildpack is flexible, though, and you can configure it through environment variables. For more information, see [Configuration and Extension](https://github.com/cloudfoundry/java-buildpack#configuration-and-extension) in the Cloud Foundry Java Buildpack repository on GitHub.
## <a id='java-client-library'></a> Java Client Library
The Cloud Foundry Client Library provides a Java API for interacting with an instance. This library, `cloudfoundry-client`, can be
used by Java based tools to interact with the platform.
For information about using this library, see [Java Cloud Foundry Library](./java-client.html).
## <a id='grails'></a> 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.
## <a id='groovy'></a> Groovy
<%= vars.app_runtime_abbr %> supports Groovy apps based on both Ratpack and a simple collection of files.
### <a id='ratpack'></a> Ratpack
Ratpack packages apps into two different styles. <%= vars.app_runtime_abbr %> 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](https://ratpack.io) website.
### <a id='raw-groovy'></a> 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](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/container-groovy.md) in the Cloud Foundry Java Buildpack repository on GitHub.
## <a id='http-2'></a> HTTP/2
To deploy Java apps that use HTTP/2, you must have:
1. A Cloud Floundry foundation that has HTTP/2 support enabled. <%= vars.http2_admin_link %>
2. Cloud Foundry Command-Line Interface (cf CLI) v8 or later. See [Upgrading to cf CLI v8](../../cf-cli/v8.html).
You can deploy any Java app and get automatic support for the HTTP/2 protocol without making any changes to your app.
When a client connects through a route mapped to your Java apps over HTTP/2, the foundation transparently downgrades the protocol and communicates with your app over HTTP/1.1.
If you require end-to-end HTTP/2, for example, because of gRPC, do the following:
1. Enable HTTP/2 support in the app:
- See <a href="https://docs.spring.io/spring-boot/docs/current/reference/html/howto.html#howto.webserver.configure-http2">Configure HTTP/2</a> in the Spring Boot documentation.
- If you are deploying a standard non-executable WAR file, you only need to make sure that you are using Java buildpack v4.43 or later. Starting with v4.43, the Java buildpack configures Apache Tomcat to accept HTTP/2 connections.
- Other frameworks, including Play, Ratpack, and apps that use the distZip format, embed an HTTP server. You must configure these apps to enable HTTP/2, specifically H2C, clear-text. See your framework's documentation for enabling HTTP/2 and H2C.
H2C is required because <%= vars.app_runtime_abbr %> uses Envoy to secure communications into the app container.
2. Configure the route to use the HTTP/2 protocol using either the cf CLI or the app manifest:
- Using the cf CLI: `cf map-route MY-APP EXAMPLE.COM --hostname host --destination-protocol http2`.
- Using a `manifest.yml` file:
```
---
applications:
- name: MY-APP
routes:
- route: MY-APP.EXAMPLE.COM
protocol: http2
```
### <a id='example'></a> HTTP/2 example
For example:
1. Fetch the Spring Music app:
```
git clone https://github.com/cloudfoundry-samples/spring-music.git
```
2. Build the app:
```
./gradle assemble
```
3. Push the app without assigning a route:
```
cf push --no-route
```
4. Map a route:
```
cf map-route spring-music EXAMPLE.COM --hostname spring-music --destination-protocol http2
```
5. Validate:
```
curl https://spring-music.EXAMPLE.COM/request --http2 -s | jq .
```
A successful response looks like the following:
```
{
"protocol": "HTTP/2.0",
"remote-addr": "198.51.100.100",
"method": "GET",
"scheme": "https",
"session-id": "1A891C614AB6B6CB59A56D7F520BBCBD"
}
```
## <a id='java-main'></a> 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](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/container-java_main.md) in the Cloud Foundry Java Buildpack repository on GitHub.
If your app is not web enabled, you must suppress route creation to avoid a <code>failed to start accepting connections</code> error.
To suppress route creation, add <code>no-route: true</code> to the app manifest or use the
<code>--no-route</code> flag with the <code>cf push</code> command. <br><br>For more information about the <code>no-route</code> attribute,
see <a href="../../devguide/deploy-apps/manifest.html">Deploying with App Manifests</a>.
### <a id='java-main-maven'></a> 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.
### <a id='java-main-gradle'></a> 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.
## <a id='play-framework'></a> Play Framework
The Play Framework packages apps into two different styles. <%= vars.app_runtime_abbr %> 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](https://www.playframework.com/) website.
## <a id='spring-boot-cli'></a> 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](https://spring.io/projects/spring-boot) on the Spring website and [Spring Boot CLI Container](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/container-spring_boot_cli.md) in the Cloud Foundry Java Buildpack repository on GitHub.
## <a id='servlet'></a> Servlet
Java apps can be packaged as Servlet apps.
### <a id='servlet-maven'></a> 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.
### <a id='servlet-gradle'></a> 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.
## <a id='services'></a> Binding to services
For more information about binding apps to services, see [Configuring Service Connections](configuring-service-connections.html).
## <a id='java-apps'></a> Java and Grails best practices
### <a id='jdbc'></a> 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.
### <a id='memory'></a> Allocate sufficient memory
If you do not allocate sufficient memory to a Java app when you deploy it, it may fail to start, or <%= vars.app_runtime_abbr %> may terminate it. You must allocate enough memory to allow for:
* Java heap
* Metaspace
* 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](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/jre-open_jdk_jre.md) in the Cloud Foundry Java Buildpack on GitHub.
To configure memory-related JRE options for your app, you can override the default memory settings of your buildpack as described in [Configuration and Extension](https://github.com/cloudfoundry/java-buildpack#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 manifests, see [Deploying with App Manifests](../../devguide/deploy-apps/manifest.html).
To see memory utilization when your app is running, run:
```
cf app YOUR-APP
```
Where `YOUR-APP` is the name of your app.
### <a id='memory-troubleshoot'></a> 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.
#### <a id='jvm'></a> JVM
* **Error**: `java.lang.OutOfMemoryError`. For example:
<pre class="terminal">
$ cf logs YOUR-APP --recent
2016-06-20T09:18:51.00+0100 [APP/0] OUT java.lang.OutOfMemoryError: Metaspace
</pre>
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](https://docs.oracle.com/javase/8/docs/api/java/lang/OutOfMemoryError.html). 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](#memory).
#### <a id='garden-container'></a> Garden container
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 <a href="https://github.com/cloudfoundry/java-buildpack-memory-calculator">java-buildpack-memory-calculator</a> repository on GitHub. If you have questions about the memory calculator, you can ask them in the <strong>#java-buildpack</strong> channel of the <a href="https://slack.cloudfoundry.org/">Cloud Foundry Slack organization</a>.
* **Error**: The Garden container terminates the Java process with the out of memory event. For example:
<pre class="terminal">
$ 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
</pre>
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, program counters, when an app forks and runs subprocesses, or when an app uses JNI to allocate memory.
* **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: {headroom: 10}}]'
```
Where `YOUR-APP` is the name of your app.
<br><br>
This example shows that 10% of the overall available `1G` is reserved for anything that is not `heap`, `metaspace`, `direct`, `code cache` or `threads`. In less common cases, this may come from companion processes started by the JVM, such as the Process API.
<br><br>
For more information about measuring how much native memory a Java app needs, see [Native Memory Tracking](https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html) in the Java documentation. For more information about configuring the Java buildpack using the native setting, see [OpenJDK JRE](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/jre-open_jdk_jre.md#memory) in the Cloud Foundry Java Buildpack on GitHub. For more information about the Process API, see [Class Process](https://docs.oracle.com/javase/8/docs/api/java/lang/Process.html) 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. Each thread consumes some memory, and, if there are enough threads, they consume a significant amount of memory.
* **Solution 2**: Set the reserved memory for stack traces to the correct value for your app.
<br>
<br>You can use the `-Xss` setting of the JVM 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}]'
JAVA_OPTS: '-Xss512k'
```
Where `YOUR-APP` is the name of your app.
<br><br>
In this example, the overall memory amount reserved by the JVM for representing the stacks of Java threads is `800 * 512k = 400m`.
<br><br>
The correct settings for `-Xss` 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 reserves for each of them. For more information, see [Executors.newCachedThreadPool() considered harmful](http://dev.bizo.com/2014/06/cached-thread-pool-considered-harmlful.html) on the Bizo website and the [newCachedThreadPool](https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/Executors.html#newCachedThreadPool--) section of the _Class Executors_ topic in the Java documentation.
### <a id='upload'></a> Troubleshoot failed upload
If your app fails to upload when you push it to <%= vars.app_runtime_abbr %>, 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. <%= vars.app_runtime_abbr %> 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 <%= vars.app_runtime_abbr %> 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 <%= vars.app_runtime_abbr %> instance, and then push it from there.
* **Out-of-date cf CLI client**: Upload of a large WAR is faster and therefore 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:
<ul>
<li><code>YOUR-APP</code> is the name of your app.</li>
<li>
<code>PATH/TO/WAR-FILE</code> 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, <code>manifest.yml</code>.
If the <code>path</code> attribute specifies the current directory, the manifest includes a line like:
<pre class="terminal">path: .</pre>
To re-push just the WAR, do one of the following:
<ul>
<li>Delete <code>manifest.yml</code> and run <code>cf push</code> again, specifying the location of the WAR using the <code>-p</code> flag.</li>
<li>Edit the <code>path</code> argument in <code>manifest.yml</code> to point to the WAR, and re-push the app.</li>
</ul>
</li>
</ul>
### <a id='debugging'></a> Debuging Java apps
Because of the way <%= vars.app_runtime_abbr %> 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 <%= vars.app_runtime_abbr %> installation:
1. Open your project in Eclipse.
2. Right-click 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 is running. If you switch to the Debug perspective, you see your app listed in the Debug panel, and it
says `Waiting for vm to connect at port`.
Next, to push your app to <%= vars.app_runtime_abbr %> and instruct <%= vars.app_runtime_abbr %> 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.
1. 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](../../devguide/deploy-apps/manifest.html#env-block).
1. Add the remote debugger configuration to the `JAVA_OPTS` variable: `-agentlib:jdwp=transport=dt_socket,address=YOUR-IP-ADDRESS:YOUR-PORT`.
1. Save the `manifest.yml` file.
1. Run:
```
cf push
```
Upon completion, you can 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.
### <a id='slow-start'></a> 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 might 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
```
or
```
$ cf push YOUR-APP --app-start-timeout 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.
The timeout threshold cannot exceed 180 seconds. Specifying a timeout threshold greater than 180 seconds causes the
following error:
```
Server error, status code: 400, error code: 100001, message: The app is invalid: health_check_timeout maximum_exceeded</code>
```
## <a id='extension'></a> Extension
The Java buildpack can also be easily extended. It creates abstractions for three types of components (containers, frameworks, and JREs) 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 this:
```
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](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/design.md), [Extending](https://github.com/cloudfoundry/java-buildpack/blob/main/docs/extending.md), and [Configuration and Extension](https://github.com/cloudfoundry/java-buildpack#configuration-and-extension) in the Cloud Foundry Java Buildpack repository on GitHub.
## <a id='env-var'></a> 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 [<%= vars.product_short %> Environment Variables](../../devguide/deploy-apps/environment-variable.html).