Scala Cucumber from IntelliJ and the command line with SBT

In this post we’ll see how to have Cucumber tests running in IntelliJ and command line using SBT.

Adding Cucumber to the project and how to configure SBT multi-module won’t be covered this time as these topics are well covered elsewhere.

Motivation

Executing Cucumber tests from IntelliJ is necessary to help during development as you just want to start your application and run the Cucumber tests right from the IDE.

Executing Cucumber tests on SBT command line is useful when you want to add the Cucumber tests as part of your application deployment pipeline.

From this point “test” means “Cucumber test”

Project architecture

This post is based on a multi-module SBT project. The separation in this architecture allows running functional tests against the same application deliverable that will be eventually running in production. The module discussed in this post is rhAcceptance:

rh-project-structure

Project architecture with modules

Before choosing to go with the fat jar approach, I tried to configure the test to execute from its natural location PROJECT_ROOT/rhAcceptance/src/test. Having Googled it quite extensively it seems only “Scalatests” are found by default in SBT.

rh-acceptance-not-running

Cucumber test was in the scope but it never ran

There are a couple of third-party plugins that enable Cucumber tests to be run as part of sbt test goal. I haven’t used them as I am wary of depending on third-party plugins with few contributors and, in this case, it was cheap to solve the problem in the way described below.

The alternative solution was to move the tests PROJECT_ROOT/rhAcceptance/src/main directory and run it as a fat jar.

rh-acceptance-test-in-main

In the acceptance test module, Cucumber tests and feature files have bee moved to the ‘main’ directory instead of ‘test’

Fat jar in SBT

In order to get the build.sbt creating a fat jar, I used the assembly plugin. Note that assembly.sbt goes into the SBT project directory.

rh-assembly-plugin

Below the SBT configuration in rhAcceptance module that create a fat jar named acceptance.jar

rh-sbt-module

For the set up above, just run sbt assembly and the fat jar will be created inPROJECT_ROOT/rhAcceptance/target/scala-2.12.

Cucumber from IntelliJ

Don’t forget to install the plugins “Scala”, “Cucumber for Java” and “Cucumber for Scala”.

Create the Cucumber runner as main function:

rh-cucumber-as-main

Cucumber runner as main function

Run the above main with the following configuration:

rh-acceptance-intellij-config

IntelliJ config to run Cucumber tests

The message “Test framework quit unexpectedly” may sound like the test run failed, but we can see that they were run successfully.

rh-acceptance-ran-from-intellij

Cucumber tests run directly from IntelliJ

Cucumber from command line

Note that --glue must point to the root package where your step definitions are located.

The last argument classpath:uk.co.myproject is the location of your feature files, classpath is necessary to load the feature files from inside the jar.

rh-run-acceptance-command-line

Cucumber tests run from command line

Here now we have the same tests run successfully from IntelliJ and the command line.

Automated monitoring with Grafana and Prometheus

Depending on the number of environments in which you have monitoring enabled for, it may be cumbersome to keep all your Grafana dashboards synchronised across environments when changes occur.

In this article I would like to show a Docker image I created to keep Grafana dashboards in sync across environments.

What is in the image

  • Prometheus
  • Grafana
  • Supervisor

How to use it

With all options:

docker run --name automated-grafana -d -p 9090:9090 -p 6666:6666 \
 -e "ENVIRONMENT=prod" \
 -e "GF_SERVER_HTTP_PORT=6666" \
 -e "WAITING_TIME=20" \
 -v `pwd`/prometheus-config:/prometheus-config \
 -v `pwd`/dashboards:/dashboards \
 -v `pwd`/users:/users \
 -v `pwd`/sources:/sources \
 serragnoli/automated-grafana-prometheus
  • -p 9090:9090: Exposes the Prometheus default port to the host;
  • -p 6666:6666: Exposes a custom Grafana port which was determined by -e “GF_SERVER_HTTP_PORT=6666” so both these parameter values need to match;
  • -e “ENVIRONMENT=prod”: This variable determines which Prometheus config to use. Ensure that a prometheus-<env>.yml suffixed with the environment where you’ll deploy to exists in the directory. In this example, the value is “prod”, /prometheus-config/prometheus-prod.yml is expected to exist;
  • -e “WAITING_TIME=20”: By default each json file is loaded 10 seconds apart from each other. There may be situations that 10 seconds may be too much or too little time. When that’s the case use this variable to control the interval of loading each json file;
  •  -v `pwd`/dashboards:/dashboards  
     -v `pwd`/users:/users  
     -v `pwd`/sources:/sources: Map the directories containing the json files to be loaded by Grafana in the container. It’s worth mentioning that the /dashboards directory must only contain dashboard json files and the same applies to the other directories.

