Monday 21 April 2014

10 ideas to improve Eclipse IDE usability

Few years ago, we had a mini IDE war inside our office. It happened between Eclipse and Netbeans supporters. Fortunately, we did not have IntelliJ supporter. Each side tried their best to convince people from the other side to use their favourite IDE.

On that war, I am the Eclipse hardcore supporter and I had a hard time fighting Netbeans team. Not as I expected, we end up on the defence side more often than attack. Look at what Netbeans offers, it is quite interesting for me to see how far Netbeans has improved and how Eclipse is getting slower and more difficult to use nowadays than in the past.

Let I share my experience on that mini war and my point of view on how Eclipse should be improved to keep its competitive edge.

What is the benefit of using Netbeans

For a long time and even up to now, Eclipse is still the dominant IDE in the market. But this did not happened before Eclipse 3.0, which was released in 2004. From there, Eclipse simply dominates the market share of Java IDE for the next decade. Even the C/C++ and Php folks also built their IDE plugin on top of Eclipse.

However, things is getting less rosy now. Eclipse is still good, but not that much better than its competitors any more. IntelliJ is a commercial IDE and we will not compare it to Eclipse in this article. The other and more serious competitor is Netbeans. I myself have tried Netbeans, compared it to Eclipse 3.0 and never came back. But the Netbeans that Eclipse is fighting now and the Netbeans that I have tried are simply too different. It is much faster, more stable, configurable and easier to use than I have known.

The key points of using Netbeans are the usability and first class support from Sun/Oracle for new Java features. It may not be very appealing to Eclipse veteran like myself but for a starter, it is a great advantage. Like any other wars in the technology worlds, Eclipse and Netbeans keep copying each other features for so long that it is very hard to find something that one IDE can do and the other one cannot. To consider the preferred IDE, what really matter is how things are done rather than what can be done.

Regarding usability, I feel Eclipse failed to keep the competitive edge it once had against Netbeans. Eclipse interface is still very flexible and easy to customize but the recent plugins are not so well implemented and error prone (I am thinking of Maven, Git support). Eclipse market is still great but lots of plugins are not so well tested and may create performance or stability issue. Moreover, careless release (Juno 4.0) made Eclipse slow and hangup often. I did not recalled restarting Eclipse in the past but that happened to me once or twice a month now (I am using Eclipse Kepler 4.3).

Plus, Eclipse did not fixed some of the discomforts that I have encountered from early day and I still need to bring along all the favourite plugins to help me ease the pain.

What I expect from Eclipse

There are lots of things I want Eclipse to have but never see from release note. Let share some thoughts:

1. Warn me before I open a big file rather than hang up

I guess this happen to most of us. My preferred view is the Package Explorer rather than Project Explorer or Navigator but it does not matter. When I search a file by Ctrl + Shift + R or left click on the file in Explorer, Eclipse will just open the file in Editor. If the file is a huge size XML file? Eclipse hangup and show me the content one minute later or I get frustrated and kill the process. Both are bad outcomes.



2. Have a single Import/Export configuration endpoint

For who does not know, Eclipse allow you to import/export Eclipse configuration to a file. When I first download a new release of Eclipse, there are few steps that I always do

  • Import -> Install -> From Existing Installation: This step help me to copy all my favourite features and plugins from old Eclipse to new Eclipse.
  • Modify Xms, Xmx in eclipse.ini
  • Import Formatter (from exported file)
  • Import Shortkey (from exported file)
  • Configure Installed JREs to point to local JDK
  • Configure Server Runtime and create Server.
  • Disable useless Validators
  • Register svn repository
  • And some other minor tasks that I cannot remember now...
Why don't make it simpler like Chrome installation when new Eclipse can copy whatever settings that I have done on the old Eclipse?



3. Stop building or refreshing the whole workspace

It happened to me and some of the folks here that I have hundred projects in my workspace. The common practice in our workplace is workspace per repository. To manage things, we create more than 10 Working Sets and constantly switch among them when moving to new task.

