A decade later
Every now and then I have to pull out all the stops and migrate legacy systems to the latest-and-greatest, leaping over several intervening versions of programming languages and platforms. Recently I have been migrating systems that have been stable for a decade or more but need to be upgraded in order to avoid their underlying systems going completely out of support. There are several technologies involved, but three of them are “old friends” and while I am looking at the migration process I am also astounded by the many positive changes that have occurred in the past decade. While most of the changes don’t have much effect on process, some of them are quite significant. Some changes are minor, but can make you wonder why it wasn’t that way from the start. (Hint: hindsight is 20-20…)
My three stalwart technologies that I want to highlight are Java, Javascript and Tomcat. I have always found these pleasant to develop with, and generally reliable to use in Production. In the past I was a fan of several other technologies: various assembly languages, C/C++, for a while Fortran, even variants of Pascal, and for quite a while I was a big fan of C#, but the three aforementioned have been with me as solution-level technologies for many years, and probably will be for several more years. (My other favourite technology is Perl, but for me that exists on another plane altogether, something I keep in my back pocket as a kind of secret super-power.)
Java
This programming language has been one of my favourites since before 2000, when it was Version 1. Prior to that my favourites included C# and C++, so the taste of Java was to my liking when it first appeared. Java version 7 came out a decade ago though it was its successor, Java 8, that appeared just over two years later that really made a mark. Version 7 expired last year. Version 8, which is in Long Term Support still has a couple of years to go. Interestingly, Java 11 is also in LTS but it will expire about two years before Java 8! That tells you how important Java 8 is to the tech community.
Most of the Java systems that I have to migrate over the next few months are using Java 8. Since the next LTS (v11) expires sooner, the migration target is now the most recent LTS, Java 17. It expires another decade from now, by which time I don’t expect to be involved. Probably…
What is it like to migrate from Java Version 8 to Java Version 17?
Fortunately, most of the features of v8 will still work in v17. There are a few gotchas to be careful of, including the following:
Almost all of the internal com.sun.*
and sun.*
class packages are now inaccessible. There is a good reason for this. Using reflection you could access these internal classes, which are responsible for many things such as passing cryptographic keys around, controlling the Time Zone, XML processing, dynamic class definition and more. This can be risky in many ways. Not only can you no longer access these from within your code, you can no longer use libraries that access them. The command-line feature (--illegal-access=permit
) that was available in v16 has been removed in v17. There are newer packages available to provide this functionality to custom code, but they are not drop-in so some code rewriting is inevitable.
The jdeps tool is very useful to check for dangerous dependencies, but be advised that on large complex projects this can become one rather deep rabbit hole. For every dependency that is no longer accessible you’ll have to either find a way to re-introduce the ageing assets, or preferably find an alternative approach using more recent assets. Fortunately the opportunities for alternatives have grown in the years since v8 (released in 2014), and you’ll find an abundance of open source and commercial packages to choose from. Check out the Apache Commons and Jakarta EE projects for good examples. In many cases, migrating to Jakarta just involves package renaming and a few minor adjustments, thanks to a lot of retention of existing structures. Just watch out for a lot of deprecated things appearing once the migration is done, as these will need to be updated (eventually).
Once you’ve moved from v8 to v17 you get a nice collection of new language toys with which to play. Among my favourites are:
Convenient factory methods
Constructors like Set.of("x","y","z")
and List.of(1,2,3)
thanks to JEP 269, simplifying static object instantiation. In particular, I like the ease with which a Map can be instantiated: Map<Integer,String>demo=Map.of(1,"first",2,"second");
Text blocks
Using the triple-character """
as delimiters, a String constant can span multiple lines. No more use of "..."+"..."+
!
Casting declarations via instanceof
Instead of if(x instanceof T){T y = (T)x; y.f();}
you can now do this: if(x instanceof T y){y.f();}
You can also do something similar with the switch
statement/expression.
Text formatter
You can now do "some pattern %s".formatted(Object value)
to use a string as a text formatter.
Case lists in Switch
Instead of case x: case y: case z: return a;
you can now do case x,y,z -> a;
Records
Java now has a class definition approach solely to deal with compound data types, with automatic get/set/equals/hash methods. Something like: public record Widget(String name, float weight){}
That’s it. You automatically obtain .getName()
etc. Nice!
Implied typing for local variables
Instead of having to know, and write out in detail, the data type of a local variable while declaring and initialising it, Java can now infer the data type from the type of the data with which you are initialising. So, instead of { ArrayList<Integer> temp = people.ages();Â /*...*/ }
you can simply write { var temp = people.ages(); /*...*/ }
and the type of temp will be figured out by the compiler. The use of var
also extends to the initialisers in for
loops. Interestingly, the enhancement treats “var” as a reserved type name instead of a reserved word, which allows legacy code with variables named “var” to continue to work as before. Also, for var
to work, the declaration has to be part of an initialisation that involves a typed value, so initialising to null
won’t do. (Some of the background information on this language enhancement makes for interesting reading, and includes the only time I’ve come across the word reequilibrate!)
Finally, the list above is far from exhaustive. These are just some of the things in the evolution of the Java language that have a practical impact on the day-to-day construction of source code. I could dedicate an entire blog post to Java’s module system (JEP 261), for example, or the support for asynchronous streams (React fans rejoice) but that’s deep and perhaps for another day.
Javascript (ES)
My next stalwart is Javascript, or ECMAscript (ECMA-262) as we should probably call it these days, or just ES if we’re feeling lazy. At the time of writing, ES5.1 is a little over a decade old, so I’ll concentrate on what I’ve noticed from ES5.1 up to ES12, though I must point out that while the specifications have evolved, support in the implementations always lags behind, and for that reason I always refer to Can I Use before diving into something new.
The problem with client-side ES is that you are relying on your users to have a Web client (browser) that supports the required specification. It’s a very hit-and-miss affair, and all it takes is one miss for all your hard work to become useless. To complicate matters, browsers do not generally support a particular edition of ES, support is more granular and you will find that browsers support almost all of the features of earlier editions, most of the features of the current edition, and a few of the features planned for future editions. This generally means that you avoid the latest features if you have no control over your clients. Things did get a lot easier once Internet Explorer was properly retired. It is getting easier to install new browsers, and easier to convince people to install them as replacements for the insecure legacy they have currently. Additionally, new browsers tend to upgrade themselves, which obviously helps with feature compatibility amongst your user base.
If all else fails, polyfills are an option. These scripts fill in the gaps between what your browser actually supports, and what it should support.
ES6 (AKA ES2015) appeared in 2015 and it is fair to say that all current major browsers support practically every feature in the specification. If your code sticks to ES6, you almost certainly don’t need polyfills. I hesitate to say the entire spec is supported, or that you definitely won’t need polyfills because there are always edge cases. (The most obvious unsupported feature across the board is recursion optimisation by avoiding the use of the call stack when the recursive call is located at the tail of the function, but unless you are doing deep recursion this won’t be an issue.)
ES6 brought a lot of goodies. Among my favourites are:
- Promises for asynchronous operations.
- Arrow expressions, enabling you to replace
call(function f(x){...});
withcall((x)=>{...});
- String interpolation, for shell/Perl fans, so you can do this:
console.info(`Value of x is ${x}`);
- Classes to facilitate defining a new object’s prototype functions and initializers, and inheritance.
- Modules to facilitate decomposition of complex code into building blocks.
- Default parameter values to avoid the annoying tests for undefined parameters in functions.
- Block scoped (non-hoisting) variables, so that using “let” instead of “var” confines the variable to its {block}.
- Block scoped functions, so that function names can be reused in separate or nested blocks.
- i18n (internationalization) classes, especially Date/Time, for content localisation.
- Immutable assignments via the
const
keyword, to help with code correctness. - String functions
startsWith
,endsWith
andincludes
, which should have been there since the start!
ES7 (ES2016) brought us the await
keyword, which made it so much easier (at a syntax level) to use Promises. It allows you to do something like result = await promiseToGetTheAnswer();
and the Promise is automatically (eventually) fulfilled to get the result. It throws an exception if the Promise is rejected, which makes sense if rejection isn’t normal.
ES8 (ES2017) offered Object.values(obj)
and Object.entries(obj)
to extract arrays from objects (associative maps). Note that Object.fromEntries()
to do the opposite would not appear until ES10.
ES9 (ES2018) gave us the spread operator so we can replace Object.assign({},obj)
with just {...obj}
. Neat! Also, the Promise got a finally
function, which definitely helps keep things tidy.
ES10 (ES2019) added a whole load of Array functions, including improved sort
that preserved input orders when their keys had the same precedence (hooray!).
ES11 (ES2020) heralded the delicious ??
operator. This does away with the use of ||
as a means of specifying a default value, which is not appropriate if a value such as 0 or the empty string “” is a permitted value, because these are not truthy and therefore treated as false. Previously x=0||123;
would assign 123 to x, but now x=0??123;
will assign zero to x. The ??
operator only uses the default value to the right if the left-hand value is null or undefined. (The Perl equivalent is the //
operator, and many shells use ${x:-y}
)
ES12 (ES2021) has a variation of ??
such that x??=y;
assigns the value y to x if x is currently null or undefined.
All of the above language features are supported in current browsers. Some of the proposed features for future specifications are also available, though not (yet) mentioned on CanIUse, such as the at()
array accessor function that gets an indexed item from an array, working from the end if the index is negative. That always made sense to me, ever since I encountered it in Perl, but in Javascript something like [-1] already had a meaning (the value whose key is -1) rather than assuming the object is an array and using -1 as a reverse index.
For maximum compatibility with the range of browsers that I generally have to support, I’ve generally kept my Javascript/ECMAScript to ES6, though the temptation to use ??
is quite extreme! At this stage I am re-evaluating that position, and I may bump my assumption higher than ES6. Perhaps ES10 (though that still wouldn’t allow me to use ??
!).
Tomcat
The third of my stalwart technologies is Apache Tomcat. It started out in 1998 as the reference implementation by Sun Microsystems of servlets, now known as Jakarta Servlets. Tomcat comes with Catalina (the servlet container), Coyote (the HTTP connector) and Jasper (the JSP engine). As a compact platform to deliver Java-generated Web content/services, it is very impressive. A seldom mentioned fact is that the Tomcat open source project is what also gave us the ANT build tool, something else I use regularly. There are many other Web platforms that are far more capable than Tomcat (such as JBoss, WebLogic, WebSphere etc.) but most of what I do starts simple and I like to keep it that way, so Tomcat plays a big role in several projects. Currently, Tomcat 8.5 is the most common version, but with the push to move to the latest Java and migrate to Jakarta EE, it’s time to upgrade from Tomcat 8.5 to Tomcat 10.1.
Currently, Tomcat 8.5 gives me Servlet 3.1, JSP 2.3, EL 3.0 and WebSocket 1.1. It also gives me HTTP/2. By moving to Tomcat 10.1 I will get Serlvet 6.0 (pre-release), JSP 3.1, EL 5.0 and WebSocket 2.1. Each of the three steps from 8.5 to 10.1 has implications, the details of which can be found on Apache’s migration guide. In practice, I found these to be the most visible (though reference to the official guides is still advised):
8.5 to 9.0
You can’t use wildcards in JSP imports, so re-write things like <%@page import="...*" ...%>
9.0 to 10.0
Migrate to Jakarta-compatible packages, beyond the ones you’ve already migrated as part of the upgrade to Java 17. These are the most common ones I have found related to Tomcat/Servlet implementations:
javax.servlet
becomesjakarta.servlet
javax.servlet.jsp
becomesjakarta.servlet.jsp
javax.websocket
becomesjakarta.websocket
javax.el
becomesjakarta.el
javax.security.auth.message
becomesjakarta.security.auth.message
Many javax.*
packages remain untouched, so you have to be selective about the refactoring.
10.0 to 10.1
In practice there’s not much to do, if anything.
What next?
Once the migration to 10.1 is complete, all of the new features of Serlvet 6.0, JSP 3.1, EL 5.0 and WebSocket 2.1 become available. However, I have increasingly noticed that I’m more likely to use JAX-RS instead of servlets. It’s just easier. I also have a legacy of existing servlet-based solutions that are working fine so heeding the advice not to fix things that aren’t broken, that legacy remains. The real reason I want to use 10.1 is to harmonise with the version of Java that I’m going to be using more frequently. Also, Tomcat 8.0 reached EOL back in 2018, so 8.5 will be next on the chopping block. Exactly when this will happen is hard to know, though 8.5 EOL is supposed to be around the release of Jakarta EE 12, if you believe the (draft) numbering plan. At least the team promises to give a year’s notice! My guess is that 8.5 EOL will be announced by 2024.
Honourable mentions
While Java, Javascript and Tomcat are top of my list of trusted decade(s)-old technologies, there are some others I just can’t avoid. Two standouts are:
HTML
Yes, the Web’s primary markup language has been with us for over two decades. After several years in the purist lands of XHTML it has finally arrived at HTML5, a “living” specification that can be thought of as a “current preferred state of the art”. For many years, I was one of the people within W3C advocating for the benefits of XHTML as a predictable authoring solution that was amenable to programmatic adaptation to varying delivery contexts. At the time, this was considered a preferred and viable approach to deliver content suitable for phones, tablets, TV and other media. But a number of things happened in real life to completely change that perspective, notably:
- Devices became powerful enough to do their own adaptation, if needed.
- Problems such as bandwidth, battery capacity, processing ability, pixel resolution and more stopped being problems.
- The chaos of the early HTML implementation variations was tamed in the HTML5 specification.
- CSS and Javascript was enhanced to support client-side adaptation.
- The verbosity, scope and precision of XML gave way to the simple informality of JSON, and eventually anything associated XML (such as XHTML) was seen as unnecessarily complicated.
These days there are so many ways to make Web content that works well in a wide variety of contexts, and a lot of it is already built-in or capable of being done within the browser using a variety of (free) components.
The only sad thing I’ve noticed in recent years on the Web is how browsers are starting to struggle with “ancient” content. One of the hopes of the Web was that content would always be renderable, but we seem to no longer care about old content. Maybe, while there are still browsers that can render the old content, it should be rendered to a more stable format for archival purposes. Paper perhaps?
Perl
My favourite scripting language also reached version 5 in the mid-90s and seemed to just stay there. Perl 6 (“Raku”) is a Perl-like deviation and has been simmering for twenty years but although it introduced some nice things like the optional data type system and expressive parameter passing syntax, I don’t know anyone who uses it. Meanwhile Perl 5 (now at version 5.34!) continues to be widely supported and is in constant use, and it’s proper successor (Perl 7) is probably only two years away. Perl might only have 1% on the popularity index, but as a programmer’s Swiss Army Knife it’s hard to beat.
And of course there are more things I could mention, but this was never meant to be exhaustive. For now, I am concentrating on an upgrade of some important projects, the three stalwart technologies, Java, Javascript/ES and Tomcat are present in all of them and I’m enjoying the process (so far).
Categorised as: Coding, Protocols & Specs, Technology, Web