After running the above command, you should have Prometheus running on localhost:9090 and Grafana running on localhost:6666.

Advantages

  • You can run the same image everywhere, just use a Prometheus config file that points to /metrics endpoints for the environment you’re deploying to.
  • You can load as many dashboards, users and data-source json files as you want by only mapping the directories that contain these files from the host to the container.
  • You get fewer moving parts in your monitoring system. Therefore, less things to worry about i.e. lost/slow network connectivity between Prometheus and Grafana.

Disadvantages

  • Difficulty to scale each component individually as they are in the same container;
  • Breaking the Docker rule of thumb “one process per Docker container“.

Final considerations

I’ve added sample json files to the image page in DockerHub:  dashboard.json,  user.json  and  data-source.json.

I can’t tell if I have tried to reinvent the wheel with this image. But, the is fact that I couldn’t find anything already automating Grafana dashboards in the same way.

As I mentioned above, this is currently my favourite solution and if I learn a better way of doing it, I’ll update this article.

Docker container cluster via AWS ECS using GitLab pipelines

Before we start, make sure you have already created a key-pair (a *.pem file) that allows admin access to AWS.

This is not a detailed guide for the deployment, so it’s assumed understanding of the concepts of ECS, Docker and Pipelines.

Without further ado, let’s dive right in.

AWS – Create a Load Balancer

This is the only manual step necessary. But it has to be done only once.

The Load Balancer will provide a single URL to reach the cluster where container can be added or removed.

On the console, click on EC2

AWS-Step01

Click on Target Groups

AWS-Step01A

Click on Create Target Group

All containers deployed under this Load Balancer will be associated directly with this Target Group.

Screen Shot 2017-05-10 at 22.48.54

Fill in this form below. The important thing to note here is the Health check settings section: you must make sure the path you add to your health check actually exists, otherwise your container will be considered unhealthy and ECS will kill it and re-run forever.

Screen Shot 2017-05-09 at 23.04.33

Create the actual Load Balancer

Screen Shot 2017-05-09 at 22.56.50

Fill in the name and port where the Load Balancer will listen to

Screen Shot 2017-05-09 at 23.08.38

Select availability zones

Screen Shot 2017-05-09 at 23.09.47

Skip the 2nd step on the wizard

On the 3rd step, select the default security group.

Screen Shot 2017-05-09 at 23.11.11

Select Existing Target Group and choose the Target Group (“MyTargetGroup”) created earlier.

Screen Shot 2017-05-09 at 23.12.24

Go to the end of the wizard and confirm/save the new Load Balancer.

The address to access the application is under DNS name.

Screen Shot 2017-05-09 at 23.15.05

AWS – ECS Initial State

Click on EC2 Container Service

Screen Shot 2017-05-10 at 23.29.14

The page should either look something like this…

Screen Shot 2017-05-10 at 23.05.46

… or contain a dashboard like this

Screen Shot 2017-05-10 at 23.30.22

GitLab

The docker-compose.yml referred to in the .gitlab-ci.yml below

version: '2'

services:
  api:
    image: "registry.gitlab.com/my_username/my_project_name/api:latest"
    ports:
      - "80:8080"

Create the .gitlab-ci.yml file

The environment variables seen below can be added on Settings => CI.

image: docker:latest
services:
  - docker:dind

stages:
  - build
  - dockerise
  - deploy

api-build:
  stage: build
  image: maven:3-jdk-8-alpine
  script:
    - cd api
    - mvn clean verify
##### 'artifacts' is the way you can pass the artifact around to the next pipelines #####
  artifacts:
    paths:
      - api/target/api.jar

##### This simply builds and pushes the Docker image #####
api-containerisation:
  stage: dockerise
  script:
    - docker version
    - docker build -t registry.gitlab.com/my_username/my_project_name/api:$CI_JOB_ID api/
    - docker build -t registry.gitlab.com/my_username/my_project_name/api:latest api/
    - docker login -u my_username -p $DOCKER_LOGIN_KEY registry.gitlab.com
    - docker images
    - docker push registry.gitlab.com/my_username/my_project_name/api:$CI_JOB_ID
    - docker push registry.gitlab.com/my_username/my_project_name/api:latest