For us, having Eclipse building, refreshing, scanning the whole workspace is so painful that whether we keep closing projects or sometimes, create a smaller workspace. But can Eclipse allow me to configure scanning Working Set rather than Workspace? Working Set is all what I care.

Plus, sometimes, Ctrl + Shift + R and Ctrl + Shift + T does not reflect my active Working Set and not many people notice the small arrow on the top right of the dialogue to select this.


4. Stop indexing by Git and Maven repository by default

Eclipse is nice, it helps us to index Maven and Git repository so that we can work faster later. But not all the time I open Eclipse to work with Maven or Git. Can these plugins be less resource consuming and let me trigger the indexing process when I want?

5. Give me process id for any server or application that I have launched

This must be a very simple task but I do not know why Eclipse don't do it. It is even more helpful if Eclipse can provide the memory usage of each process and Eclipse itself. I would like to have a new views that tracking all running process (similar to Debug View) but with process id and memory usage.

6. Implement Open File Explorer and Console here

I bet that most of us use console often when we do coding, whether for Vi, Maven or Git command. However, Eclipse does not give us this feature and we need to install additional plugin to get it.



7. Improve the Editor 

I often install AnyEdit plugin because it offer many important features that I found hard to live without like converting, sorting,...

These features are so crucial that they should be packaged together with Eclipse distribution rather than in a plugin.


8. Stop showing nonsense warning and suggestion

Have any of you build a project without a single yellow colour warning? I did that in the past, but let often now.

For example, Eclipse asked me to introduce serialVersionUID because my Exception implements Serializable interface. But seriously, how many Java classes implement Serializable? Do we need to do this for every of them?

9. Provide me short keys for the re-factoring tools that I always use

Some folks like to type and seeing IDE dependent as a sin. I am on the opposite side. Whatever things can be done by IDE should be done by IDE. Developer is there to think rather than type. It means that I use lots of Eclipse short-keys and re-factoring tool like

  • Right click -> Surround With
  • Right click -> Refactor
  • Right click -> Source
Some of most common short keys I use everyday are Ctrl + O, Alt + Shift + L, Alt + Shift + M, Ctrl + Shift + F,... and I would like to have more. Eclipse allows me to define my own short keys but I would like it to be part of Eclipse distribution so that I can use them on other boxes as well. 

From my personal experience, some tools that worth having a short key are
  • Generate Getters and Setters
  • Generate Constructor using Fields
  • Generate toString()
  • Extract Interface
  • ...
I also want Eclipse to be more aggressive in defining Templates for Java Editor. Most of use are familiar with well-known Template like sysout, syserr, switch, why don't we have more for log, toString(), hashCode(), while true,...

10. Make the error messages easier to read by beginner

I have answered many Eclipse questions regarding some common errors because developers cannot figure out what the error message means. Let give few examples:

A developer uses command "mvn eclipse:eclipse". This command generates project classpath file and effectively disable Workspace Resolution. Later, he want to fix things by Update Project Configuration and encounter an error like below (if you want to understand this further, can take a look at the last part of my Maven series)



Who understand that? The real problem is the m2e plugin fail to recognize some entries populated by Maven and the solution is to delete all Eclipse files and import Maven project again.

Another well-known issue is the error message on pom editor due to m2e does not recognize Maven plugin. It is very confusing for newbie to see this kind of errors.

Conclusion

These are my thoughts and I wish Eclipse will grant my wishes some days. Do you have anything to share with us about how you want Eclipse to improve?

Saturday 19 April 2014

Maven Explanation - part 3


The two earlier parts of the Maven series can be found here:
Part 1
Part 2

Up to now, we have covered Maven build lifecycle, plugin, repository and dependency management. In this part, let explore Maven module, setting, profile and IDE plugin.

Maven Modules

Parent Project 

