Monday, November 6, 2023

Java records: Serialization, marshaling, and bean state validation

Java records: Serialization, marshaling, and bean state validation

Existing frameworks and libraries that access instance variables through getters and setters won’t work with records. Here’s what to do.

Records were first introduced in Java 14 as a preview feature. Recently, there has been a second preview with the arrival of Java 15. Record classes are therefore not yet a regular part of the JDK and they are still subject to change.

In brief, the main goal of record classes is to model plain data aggregates with less ceremony than normal classes. A record class declares a sequence of fields, and may also declare methods. The appropriate constructor, accessor, equals, hashCode, and toString methods are created automatically. The fields are final because the class is intended to serve as a simple data carrier.

A record class declaration consists of a name, a header (which lists the fields of the class, known as its components), and a body. The following is an example of a record declaration:

record RectangleRecord(double length, double width) {
}

In this article, I will focus on serialization and deserialization, marshaling and unmarshaling, and state validation of records. But first, take a look at the class members of a record using Java’s Reflection API.

Introspection


With the introduction of records to Java, two new methods have been added to java.lang.Class:

  • isRecord(), which is similar to isEnum() except that it returns true if the class was declared as a record
  • getRecordComponents(), which returns an array of java.lang.reflect.RecordComponent objects corresponding to the record components

I’ll use the latter with the record class declared above to get its components:

System.out.println("Record components:");
Arrays.asList(RectangleRecord.class.getRecordComponents())
        .forEach(System.out::println);

Here’s the output:

Record components:
double length
double width

As you can see, the components are the variables (type and name pairs) specified in the header of the record declaration. Now, look at the record fields that are derived from the components:

System.out.println("Record fields:");
Arrays.asList(RectangleRecord.class.getDeclaredFields())
        .forEach(System.out::println);

The following is the output:

Record fields:
private final double record.test.RectangleRecord.length
private final double record.test.RectangleRecord.width

Note that the fields are generated by the compiler with the private and final modifiers. The field accessors and the constructor parameters are also derived from the record components, for example:

System.out.println("Field accessors:");
Arrays.asList(RectangleRecord.class.getDeclaredMethods())
        .filter(m -> Arrays.stream(RectangleRecord.class.getRecordComponents()).map(c -> c.getName()).anyMatch(n -> n.equals(m.getName())))
        .forEach(System.out::println);

System.out.println("Constructor parameters:");
Arrays.asList(RectangleRecord.class.getDeclaredConstructors())
        .forEach(c -> Arrays.asList(c.getParameters())
        .forEach(System.out::println));

Here’s the output:

Field accessors:
public double record.test.RectangleRecord.length()
public double record.test.RectangleRecord.width()
Constructor parameters:
double length
double width

Notice that the name of the field accessors does not start with get and, therefore, does not conform to the JavaBeans conventions.

You’re probably not surprised to not see any methods for setting the contents of a field, because records are supposed to be immutable.

Record components can also be annotated in the same way you would do for constructor or method parameters. For this purpose, I’ve created a simple annotation such as the following one:

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

Be sure to set the retention policy to RUNTIME; otherwise, the annotation is discarded by the compiler and will not be present at runtime. So, this is the modified record declaration with annotated components:

record Rectangle(@MyAnnotation double length, @MyAnnotation double width) {
}

The next step is to retrieve the annotation on the record components via reflection, for example:

System.out.println("Record component annotations:");
Arrays.asList(RectangleRecord.class.getRecordComponents())
        .forEach(c -> Arrays.asList(c.getDeclaredAnnotations())
        .forEach(System.out::println));

The following is the output:

Record component annotations:
@record.test.MyAnnotation()
@record.test.MyAnnotation()

As expected, the annotation is present on both components specified in the header of the record.

For records, however, the annotations that you add to the components are also propagated to the derived fields, accessors, and constructor parameters. I will quickly verify this by printing out the annotations of the component-derived artifacts:

Here are annotations on record fields:

System.out.println("Record field annotations:");
Arrays.asList(RectangleRecord.class.getDeclaredFields())
        .forEach(f -> Arrays.asList(f.getDeclaredAnnotations())
        .forEach(System.out::println));

And here is the output:

Record field annotations:
@record.test.MyAnnotation()
@record.test.MyAnnotation()

Here are annotations on field accessors:

System.out.println("Field accessor annotations:");
Arrays.asList(RectangleRecord.class.getDeclaredMethods())
        .filter(m -> Arrays.stream(RectangleRecord.class.getRecordComponents()).map(c -> c.getName()).anyMatch(n -> n.equals(m.getName())))
        .forEach(m -> Arrays.asList(m.getDeclaredAnnotations())
        .forEach(System.out::println));

And here is the output:

Field accessor annotations:
@record.test.MyAnnotation()
@record.test.MyAnnotation()

Finally, here are annotations on record constructor parameters:

System.out.println("Constructor parameter annotations:");
Arrays.asList(RectangleRecord.class.getDeclaredConstructors())
        .forEach(c -> Arrays.asList(c.getParameters())
        .forEach(p -> Arrays.asList(p.getDeclaredAnnotations())
        .forEach(System.out::println)));

And the following is the output:

Constructor parameter annotations:
@record.test.MyAnnotation()
@record.test.MyAnnotation()

As seen above, if you put an annotation on a record component, it will be automatically propagated to the derived artifacts. However, this behavior is not always desirable, because you might want the annotation to be present only on record fields, for instance. That’s why you can change this behavior by specifying the target of an annotation.

For example, if you want an annotation to be present only on the record fields, you would have to add a Target annotation with a parameter of ElementType.FIELD:

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}

Rerunning the above code yields this output:

Record component annotations:
Record field annotations:
@record.test.MyAnnotation()
@record.test.MyAnnotation()
Field accessor annotations:
Constructor parameter annotations:

As you can see, the annotation is now present only on the record fields. In the same way, you can state that the annotation should be present only on the accessors (ElementType.METHOD), or the constructor parameters (ElementType.PARAMETER), or any combination of those two and the record fields.

Be aware that in any of these cases, you must put the annotation always on the record components, because the fields, accessors, and constructor parameters simply don’t exist in a record declaration. Those are generated and annotated (according to the element types specified in the annotation declaration) by the compiler and, thus, are present only in the compiled record class.

Serialization and deserialization


Because they are ordinary classes, records can also be serialized and deserialized. The only thing you need to do is to add the java.io.Serializable interface to the record’s header, for example:

record RectangleRecord(double length, double width) implements Serializable {
}

Here’s the code to serialize a record:

private static final List<RectangleRecord> SAMPLE_RECORDS = List.of(
        new RectangleRecord(1, 5),
        new RectangleRecord(2, 4),
        new RectangleRecord(3, 3),
        new RectangleRecord(4, 2),
        new RectangleRecord(5, 1)
);