CI_deploy:
  stage: deploy
  image: python:3-alpine
  variables:
    AWS_DEFAULT_REGION: "us-east-2"
  before_script:
##### Install the AWS ECS-CLI #####
    - apk add --update curl
    - curl -o /usr/local/bin/ecs-cli https://s3.amazonaws.com/amazon-ecs-cli/ecs-cli-linux-amd64-latest
    - chmod +x /usr/local/bin/ecs-cli
  script:
##### Configure ECS-CLI, run the container and scale to 2 #####
    - ecs-cli configure --region $AWS_DEFAULT_REGION --access-key $AWS_ACCESS_KEY_ID --secret-key $AWS_SECRET_ACCESS_KEY --cluster CI-GreetingsAPI
    - ecs-cli up --keypair $AWS_KEY_PAIR --capability-iam --size 2 --instance-type t2.micro --vpc vpc-xxxxxxx --subnets subnet-123abc,subnet-321cba
##### This docker-compose.yml is the one described above #####
    - ecs-cli compose --file api/docker-compose.yml service up --target-group-arn $PROD_TARGET_GROUP_ARN --container-name api --container-port 8080 --role ecsServiceRole
    - ecs-cli compose --file api/docker-compose.yml service scale 2
  environment:
    name: ci
    url: $PROD_LOAD_BALANCER_URL
  only:
    - master

When the file above is committed to master in GitLab the pipelines should be automatically triggered…

Screen Shot 2017-05-09 at 23.53.23

… and by clicking on the pipeline the console should show the logs. The example below is the CI_deploy logs.

Screen Shot 2017-05-09 at 23.55.25

Back to AWS – ECS

The same dashboard that was all zeroed/empty before now shows the status of this cluster

Screen Shot 2017-05-10 at 23.57.05

Testing the cluster

Notice the request is distributed evenly across the available containers in the cluster (in this case 2) by hitting the  Load Balancer URL several times.

Screen Shot 2017-05-11 at 00.13.38Screen Shot 2017-05-11 at 00.13.59

 

Vaadin 8 + Scala 2.12

This post is about running the Vaadin 8 Hello World with Scala 2.12 and Jetty.

Following my frustrated attempt to Dockerise an Ionic 3 application for web browser, I went back to Vaadin in order to get a UI quickly working for my Akka project.
I found the following Vaadin+Scala documentation which is a good start but it doesn’t show how to actually run it using the Maven Jetty plugin.

Execute the Maven archetype below to get a Hello World app:

mvn archetype:generate -DarchetypeGroupId=com.vaadin -DarchetypeArtifactId=vaadin-archetype-application -DarchetypeVersion=8.0.5 -DgroupId=com.pany -DartifactId=ui -Dversion=1.0-SNAPSHOT -Dpackaging=war

Now add the Scala dependencies to your pom.xml:

<project>
<dependencies>
  <dependency>
   <groupId>org.scala-lang</groupId>
   <artifactId>scala-library</artifactId>
   <version>2.12.1</version>
  </dependency>
 </dependencies>
 <plugins>
  <plugin>
   <groupId>net.alchim31.maven</groupId>
   <artifactId>scala-maven-plugin</artifactId>
   <version>3.2.2</version>
   <executions>
    <execution>
     <goals>
      <goal>compile</goal>
      <goal>testCompile</goal>
     </goals>
    </execution>
   </executions>
   <configuration>
    <scalaCompatVersion>2.12</scalaCompatVersion>
    <scalaVersion>${scala.version}</scalaVersion>
   </configuration>
  </plugin>
 </plugins>
</project>

Finally, create you UI class in Scala:

import java.util.Date
import javax.servlet.annotation.WebServlet

import com.vaadin.annotations.{Theme, VaadinServletConfiguration}
import com.vaadin.server.{VaadinRequest, VaadinServlet}
import com.vaadin.ui.Button.{ClickEvent, ClickListener}
import com.vaadin.ui._

@WebServlet(urlPatterns = Array("/*"), name = "MyScalaUIServlet", asyncSupported = true)
@VaadinServletConfiguration(ui = classOf[MyScalaUI], productionMode = false)
class MyScalaUIServlet extends VaadinServlet {
}

@Theme("mytheme")
class MyScalaUI extends UI {