Maven module is the part where Maven offer additional value to Ant. If you remember, Ant do not have project dependency, rather it only has task dependency. Of course, developers can use dependent tasks to execute other project build file, and through that, indirectly achieve project dependency. However, this is tedious. Moreover, if you want to child build.xml to inherit some properties, settings or even some tasks from parent build file, then it is even more difficult.

Maven take the project modules as part of its core concept and natively support it. Let start with a simple example.


We have a nested project here with the parent project named sample_maven_module. This project has 2 child projects, sub_module and web. The child project sub_module has another child project named sub_sub_module.

Below is the pom definition of the parent project:

<?xml version="1.0" encoding="UTF-8"?>
<project>
  <modelVersion>4.0.0</modelVersion>
  <groupId>com.blogspot.sgdevblog</groupId>
  <artifactId>sample_maven_module</artifactId>
  <packaging>pom</packaging>
  <version>0.0.1-SNAPSHOT</version>
  <name>Parent pom</name>

  <build>
    <pluginManagement>
      <plugins>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>2.3.2</version>
          <configuration>
            <source>1.6</source>
            <target>1.6</target>
          </configuration>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>3.8.1</version>
      <scope>test</scope>
    </dependency>
  </dependencies>

  <modules>
    <module>web</module>
    <module>sub_module</module>
  </modules>
</project>

As you can see, a project can only have modules when it is packaging as pom. Parent project serve a special purpose, it is used to define common settings and declaration for child modules. If take a look at the effective pom view of child module, then we will see the parent plugins and dependencies above will be automatically inherited by the child modules.

With this feature, developers can define a common version for log4j, Spring framework, Junit or whatever plugins setting in the project.

In the above example, we have multiple layer of nested projects. If there is conflict among configurations, the configuration of nearest parent will be applied. For example, if in sub_module project, we declare

<plugin>
 <artifactId>maven-compiler-plugin</artifactId>
 <version>2.3.2</version>
 <configuration>
  <source>1.7</source>
  <target>1.7</target>
 </configuration>
</plugin>

Then, in the sub_sub_module project the source will be set to 1.7 rather than 1.6 as declared in grant parent project.

Maven module

There are two things to note about modules. Firstly, whatever mvn command trigger on parent project will be triggered by child projects as well. Secondly, Maven is smart enough to identify cross dependency among child modules and trigger the command in a proper order. However, if there is no cross dependency, the order of execution will follow declaration order.

For example, in the above project, we declare of sub_module in front of web. Normally, Maven will trigger command on sub_module, then web.

Put into practice, when we trigger mvn clean install on sample_maven_module project, here is the execution order

mvn clean install on sub_module
mvn clean install on sub_sub_module
mvn clean install on web

However, if we declare web as dependency of sub_sub_module, then sub_module will always be built first. The order will be changed as follow

mvn clean install on web
mvn clean install on sub_module
mvn clean install on sub_sub_module

It is understandable because the child module appear as dependency should be build first and Maven explore sub_sub_module only after exploring its direct parent project.

Maven Settings & Profiles

Assume that with the knowledge above, you have setup a wonderful Maven project that build like charm. Now, there are few common challenges that you may need to face if we deploy our project to other servers.

The database is set-up differently in Jenkins server and you may need another configuration for the sql plugin to populate test data. Moreover, the remote repository to deploy artifact may require authentication. For this kind of purpose, we need Maven Settings and Profiles.

For Maven newbie, the main difference between Settings and Profiles is Setting is stored in the box and Profile is part of project pom file. There are two possible places for settings.xml
  • The Maven install: $M2_HOME/conf/settings.xml
  • A user's install: ${user.home}/.m2/settings.xml
The first file contain global settings for Maven while the second file contains settings for specific user. Most of the time, we only need one.

There are reason for different mechanism of storage. Settings is considered more environment related than profile. Moreover, Settings is hidden from the projects itself.

Settings

From personal experience, I often use the Maven Settings to store information about remote repository and active profile. If you want to shorten the pom file, whatever common information about the infrastructure can be put under this file as well.