try (
        var fos = new FileOutputStream("C:/Temp/Records.txt");
        var oos = new ObjectOutputStream(fos)) {
    oos.writeObject(SAMPLE_RECORDS);
}

And the following code can be used to deserialize a record:

try (
        var fis = new FileInputStream("C:/Temp/Records.txt");
        var ois = new ObjectInputStream(fis)) {
    List<RectangleRecord> records = (List<RectangleRecord>) ois.readObject();
    records.forEach(System.out::println);
    assertEquals(SAMPLE_RECORDS, records);
}

This is the output:

RectangleRecord[length=1.0, width=5.0]
RectangleRecord[length=2.0, width=4.0]
RectangleRecord[length=3.0, width=3.0]
RectangleRecord[length=4.0, width=2.0]
RectangleRecord[length=5.0, width=1.0]

However, there’s one major difference compared to ordinary classes: When a record is deserialized, its fields are set, via the record constructor, to the values deserialized from the stream. By contrast, a normal class is first instantiated by invoking the no-argument constructor, and then its fields are set via reflection to the values deserialized from the stream.

Thus, records are deserialized using their constructor. This behavior allows you to add invariants to the constructor to check the validity of the deserialized data. Since this is not possible with normal classes, there’s always a certain risk of deserializing bad or even hazardous data, which should not be underestimated, especially if the data comes from external sources.

import java.io.Serializable;
import java.lang.IllegalArgumentException;
import java.lang.StringBuilder;

public record RectangleRecord(double length, double width) implements Serializable {

    public RectangleRecord {
        StringBuilder builder = new StringBuilder();
        if (length <= 0) {
            builder.append("\nLength must be greater than zero: ").append(length);
        }
        if (width <= 0) {
            builder.append("\nWidth must be greater than zero: ").append(width);
        }
        if (builder.length() > 0) {
            throw new IllegalArgumentException(builder.toString());
        }
    }

}

Note that this code is using the record’s compact constructor here, so there’s no need to specify the parameters or to set the record fields explicitly. If you now deserialize the previously serialized records, every single instance is supposed to have a valid state; otherwise, an IllegalArgumentException is thrown by the record constructor.

You can verify this by modifying the serialized data of just one record in such a way that it doesn’t conform to the validation logic anymore: RectangleRecord[length=0.0, width=-5.0].

If you now execute the deserialization code from above, you’ll get the expected

IllegalArgumentException:
java.lang.IllegalArgumentException: 
Length must be greater than zero: 0.0
Width must be greater than zero: -5.0
  at record.test.RectangleRecord.<init>(RectangleRecord.java:18)
  at java.base/java.io.ObjectInputStream.readRecord(ObjectInputStream.java:2320)

If you tried the same process with a normal class, no exception would occur, since the class’s constructor wouldn’t be called. The object would be deserialized with the erroneous data, without anyone noticing.

Look at the following RectangleClass, which is the counterpart of the RectangleRecord:

import java.io.Serializable;
import java.util.Objects;

public class RectangleClass implements Serializable {

    private final double width;
    private final double length;

    public RectangleClass(double width, double length) {
        StringBuilder builder = new StringBuilder();
        if (length <= 0) {
            builder.append("\nLength must be greater than zero: ").append(length);
        }
        if (width <= 0) {
            builder.append("\nWidth must be greater than zero: ").append(width);
        }
        if (builder.length() > 0) {
            throw new IllegalArgumentException(builder.toString());
        }
        this.width = width;
        this.length = length;
    }

    @Override
    public String toString() {
        return "RectangleClass[" + "width=" + width + ", length=" + length + ']';
    }

    @Override
    public int hashCode() {
        return Objects.hash(width, length);
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        RectangleClass other = (RectangleClass) obj;
        return Objects.equals(length, other.length) && Objects.equals(width, other.width);
    }

    public double width() {
        return width;
    }

    public double length() {
        return length;
    }

}

Although the constructor of the RectangleClass contains the same validation logic as the constructor of the RectangleRecord, it is not called during the deserialization process and, therefore, cannot prevent the creation of objects with invalid state.

Marshaling and unmarshaling


Just like normal classes, records can also be unmarshaled from and marshaled to a format of your choice, such as JSON, XML, or CSV. If you’d like to use an existing library to do so, be aware that it has to access the class fields via the Field.set(Object obj, Object value) method and not via the getter and setter methods, because records don’t have those methods.

However, you should know about some restrictions. In JDK 15’s second preview of Java records, a record’s field can no longer be accessed via the Field.set(Object obj, Object value) method (which was possible in JDK 14).

The reason for this restriction is to ensure the immutability of records by preventing this kind of backdoor manipulation by libraries. However, most of the current libraries aren’t aware of records yet. The libraries therefore treat records as ordinary classes and try to set the field values via the Field.set(Object obj, Object value) method. That’s not going to work.

Here is an example that uses the popular Gson library to demonstrate the above restriction. With this library, marshaling to JSON should work without any problem because Gson reads the record data using the Field.get(Object obj) method:

private static final List<RectangleRecord> SAMPLE_RECORDS = List.of(
        new RectangleRecord(1, 5),
        new RectangleRecord(2, 4),
        new RectangleRecord(3, 3),
        new RectangleRecord(4, 2),
        new RectangleRecord(5, 1)
);

try (Writer writer = new FileWriter("C:/Temp/Records.json")) {
    new Gson().toJson(SAMPLE_RECORDS, writer);
}

And here is the file output:

[{"length":1.0,"width":5.0},{"length":2.0,"width":4.0},{"length":3.0,"width":3.0},{"length":4.0,"width":2.0},{"length":5.0,"width":1.0}]

But a problem will occur during the unmarshaling process in which Gson tries to set the field values using the Field.set(Object obj, Object value) method:

try (Reader reader = new FileReader("C:/Temp/Records.json")) {
    List<RectangleRecord> records = new Gson().fromJson(reader, new TypeToken<List<RectangleRecord>>(){}.getType());
    records.forEach(System.out::println);
}

The output:

java.lang.IllegalAccessException: Can not set final double field record.test.RectangleRecord.length to java.lang.Double
  at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:76)
  at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwFinalFieldIllegalAccessException(UnsafeFieldAccessorImpl.java:80)
  at java.base/jdk.internal.reflect.UnsafeQualifiedDoubleFieldAccessorImpl.set(UnsafeQualifiedDoubleFieldAccessorImpl.java:79)
  at java.base/java.lang.reflect.Field.set(Field.java:793)

Note that write access to the RectangleRecord.length field has been prevented by throwing a java.lang.IllegalAccessException. This means that the current libraries will need to be changed to take this restriction into account when dealing with records.