 override def init(request: VaadinRequest): Unit = {
 val content: VerticalLayout = new VerticalLayout
 setContent(content)

 val label: Label = new Label("Hello, world!")
 content addComponent label

 // Handle user interaction
 content addComponent new Button("Click Me from Scala!",
 new ClickListener {
 override def buttonClick(event: ClickEvent): Unit =
 Notification.show("The time is " + new Date)
 })
 }
}

Now just execute:

mvn clean package jetty:run -Dvaadin.productionMode=true

Android losing app state on device rotation

Q.: Why is the screen of my Android app reset when I rotate the device from Portrait to Landscape, and vice versa?

A.: The rotation of the device is seen by Android as a configuration change which causes the current activity (including any instance variables) to be destroyed. Android then invokes onCreate(Bundle savedInstanceState) for this new configuration which resets your application to its initial state.

Q.: How can I keep the state of my app when the device is rotated?

A.: Override the method onSaveInstanceState(Bundle savedInstanceState) in your activity class and add key/value to save the state you want to get back. For example:

  @Override
    protected void onSaveInstanceState(Bundle savedInstanceState) {
        savedInstanceState.putInt("MY_KEY", "VALUE I CARE ABOUT"); <<<< Save the state
    }

Then simply restore the values saved in onCreate(Bundle savedInstanceState). For example:

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_stopwatch);

        if(savedInstanceState != null) {
            savedState = savedInstanceState.getInt("MY_KEY"); <<<< Retrieve the state
        }
    }

Rant on Recruitment Pairing Sessions

Following Powa’s bankruptcy, I found myself having to, rather unexpectedly, search for a new job.

Here I would like to share a few thoughts regarding one of the stages of the recruitment process that I am against: the pairing session.

Acknowledgement

It’s understandable that an organisation hiring a new developer must separate the wheat from the chaff, especially in a city like London where there are quite a few developers to choose from.

The hiring process

The typical stages in the hiring process that software developers go through are:

  1. Phone interview with the external recruitment agent (if applicable);
  2. Phone interview with the employer’s internal recruiter;
  3. Screening questions over phone/email;
  4. Unattended coding assignment;
  5. Pairing session with someone from the employer;

In my opinion, the importance of stage #5 in this process is overrated by employers.

What’s wrong with #5?

The “pairing” exercises I have been through were hardly any kind of partnership. Out of the 12 sessions I attended in 2.5 weeks, only 2 bore any similarity to a partnership.

The overwhelming majority of the sessions turned out to be nothing more than your pairing partner acting like a sheriff watching over your shoulder and reminding you of the time remaining for your session to end.

The scope of these kind of exercise ranges from small greenfield to pieces of legacy code that can touch anything from simple data structures to reasonably complex concurrency issues, therefore, you never know what to expect from these sessions.

I feel uncomfortable with the pairing stage (other developers, who I had casual conversations about the subject, feel the same way) and fail to see the real benefit of this kind of exercise.

The major issues I had to deal with during pairing exercises were:

  • unfamiliarity with the development environment: keyboard layout, operating system, IDE, shortcuts etc;
  • accusations of over-engineered code;
  • pressure to prove your worth in few moments;

The bottom line is that I normally underperform in recruitment pairing sessions.

Any better options?

Discussions about the unattended coding exercise would probably suffice.

It was no coincidence that I succeeded in 100% of the unattended exercises but failed most of the “pairing” sessions.

I think the unattended coding assignment reflects more realistically the coding skills of a candidate as he/she can develop in his/her comfort zone:

  • using tools he/she is familiar with;
  • having time (even with a deadline) to plan the design/implementation;
  • having the freedom to change his/her mind at any point during design/implementation without that awkward feeling that he/she already failed because of it;

No pairing at Powa

In a nutshell, these were the steps we used to hire new developers at Powa:

  1. a screening call with the team’s line manager;
  2. an unattended coding assignment was sent to the candidate;
  3. once the candidate submitted his/her implementation, each developer in our team evaluated it;
  4. as a team, we agreed whether to invite the candidate for a final round;
  5. in the final round, we’d spend 1 hour having a thorough conversation about the unattended exercise’s implementation, design, potential problems with the current solution etc;

We hired some of the finest developers in London without resorting to pairing.

Probation

I haven’t yet seen any organisation which simply throws a new joiner in a team and expects him/her to start coding immediately. There is a period to allow the new joiner to get familiarised with the new team, the work, the office facilities etc.