For example, only in Jenkins, you may want to generate version.txt that contain Jenkins build number and commit logs when Maven is built. Then in the pom file, declare plugin to do this task inside Jenkins profile. Then, in Jenkins settings.xml, we can activate this profile by

<settings>
  ...
  <activeProfiles>
    <activeProfile>jenkins</activeProfile>
  </activeProfiles>
  ...
</settings>

Profiles

Maven support this multiple profiles, so you can choose more than one profile to activate. There are many ways of activate a profile:

  • By jdk version
  • By OS
  • By existence of property
  • Activate by default
  • Activate by existence of file
  • Activate by specifying profile in mvn command

If a profile is not activated, all the contents inside are simply ignored by Maven.

Maven Plugins for IDE

All famous IDEs has support for Maven. However, they follow different approachs to make Maven work in IDE. With the only exceptions is Eclipse, all other IDEs let Maven run natively within the IDE. It means that any mvn command you trigger in IDE will effectively send the identical command to the console in project home. This works like you have a console open next to your IDE, just more convenient.

Eclipse Maven plugin (m2e) is much more ambitious. It tries to interpret Maven pom file to Eclipse project configuration. However, the biggest problem with this approach is the incomplete implementation. Many years after first release, the conversion of many Maven plugins to Eclipse configuration are not yet supported. It may be frustrated that Eclipse show error in pom file even the file is perfectly correct because m2e does not recognize the setting.  

Still, Eclipse approach bring some benefit, the first and foremost is workspace resolution. It helps when you build a project with multiple module. In stead of downloading dependencies from repository, m2e attempt to find any corresponding project and include this project in classpath rather than the packaged jar file. This provides immediate reflection of any API changes without going through the hassle of building dependent project.

As Eclipse settings do not fully support Maven configuration, here are some of  the common problems that you may encounter when using m2e plugin in Eclipse.

1/ Eclipse does not support dependency scope. Simply speaking, when the plugin help you to configure the classpath, it does not differentiate compile, runtime or test scope. Hence, in your IDE, no matter what scope you put, it always work but when you create deployment, only compile scope dependencies are available.

2/ Another similar issue is the lack of scope support for source folder. Most of common Maven projects includes src/main/java and src/test/java folder. However, if this project in included as dependency, Eclipse cannot differentiate the test source and main source folder. The consequence is the inclusion of test source folder into project classpath, which is not wanted.

Saturday 12 April 2014

Maven explanation - part 2


In the earlier article, we have discussed Maven build lifecycle and plugins. In this article, we will continue to discuss Maven repository and dependency management.
Maven Repository & Dependency Management

Maven repository may be the most well known feature of Maven. The benefit of having a repository is obvious. If we take a look back at the time most of Java projects were built with Ant, it is a must to include all the libraries needed in the project folder. If the application is a webapp, the wanted libraries can be stored in WEB-INF/lib folder. Otherwise, developers need to manually create the libraries folder and include this folder in the project classpath. It is also important that developers may need to split out libraries folder if they are for different usage. For example, JUnit and Mock libraries should only be used to testing, not compiling or packaging project.

Maven bring a much more convenient practice where developers only need to specify what they need and Maven helps them to download the libraries from somewhere, plus including them to project classpath. In Maven terms, this somewhere is called repository and the libraries can be specified by dependency.

There are two kind of repositories, remote and internal.


Maven is generous. It gives you a free remote repository that suppose to host all the libraries you need to use. Internal repository is basically a place in your computer where Maven stored all the libraries it has downloaded from remote repository.

Repository and Dependency Declaration

Let use the same trick of checking effective pom view again. Here is what it gave us:

<repositories>
    <repository>
      <snapshots>
        <enabled>false</enabled>
      </snapshots>
      <id>central</id>
      <name>Central Repository</name>
      <url>http://repo.maven.apache.org/maven2</url>
    </repository>
</repositories>