At the present time, the only way to set the field values of a record is by using its constructor. And if the constructor arguments are all immutable themselves (for example, when using primitive data types), it will indeed become very hard to change a record’s state. Fortunately, this restriction also helps ensure consistent state validation of records, as discussed in the earlier section about deserialization.

If you currently have to unmarshal records from JSON or any other format, you’ll probably have to write your own unmarshaler. Most libraries won’t support explicit marshaling or unmarshaling for records until they have become a regular Java feature.

As long as they’re not, they are still subject to change. Record field access has been restricted in JDK 15 by no longer allowing the fields to be changed via reflection, something that was still possible in JDK 14 (the first preview of records). That’s a change in behavior that should not be neglected—especially not by library designers—as everyone looks forward to JDK 16.

Bean validation


You may think that records can’t be subject to the bean validation specification (also known as JSR 303) because they do not adhere to the JavaBeans standard. That’s only partly true. A record’s state cannot be validated through its getters or setters, because records don’t have any getters or setters. However, a record’s state can very well be validated via its constructor parameters or its fields.

The Bean Validation API defines a way for expressing and validating constraints using Java annotations. Because these annotations are reusable, they help to avoid code duplication and, thus, contribute to more-concise and less error-prone code. By putting constraint annotations on the components of a record, you can enforce constraint validation and guarantee that a record’s state is always valid. Since records are immutable, you need to validate the constraints only once when you create a record instance. If no constraints are violated, the created instance always meets its invariants.

The following example shows how a record’s state can be validated. To do so, I’m using the bean validation reference implementation, which is the Hibernate Validator.

But first, I’ll add the necessary dependencies with the help of a favorite build tool:

<dependency>
    <groupId>org.hibernate</groupId>
    <artifactId>hibernate-validator</artifactId>
    <version>6.1.5.Final</version>
</dependency>
<dependency>
    <groupId>org.glassfish</groupId>
    <artifactId>javax.el</artifactId>
    <version>3.0.0</version>
</dependency>

Note that the Hibernate Validator also requires an implementation of the Expression Language to evaluate dynamic expressions in constraint violation messages.

Now, I’ll add some validation constraints to the RectangleRecord by the means of the @javax.validation.constraints.Positive annotation, which checks whether the element is strictly positive (zero values are considered invalid).

import javax.validation.constraints.Positive;

public record RectangleRecord(
    @Positive(message = "Length is ${validatedValue} but must be greater than zero.") double length,
    @Positive(message = "Width is ${validatedValue} but must be greater than zero.") double width
) {}

To be able to validate the state of a record, you need an instance of javax.validation.Validator. But to get a Validator instance, you first have to create a ValidatorFactory, for example:

ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
Validator validator = factory.getValidator();

Now you can validate the state of a record instance as follows:

RectangleRecord rectangle = new RectangleRecord(0, -5);
Set<ConstraintViolation<RectangleRecord>> constraintViolations = validator.validate(rectangle);
constraintViolations.stream().map(ConstraintViolation::getMessage).forEach(System.out::println);

Here’s the output:

Length is 0.0 but must be greater than zero.
Width is -5.0 but must be greater than zero.

The previous example demonstrates that record classes can be validated like normal classes using the Bean Validation API. However, since records do not conform to JavaBeans conventions, their state cannot be validated using getters or setters, for instance.

Wouldn’t it be better to check the validity of an object’s state during its construction process and, thus, avoid the creation of an instance with incorrect data? Well, this is possible by calling the constraint validation logic in the record’s constructor itself.

In order not to have to add the above validation code to every single record constructor, I am going to implement it by using an interface. Because records are final, they cannot extend any other record class to inherit its methods. But a similar behavior can be achieved by declaring a default method in an interface, for example:

import java.lang.reflect.Constructor;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validator;

public interface Validatable {

    default void validate(Object... args) {
        Validator validator = ValidatorProvider.getValidator();
        Constructor constructor = getClass().getDeclaredConstructors()[0];
        Set<ConstraintViolation<?>> violations = validator.forExecutables()
                .validateConstructorParameters(constructor, args);
        if (!violations.isEmpty()) {
            String message = violations.stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(System.lineSeparator()));
            throw new ConstraintViolationException(message, violations);
        }
    }

}

The following class provides the required Validator instance:

import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;

public class ValidatorProvider {

    private static final Validator VALIDATOR;

    static {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        VALIDATOR = factory.getValidator();
    }

    public static Validator getValidator() {
        return VALIDATOR;
    }

}

Now, everything’s in place to call the interface’s validate method in my record constructor. To do so, I have to specify an explicit constructor, which allows me to call the validate method:

import javax.validation.constraints.Positive;

public record RectangleRecord(double length, double width) implements Validatable {

    public RectangleRecord (
            @Positive(message = "Length is ${validatedValue} but must be greater than zero.") double length,
            @Positive(message = "Width is ${validatedValue} but must be greater than zero.") double width
        ) {
        validate(length, width);
        this.length = length;
        this.width = width;
    }

}

Note that when you provide an explicit constructor, you have to annotate the constructor parameters and not the components of the record. You have previously seen that the annotations added to the components are also propagated to the derived fields, accessors, and constructor parameters. Regarding the constructor parameters, this is true only as long as you do not provide an explicit constructor.

Now, I’ll try to create a RectangleRecord instance with an invalid length and width:

RectangleRecord rectangle = new RectangleRecord(0, -5);

Here’s the output:

javax.validation.ConstraintViolationException: 
Length is 0.0 but must be greater than zero.
Width is -5.0 but must be greater than zero.
  at record.test.Validatable.validate(Validatable.java:21)
  at record.test.RectangleRecord.<init>(RectangleRecord.java:11)

So, with the validation logic called already at instantiation time (in the record constructor), you can prevent the creation of an object with invalid data. In the first bean validation example from above, you first had to create an object with invalid state before you were able to validate it. But that’s exactly what you want to avoid: creating records with invalid state.

However, by providing an explicit canonical constructor, you also have to explicitly specify all the constructor parameters and set all the record field values manually. But isn’t that again quite a lot of clutter that you are trying to avoid when using records? In the following section, I’m going to show how you can omit an explicit constructor declaration and still get the record’s data validated during the instantiation process.

Byte Buddy


Byte Buddy is a library for creating and modifying Java classes during the runtime of Java applications without the need of a compiler. Unlike the code generation utilities included in the JDK (such as the Java Instrumentation API), Byte Buddy allows you to create arbitrary classes, and it does not require the implementation of any interface to create runtime proxies.

In addition, it offers a convenient API. Using the API, you can change classes either manually using a Java agent or during a build. You can use the library to manipulate existing classes, create new classes on demand, or intercept method calls, for instance. Using Byte Buddy does not require you to have an understanding of Java bytecode or the class file format. However, you can define custom bytecode, if needed.