The pairing sessions are not in-line with reality since it doesn’t reflect the way people actually work in projects. So, what good does it make having a “pairing” torture that doesn’t prove anything?

Probation period exists for a reason.

Rant over.

Vaadin 7 SEO

I have been experimenting with Vaadin in one of my petty projects for a few weeks now.

As it happens to any technology, there are good and bad things about it, but so far it’s been a pleasure to develop with it.

The problem

Google doesn’t index the main page as expected.

Despite having read in a blog post (listed on the Resources section) that Google is now able to interpret Javascript pages with its crawlers, this is what I got:

GoogleSearchResult-EnableJS-Blurred“You have to enable javascript in your browser to use …” clearly is not the expected result.

Workaround

In order to solve this problem, you can simply create an HTML page to contain all the information you need the search engine to see and embed the Vaadin app as it is n an iFrame:

Create an the outer .html page

<body>
<iframe src="/my-context" style="position:fixed; top:0; left:0; bottom:0; right:0; width:100%; height:100%; border:none; margin:0; padding:0; overflow:hidden; z-index:999999;">
    Your browser doesn't support iframes. Upgrade to a more modern version of the browser.
</iframe>
</body>

 

Change the Vaadin context

Note that the path here needs to match  the path in the iFrame src of the HTML page above.

@SpringUI(path = "/my-context")
public class MyUI extends UI {
}

 

Make the outer HTML the welcome page

Change this in you Spring configuration class.

@Configuration
public class MyConfig {
    @Bean
    public WebMvcConfigurerAdapter forwardToIndex() {
        return new WebMvcConfigurerAdapter() {
            @Override
            public void addViewControllers(ViewControllerRegistry registry) {
                registry.addViewController("/").setViewName(
                        "forward:/my.html");
            }
        };
    }

The result, as expected

GoogleSearchResult-Blurred

 

Resources

  1. https://googlewebmastercentral.blogspot.co.uk/2014/05/understanding-web-pages-better.html
  2. https://googlewebmastercentral.blogspot.co.uk/2015/10/deprecating-our-ajax-crawling-scheme.html
  3. https://www.google.com/webmasters/tools/home?hl=en
  4. http://www.bing.com/toolbox/webmaster
  5. http://stackoverflow.com/questions/3982422/full-screen-iframe

Debugging Spring Rest endpoint from your IDE

Today I had trouble debugging a Rest endpoint in a Spring Boot application.

 

The Problem

To start the debugger from the IDE, I simply right-clicked the main method and ran the debugger:

DebugBoot

Despite getting all the logs for the scheduled tasks and no error messages, I couldn’t hit the breakpoint in my endpoint method.

 

Realisation

Eventually I noticed that Tomcat wasn’t logging its initialisation. Therefore, I was missing this line from the logged messages:

INFO 4447 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8050 (http)

 

Solution

The plugin

It took just a few minutes googling to come across Spring Boot Maven Plugin.

 

In pom.xml

Add the plugin:

<plugin>
    <groupid>org.springframework.boot</groupid>
    <artifactid>spring-boot-maven-plugin</artifactid>
    <configuration>
        <jvmarguments>
            -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=5005
        </jvmarguments>
    </configuration>
</plugin>

In the console

To prepare the application for debugging, run:

mvn spring-boot:run

The app at this stage will halt execution until the debugger is attached to it:

[INFO] Attaching agents: []
Listening for transport dt_socket at address: 5005

In the IDE

Add a new remote debugger making sure of using the same port the plugin broadcasts on.
IntelliJCreateRemoteDebug

That is it. Now the endpoints are running on debug mode.

Google Analytics plugin in Vaadin 7

I’ve recently started using the Google Analytics Tracker plugin for Vaadin and it’s been working flawlessly, but I ran into a couple of small issues when configuring it for Vaadin 7 that I thought it’d be worth sharing them here.

Issue 1 – Basic usage

The main difference between Vaadin 6 and 7 in terms of the basic configuration is highlighted in lines of the code snippets below.

public class GoogleanAlyticsSampleApplication extends Application {
    @Override
    public void init() {
        Window mainWindow = new Window("GoogleanAlyticsWidget Sample Application");

        // Create a tracker for vaadin.com domain.
        GoogleAnalyticsTracker tracker = new GoogleAnalyticsTracker("UA-658457-8", "vaadin.com");

        // Add only one tracker per window.
        mainWindow.addComponent(tracker);

        // Track the page view
        tracker.trackPageview("/samplecode/googleanalytics");

        // Assign main window
        setMainWindow(mainWindow);
    }
public class MyUI extends UI {
    final VerticalLayout layout = new VerticalLayout();