So we know that the website that hosting Maven central repository is http://repo.maven.apache.org/maven2
Normally, you can use your browser to hit this URL and browse it but unfortunately, this feature has been disabled recently. 

Now, let take a look at the declaration of a dependency

<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.7</version>
    <scope>test</scope>
</dependency>

This dependency will end up appear in your project classpath as junit-4.7.jar. The jar file name will always be ${artifactId}-${version}. The groupId helps Maven to further differentiate dependencies with similar name. 

Similar to Java package, the groupId define the physical folder that  the dependency is stored. Therefore, to search for the dependency above, Maven will look at

http://repo.maven.apache.org/maven2/junit/junit/4.7/junit-4.7.jar

The naming convention for searching for jar file in remote repository is

${repositoryUrl}/${groupId}/${version}/${artifactId}-${version}.jar

For internal repository, it will be stored at the folder

${USER_HOME}/.m2/repository/junit/junit/4.7

The folder ${USER_HOME}/.m2/repository is a pre-defined place to stored internal repository. If you have time to look at the folder, you will see at least 3 files: junit-4.7.jar, junit-4.7.jar.sha1, junit-4.7.pom. This tell us, dependency is effectively a Maven library. Even if the jar file is not packaged by Maven, by the time it is uploaded to Maven repository, there will be a Maven pom file for each dependency.

Sometimes, developers also upload source together with compiled package. By maven convention, the source file name will be ${artifactId}-${version}-source.jar

Dependency Download and Upload

If you take a look at the diagram above, you can see that I draw the arrow from left to right and opposite. Whenever we build a Maven project in local box with command 

mvn install

It will upload the packaged file to internal repository. If we want the packaged file to by synced back to remote repository, the command is 

mvn deploy

Deploy and install are two consecutive phases in Default lifecycle; therefore you cannot bypass internal repository while uploading. Similarly for downloading. 

It is worth to note that you can declare multiple remote repositories but only one internal repository. In this case, when searching for dependency, Maven will scan through each repository, following the order of repository declaration in your pom file.  

In the diagram above, I put the arrow from internal repository back to remote repository but actually it is not that straightforward. Because, there may be more than one remote repository, mvn deploy is a complex  command, where you may need to provide authentication to upload. Maven provide instruction for deploy command here. However, we rarely need to use that. In local environment, developers should not need to upload to remote repository. If there is a place to upload, it should be Continuous Integration server, after the passing all the tests. At least for Jenkins, there is a plugin to deploy to remote repository automatically after build success. 

Proxy

For all the corporations I know, there always be at least one own hosting remote repository. The reason is simple, you cannot upload your project to Maven central repository. Most of the time, this remote repository also serve as proxy to Maven Central or any other external repository. Internal Repository can boost up performance if you have downloaded the dependency on the same box before. Proxy help to boost up performance for all the boxes in the office. Currently, we use Nexus as remote repository in our work environment.

Snapshot

A snapshot-dependency is a non-final dependency. It means that it is possible for the remote repository to contain an identical version of the dependency with newer content. Hence, Maven supposes to do a check of time stamp to see if it need to download new content for each build. This check may be slow and Maven only do it one time a day. 

For an actively developed project, once a day is definitely not enough, and we should put parameter -u to any Maven command to force it to download snapshot. 

Snapshot is a handful feature. Most of the time, we develop project with multiple sub-modules. Then, it is crucial that we declare each sub-module as snapshot, so that Maven keep downloading latest update while building the parent project. Whenever we have a release, we can finalise the version and move to the next snapshot.

To illustrate, take a look at the example below:

Start Project: 0.1-SNAPSHOT
First Release: 0.1
Continue develop for next milestone: 0.2-SNAPSHOT
Next Release: 0.2
Continue develop for next milestone: 0.3-SNAPSHOT   
...

As you can see, Maven uses the word "SNAPSHOT" to identify if a dependency is final or snapshot.

Dependency Management