The API was designed to be nonintrusive, so Byte Buddy does not leave any traces in class files after the code manipulation has taken place. That’s why the generated classes do not require Byte Buddy on the classpath.

Byte Buddy is a lightweight library that depends only on the visitor API of the ASM Java bytecode parser library, so it offers excellent runtime performance.

What I am interested in here is code manipulation at build time, which can be achieved easily by using a dedicated Maven plugin that ships with the Byte Buddy library.

As you probably know, a Maven build lifecycle consists of phases. One of these phases is the so-called compile phase after which Byte Buddy plugs in and changes the Java bytecode according to your instructions. Hence, there’s no code manipulation at runtime that could affect runtime performance.

I’ll start by adding the required dependencies for the Byte Buddy library:

<dependency>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy</artifactId>
    <version>1.10.14</version>
</dependency>

The following XML adds the Byte Buddy Maven plugin to the build lifecycle:

<plugin>
    <groupId>net.bytebuddy</groupId>
    <artifactId>byte-buddy-maven-plugin</artifactId>
    <version>1.10.14</version>
    <executions>
        <execution>
            <goals>
                <goal>transform</goal>
            </goals>
        </execution>
    </executions>
    <configuration>
        <transformations>
            <transformation>
                <plugin>
                    record.test.RecordValidationPlugin
                </plugin>
            </transformation>
        </transformations>
    </configuration>
</plugin>

The Byte Buddy Maven plugin uses a custom class called RecordValidationPlugin that implements the net.bytebuddy.build.Plugin interface, for example:

import java.io.IOException;
import javax.validation.Constraint;

import static net.bytebuddy.matcher.ElementMatchers.hasAnnotation;
import static net.bytebuddy.matcher.ElementMatchers.annotationType;

import net.bytebuddy.build.Plugin;
import net.bytebuddy.description.method.MethodDescription;
import net.bytebuddy.description.type.TypeDescription;
import net.bytebuddy.dynamic.ClassFileLocator;
import net.bytebuddy.dynamic.DynamicType.Builder;
import net.bytebuddy.dynamic.scaffold.TypeValidation;
import net.bytebuddy.implementation.MethodDelegation;
import net.bytebuddy.implementation.SuperMethodCall;

public class RecordValidationPlugin implements Plugin {

    @Override
    public boolean matches(TypeDescription target) {
        return target.isRecord() && target.getDeclaredMethods()
                .stream()
                .anyMatch(m -> m.isConstructor() && hasConstrainedParameters(m));
    }

    @Override
    public Builder<?> apply(Builder<?> builder, TypeDescription typeDescription, ClassFileLocator classFileLocator) {
        try {
            builder = new ByteBuddy().with(TypeValidation.DISABLED).rebase(Class.forName(typeDescription.getName()));
        } catch (ClassNotFoundException ex) {
            throw new RuntimeException(ex);
        }
        return builder.constructor(this::hasConstrainedParameters)
                .intercept(SuperMethodCall.INSTANCE.andThen(MethodDelegation.to(RecordValidationInterceptor.class)));
    }

    private boolean hasConstrainedParameters(MethodDescription m) {
        return m.getParameters()
                .asDefined()
                .stream()
                .anyMatch(p -> !p.getDeclaredAnnotations()
                .asTypeList()
                .filter(hasAnnotation(annotationType(Constraint.class)))
                .isEmpty());
    }

    @Override
    public void close() throws IOException {
    }

}

The interface has three methods: matches, apply, and close. I don’t need to implement the last one.

The first method is used by Byte Buddy to find all the classes whose code I want to change. I need only the record classes that have a constructor with constrained parameters (having bean validation annotations). This is where the new method Class.isRecord() comes into play.

The second method applies the changes to the bytecode generated during the compile phase. It adds to those record constructors that have constrained parameters a call to a method in a custom class called RecordValidationInterceptor.

Also, note that I have to use a custom Builder instance as follows, because Java records are still a preview feature and, therefore, type validation needs to be disabled:

builder = new ByteBuddy().with(TypeValidation.DISABLED).rebase(Class.forName(typeDescription.getName()));

And here’s the code for the RecordValidationInterceptor:

import java.lang.reflect.Constructor;
import java.util.Set;
import java.util.stream.Collectors;
import javax.validation.ConstraintViolation;
import javax.validation.ConstraintViolationException;
import javax.validation.Validation;
import javax.validation.Validator;
import javax.validation.ValidatorFactory;
import net.bytebuddy.implementation.bind.annotation.AllArguments;
import net.bytebuddy.implementation.bind.annotation.Origin;

public class RecordValidationInterceptor {

    private static final Validator VALIDATOR;

    static {
        ValidatorFactory factory = Validation.buildDefaultValidatorFactory();
        VALIDATOR = factory.getValidator();
    }

    public static <T> void validate(@Origin Constructor<T> constructor, @AllArguments Object[] args) {
        Set<ConstraintViolation<T>> violations = VALIDATOR.forExecutables()
                .validateConstructorParameters(constructor, args);
        if (!violations.isEmpty()) {
            String message = violations.stream()
                    .map(ConstraintViolation::getMessage)
                    .collect(Collectors.joining(System.lineSeparator()));
            throw new ConstraintViolationException(message, violations);
        }
    }

}

As a result of the code manipulation, the validate method gets called from the record constructor and passes a Constructor object along with the according parameter values to the bean validator instance.

You can give the method any name; Byte Buddy will identify it with the help of its own annotations such as @Origin or @AllArguments.

Now I’ll build the project using the previously declared RectangleRecord with validation constraints added to the components, for example:

import javax.validation.constraints.Positive;

public record RectangleRecord(
    @Positive(message = "Length is ${validatedValue} but must be greater than zero.") double length,
    @Positive(message = "Width is ${validatedValue} but must be greater than zero.") double width
) {}

After the build has completed, you can look at the resulting bytecode. To do so, execute the following command (allowing you to disassemble a class file) from the command line:

javap -c RectangleRecord

In the following, I show only the constructor bytecode:

public record.test.RectangleRecord(double, double);
    Code:
       0: aload_0
       1: dload_1
       2: dload_3
       3: aconst_null
       4: invokespecial #75                 // Method "<init>":(DDLrecord/test/RectangleRecord$auxiliary$Vd34tcl4;)V
       7: getstatic     #79                 // Field cachedValue$RxYQQtAf$d63lk91:Ljava/lang/reflect/Constructor;
      10: iconst_2
      11: anewarray     #81                 // class java/lang/Object
      14: dup
      15: iconst_0
      16: dload_1
      17: invokestatic  #87                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      20: aastore
      21: dup
      22: iconst_1
      23: dload_3
      24: invokestatic  #87                 // Method java/lang/Double.valueOf:(D)Ljava/lang/Double;
      27: aastore
      28: invokestatic  #93                 // Method csv/to/records/RecordValidationInterceptor.validate:(Ljava/lang/reflect/Constructor;[Ljava/lang/Object;)V
      31: return

