BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage Articles A First Look at Java Inline Classes

A First Look at Java Inline Classes

Bookmarks

Key Takeaways

  • Project Valhalla is developing inline classes to improve affinity of Java programs to modern hardware
  • Inline classes enable developers to write types that behave more like Java's inbuilt primitive types
  • Instances of inline classes do not have object identity, which opens up a number of optimization opportunities
  • The arrival of inline classes reopens the debate around Java's generics and type erasure
  • Although promising, this is still a work in progress and not production ready yet

In this article, I'll introduce inline classes. This feature is the evolution of what were previously referred to as "value types." The exploration and research of this feature is still ongoing and is a major work stream within Project Valhalla, which has already been covered by InfoQ and in Oracle's Java magazine.

Why Inline Classes?

The goal of inline classes is to improve the affinity of Java programs to modern hardware. This is to be achieved by revisiting a very fundamental part of the Java platform — the model of Java's data values.

From the very first versions of Java until the present day, Java has had only two types of values: primitive types and object references. This model is extremely simple and easy for developers to understand, but can have performance trade-offs. For example, dealing with arrays of objects involves unavoidable indirections and this can result in processor cache misses.

Many programmers who care about performance would like the ability to work with data that utilizes memory more efficiently. Better layout means fewer indirections, which means fewer cache misses and higher performance.

Another major area of interest is the idea of removing the overhead of needing a full object header for each data composite — flattening the data.

As it stands, each object in Java's heap has a metadata header as well as the actual field content. In Hotspot, this header is essentially two machine words — mark and klass. First the mark word, which contains metadata that is specific to this specific object instance.

The second word of metadata is known as the klass word, which is a pointer to metadata (stored in the Metaspace area of memory) that is shared with all other instances of the same class. This klass pointer is crucial to understanding how the runtime implements certain language features, such as virtual method lookup.

However, for this discussion of inline classes, the data held in the mark word is especially important, as it is inherently tied to the concept of identity of Java objects.

Inline Classes and Object Identity

Recall that in Java, two object instances are not considered equal just because they have the same values for all their fields. Java uses the == operator to determine whether two references are pointing at the same memory location, and objects are not considered identical if they are stored separately in memory.

NOTE: This notion of identity is linked with the ability to lock a Java object. In fact the mark word is used to store the object monitor (among other things).

For inline classes, however, we want the composites to have semantics that are essentially those of primitive types. In that case the only thing that matters for equality is the bit pattern of the data, not where in memory that pattern appears.

Therefore, by removing the object header, we also remove the composite's unique identity. This change frees the runtime to make significant optimizations in layout, calling convention, compilation, and allocation.

NOTE: The removal also has other implications for the design of inline classes. For example they cannot be synchronized upon (because they have neither a unique identity nor anywhere to store the monitor).

It is important to realize that Valhalla is a project that goes all the way down through the language and VM and eventually reaches the metal. This means that it might look just like one new construct (inline class) to the programmer, but there are so many layers that the feature depends upon.

NOTE: Inline classes are not the same as the forthcoming records feature. A Java record is just a regular class that is declared with reduced boilerplate and has some standardized, compiler generated methods. Inline classes, on the other hand, are a fundamentally new concept within the JVM, and change Java's model of memory in fundamental ways.

The current prototype of inline classes (referred to as LW2) is functional, but it is still at a very, very early stage. Its target audience is advanced developers, library authors, and toolmakers.

Working with the LW2 Prototype

Let's dive into some examples of what can be done with inline classes in their current state in LW2. I will be able to show the effects of inline classes using low-level techniques (such as bytecode and heap histograms). Future prototypes will add more user-visible and higher-level aspects, but they haven't been completed yet, so I will have to stick to the low-level.

To obtain a build of OpenJDK that supports LW2, the easiest option is to download it from here — Linux, Windows and Mac builds are available. Alternatively, experienced open-source developers can build their own binary from scratch.

Once the prototype is downloaded and installed, we can develop some inline classes using it.

To make an inline class in LW2, a class declaration is tagged with the inline keyword.