In the example above, when we include junit dependency, Maven gives us one junit jar file. This is simple and straightforward. However, Maven can offer us more than that. Let include another dependency

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>4.0.3.RELEASE</version>
</dependency>

If you capture the Maven console when it run first time, you may find it prints out

[INFO] Downloaded http://repo.maven.apache.org/maven2/org/springframework/spring-core/4.0.3.RELEASE/spring-core-4.0.3.RELEASE.pom
[INFO] Downloaded http://repo.maven.apache.org/maven2/commons-logging/commons-logging/1.1.3/commons-logging-1.1.3.pom 

It may look surprise at first if you have not used Maven before. After all, remember that all Maven dependencies must have its own pom file. Spring-core pom file specify commons-logging as its dependency.

<dependency>
     <groupId>commons-logging</groupId>
     <artifactId>commons-logging</artifactId>
     <version>1.1.3</version>
     <scope>compile</scope>
</dependency>

Maven help us to recursively build the whole dependency hierarchy before starting downloading content. The dependency automatically download by Maven is called transitive dependency. This feature explain why Maven is widely and quickly adopted over Ant. Dependency Management save us from the effort of figuring out which library use which library. Even if we managed to do it one time, when we need to update version for one major library, the pain come back again.

In the diagram above, I tried to describe this behaviour by having a dependency contains other dependencies within. When copying over to project, they all end up as jar files in classpath.

Dependency Scope

If you look carefully at some of the dependency declarations above, you may find a scope attribute that I have not mentioned. To understand Dependency Scope, we need to equip ourselves with some basic concepts first.

Maven support 4 kind of classpaths:
  • Compile classpath
  • Runtime classpath
  • Test classpath
  • Plugin classpath
Do not worry about this, I myself also do not remember clearly the definition of each classpath. We only need to know that Compile classpath is used when compiling source code, Test classpath is only available for compiling and running test. Runtime classpath is used when project is deployed and run. When Maven package a project, it will includes any dependency only from compile classpath, not test, plugin or runtime.

Dependency scope defines which classpath a dependency will appear. Any dependency appear in compile classpath will appear in test classpath as well. Maven provide 6 dependency scopes:
  • Compile: This is the default scope, used if none is specified. Compile dependencies are available in all classpaths of a project. Furthermore, those dependencies are propagated to dependent projects.
  • Provided: This is much like compile, but indicates you expect the JDK or a container to provide the dependency at runtime.
  • Runtime: This scope indicates that the dependency is not required for compilation, but is for execution. It is in the runtime and test classpaths, but not the compile classpath.
  • Test: This scope indicates that the dependency is not required for normal use of the application, and is only available for the test compilation and execution phases.
  • System: This scope is similar to Provided except that you have to provide the JAR which contains it explicitly. The artifact is always available and is not looked up in a repository.
  • Import (only available in Maven 2.0.9 or later): Too minor to mention.
We are not done yet. Let torture your mind with this matrix provided by Maven:

compileprovidedruntimetest
compilecompile(*)-runtime-
providedprovided-provided-
runtimeruntime-runtime-
testtest-test-

Look at the diagram above, the left most column define the dependency scope a dependency and the top row define the dependency scopes of its dependencies. The value in the table specify the final dependency scope of the transitive dependencies. System and Import scopes are not included, means Maven do not resolve transitive dependency for both of them.

Go back to earlier example, we do not specify scope when declaring spring-core dependency, it should have compile scope. commons-logging has compile scope inside spring-core pom file. Therefore, it should have compile as final scope. If spring-core has any dependency with test scope, it will be omitted.

This look tough, but to apply to real life, you only need to remember some guidelines:

  • For any test libraries use test scope
  • For any container libraries or environment specific libraries, use runtime
  • For any api, use provided
  • The rest use compile

Resolving dependency version conflict

Resolving version conflict is the source of confusion for dependency management. I personally feel that Maven has not done very well in this part.

To summarize, when resolving dependency if Maven found an identical dependencies with what it has found before, it will omitted the dependency but update the scope of existing dependency. At the end, there is only one dependency with unique groupId and artifactId on the dependency hierarchy.

