Modern Web Development in Java - The (Never) Complete Guide
Coming to Java from nodejs, web development seemed extremely and unnecessarily complicated to me. After navigating the standards and specs and implementations and javadocs (and countless stackoverflow posts) here is what I know.
Points to remember:
- Java is used for web development mostly by large teams (like Amazon)
- Java has been around for decades and there is a lot of history
- Java is object oriented and statically typed
What does a web server do?
It listens on a TCP port for connections. When someone from somewhere on the internet makes a connection to that port, it happily recieves the “request”, processes it, and sends a “response”.
What is “modern” web development?
Modern web development refers to the prevalent web development practices right now. I’m writing this in 2020, and right now web development revolves around the concepts of “frontend” and “backend”. Let’s rewind a bit.
Long ago, web development was about creating pages in HTML which gets transmitted over the wire to browser which then displays it. People could click and navigate to other pages. But that was mostly it. Everything was static and everyone would see the same things.
Then “dynamic” web came into existence. Here, “web servers” would create HTML pages on the fly depending on the user who is requesting it. “Users” could also add/remove content from these servers. There was a lot of “data” moving around and naturally databases became prominent. Also, languages like PHP which allowed dynamically creating HTML became popular.
Then came the smartphones. With smartphones came “native apps” and also smaller screens. This was a double blow to web development. One, websites now have to be visually attractive on large screens and small screens (responsive design). Two, websites would have to compete with the interactivity offered by native applications. Although responsive design has largely been a successful change in web development, competing with native applications is something web applications are still struggling in. This is partly because native code on Android and iOS simply have more capabilities on the platform. Thus the term “native”.
That is what drives the differentiation between frontend and backend in modern web development. Large organizations have native applications for each platform and web version of their applications. This means that the “backend” server can be the lowest common factor - which is “REST APIs”. And the UI on different platforms are their own codebases. For the web, the UI is called frontend. Large organizations also drive innovation and technology. And therefore most of the popular web technologies we have now is devoted to this frontend-backend divide.
On the frontend javascript is the default choice and that is why popular frontend frameworks like React and Vue are all also javascript frameworks.
But backend can be anything. Javascript itself, python, java, rust, kotlin, go, ruby, php, or even just JSON files. The only requirement for a backend technology, as can be seen from that last example, is that it is able to return strings over a web connection. Most applications would also have to interact with databases, other internet services (APIs), and the filesystem, and also encapsulate “business logic”. And that is where things become interesting.
Further reading: WTF is JAMstack?
What is Java’s role in modern web development?
No matter how much stuff happens on the frontend, centralized services will have a backend with components that I described previously. Even if things are dockerized and deployed on the cloud thus giving rise to multiple “backends”, there is something at the “back”.
Java, over the last few years has been evolving rapidly (compared to its own past) to accomodate the needs of modern web development. Because the JVM is battle tested and because the development paradigms that Java enforces are also useful for large teams to work together, Java is a powerful player in the backend space of modern web development.
What should I know about Java to understand modern web development in Java?
Specifications vs Implementations
The biggest insight I can share is that in Java world everything has specifications and implementations. “Specification”/standards are API interfaces (usually), configuration formats, or expected behaviour that are meant to be interoperable. (This plays well with the concept of Interface
in Java’s object-oriented paradigm)
What this means is that you could write software that targets a specification and then it could work with any of the implementations of that specification. This is a concept very much alien to javascript developers.
For a concrete example, let us take the case of REST API development. If you are building a framework that simplifies development of REST APIs in javascript, you can build it however the way you want. Then you write documentation on how the user of your framework should write their functions such that it can be hooked into your framework. For example, express developer would want you to write a function like this:
app.get('/', (req, h) => h.send('Hello World!'))
But, hapi will ask you to write the same like this:
app.route({
method: 'GET',
path: '/',
handler: (req, h) => {
return 'Hello World!';
}
});
So, as a user, you get locked in to the framework early on.
But in Java, this is not necessarily the case. There is “JAX-RS” [pdf link] specification which talks about how the user will write their classes and methods, how these will be annotated, how the configuration will be specified, and so on. The specification also tells you how your framework should behave when interacting with such user code as well.
So, no matter whether you’re using Jersey, RESTEasy, or any other JAX-RS compliant framework, your method will be the same:
@Path("/")
public class HelloWorld {
@GET
public String helloWorld() {
return "Hello World!";
}
}
You can write this and deploy it with Jersey and it will work. You can then remove Jersey and put something else that implements JAX-RS, it will work. That is what a standard does.
Now, this does not mean that Java is a boring place where everyone writes programs the same way. There are other REST frameworks that do not conform to the JAX-RS specification like Spring MVC and they can have their own ways of how to write web resource classes.
Did I mention specifications?
Java has to be understood in its historical context. Java was under Oracle for a long long time. And Oracle used to provide various cloud services that uses Java based technologies. And Oracle used to maintain the specifications in and around Java. And Oracle used to provide proprietary technologies on top of these specifications.
The proliferation of such specifications (which no other programming language seems to have) is good and bad. Bad because it makes a programming language an economic product rather than a language of creative expression. Good because you can move your enterprise from one provider to another, provided they both support the same specifications equally. Enterprises love this promise of not being vendor locked in. It is just an added bonus to them that they can swap developers around constantly because everyone is practically writing the same thing.
So, the story of specifications doesn’t end with specifications. Java also has collections of specifications. There is something called “Standard Edition” (SE), something called “Enterprise Edition” (EE). SE “contains” the basic things that makes it a programming language (like data types, IO, etc.) whereas EE “contains” further things like the JAX-RS specification we mentioned above. Meaning, if you have a cloud provider who implements the Java EE platform, then there are a lot of specifications that they are complying with (almost all the technologies that an enterprise needs).
On the other hand there are also projects like Eclipse MicroProfile which form useful alternatives to things like Java EE. There are also frameworks like Spring, Micronaut, and Quarkus which are comprehensive solutions of their own (often with interchangeable parts).
That ends part 1 of this post. Have a coffee now.
Tell me about web development in Java
I’m sorry it has to be this way. Much of Java is stuck in the past. And without the historical context, you will have great trouble in navigating the Java ecosystem. I’ve spent countless nights doing this. Just show some patience.
History of Web Development in Java
Java has seen all of it. So we have to start from the very beginning.
Servlets
A servlet is very similar to the hello world function in express that we saw earlier.
public class ServletHelloWorld extends HttpServlet {
@Override
public void init(final ServletConfig config) throws ServletException {
super.init(config);
}
@Override
protected void service(final HttpServletRequest request, final HttpServletResponse response) throws ServletException, IOException {
response.getWriter().write("Hello World!");
}
@Override
public void destroy() {}
}
That is a hello world servlet. The request object would have information about path and other info required to form a response.
Java Servlet Specification is the specification that defines how servlets should be.
Servlet Containers
These are frameworks which, based on the servlet specification, are designed to run servlets. An example is “Catalina” which is a part of Tomcat.
Web servers
Tomcat is a group of technologies which could be considered a complete “web server”. Tomcat includes components like “Coyote” which listens to a port for HTTP connections, and Catalina mentioned above which takes that request and forwards it to a servlet and later takes the response from the servlet and passes it back.
JavaServer Pages, Java Expression Language, JavaServer Faces, …
These are all technologies that enables dynamic generation of HTML pages.
Are any of these relevant in modern web development?
Mostly, no.
Modern web development, like we saw in the beginning, is mostly about REST APIs. So most specifications related to dynamic page generation are useless now. Also, servlet specification is pretty dated and superceded by JAX-RS. So, most of the stuff mentioned above are irrelevant. Nevertheless, they will keep popping up. So it is good to know.
Is there any specification that is actually useful in modern web development?
Yes! A lot of them. MicroProfile lists them - CDI, JAX-RS, JSON-P, JWT Authentication, OpenAPI, and so on.
Before I go into them, let me talk about one popular specification which I don’t think makes the cut and also about the frameworks that this post won’t talk about.
Why JPA doesn’t make the cut
Java Persistence API is a way to annotate Plain Old Java Objects (POJOs) (simple classes) such that they can be persisted to a database (mostly a relational database).
The biggest issue with JPA is that it gets too complicated too quickly. And all of the issues with JPA has been summarized in this talk “Gordon Ramsay doesn’t use cake mixes” by Christin Gorman.
Also, a lot of modern web services do require non-relational databases.
Why I won’t talk about Spring, Micronaut, etc
During the years that Java EE was languishing, Spring became the de facto standard of web development in Java. But with the entry of JAX-RS, there is now the possibility that one can live without entering the Spring world. Also, I could not find a lot of beginner friendly guides on getting started with JAX-RS, CDI, etc. And therefore, this post is going to focus on these “standard” standards.
JAX-RS
We already looked at a simple JAX-RS method earlier. JAX-RS considers Java classes as resources. And methods of this class become handlers to HTTP method calls/requests. The return value becomes the response to the request. It is pretty simple that way. It also allows organization of code in a very neat way.
JAX-RS specification also includes details on how to configure the deployment of such resources. This is through an Application
sub-class. This can also be used to deploy this as a servlet in a servlet container. More info on JSR 339.
Jersey
Jersey is the reference implementation of JAX-RS. Jersey 1 is outdated now. Jersey 3 is in alpha stage. Jersey 2 is the correct version to use now. But there was a time in Jersey’s development right at the beginning of Jersey 2 that people hated Jersey because it broke a lot of things that were working in Jersey 1. I think Jersey even lost a lot of users in this version upgrade.
But if you want to use Jersey, it is absolutely a must that you use the latest version of Jersey. If you go through the release notes, you can see a lot of breaking changes and also very big bugs being fixed. All this is because Jersey keeps changing with the specification.
Also, jersey was transferred from Oracle to Eclipse foundation somewhere in between. So, there are a lot of broken links, a lot of outdated links, and such confusions.
Can you tell me what Jersey does in simple words?
Imagine you have a class like this.
@Path("myresource")
public class MyResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
public String getIt() {
return "Got it!";
}
}
Every time a GET request is made to /myresource, Jersey will do something like this:
MyResource myResource = new MyResource();
String response = myResource.getIt();
This is not code from the original source code, but this is essentially what Jersey (JAX-RS) does. It initiates the class (resource) that matches the path, runs the method with the annotations that match the request, and returns the response as result.
But, what if I return some other type?
You could return something that is not string, right?
public class User {
private String name;
public User(String name) {
this.name = name;
}
//getter and setter omitted
}
@Path("myfancyresource")
public class MyFancyResource {
@GET
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public User getUser(String name) {
return new User(name);
}
}
MyFancyResource has a getUser method that consumes JSON and produces JSON. But the parameter is String and the return value is User. How does this get converted to JSON?
That is where Entity Providers come into play. An entity provider knows how to convert a message body (HTTP request body) to the input parameter type, or vice versa - from the return value to the media type of the response. The specification lists down certain basic entity providers that are supposed to ship with the implementation. But for extra entity providers, we can add extra libraries, like Jackson is an excellent provider which converts almost everything to JSON and vice-versa.
Dependency Injection
Explaining dependency injection is out of the scope of this article. Martin Fowler has a nice article.
But I can give an example here and it becomes pretty straightforward.
public class Counter {
private Integer count = 0;
public Integer getCount() {
count += 1;
return count;
}
}
@Path("counter")
public class CounterResource {
private final Counter counter;
@GET
@Produces(MediaType.TEXT_PLAIN)
public Integer getCount() {
return counter.getCount();
}
}
The counter is a simple class that keeps a count of how many times it has been called and returns the number each time. A GET to /counter is supposed to give us the count in the counter.
There is one major bug, though. Can you figure it out?
The counter in CounterResource is never initialized! But how can we? The CounterResource class is instantiated by Jersey. How do we tell Jersey to set the counter to the right value? Besides, when jersey creates a new instance of CounterResource to respond to the next request, how do we make Jersey pass in the old counter instance?
That’s where injection comes in.
@Path("counter")
public class CounterResource {
private final Counter counter;
@Inject
public CounterResource(Counter counter) {
this.counter = counter;
}
@GET
@Produces(MediaType.TEXT_PLAIN)
public Integer getCount() {
return counter.getCount();
}
}
Here we are telling Jersey to inject the parameters when creating CounterResource. When dependency injection is configured correctly, Jersey will pass an instance of the Counter class to the constructor.
But we still have to tell Jersey to pass the same instance every time. That’s done through scope.
@ApplicationScoped
public class Counter {
private Integer count = 0;
public Integer count() {
count += 1;
return count;
}
}
The @ApplicationScoped
annotation tells Jersey that an instance created for this class is valid for the entire application’s lifetime (till it is shutdown) as opposed to @RequestScoped
which is valid only for one particular request.
This is how dependency injection is supposed to work.
But again, there are specifications. JSR 330 is what deals with Dependency Injection. Implementations of these include hk2, guice, etc. But there is also JSR 299 which is about Contexts and Dependency Injection (CDI). Weld is the reference implementation for CDI. CDI goes one step ahead of DI
HK2
HK2 is the default dependency injection framework used within Jersey and with Jersey.
To inject our own dependencies through HK2, either we have to bind it in ResourceConfig or use automatic service population.
Weld
Weld is a CDI framework and can substitute HK2 for all purposes. Weld requires a bootstrapping but this is simplified through jersey’s own library.
Do we have to do all this!? Isn’t there a simple way to start off?
If you use JavaEE platform like GlassFish (the reference implementation) or WildFly most of this work is done for you already and you can focus on writing your code.
What about deployment?
If you have stuck with me till this point, great job. There are a wide variety of options when it comes to deployment of java web projects these days.
You can have your own server in the traditional way. You can use cloud providers like AWS (Elastic Bean Stalk, Lambda), Google Cloud Platform. Even Oracle.
Uber-JARs
You might come across Uber JAR/fat JAR/shaded JAR. This is essentially one JAR which is just a sum of all jars that make up your application and its dependencies. There is a maven-shade-plugin which does this shady job. Although resource transformers are supposed to prevent things like Manifest Resources (that help in autodiscovery of features) from getting nuked, I find that the trouble in making sure these work is not worth it.
Which Java version should I use?
The current Long Term Release version is a safe bet. I use the latest version for local development, but the LTS for deployment.
A note on java versions, though. Java 8 was a great release which introduced awesome things like lambdas. Many libraries might assume that there were no versions released after that. Some libraries won’t be tested in newer versions and you might be the first to detect certain bugs.
What IDE should I use?
Please use IntelliJ IDEA Community Edition. You can thank me later.
I hate Java
I used to hate Java too. Then I started listening to Venkat Subramaniam’s talks.
What did I miss?
If you think I have not included something I should have, let me know