The rules for inline classes (for now — some of these may be relaxed or changed in future prototypes) are:

  • Interfaces, annotation types, enums cannot be inline classes
  • Top level, inner, nested, local classes may be inline classes
  • Inline classes are not nullable and instead have a default value
  • Inline classes may declare inner, nested, local types
  • Inline classes are implicitly final so cannot be abstract
  • Inline classes implicitly extend java.lang.Object (like enums, annotations, and interfaces)
  • Inline classes may explicitly implement regular interfaces
  • All instance fields of an inline class are implicitly final
  • Inline classes may not declare instance fields of their own type
  • javac automatically generates hashCode(), equals(), and toString()
  • javac does not allow clone(), finalize(), wait(), or notify() on inline classes

Let's look at our first example of an inline class, and see what an implementation of a type like Optional would look like as an inline class. To reduce indirection and for clarity of demonstration, we are going to write a version of an optional type that holds a primitive value, similar to the type java.util.OptionalInt in the standard JDK class library:

public inline class OptionalInt {
    private boolean isPresent;
    private int v;

    private OptionalInt(int val) {
        v = val;
        isPresent = true;
    }

    public static OptionalInt empty() {
        // New semantics for inline classes
        return OptionalInt.default;
    }

    public static OptionalInt of(int val) {
        return new OptionalInt(val);
    }

    public int getAsInt() {
        if (!isPresent)
            throw new NoSuchElementException("No value present");
        return v;
    }

    public boolean isPresent() {
        return isPresent;
    }

    public void ifPresent(IntConsumer consumer) {
        if (isPresent)
            consumer.accept(v);
    }

    public int orElse(int other) {
        return isPresent ? v : other;
    }

    @Override
    public String toString() {
        return isPresent
                ? String.format("OptionalInt[%s]", v)
                : "OptionalInt.empty";
    }
}

This should compile using the current LW2 version of javac. To see the effects of the new inline classes technology, we need to look at bytecode, using the javap tool that can be invoked like this:

$ javap -c -p infoq/OptionalInt.class

Disassembling our OptionalInt type, we see some interesting aspects of the inline class in the bytecode:

public final value class infoq.OptionalInt {
  private final boolean isPresent;

  private final int v;

The class has a new modifier value that is left over from an earlier prototype where the feature was still called value types. The class and all instance fields have been made final even though that wasn't specified in the source code. Next, let's look at the object construction methods:

public static infoq.OptionalInt empty();
    Code:
       0: defaultvalue  #1                  // class infoq/OptionalInt
       3: areturn

  public static infoq.OptionalInt of(int);
    Code:
       0: iload_0
       1: invokestatic  #11                 // Method "<init>":(I)Qinfoq/OptionalInt;
       4: areturn

  private static infoq.OptionalInt infoq.OptionalInt(int);
    Code:
       0: defaultvalue  #1                  // class infoq/OptionalInt
       3: astore_1
       4: iload_0
       5: aload_1
       6: swap
       7: withfield     #3                  // Field v:I
      10: astore_1
      11: iconst_1
      12: aload_1
      13: swap
      14: withfield     #7                  // Field isPresent:Z
      17: astore_1
      18: aload_1
      19: areturn

For a regular class, we would expect to see a compiled construction sequence like this simple factory method:

  // Regular object class
  public static infoq.OptionalInt of(int);
    Code:
       0: new           #5  // class infoq/OptionalInt
       3: dup
       4: iload_0
       5: invokespecial #6  // Method "<init>":(I)V
       8: areturn

The difference in the two bytecode sequences is clear — inline classes do not use the new opcode. Instead, we encounter two brand new bytecodes that are specific to inline classes — defaultvalue and withfield.

  • defaultvalue is used to create new value instances
  • withfield is used instead of setfield

NOTE: One of the consequences of this design is that the result of defaultvalue must, for every inline class, be a consistent and usable value of the type.

It's worth noticing that the semantics of withfield is to replace the value instance on top of stack with a modified value with an updated field. This is slightly different from setfield (which consumes the object reference on the stack) because inline classes are always immutable and are not necessarily always represented as references.

To complete our first look at the bytecode, we notice that, among the other methods of the class are auto-generated implementations of hashCode() and equals() that use invokedynamic as a mechanism.

public final int hashCode();
    Code:
       0: aload_0
       1: invokedynamic #46,  0             // InvokeDynamic #0:hashCode:(Qinfoq/OptionalInt;)I
       6: ireturn

  public final boolean equals(java.lang.Object);
    Code:
       0: aload_0
       1: aload_1
       2: invokedynamic #50,  0             // InvokeDynamic #0:equals:(Qinfoq/OptionalInt;Ljava/lang/Object;)Z
       7: ireturn

 In our case, we have explicitly provided an override of toString(), but this method would also usually be auto-generated for inline classes.

 public java.lang.String toString();
    Code:
       0: aload_0
       1: getfield      #7                  // Field isPresent:Z
       4: ifeq          29
       7: ldc           #28                 // String OptionalInt[%s]
       9: iconst_1
      10: anewarray     #30                 // class java/lang/Object
      13: dup
      14: iconst_0
      15: aload_0
      16: getfield      #3                  // Field v:I
      19: invokestatic  #32                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
      22: aastore
      23: invokestatic  #38                 // Method java/lang/String.format:(Ljava/lang/String;[Ljava/lang/Object;)Ljava/lang/String;
      26: goto          31
      29: ldc           #44                 // String OptionalInt.empty
      31: areturn

To drive our inline class, let's look at a small driver program contained in Main.java:

public static void main(String[] args) {
        int MAX = 100_000_000;
        OptionalInt[] opts = new OptionalInt[MAX];
        for (int i=0; i < MAX; i++) {
            opts[i] = OptionalInt.of(i);
            opts[++i] = OptionalInt.empty();
        }
        long total = 0;
        for (int i=0; i < MAX; i++) {
            OptionalInt oi = opts[i];
            total += oi.orElse(0);
        }
        try {
            Thread.sleep(60_000);
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("Total: "+ total);
    }

The bytecode for Main is not shown as it contains no surprises. In fact, it is the same (apart from package names) as the code that would be generated if Main used java.util.OptionalInt instead of our inline class version.

This is, of course, part of the point — to make inline classes minimally intrusive to mainstream Java programmers and provide their benefits without too much cognitive overhead.

Heap Behaviour for inline classes

Having noted the features of the compiled value class's bytecode, we can now execute Main and take a quick look at runtime behavior, starting with the contents of the heap.

$ java infoq.Main

Note that the thread delay at the end of the program is only there to allow us to have time to produce a heap histogram from the process.

We do this by running another tool in a separate window: jmap -histo:live <pid>, which produces results like this:

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:             1      800000016  [Qinfoq.OptionalInt;
   2:          1687          97048  [B (java.base@14-internal)
   3:           543          70448  java.lang.Class (java.base@14-internal)
   4:          1619          51808  java.util.HashMap$Node (java.base@14-internal)
   5:           452          44600  [Ljava.lang.Object; (java.base@14-internal)
   6:          1603          38472  java.lang.String (java.base@14-internal)
   7:             9          33632  [C (java.base@14-internal)

This shows that we have allocated one single array of infoq.OptionalInt values, and that it occupies roughly 800M (100 million elements each of size 8).

As expected, there are no standalone instances of our inline class.

NOTE: Readers who are familiar with the internal syntax for Java type descriptors may note the appearance of a new, Q-type descriptor to denote a value of an inline class.

To have something to compare this to, let's recompile Main using the version of OptionalInt from java.util instead of our inline class version. Now the histogram looks completely different (output from Java 8):

 num     #instances         #bytes  class name (module)
-------------------------------------------------------
   1:      50000001     1200000024  java.util.OptionalInt
   2:             1      400000016  [Ljava.util.OptionalInt;
   3:          1719          98600  [B
   4:           540          65400  java.lang.Class
   5:          1634          52288  java.util.HashMap$Node
   6:           446          42840  [Ljava.lang.Object;
   7:          1636          39264  java.lang.String

We now have a single array comprising 100 million elements of size 4 — which are references to the object type java.util.OptionalInt. We also have 50 million instances of OptionalInt, plus one for the empty value instance, giving a total memory utilization for the non-inline class case of around 1.6G.

This means that the use of inline classes reduces memory overhead by about 50%, in this extreme case. This is a good example of what is meant by the phrase "codes like a class, works like an int."

Benchmarking with JMH

Let's also take a look at a simple JMH benchmark. This is intended to allow us to see the effect of removing the indirections and cache misses, in terms of reduced program run time.

Details of how to set up and run a JMH benchmark can be found on the OpenJDK site.

Our benchmark will directly compare our inline implementation of OptionalInt with the version found in the JDK.

import org.openjdk.jmh.annotations.*;
import java.util.concurrent.TimeUnit;

@State(Scope.Thread)
@BenchmarkMode(Mode.Throughput)
@OutputTimeUnit(TimeUnit.SECONDS)
public class MyBenchmark {

    @Benchmark
    public long timeInlineOptionalInt() {
        int MAX = 100_000_000;
        infoq.OptionalInt[] opts = new infoq.OptionalInt[MAX];
        for (int i=0; i < MAX; i++) {
            opts[i] = infoq.OptionalInt.of(i);
            opts[++i] = infoq.OptionalInt.empty();
        }
        long total = 0;
        for (int i=0; i < MAX; i++) {
            infoq.OptionalInt oi = opts[i];
            total += oi.orElse(0);
        }

        return total;
    }

    @Benchmark
    public long timeJavaUtilOptionalInt() {
        int MAX = 100_000_000;
        java.util.OptionalInt[] opts = new java.util.OptionalInt[MAX];
        for (int i=0; i < MAX; i++) {
            opts[i] = java.util.OptionalInt.of(i);
            opts[++i] = java.util.OptionalInt.empty();
        }
        long total = 0;
        for (int i=0; i < MAX; i++) {
            java.util.OptionalInt oi = opts[i];
            total += oi.orElse(0);
        }

        return total;
    }
}

Performing a single run on a modern, high-spec MacBook Pro gave this result:

Benchmark                             Mode  Cnt  Score   Error  Units
MyBenchmark.timeInlineOptionalInt    thrpt   25  5.155 ± 0.057  ops/s
MyBenchmark.timeJavaUtilOptionalInt  thrpt   25  0.589 ± 0.029  ops/s

This shows that inline classes are much, much faster in this specific case. However, it is important not to read too much into this example — it is merely for demonstration purposes.

As the JMH framework itself warns: "Do not assume the numbers tell you what you want them to tell."

For example, in this case the infoq.OptionalInt version of the benchmark allocates roughly 50% — is it this reduction in allocation that accounts for the performance speedup? Or are there other performance effects as well? This benchmark, in isolation, does not tell us — it is simply a single data point.

This rough benchmark should not be taken seriously or used as anything other than an indication that inline classes have the potential to show significant speedups under some carefully chosen circumstances.

For example, in the LW2 prototype, only interpreted mode and the C2 (server) JIT compiler are supported. There is no C1 (client) compiler, no tiered compilation, and no Graal. In addition, the interpreter is not optimized, as the focus has been on the JIT implementation. All of these features would be expected to be present in a shipping version of Java, and in their absence all performance numbers are completely unreliable.

In fact, it's not just performance where so much work still remains to be done, compared to the current LW2 preview. Fundamental questions still remain, such as:

  • How to extend generics to allow abstraction over all types, including primitives, values, and even void?
  • What should the true inheritance hierarchy look like for inline classes?
  • What to do about type erasure and backwards compatibility?
  • How to enable existing libraries (especially the JDK) to compatibly evolve to fully take advantage of inline classes?
  • How many of the current LW2 constraints can, or should, be relaxed?

While most of these are still open questions, one area where LW2 has tried to provide answers is by prototyping a mechanism for inline classes to be used as the type parameter (the "payload") in a generic type.

Inline classes as type parameters

In the current LW2 prototype we must overcome a problem, as Java's model of generics implicitly assumes nullability of values, and inline classes are not nullable.

To solve this, LW2 uses a technique called indirect projection. This is like a form of autoboxing for inline classes, and allows us to write a type Foo? for any inline type Foo.

The end result is that the indirect projection type can be used as the parameter in a generic type (whereas the real inline type cannot) like this:

   public static void main(String[] args) {
        List<OptionalInt?> opts = new ArrayList<>();
        for (int i=0; i < 5; i++) {
            opts.add(OptionalInt.of(i));
            opts.add(OptionalInt.empty());
            opts.add(null);
        }
        int total = opts.stream()
                        .mapToInt(o -> {
                            if (o == null) return 0;
                            OptionalInt op = (OptionalInt)o;
                            return op.orElse(0);
                        })
                        .reduce(0, (x, y) -> x + y);

        System.out.println("Total: "+ total);
    }

Instances of the inline class can always be cast to an instance of the indirect projection, but to go the other way, a null check is required, as seen in the body of the lambda in the example.

NOTE: The use of indirect projections is still highly experimental. The final version of inline classes may well use a different design altogether.

There is still a huge amount of work to be done before inline classes are ready to become a real feature in the Java language. Prototypes like LW2 are fun for the interested developer to experiment with, but it should always be remembered that these are just an intellectual exercise. Nothing in the current builds guarantees anything about the final form that the feature may eventually take.

About the Author

Ben Evans is a co-founder of jClarity, a JVM performance optimization company. He is an organizer for the LJC (London's JUG) and a member of the JCP Executive Committee, helping define standards for the Java ecosystem. Ben is a Java Champion; 3-time JavaOne Rockstar Speaker; author of "The Well-Grounded Java Developer", the new edition of "Java in a Nutshell" and "Optimizing Java" He is a regular speaker on the Java platform, performance, architecture, concurrency, startups and related topics. Ben is sometimes available for speaking, teaching, writing and consultancy engagements - please contact for details.

Rate this Article

Adoption
Style

Hello stranger!

You need to Register an InfoQ account or or login to post comments. But there's so much more behind being registered.

Get the most out of the InfoQ experience.

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Community comments

  • Regarding that ? mark

    by Billy Sjöberg,

  • Valhalla is the second coming

    by Doug Ly,

  • so sad it takes forever

    by Nick Evgeniev,

  • Java is getting new again

    by Johnny Costa,

    • Re: Java is getting new again

      by Ben Evans,

      • Re: Java is getting new again

        by Johnny Costa,

      • Re: Java is getting new again

        by Constantine Plotnikov,

        • Re: Java is getting new again

          by Ben Evans,

          • Regarding that ? mark

            by Billy Sjöberg,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            Guess it's going to trip up the Kotlin users quite a bit, hope they get around without it somehow :-P

          • Valhalla is the second coming

            by Doug Ly,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            Great article!

            I believe that Valhalla will be the most important release of the Java programming language and the JVM platform in general. There are so many great things are planned into that project. For example, for native integration we have: records/structs, easier FFI integration (model after JNR?), exposure to vector intrinsics' to name a few. For ergonomics we have raw string literal (multi-line string included).

            As much as we hate Oracle, I wouldn't think the JVM would evolve this fast if not for the acquisition from Sun.

            I have a few confusions starting from the 2nd diagram. The 1st one is understandable. But the rest of them are shown without comments and contexts which I don't know what they represent.

          • so sad it takes forever

            by Nick Evgeniev,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            so sad it takes forever for java to implement c# structs :(. even with inline classes ... let's say you want to embed fixed array of chars... in c# it's done using 'fixed' keyword... what about java?

          • Re: so sad it takes forever

            by Cameron Purdy,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            Different mind-set. C# is taking the (early) C++ route, of adding whatever they can think of. Java is a bit more disciplined in the approach to changing the language. In the near term, maybe it helps C#, but in the end, it will hurt C#, because all that crap has to be supported forever.

          • Re: so sad it takes forever

            by Nick Evgeniev,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            well *language* wise java is inferior to c# for years honestly not sure what part of the language you meant by 'crap'. hotspot & graal are superior to .net yet for latency sensitive code it's funny just how much dogmatic decisions made by oracle (crazy jni, lack of structs) are holding jvm back :(

          • Re: so sad it takes forever

            by Constantine Plotnikov,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            They just do not "support crap forever". The motivation for .NET core was to allow packaging older versions with the application, so they could completely drop backward compatibility and evolve platform freely. They call it multiversioning or something like it. From my limited experience, major .net core versions, are basically incompatible and require minor rewrites there and there. For a small example see:

            docs.microsoft.com/en-us/aspnet/core/migration/...

            So, no, MS approach is different from Java. And it is different from C++ too. There are some advantages and disadvantages to it.

            I presonally like MS VM better as they fixed many known Java problems, but I like Java ecosystem better. MS is fixing problems with ecosystem starting with .NET core, but they have a long way to go.

          • Re: so sad it takes forever

            by Cameron Purdy,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            "dogmatic decisions made by oracle (crazy jni, lack of structs)"

            Um. JNI was Sun; it was designed to suck, on purpose, so that people wouldn't use native code. Oracle announced that they're going to replace it with something that doesn't suck.

            As for "lack of structs", Java has a few different approaches to value types already in preview, again, done by Oracle.

            Trust me, nobody in the world (who is not named Larry) knows more about Oracle's faults than I do, but in this case, Oracle has done good.

          • Re: so sad it takes forever

            by Cameron Purdy,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            They just do not "support crap forever". The motivation for .NET core was to allow packaging older versions with the application, so they could completely drop backward compatibility and evolve platform freely.

            Yes, I agree that Microsoft has often broken backwards compatibility with .NET and C#. At least a few dozen times now, they've broken relatively simple code (serialization, caching, distributed computing, etc.) that I worked on.

            But that said, lots of very bad choices that they put into .NET 1.0 and C# 1.0 are still there. It's very hard to get rid of bad design decisions, even when you're willing to break everyone's code with every release.

          • Re: Regarding that ? mark

            by Ben Evans,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            It's pretty preliminary, and I wouldn't be at all surprised if it changed syntax or went away entirely.

          • Java is getting new again

            by Johnny Costa,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            I believe that this is the first step to reduce the gap between java and python.
            In python, using pandas and numpy, we can hold millions object arrays in memory running in CPU or in the GPU easily.
            Java needs urgently destroy retrocompatibility, and simplify the development.

          • Re: Java is getting new again

            by Ben Evans,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            Hi Johnny.

            Firstly, I think it is very, very unlikely that Java will ever break backwards compatibility under its current owners. The protection of investments that customers have made in Java tech (which is really what backwards compatibility means) is just too important.

            Java's design aesthetic is to be cautious and careful and try to avoid any mis-steps and to aim for the long-term use cases. Marathons, not sprints. As a result, it tends to take a more considered approach to adding features. A missing feature is merely missing and can be worked around; a bad feature is bad forever.

            Furthermore, given the finite space of language constructs and possible syntax, we can't just keep adding features - we'll run out of semantic and syntactic headroom. Realistically, features once added to Java can't be removed again easily (or perhaps at all!)

            So, the high-density ML-type operations that are possible because Python binds tightly to low-level C libraries are useful for some types of modern application. However, what if it turns out that ML / AI is not that useful for most programming tasks? What if it's only a tiny minority of programmers who end up needing access to those features, and most applications actually don't use them (and have a library to abstract them away for those few cases where they're needed by general programs)?

            Once upon a time, XML processing was all the rage, and people argued hard that XML-literal syntax should be added. Some languages (e.g. Scala) did add it - and from the point of view of 2019, that now looks like an obvious misfeature.

            A recent study showed that 84% of the "Big Data" we collect is never touched again after being collected - it just hangs around in data stores waiting to be purged. That's hugely wasteful, and suggests to me that the "ML / AI will conquer everything" narrative may be too simplistic.

            Designing and implementing languages for the long term, is difficult. When doing so in the context of a mature language with untold millions of users, it is extremely difficult.

          • Re: Java is getting new again

            by Johnny Costa,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            I know it is not easy to evolve language while maintaining backward compatibility. Many many companies have legacy projects in java.

            10 years ago I expected Java today to be better, but no .. I went to python precisely because there is nothing equivalent in java in artificial intelligence ..

            Javascript is also booming taking the java web market with Node.js, reactJS , vueJS, angular. If java (platform, language) doesn't break backward compatibility, the difference between java and (python, javascript) just tends to widen.

          • Re: Java is getting new again

            by Constantine Plotnikov,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            There were major breaks of backward compatibility in Java originated from Oracle:
            * Java 9 has hidden a lot of widely used components (sun.misc.Unsafe is a major problem for a lot of libraries)
            * Java 11 removed bunch of Java EE packages and CORBA. Java XML Bind package is used suprisingly widely in the code.
            * There are API makred as @Deprecated(forFremoval=true) and they will be removed from platform, and I think they are already removed. Such API are removed in non-LTS version. So if you move only from LTS to LTS, you could suddenly find that some API is missing w/o prior warning.
            * GraalVM native image is not quite Java and it creates additional compatibility profile (restricted reflection, no runtime class generation, etc.), so write-one-run-anywhere goes out in smoke. Before that was Google's Android with similar restrictions, but GraalVM is from Oracle

          • Re: Java is getting new again

            by Ben Evans,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            I know it is not easy to evolve language while maintaining backward compatibility. Many many companies have legacy projects in java.
            Javascript is also booming taking the java web market with Node.js, reactJS , vueJS, angular. If java (platform, language) doesn't break backward compatibility, the difference between java and (python, javascript) just tends to widen.



            Sorry, but in my experience this is just not true.

            For example, I know for a fact that many Fortune 500 companies completely forbid serverside development in Node for compatibility and support reasons. In my opinion, Node is over-represented in the statistics simply because JS lacks a decent SDK and dev teams have turned to npm as a crutch. Teams that use npm show up as Node users, even if all they are using it for is client-side package management.

            The statistics from AdoptOpenJDK or Spring Boot tell a very different story than the narrative you're promoting.

            One thought I'd like you to consider is that the "Tech X vs Tech Y" story that the tech media loves is actually not that insightful! The global technology market is undergoing huge, rapid growth. When you have a static market then for someone to win, another participant must lose. That isn't true in a growing market. We are in a growing market. It is entirely possible for everyone to win at once!

          • Re: Java is getting new again

            by Ben Evans,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            There were major breaks of backward compatibility in Java originated from Oracle:



            I don't think I said in my piece that "moving from LTS to LTS will never break anything". In fact, I'm not sure what your comment has to do with my article, which is about a long-term research project and not about LTS at all?

          • Re: Java is getting new again

            by Constantine Plotnikov,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            I don't think I said in my piece that "moving from LTS to LTS will never break anything". In fact, I'm not sure what your comment has to do with my article, which is about a long-term research project and not about LTS at all?


            You made even stroger statement in your comment above:

            Firstly, I think it is very, very unlikely that Java will ever break backwards compatibility under its current owners. The protection of investments that customers have made in Java tech (which is really what backwards compatibility means) is just too important.


            Oracle broke backward compatibility in the past and plans to break it in the future.

          • Re: Java is getting new again

            by Ben Evans,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            Of the examples you cite, Unsafe is still accessible, it just warns (for now). Technically speaking it is *not* a break of backwards compatibility as the sun.* packages *never* had any guarantees of compatibility. The removal of the EE APIs does not break binary compatibility - you just have to supply an additional runtime dependency.

            You might as well argue that backwards compatibility is impossible, because any version of a language that introduces new syntax breaks compatibility (files that didn't use to compile are now suddenly valid).

          • Re: Java is getting new again

            by Constantine Plotnikov,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            My understanding of backward compatibility is that code that worked, continue to work. And that was mostly true up to Java 8. And at least code compiled against libraries (some methods were no-op).

            Removal of java.xml.bind particularly hits in surprising places, as many libraries used it for base64 (now supported in JDK), base16 (still not supported in JDK), ISO format datetime and duration encoding/decoding. So it sometimes hits on production when some obscure branch of code inside third-party some library is executed.

            If need harder examples, java.awt.peer package is completely removed. So checks like component.getPeer() != null stoped to work (there is an alternative to this check, but it was still an API change under java package). The methods finalize() were removed from IO streams, so libraries that overriden them stop to compile (there are different thrown exception between Object.finialize() and FileInputStream.finalize()). Removal of AWT toolkit broken applications that used mock AWT toolkits in server environment to run some AWT components for server-side rendering.

            For Unsafe, this is a grey area, as there were simply no alternative for it for high performance concurreny utilities, so practically every framework that cared about performance and concurrency used Unsafe. Some replacements like var handles appeared only recently.

            I like that Java tried to keep backward compatibility, but starting with Java 9, backward-compatibility is not an absolute law, but only one of factors to consider. They started to break it there and there. This is both good and bad.

          • inline classes with embedded fixed arrays

            by Nick Evgeniev,

            Your message is awaiting moderation. Thank you for participating in the discussion.

            it's not clear whether it will allow something like:
            public inline class Person {
            private char[20] firstName;
            private char[20] lastName;
            }

            if not ... it will be of very limited use, unfortunately

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

Allowed html: a,b,br,blockquote,i,li,pre,u,ul,p

BT