Notice the last instruction just before the return statement. That’s where the method RecordValidationInterceptor.validate is called.

Now I’ll test the code refactored by Byte Buddy:

RectangleRecord rectangle = new RectangleRecord(0, -5);

Here’s the output:

javax.validation.ConstraintViolationException: 
Length is 0.0 but must be greater than zero.
Width is -5.0 but must be greater than zero.
  at csv.to.records.RecordValidationInterceptor.validate(RecordValidationInterceptor.java:32)
  at record.test.RectangleRecord.<init>(RectangleRecord.java)

As you can see, the creation of a RectangleRecord instance with invalid data has been avoided just by using regular bean validation constraints on record components. The use of the Byte Buddy plugin helps you to enforce Java record invariants through the means of bean validation.

Source: oracle.com

Friday, November 3, 2023

Java for the enterprise: What to expect in Jakarta EE 10

Java for the enterprise: What to expect in Jakarta EE 10

Table 1. The history and latest release projections for Java EE and Jakarta EE

Comparing the table shown in the previous article to this one, you can see that the JDK 11 compatibility theme moved from Jakarta EE 9 to Jakarta EE 9.1, which is still to be released this year.

While this obviously takes some time away from Jakarta EE 10, planning for that latter release has started to some degree nevertheless, and some of the individual specifications and API projects have started their discussions. Note that everything presented in this article is preliminary and represents the current state of what is thought to be the direction in which Jakarta EE 10 will be heading, but it provides no guarantees that any of this will actually end up in Jakarta EE 10.

It’s all about CDI alignment


One of the topics that is likely to be adopted for the Jakarta EE 10 overall theme might be “Contexts and Dependency Injection (CDI) alignment,” that is, closing the gap between Enterprise JavaBeans (EJB) and CDI. From roadmaps, to discussions among vendors, to wishes from the community, this often comes out on top.

Historically Jakarta EE has used different component models for many of its constituent specifications. Java Server Faces (JSF), now called Jakarta Server Faces, had its own managed beans as did, for example, the REST (JAX-RS), Java Servlet, and EJB specifications. For vendors this meant implementing similar things multiple times over, every time in a slightly different way, while for developers it meant learning similar things multiple times over—and especially wondering why certain things can’t be combined in their applications.

For instance, an interceptor can’t be applied to a Servlet method, while @RolesAllowed doesn’t work on either a Servlet method or a JSF-managed bean. To fix these issues, a single platform-wide component model was introduced in Java EE 6: CDI. The CDI API fully focuses on being a standalone component model with several well-defined services such as interceptors and decorators.

Jakarta Transactions (JTA) was one of the first APIs to start this alignment process by providing a CDI-compatible interceptor, @Transactional, and scope, @TransactionScope, in Java EE 7.

JSF followed right away by introducing new scopes such as @FlowScoped and a CDI version of the existing @ViewScoped in Java EE 7. Quite a few other things such as @Asynchronous, @Lock, @Startup/@DependsOn, and @Schedule were, unfortunately, not included as CDI versions in Java EE 7. Sadly, those didn’t even appear in Java EE 8, though that version did introduce Java EE Security (now Jakarta Security), which is built on top of CDI. That release also delivered JSF 2.3, which provided CDI-based injection and expression language lookup of a large number of artifacts. Additionally, JSF 2.3 officially deprecated its own managed bean system in favor of using CDI beans.

Jakarta EE is expected to pick up the pace again, providing CDI versions of those enterprise beans and common annotations, as well as upgrading and enhancing the existing CDI support in several Jakarta APIs.

Here are several changes you should expect in the Jakarta EE 10 specs.

Jakarta Server Faces


The next version of JSF will be JSF 4.0. Its own major theme will be removing legacy functionality that has already been deprecated. Plus, legacy features that haven’t been deprecated before will be deprecated and likely removed in a future release.

For example, the native expression language that JSF still includes will be removed. This was deprecated over 15 years ago but is still there. That expression language shows up in a number of API types, for example, here in ActionSource:

public interface ActionSource {
    MethodBinding getAction();
    void setAction(MethodBinding action);
    // other methods omitted for brevity
}

All methods referencing types from the native expression language, such as MethodBinding, will be removed.

Support for Jakarta Server Pages (JSP) as a view declaration language will be removed as well, meaning Facelets will remain as the only default view language. Corresponding with the potential overall Jakarta EE 10 theme, the native managed bean system will be completely removed, making CDI beans the designated bean type for JSF.

Finally, some of the extension tags will be removed, such as composite:extension. These were related to the big plans JSF designers once had for visual editors, such as those that existed for Microsoft Visual Basic. These plans never came to fruition, and despite some attempts, most of it was withdrawn. Some remnants of these plans, however, remained in JSF and will now finally be removed.

You can expect some new small features and refinements in the API, for instance, default methods in the PhaseListener interface, use of suppliers in several places, adding generics that were still not present, and small utility methods helpful for component libraries. One example: There will be a release() method on FacesContext as part of PrimeFaces.

As for bigger features, a prototype is currently in the works to add a simple REST lifecycle to JSF. This is not intended as a full-featured REST framework, but instead it is to simplify the use case where JSF applications now use a view action in combination with an empty page. This looks as follows:

@RequestScoped
public class RestBean {

   @Inject FacesContext context;
   
   @RestPath("/helloWorld")
   public String helloWorld() {
        return "Hello World! Postback is " + context.isPostBack();
   }
}

Another feature being looked at is supporting extensionless URLs by default or by using a single configuration option. JSF 2.3 provided basic support for this by officially supporting exact mapping, and JSF 4.0 may expand on this support. Thus, a URL such as http://localhost:8080/foo.xhtml (the current default) will be accessible via http://localhost:8080/foo as well.

Scopes have always played an important role in JSF, and one of the things the team is looking forward to is adding a new scope, @ClientWindowScoped, which builds on the Client Id feature that was introduced in JSF 2.2 as a base facility but was not expanded upon in JSF 2.3.

The Jakarta Security API


Jakarta Security was a new API in Java EE 8. It came out of the box with three authentication mechanisms: Basic, Form, and a variant on Form that’s best for working with JSF.