To illustrate how Maven works, let looks at the dependency hierarchy generated by Eclipse for my project:


In this project, I include spring-core with compile scope and html-unit with test scope. html-unit has transitive dependency commons-logging version 1.0.4 while spring-core has identical dependency with version 1.1.3.

When Maven resolving dependency, it note that there is already commons-logging in the resolved dependencies and choose to omit version 1.1.3 even if it is the later version. Still, it update the commons-logging dependency of html-unit to compile scope because this is the widest scope of the dependency.

End up, I package an older version of commons-logging dependency. If I swap the order of declaration between spring-core and html-unit, I have



So this time, Maven give me commons-logging version 1.1.3 rather than 1.0.4.

If you do not know how Maven resolve dependencies, this will be an endless source of confusion. If you know it well, it is kind of easy. Please remember to use a tool to generate dependency hierarchy and keep track of it.

To avoid this problem, please clearly specify the version of dependency in pom file. In this case Maven will give higher priority for dependency over transitive dependency.


In this last example, I manually declare commons-logging with version 1.0.2 and it override the versions for all 3 occurrences of transitive dependencies.

Wednesday 2 April 2014

Servlet API - Part 2

In the earlier part, we have discussed Servlet API 2.5. There are 3 more versions of Servlet API before 2.5 but the differences is too minor to mentioned. If you want to know in details, please search for the change log of Servlet API.

The next version after 2.5 is Servlet API 3.0 (the latest at the time of writing is 3.1). There is a reason for the big jump in version number. Servlet API 3.0 is a total revamp, which totally change the way the web application was developed. This article will discuss the changes of Servlet API 3.x versus earlier versions.

Background

Deployment Descriptor

When J2EE was first developed, it combined of Java components plus deployment descriptor. The goal is to make application configurable by changing deployment descriptor file. In deed, it offers flexibility for developers to modify application behaviour without code change in production environment. However, this feature was overrated. Let take a look at web.xml file. How often do we need to change servlet mapping, adding filter or even changing security constraint?

Moreover, the deployment descriptor file is long and tedious to write. It was proposed to have dedicated person to maintain the file but this wish never came true. Most of the time, developers end up creating deployment descriptor file and even creating deployment script.

This time also observed the trend of single application being developed by multiple teams. Sharing a single deployment descriptor is harder to developer feature in parallel.

The rise of RoR and Annotation

As we already know, good developers are innovative and lazy. They always manage to find way to make development easier, faster and better. There are major events happened in this time that marked the changing of mindset in industry.

At first, it witnessed the success of Ruby on Rail framework. This framework was built with Convention over Configuration principle. It help to greatly reduce the effort of configuring webapp.

Secondly, Java 5 introduction brought developers the choice to use annotation. At first, it was not so cleared how annotation suppose to change the way application was developed but very soon, the community find a great use of it. Annotation can be included in the runtime (RetentionPolicy.RUNTIME) and it can be used together with Java reflection to define how the component suppose to be used.

Alternative ways to build web application

To avoid complexity of Servlet API, developers often make use of MVC and IOC framework to build web application. For example, if a developer build a J2EE web application, he/she should adopt an architect diagram similar to this:


Rather, most of them choose an alternative solution


Comparing these two designs. the below is much  simpler to create, provide that the Service can be injected automatically and the annotation or convention can be used to identify mapping between HTTP request and the service method serving it.

Slowly, J2EE lost its popularity and Sun knew that they need to change.

Servlet API 3.0 & 3.1

Servlet API 3.x adopts proven ideas of the Java community and pull out a very sophisticated solution. If you look from the bird eye view, it look pretty much similar to existing frameworks in the market. It actually built on the same idea.

Our team has been one of the early adopters when Java EE 6 arrived but later switched back to Spring framework due to memory leak issue with Glassfish. One of our senior developer took only 3 days to completed the switch for a application built by 6 developers in 1 year. It should not be that fast if the two technologies stacks are not similar.

