Language Guide: Java (with Maven)
This guide will help you get started with a Java application building with Maven on CircleCI.
- Overview
- Sample configuration: version 2.1:
- For 2.0 Configuration (recommended for CircleCI server v2.x users only):
- See also
Overview
This is an example application showcasing how to run a Java app on CircleCI 2.1. This application uses the Spring PetClinic sample project. This document includes pared down sample configurations demonstrating different CircleCI features including workspaces, dependency caching, and parallelism.
Sample configuration: version 2.1:
A basic build with an orb:
version: 2.1
orbs:
maven: circleci/maven@0.0.12
workflows:
maven_test:
jobs:
- maven/test # checkout, build, test, and upload test results
This config uses the language-specific orb to replace any executors, build tools, and commands available. Here we are using the maven orb, which simplifies building and testing Java projects using Maven. The maven/test command checks out the code, builds, tests, and uploads the test result. The parameters of this command can be customized. See the maven orb docs for more information.
For 2.0 Configuration (recommended for CircleCI server v2.x users only):
version: 2.0
jobs:
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw package
Version 2.0 configs without workflows will look for a job named build
. A job is a essentially a series of commands run in a clean execution environment. Notice the two primary parts of a job: the executor and steps. In this case, we are using the docker executor and passing in a CircleCI convenience image.
Using a workflow to build then test
A workflow is a dependency graph of jobs. This basic workflow runs a build job followed by a test job. The test job will not run unless the build job exits successfully.
version: 2.0
jobs:
test:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw test
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw -Dmaven.test.skip=true package
workflows:
version: 2
build-then-test:
jobs:
- build
- test:
requires:
- build
Caching dependencies
The following code sample details the use of caching.
version: 2.0
jobs:
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }} # appends cache key with a hash of pom.xml file
- v1-dependencies- # fallback in case previous cache key is not found
- run: ./mvnw -Dmaven.test.skip=true package
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
The first time this build ran without any dependencies cached, it took 2m14s. Once the dependencies were restored, the build took 39 seconds.
Note that the restore_cache
step will restore whichever cache it first matches. You can add a restore key here as a fallback. In this case, even if pom.xml
changes, you can still restore the previous cache. This means the job will only have to fetch the dependencies that have changed between the new pom.xml
and the previous cache.
Persisting build artifacts to workspace
The following configuration sample details persisting a build artifact to a workspace.
version: 2.0
jobs:
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw -Dmaven.test.skip=true package
- persist_to_workspace:
root: ./
paths:
- target/
test:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- attach_workspace:
at: ./target
- run: ./mvnw test
workflows:
version: 2
build-then-test:
jobs:
- build
- test:
requires:
- build
This persist_to_workspace
step allows you to persist files or directories to be used by downstream jobs in the workflow. In this case, the target directory produced by the build step is persisted for use by the test step.
Splitting tests across parallel containers
version: 2.0
jobs:
test:
parallelism: 2 # parallel containers to split the tests among
docker:
- image: cimg/openjdk:17.0
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: |
./mvnw \
-Dtest=$(for file in $(circleci tests glob "src/test/**/**.java" \
| circleci tests split --split-by=timings); \
do basename $file \
| sed -e "s/.java/,/"; \
done | tr -d '\r\n') \
-e test
- store_test_results: # We use this timing data to optimize the future runs
path: target/surefire-reports
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw -Dmaven.test.skip=true package
workflows:
version: 2
build-then-test:
jobs:
- build
- test:
requires:
- build
Splitting tests by timings is a great way to divide time-consuming tests across multiple parallel containers. You might think of splitting by timings as requiring 4 parts:
- a list of tests to split
- the command:
circleci tests split --split-by=timings
- containers to run the tests
- historical data to intelligently decide how to split tests
To collect the list of tests to split, simply pull out all of the Java test files with this command: circleci tests glob "src/test/**/**.java"
. Then use sed
and tr
to translate this newline-separated list of test files into a comma-separated list of test classes.
Adding store_test_results
enables CircleCI to access the historical timing data for previous executions of these tests, so the platform knows how to split tests to achieve the fastest overall runtime.
Storing code coverage artifacts
version: 2.0
jobs:
test:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- run: ./mvnw test verify
- store_artifacts:
path: target/site/jacoco/index.html
workflows:
version: 2
test-with-store-artifacts:
jobs:
- test
The Maven test runner with the JaCoCo plugin generates a code coverage report during the build. To save that report as a build artifact, use the store_artifacts
step.
A configuration
The following code sample is the entirety of a configuration file combining the features described above.
version: 2.0
jobs:
test:
parallelism: 2
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- v1-dependencies-
- attach_workspace:
at: ./target
- run: |
./mvnw \
-Dtest=$(for file in $(circleci tests glob "src/test/**/**.java" \
| circleci tests split --split-by=timings); \
do basename $file \
| sed -e "s/.java/,/"; \
done | tr -d '\r\n') \
-e test verify
- store_test_results:
path: target/surefire-reports
- store_artifacts:
path: target/site/jacoco/index.html
build:
docker:
- image: cimg/openjdk:17.0.1
auth:
username: mydockerhub-user
password: $DOCKERHUB_PASSWORD # context / project UI env-var reference
steps:
- checkout
- restore_cache:
keys:
- v1-dependencies-{{ checksum "pom.xml" }}
- v1-dependencies-
- run: ./mvnw -Dmaven.test.skip=true package
- save_cache:
paths:
- ~/.m2
key: v1-dependencies-{{ checksum "pom.xml" }}
- persist_to_workspace:
root: ./
paths:
- target/
workflows:
version: 2
build-then-test:
jobs:
- build
- test:
requires:
- build
The configuration above is from a demo Java app, which you can access here. If you want to step through it yourself, you can fork the project on GitHub and download it to your machine. Go to the Projects page in CircleCI and click the Follow Project button next to your forked project. Finally, delete everything in .circleci/config.yml. Nice! You just set up CircleCI for a Java app using Gradle and Spring.
See also
- See the Deploy document for example deploy target configurations.
- See the Debugging Java OOM errors document for details on handling Java memory issues.
Help make this document better
This guide, as well as the rest of our docs, are open source and available on GitHub. We welcome your contributions.
- Suggest an edit to this page (please read the contributing guide first).
- To report a problem in the documentation, or to submit feedback and comments, please open an issue on GitHub.
- CircleCI is always seeking ways to improve your experience with our platform. If you would like to share feedback, please join our research community.
Need support?
Our support engineers are available to help with service issues, billing, or account related questions, and can help troubleshoot build configurations. Contact our support engineers by opening a ticket.
You can also visit our support site to find support articles, community forums, and training resources.
CircleCI Documentation by CircleCI is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.