For the version in Jakarta EE 10, the plan is to add new authentication mechanisms. High on the list are at least Client-Cert and Digest, to make Jakarta Security a full replacement for authentication mechanisms provided by Java Servlet, and to add new methods to support OpenID, OAuth, and JSON Web Token (JWT). The latter is an especially interesting case, because during the Java EE transfer, JWT had already been added to MicroProfile. It’s an open question how to deal with this.

Java for the enterprise: What to expect in Jakarta EE 10

Supporting the CDI-alignment theme, the Jakarta Security wish list includes CDI-based alternatives for the common annotations @RolesAllowed and @RunAs, including, perhaps, supporting the existing annotations. Currently in Jakarta EE, @RolesAllowed is supported only by EJB, where it throws an exception if access is denied to a bean method. However, in MicroProfile or, more precisely in JWT, it’s implied that @RolesAllowed triggers a mandatory authentication mechanism invocation when access is initially denied to a Jakarta REST resource method. Jakarta Security should cover both cases and define those well.

A major new feature being considered for Jakarta Security is that of user-friendly authentication modules, thereby enabling custom authorization rules. There’s some history here. One of the main interfaces in Jakarta Security is the HttpAuthenticationMechanism, which is effectively an HTTP-specific and CDI-enabled ease-of-use layer on top of the lower-level ServerAuthModule from Jakarta Authentication.

By the way, there is a Jakarta Authorization feature that provides low-level portable authorization modules. However, due to the way modules must be created and installed, modules are not really suitable for use in ordinary applications. Let’s hope Jakarta Security provides a similar CDI-enabled ease-of-use layer.

A prototype for this functionality was developed all the way back in 2016, but it was not incorporated in Jakarta Security 1.0 due to lack of time to properly evaluate it. For instance, bridging role checking to an external service instead of assigning all roles when a caller is authenticated would look like the following:

@ApplicationScoped
public class MyAuthorizationModule {

    @Inject
    SecurityConstraints securityConstraints

    @Inject
    MyService service;
   
    @PostAuthenticate
    @PreAuthorize
    @ByRole
    public Boolean myLogic(
        Caller caller, Permission requestedPermission) {
        
        return securityConstraints.getRequiredRoles(requestedPermission)
                .stream()
                .anyMatch(role -> service.isInRole(caller, role));
    }
   
}

The authorization module is called by the container to check whether a caller can access a protected URL such as https://localhost:8080/myapp/admin/foo, or in response to HttpServletRequest.isCallerInRole(), or following a @RolesAllowed annotation.

As part of Jakarta Security, the lower-level Jakarta Authentication and Jakarta Authorization APIs may get some smaller updates as well. These APIs (technically service provider interfaces, or SPIs) are not directly aimed at application developers; the goal is to extend them somewhat and adding clarifications to help higher-levels APIs. For Jakarta Authorization, an important new feature planned is to allow low-level authorization modules to be installed per application—and allow that to be done by the application. Currently this can be done only at the server level.

The Jakarta Servlet API


Jakarta Servlet is the quintessential API in Jakarta EE. Over time it has been adapted to support the somewhat lesser known Jakarta Managed Beans 2.0 specification, meaning that in Jakarta EE, a servlet is a managed bean. In practice this means some CDI features are supported, such as @Inject, but for instance scopes or CDI-style interceptor bindings are not supported.

To align Jakarta Servlet further with CDI is difficult. More than most other APIs in Jakarta EE, Jakarta Servlet has a huge active user base that uses it separately from Jakarta EE, and there are several vendors that exclusively focus on this user base.

So far, the proposals for further alignment vary between multiple options. One is to include a Jakarta EE–specific chapter in the Jakarta Servlet specification that says that in a Jakarta EE environment, servlets should be full CDI beans. This would require no API changes, which is a plus, but it would still require the traditional Servlet base class to be extended, which by default delegates all HTTP methods to a single service() method. This, for instance, is not ideal for security interceptors.

A potential solution is to make all the methods from the Servlet base interface into default methods, so that in a Jakarta EE environment you could write something like the following:

@RequestScoped
@WebServlet("/foo/bar")
public class MyBean implements Servlet {
      
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        // ...
    }
}

Another proposal is to change nothing in the API but to specify that if a servlet is treated as a CDI bean, and the container detects (for example) that the service() method has not been overridden, the doGet() methods are called directly. Such a CDI bean would then almost look like a regular servlet:

@RequestScoped
@WebServlet("/foo/bar")
public class MyBean extends HttpServlet {
      
    public void doGet(HttpServletRequest req, HttpServletResponse res) {
        // ...
    }
}

Another CDI-alignment issue concerns the additional built-in beans for HttpServletRequest, HttpSession, and ServletContext, which are now defined by the CDI specification. Conceptually those don’t belong in the CDI spec, and for this reason alone it would be better if they were moved to the Jakarta EE part of the Jakarta Servlet spec. Practically, the injected HttpServletRequest is the most troublesome because it doesn’t define which HttpServletRequest is injected. ServerAuthModules and Filters can wrap it and after forwarding to another servlet, there’s another version of the request coming into view. Most implementations today inject HttpServletRequest in the state in which it entered the request pipeline, and this is often not what applications expect. A Jakarta Servlet native version of HttpServletRequest could provide the actual current request.

At the other end of the spectrum of alignment, there’s the issue in Jakarta EE that Jakarta REST, which listens to HTTP requests as well, technically does not depend on Jakarta Servlet. In a Jakarta EE environment, it always practically depends on Jakarta Servlet, but in other environments this doesn’t need to be the case. To align these two, the idea has been expressed to extract from Jakarta Servlet a low-level flexible HTTP API, on which both Jakarta Servlet and Jakarta REST could be based in Jakarta EE and, potentially, in other frameworks. In practice, this separation already takes place. For example, in GlassFish this is implemented by Grizzly, and in Tomcat there’s Coyote.

Besides these alignment issues, there are a number of more native features in the pipeline, with the most important one being RFC 6265: state-management cookies with SameSite behavior.

Other small enhancements to Jakarta Servlet include distinguishing between the query string and POST body parameters, as well as gaining an easier-to-use HttpServletRequestWrapper such that only a minimal amount of work has to be done to override the URL.

The Jakarta REST API


Like JSF, Jakarta REST has its own native managed bean system. Because Jakarta REST was introduced together with CDI in Java EE 6, it had some alignment facilities from the get-go, but nevertheless Jakarta REST uses its own injection annotations (specifically @Context) and its own rules around these.

Just like JSF 4.0, Jakarta REST 4.0 will drop its own managed bean system and its own injection annotations. This means that moving forward, Jakarta REST resources will be only CDI beans. An intermediate version, Jakarta REST 3.1, is planned, which will formally deprecate this managed bean system and will allow at least class-level injection of the artifacts currently injected using @Context via @Inject. This release will likely also deprecate the use of Java Architecture for XML Binding (JAXB) in the API, specifically by deprecating Link.JaxbLink and Link.JaxbAdapter.