Servlet API is not supposed to be used alone, it always comes with Context and Dependency Injection.

Let take a look in details of the changes

Annotation

Servlet API 3.x do not abort any concept from Servlet 2.x, Rather, it allows the entity to be declared by annotation in stead of using deployment descriptor. For example, here it how we declare a Servlet by annotation:

@WebServlet(name="Servlet", urlPatterns={"/anyUrl"})
public class AnnotatedServlet extends HttpServlet {  
}

It is definitely simpler than writing XML in deployment descriptor. When Servlet API 3.0 was introduced, Servlet can be POJO, but later, it was switched back to inheriting HttpServlet. It gave a clear signal that Java is and will still be conservative. It will not move that far like Rails by totally removing URL mapping.

Even after using annotation, Servlet API still has a major difference with Spring MVC. The annotation @RequestMapping in Spring MVC can be placed on both Controllers and methods. It is convenient when you want to your Controller to serve multiple URLs (think of it when you want to implement Rest API). 

In contrast, Servlet only support HTTP methods like doGet, doPost,... It means that you still need to add a bit more effort to implements Restful API or stick with Java Server Face. As JSF page is rendered in server, it knows how to access Servlet methods without going through URL Mapping

Context and Dependency Injection

CDI has never been part of Servlet API but I cannot resist the temptation to talk about it. Servlet API will not be that helpful if you cannot use Dependency Injection to inject the beans to Servlet. If you use Spring MVC, the Bean is pretty much stateless and singleton. It does not relate directly with User Session. If you need to access user session, you need to add session support from a security framework or simply pass the values in as method parameters. 

In contrast, EJB has the stateful bean. Container supposes to serve the same stateful bean for all requests from same user, which make it effectively user session. EJB also support stateless bean but they are not singleton. Normally, container serve stateless bean from stateless pool. 

This difference will affect how you want to design your application. As the world is moving toward stateless session for scalability, I would prefer to use cookie-based session rather than server side session. In this case, stateful bean provide little value. Fortunately, we still have Request scope bean for this purpose. 

The word 'Context' from CDI come from the fact that you can define the scope for beans when injecting it. There are fours scopes: @RequestScoped, @SessionScoped, @ApplicationScoped, and @ConversationScoped. As we can see, most of the scopes are only meaningful in Web context.

Asynchronous Support

Servlet API 3.x did a good job by introducing Asynchronous support. As I have discussed in one of my earlier articles, asynchronous in Servlet API 3.x has nothing to do with Asynchronous HTTP request. It aims to solve different problems. 

Container comes with HTTP thread pool. Each thread serve one request at one time. However, as explained here, sometimes the server cannot serve the request quickly enough due to some inputs waiting. In this scenario, we want to free HTTP thread so that it can serve other requests. 

The new model is to let the current HTTP thread exit, waiting for result to be available and use another HTTP thread to render response. As the information necessary to render response are passed to Servlet methods as parameters (which means data are stored in stack memory), it cannot be shared among 2 HTTP threads. This was overcome by storing shared data in AsyncContext.

Deployment Descriptor

Introducing annotation does not block you from using deployment descriptor. However, deployment descriptor in Servlet API 3.x is very flexible. It even can be split to modules. Each jar files can have their own modular deployment descriptor file store inside META-INF folder. The modular deployment descriptor will only be detected at runtime. This make the modules pluggable.

Both annotation scanning and web fragment scanning can be turned on or off in web.xml.

Programmatic Addition   

Servlet API 3.x allows you to add Servlet, Filter and Listener programmatic. This features came as a surprise because no open-source framework in the market offer any equivalent. Unfortunately, I cannot comment much on this feature as I have not gone through any use case for it.

Conclusions

Servlet API 3.x also provide some other minor improvements that I will not dig further in the scope of this articles. However, with what have been covered, I hope readers can have a basic understanding about Servlet API 3.x