    // Create a tracker for vaadin.com domain.
    final GoogleAnalyticsTracker googleAnalyticsTracker = new GoogleAnalyticsTracker("UA-658457-8" "vaadin.com");

    // This line is the main difference between Vaadin 6 and 7
    googleAnalyticsTracker.extend(this);

    // Track the page view
    googleAnalyticsTracker.trackPageview(MyView.NAME);

    // Assign main window
    setContent(layout);
}

The method extend(UI) is the one that enables the plugin to be added to the page.

Issue 2 – Dependency injection with Spring

If you prefer to use dependency injection, provided by Spring in this example, make sure the instance of the tracker is not a singleton. In Spring, this can be achieved by changing the scope of the bean to prototype:

@Configuration
public class AppConfig {
    @Bean
    @Scope("prototype")
    public GoogleAnalyticsTracker googleAnalyticsTracker() {
        return new GoogleAnalyticsTracker("UA-658457-8", "vaadin.com");
    }
}

You’ll know when your bean scope is singleton, if the plugin eventually throws the exception below.
The exception tells us that the singleton tracker cannot be shared by another instance of the UI.

2016-02-03 12:06:51.875 ERROR 15631 --- [nio-8080-exec-8] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [com.vaadin.server.ServiceException: java.lang.IllegalStateException: Moving an extension from one parent to another is not supported] with root cause

java.lang.IllegalStateException: Moving an extension from one parent to another is not supported
at com.vaadin.server.AbstractClientConnector.addExtension(AbstractClientConnector.java:581) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.AbstractExtension.extend(AbstractExtension.java:77) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at org.vaadin.googleanalytics.tracking.GoogleAnalyticsTracker.extend(GoogleAnalyticsTracker.java:220) ~[googleanalyticstracker-2.1.0.jar!/:2.1.0]
at my.package..MyUI.init(MyUI.java:60) ~[web-0.2.0-SNAPSHOT.jar!/:0.2.0-SNAPSHOT]
at com.vaadin.ui.UI.doInit(UI.java:675) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.communication.UIInitHandler.getBrowserDetailsUI(UIInitHandler.java:214) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.communication.UIInitHandler.synchronizedHandleRequest(UIInitHandler.java:74) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.SynchronizedRequestHandler.handleRequest(SynchronizedRequestHandler.java:41) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.VaadinService.handleRequest(VaadinService.java:1408) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at com.vaadin.server.VaadinServlet.service(VaadinServlet.java:351) ~[vaadin-server-7.5.5.jar!/:7.5.5]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:719) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationDispatcher.processRequest(ApplicationDispatcher.java:465) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationDispatcher.doForward(ApplicationDispatcher.java:357) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationDispatcher.forward(ApplicationDispatcher.java:317) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.servlet.mvc.ServletForwardingController.handleRequestInternal(ServletForwardingController.java:128) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.mvc.AbstractController.handleRequest(AbstractController.java:147) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.mvc.SimpleControllerHandlerAdapter.handle(SimpleControllerHandlerAdapter.java:50) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:959) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:893) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:969) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:871) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:648) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:845) ~[spring-webmvc-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) ~[tomcat-embed-websocket-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:99) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.filter.HttpPutFormContentFilter.doFilterInternal(HttpPutFormContentFilter.java:87) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:77) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:121) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-4.2.4.RELEASE.jar!/:4.2.4.RELEASE]
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212) ~[tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) [na:1.7.0_80]
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) [na:1.7.0_80]
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.0.30.jar!/:8.0.30]
at java.lang.Thread.run(Thread.java:745) [na:1.7.0_80]

App version number in Java code with Maven and Spring Boot

This is an easy way to access the version number of your application version in Java using Spring Boot.

Environment

  • Maven 3.3.3
  • Spring Boot v1.3.1.RELEASE
  • Java 1.7.0_80

Steps

  • In you application.properties add a placeholder application.version.number that will be passed in from Maven.
version.number=@application.version.number@
  • Added the placeholder as new property in you pom.xml
<application.version.number>${version}</application.version.number>
  • Inject the new config into the Spring bean you need
@Value("${version.number}")
private String versionNumber;