In addition to the switch over to CDI, there will be a number of smaller features introduced. For instance, parameters annotated with @CookieParam, @FormParam, @HeaderParam, @MatrixParam, and @QueryParam can now also have an array type. In earlier versions of Jakarta EE, they could use only a type of List, Set, or SortedSet. For instance, now you can code the following:

@Path("/users")
public class UserResource {
    @GET
    public Response getUsers(@QueryParam("orderBy") String[] orderBy) {
        return …
    }
}

Another addition is a default exception mapper that implements ExceptionMapper<Throwable> and sets the response to status 500 unless the exception is a WebApplicationException. In that case, the mapper sends the embedded response and its own status code.

The Jakarta Concurrency API


The Java EE Concurrency API was first created in 2003, but it was then stalled for many years, only to be released in Java EE 7, seemingly under some time constraints.

The API shows its age a little by still strongly adhering to the container-managed principle. This practically means that the configuration of the concurrency resources is supposed to be done in a proprietary way using specific tools of the Jakarta EE server (for instance, an admin GUI, a CLI, or an XML file inside a server folder).

While this principle may have been the norm in 2003, the world moved on in the years that the Concurrency API lay dormant. More common, concurrency evolved to a hybrid model where resources can be defined and configured by either the server or the application. Therefore, a long overdue addition to the Jakarta Concurrency API is a @ManagedExecutorServiceDefinition—just like @LdapIdentityStoreDefinition and @DataSourceDefinition—which allows applications to define and configure their own managed executor.

By the way, the Jakarta Concurrency API is very important for the CDI-alignment story, because nearly all the things that are still very useful and available only in EJB are related to concurrency. This concerns specifically the following annotations:

  • @Asynchronous
  • @Lock and @AccessTimeout
  • @Schedule and @Timeout
  • @Stateless

@Asynchronous in EJB is pretty basic, so a newer version could go a little beyond those basics. One proposal is to optionally allow a managed thread pool to be specified on which the annotated method will be executed. That way with two such pools, you can avoid a certain type of deadlock for cooperating asynchronous methods. As with JWT for Jakarta Security, here too a potential difficulty is that MicroProfile has already introduced a CDI-based @Asynchronous (in the Fault Tolerance API, which is a little unexpected perhaps).

@Stateless itself will not be directly transferred into the Jakarta Concurrency API. One implied aspect is that @Stateless beans are pooled, and a single-bean instance is defined to handle only a single call at the same time. Together, these two beans would form a natural way to throttle concurrency. Discussions around this led to a proposed @Pooled or @MaxConcurrency annotation for the new version of the Jakarta Concurrency API.

A particular problem when doing concurrent programming in Jakarta EE is when, for example, an initial request thread holds a lot of contextual information, such as the current application for which the request is needed (for proper Java Naming and Directory Interface lookups), the authenticated identity, or the current active CDI scopes. When work starts in a new thread, some or all of that context needs to be transferred (propagated).

When the Jakarta Concurrency API was revived from initial work done in early 2000, the designers didn’t take CDI into account. This has been a major hindrance ever since because nothing concerning scopes propagates now in a portable way. To solve this problem, an explicit context propagation API is in the works. This API has been prototyped under MicroProfile, with a stated goal that it is to be included in the Jakarta Concurrency API.

Variants of CDI


With Jakarta EE likely having CDI alignment as one of its main themes, the main new feature that is being planned for CDI itself is another variant of CDI. The specification has already been split into three parts: Core CDI, CDI in Java SE, and CDI in Jakarta EE. The new variant, called CDI-Lite, will focus on build-time concerns, specifically detecting beans during build-time and providing a new kind of extension that can run during build-time.

There’s some interesting history here, because this is how EJB 1.0 actually worked; there was no reflection, and skeletons, stubs, and proxies were all generated using tools at build-time. Because this was seen as a lot of hassle, newer versions of EJB built those automatically at runtime using reflection, an approach later adopted by CDI. With CDI now explicitly supporting build-time, it’s gone full circle.

Plans for CDI-Lite are still greatly in flux, and it hasn’t even been decided yet whether CDI-Lite will be a proper subset of its higher layer, but potentially the stack could look approximately like the following:

  1. Jakarta CDI: A small set of key annotations, shared with Guice, HK2, and Spring, including @Inject, @Named, @Qualifier, and @Scope
  2. Jakarta CDI Lite: Beans, qualifiers (behavior), scopes (behavior), stereotypes, and build-time portable extensions
  3. Jakarta CDI Core: Alternatives, decorators, runtime portable extensions (potentially, the build-time extensions are excluded)
  4. Jakarta CDI EE: Rules for EJB beans and servlet components, bean names, and scope in expression language, specifically including JSF and JSP, built-in beans for Jakarta Transaction, Jakarta Security, and Jakarta Servlet

While most focus has been on CDI-Lite until now, some features for the main CDI functionality are planned as well. Many of those are specifically for the overall CDI-alignment theme, meaning that they are intended to make it easier for other APIs in Jakarta EE to integrate with CDI.

One such proposal concerns the introduction of executable methods, which effectively lets arbitrary business methods in beans use parameter injection. (Note that CDI already supports this for some callback methods.) An example would be the following:

@RequestScoped
public class MyBean {
      String hello(@ConfigOption("foo") String foo) {
   }
}

A framework such as Jakarta REST or JSF, but of course also application code itself, could then execute this method in some way. Perhaps something like:

beanManager.execute(bean, method);

Some APIs building on CDI struggle because they have fewer options to define or use certain things than CDI itself has, making them a second class citizens. Two examples concern bean-defining annotations and built-in beans.

At the moment, only CDI itself defines which annotations are bean defining. To truly integrate other APIs, they should also be able to create bean-defining annotations. This is something the next version of CDI will likely take a look at.

As discussed above, the CDI spec defines several built-in beans, and so do APIs such as Jakarta Security, JSF and, soon, Jakarta REST. The way this is typically done is via a CDI extension, which programmatically adds a Bean<T> instance. These are low-level types, so they have to find their own decorators and generate a proxy to apply them.

Unfortunately, there’s no portable API in CDI to find decorators and generate proxies, so many implementations of Jakarta APIs don’t actually do this. The result is that such built-in beans are not decoratable and also can’t be specialized, which can be quite problematic.

Built-in beans might also need the ability to obtain the current InjectionPoint. There’s currently no well-defined portable way to obtain such an InjectionPoint from within a Bean<T> instance. Making this possible is proposed for the next version of CDI.

Another proposed feature gives interceptors in CDI access to their actual (nonbinding) annotation parameters. Currently there’s no portable way to achieve this, so interceptors resort to looking at their target class and inspecting that. This works for interceptor annotations that are physically present on those classes, but it does not work for interceptors that have been dynamically added.

There are a few other CDI proposals that have been discussed less but are nevertheless worth mentioning.

The first is the ability to easily apply interceptors to built-in beans. Interceptors are easy to apply to your own code, but they are more troublesome to add to existing types. Using a producer that’s an @Alternative can use the InterceptionFactory, but then you need to get ahold of the type that the @Alternative overrides. This can be done using BeanManager#getBeans and some filtering, but it’s quite verbose. It would be much easier if this overridden instance (the instance that would have been selected for a type if you didn’t provide your alternative producer) could be injected directly.

The second issue concerns the programmatic API for obtaining bean instances. This API should provide the same expressive power to select instances that the declarative (injection) API has. At the moment, this is not the case for beans where the beans’ producer or Bean<T> makes use of an InjectionPoint. As a contrived example, consider the MicroProfile Config API, where a combination of the ConfigProperty qualifier and the name of the injected field is used to obtain the right configuration value. Via injection, this works as follows:

@Inject
@ConfigProperty
String foo;

The inputs to the selection mechanism here are string, ConfigProperty, and foo. The last part is something that can’t be provided to the programmatic selection mechanism today. In a proposed feature for CDI, this would be possible and would look something like the following:

CDI.current()
      .select(
           String.class, 
           new ConfigProperty.Literal(),
           injectionPoint().withMemberName("foo"))
       .get();

Other Jakarta EE 10 APIs


Several other Jakarta EE APIs have pending new features that have not been actively discussed as candidates for inclusion in Jakarta EE 10.

For example, Jakarta Persistence has ideas around adding support for transforming Java Persistence Query Language queries to the Criteria API and the other way around, adding higher-level pagination support (the well-known filtering, sorting, and paging paradigm), adding support for specifying which data a fetch graph should not fetch (as opposed to specifying what it should fetch), and supporting some smaller things such as allowing empty collections as a parameter in an in(…) clause.

Likewise, Jakarta Messaging has a lot of pending new features. During the Java EE 8 cycle, a number of them had actually been worked on quite a bit for what was to become Messaging 2.1 (which was never released). Specifically features for the CDI-alignment story have been proposed, such as CDI Message Consumers (having a CDI bean listen to incoming messages) and replacing the string-based activationConfig, which is in practice a rather thin layer on top of the original XML format used to configure message-driven beans. Smaller features include being able to easily send JSON- or XML-based messages.

There are also various new APIs in the works, for instance, NoSQL and model-view-controller, which may target Jakarta EE 10. For years now, there has also been talk about including a caching and a configuration API in Jakarta EE. A configuration API actually came to fruition but was developed in MicroProfile after an attempt for Jakarta EE was aborted during the Java EE 8 cycle.

Development of a temporary caching API started as early as 2001, in JSR 107: JCACHE. This was a candidate to include in Jakarta EE multiple times, but it never happened. Whether JCACHE will be transferred to Eclipse and finally be included in Jakarta EE 10 is a big question, and at this point, I don’t know the answer.

Source: oracle.com

Wednesday, November 1, 2023

The Ultimate Guide to JVM Developers: Mastering Java Virtual Machine

JVM Developers: Mastering Java Virtual Machine

Are you a tech enthusiast, a software developer, or a curious learner looking to dive into the world of Java Virtual Machine (JVM)? If you are, you've come to the right place. We, as SEO experts and high-end copywriters, are here to provide you with the ultimate resource on JVM developers, equipping you with all the knowledge you need to excel in the JVM ecosystem.

Understanding the JVM


The Java Virtual Machine (JVM) is a fundamental component of Java technology, responsible for executing Java applications. It's a virtualized platform that allows Java programs to run on a variety of hardware without modification. Let's dive deeper into what JVM is all about:

What is JVM?

JVM is a virtual machine that enables Java applications to run on various platforms without modification. It converts Java bytecode into machine code, making it a cross-platform solution.

How Does JVM Work?

JVM executes Java bytecode by Just-In-Time (JIT) compilation. It translates bytecode into machine code for the host CPU, ensuring optimal performance.

JVM Components

JVM comprises several key components, including the Class Loader, Execution Engine, and Java Native Interface (JNI). Each plays a vital role in the execution of Java programs.

Becoming a Proficient JVM Developer


Now that you have a grasp of what JVM is, let's explore how you can become a proficient JVM developer and master this versatile technology:

1. Learn Java Inside Out

To excel in JVM development, a strong foundation in Java is essential. Understand the language's syntax, features, and best practices.

2. Master the JVM Ecosystem

Familiarize yourself with the Java ecosystem, including tools like Maven, Gradle, and popular IDEs like IntelliJ IDEA and Eclipse.

3. Understanding JVM Internals

Delve deep into the inner workings of the JVM, learning about class loading, bytecode, garbage collection, and memory management.

4. Troubleshooting and Optimization

Learn to diagnose and optimize your Java applications. Proficiency in tools like JVisualVM and YourKit can make a significant difference in your development journey.

5. Java Frameworks and Libraries

Explore popular Java frameworks like Spring and libraries such as Apache Commons to enhance your Java development skills.

JVM Best Practices


To ensure your JVM-based applications are efficient and performant, consider these best practices:

1. Code Optimization

Write clean, efficient code that minimizes resource usage. Utilize profiling tools to identify bottlenecks and make improvements.

2. Garbage Collection Tuning

Understand the different garbage collection algorithms and tune them according to your application's needs for optimal memory management.

3. Thread Management

Effectively manage threads to maximize concurrency and ensure your application can handle multiple tasks concurrently.

4. Security Considerations

Be mindful of security practices when developing JVM-based applications, especially if they handle sensitive data.

5. Monitoring and Logging

Implement robust monitoring and logging to track application performance and troubleshoot issues proactively.

Advanced Topics in JVM Development


For those who want to take their JVM development skills to the next level, consider these advanced topics:

1. Java Performance Tuning

Learn the intricacies of JVM performance tuning, including JVM flags, heap management, and thread optimizations.

2. Cloud-Native JVM

Explore how JVM fits into the world of cloud-native development, containerization, and microservices.

3. JVM Alternatives

Discover alternative JVM implementations like GraalVM and OpenJ9, and evaluate when to use them.

4. Real-time Java

Explore real-time Java development and its applications in industries like finance and gaming.

Conclusion

Becoming a proficient JVM developer is a rewarding journey, opening doors to exciting career opportunities and the ability to create high-performance Java applications. With this comprehensive guide, you now have the knowledge and resources to master the Java Virtual Machine. Keep exploring, learning, and innovating, and you'll be well on your way to becoming a JVM expert.