MicroProfile 4.0 Changes
MicroProfile 4.0 was released on December 2020 and announced here.
Let’s see the most relevant changes between 3.0 and 4.0 releases!
Changes that affect all modules
- Updated to use Jakarta EE 8 dependencies
MicroProfile Config 2.0
- Added bulk extraction of properties into POJO using @ConfigProperties
Now, we can inject a group of properties with the same prefix into a POJO.
For example, having these properties:
server.host = localhost
server.port=9080
server.endpoint=query
server.old.location=London
Then, we can map these properties onto a POJO class doing:
@ConfigProperties(prefix="server")
@ApplicationScoped
public class Details {
String host;
int port;
String endpoint;
@ConfigProperty(name="old.location")
String location;
}
- Property Expression Enhancements
The logic to resolve properties has been enhanced. For example, we can now do:
server.url=http://${server.host}/endpoint
server.host=example.org
And the property server.url will be http://example.org/endpoint.
Moreover, we can achieve the following syntact for property expressions:
server.url=http://${server.host:default.org}/endpoint # default properties
server.url=http://${host:${default-host}}/endpoint # nested properties
server.url=http://${server.host}/${path} # composed properties
server.url=\\${server.host} # no substitution: we would get "${server.host}"
Note that this can be a breaking change, so if you need to disable it by default, you need to set mp.config.property.expressions.enabled to false. |
Finally, we can inject directly ConfigValue in our Java classes:
@Dependent
public class MyBean {
@Inject
@ConfigProperty(name = "server.host")
private ConfigValue configValue;
// configValue.getName() -> server.host
// configValue.getValue() -> example.org
// configValue.getSourceName() -> where the value is coming from
// configValue.getSourceOrdinal() -> the ordinal of the injected value (depends on the source)
// configValue.getRawValue() -> No substitution: ${server.host}
}
- Added configuration profiles (ex: dev, testing, live)
We can now control the profile we’re using in MicroProfile using the property mp.config.profile.
And then we can make use of this properties via the properties:
%dev.vehicle.name=car
%live.vehicle.name=train
%testing.vehicle.name=bike
vehicle.name=lorry
Or directly via the properties files:
META-INF\microprofile-config.properties
META-INF\microprofile-config-dev.properties
META-INF\microprofile-config-prod.properties
META-INF\microprofile-config-testing.properties
- Changes in the ConfigSource interface
By adding your own implementation of the ConfigSource interface, you can provide a new way to inject properties into your application.
As an example, we have implemented a in memory config source where we’ll keep properties in a in memory map:
public class CustomConfigSource implements ConfigSource {
private static final int ORDINAL = 999;
Map<String, String> customProperties = new HashMap<>();
@Override
public Map<String, String> getProperties() {
return customProperties;
}
@Override
public int getOrdinal() {
return ORDINAL;
}
@Override
public String getValue(String propertyName) {
return customProperties.get(propertyName);
}
@Override
public String getName() {
return "Custom Config Source";
}
}
The above implementation will not compile when using MicroProfile 4.x since now the method getProperties() is optional and the method getPropertyNames has to be provided:
public class CustomConfigSource implements ConfigSource {
private static final int ORDINAL = 999;
Map<String, String> customProperties = new HashMap<>();
@Override
public Set<String> getPropertyNames() {
return customProperties.keySet();
}
@Override
public int getOrdinal() {
return ORDINAL;
}
@Override
public String getValue(String propertyName) {
return customProperties.get(propertyName);
}
@Override
public String getName() {
return "Custom Config Source";
}
}
- Empty values are no longer valid
Having this property:
server.url=
It will throw a NoSuchElementException exception now.
MicroProfile Health 3.0
- Default Readiness Statuses (UP/DOWN) for empty check responses
Prior to MicroProfile 4.0, the status for an empty response was always DOWN. Now, we can control the default status for empty responses by setting:
mp.health.default.readiness.empty.response=UP
- The annotation @Health has been removed
If you were implementation a custom health check such as:
@Health
@ApplicationScoped
public class GreetingHealthCheck implements HealthCheck {
@Override
public HealthCheckResponse call() {
return HealthCheckResponse.builder().name("greeting").up().build();
}
}
You would need to change the annotation @Health to either @Readiness or @Liveness depends on what you’re doing in the health check.
- HealthCheckResponse.state is renamed to status
Before:
@Produces
@Liveness
HealthCheck check() {
return () -> HealthCheckResponse.named("heap-memory").state(getMemUsage() < 0.9).build();
}
After:
@Produces
@Liveness
HealthCheck check() {
return () -> HealthCheckResponse.named("heap-memory").status(getMemUsage() < 0.9).build();
}
MicroProfile JWT Auth 1.2
- New method has been added to get claims from JsonWebToken
JsonWebToken token = ...
long issuedAtTime = token.getClaim(Claims.iat);
Set<String> groups = token.getClaim(Claims.groups);
// ...
- Support for JWT token cookies
Before, MicroProfile only supported the header Authentication to set the Bearer JWT token. Now, this token can be retrieved from the cookies by setting:
mp.jwt.token.header=Cookie # where to find the token. Either `Authorization` or `Cookie`
mp.jwt.token.cookie=mycookiename # the cookie name where to get the JWT token. Default is `Bearer`.
MicroProfile Metrics 3.0
- Scopes: Base, Application, Vendor
The following three sets of sub-resource (scopes) are exposed.
base: metrics that all MicroProfile vendors have to provide and exposed under/metrics/basevendor: vendor specific metrics (optional) and exposed under/metrics/application-
application: application-specific metrics (optional) and exposed under/metrics/vendor - Timer now exposes total elapsed time duration as a metric value
Having this bean with the @Timer annotation:
public class TimedMethodBean {
@Timed(name = "timedMethod")
public void timedMethod() {
// do something
}
}
Now, we can get the elapsed time this way:
public class TimedMethodBeanLookupTest {
private final static String TIMER_NAME = MetricRegistry.name(TimedMethodBean.class, "timedMethod");
private static MetricID timerMID;
@Inject
private MetricRegistry registry;
@Inject
private TimedMethodBean bean;
@Test
@InSequence(2)
public void getElapsedTime() {
Timer timer = registry.getTimers().get(timerMID);
// Call the timed method and assert it's been timed
bean.timedMethod();
// Make sure that the timer has been called
assertNotNull(timer.getElapsedTime());
}
- New REST metric for unmapped endpoints
This is a super useful enhancement to retrieve the number of unmapped REST endpoints has been invoked. The new metric name is called base_REST_request_unmappedException_total and counts the occurrences of unmapped exceptions for each REST endpoint.
- CDI producers annotated with @Metric no longer trigger metric registration
Before, when having this producer:
@Produces
@Metric(name = "hitPercentage")
@ApplicationScoped
protected Gauge<Double> createHitPercentage() {
return // ...
}
The hitPercentage metric was automatically registered. Now, we need to explicitly register it using the MetricRegistry:
@Inject
MeterRegistry registry;
@Inject
Gauge<Double> hitPercentageGauge;
// ...
registry.register("hitPercentage", hitPercentageGauge);
- MetricRegistry changed from abstract class to interface
- Changed
Timer.update(long duration, java.util.concurrent.TimeUnit)toTimer.update(java.time.Duration duration) - Changes in the
MetadataBuilderAPI
| Old Method | New Method | Accepts Null Values |
|---|---|---|
| withOptionalDisplayName | withDisplayName | Yes |
| withOptionalDescription | withDescription | Yes |
| withOptionalType | withType | Yes |
| withOptionalUnit | withUnit | Yes |
- Changes in the
MetadataAPI
Changed existing getDescription() and getUnit() methods to return String (before they returned Optional
MicroProfile Rest Client 2.0
- Added support for Server Sent Events
Server Sent Events, part of the HTML 5 spec, enable a server to push data to a client asynchronously via events, over HTTP and is now supported in MicroProfile Rest Client!
The MicroProfile Rest Client specification uses the Reactive Streams APIs to consume events:
@RegisterRestClient
public interface EventsClient {
@GET
@Path("/events/sse")
@Produces(MediaType.SERVER_SENT_EVENTS)
Publisher<String> getEvents();
}
- Added support for configuring HTTP proxy servers
If a REST endpoint needs to be accessed using a proxy server, we can now configure MicroProfile Rest Client to use it. Via properties:
com.sgitario.EventsClient/mp-rest/proxyAddress=myproxy.com:2000
Or programmatically:
EventsClient client = RestClientBuilder.newBuilder()
.proxyAddress("myproxy.com", 2000)
//...
.build(EventsClient.class);
- Added support for automatically following redirect requests
If a REST endpoint has been moved, we’ll get a 300 HTTP code to indicate the new location. Now, we can configure the MicroProfile Rest Client to automatically redirect onto the new location by configurating the property:
com.sgitario.EventsClient/mp-rest/followRedirects=true
Or by doing it in Java:
EventsClient client = RestClientBuilder.newBuilder()
.followRedirects(true)
//...
.build(EventsClient.class);
MicroProfile Fault Tolerance 3.0
- Metric names and scopes changed
There are two breaking changes in here: (1) the scope of the MicroProfile Fault Tolarence metrics have been moved from application to base; and (2) the method names are now a method tag.
Old metric:
application:ft.<name>.timeout.callsTimedOut.total
New metric:
base:ft.timeout.calls.total{method="<name>", timedOut="true"}
- Lifecycle of circuit breakers and bulkheads is now Singleton
Therefore, even when doing something like:
@ApplicationScoped // or @RequestScoped
public class MyService {
@CircuitBreaker(requestVolumeThreshold=2, failureRatio=0.5)
public void doSomething() {
// ...
}
}
The MyService bean will use the specified scope (Application or Request or other), but the circuit breaker will be a Singleton.
MicroProfile OpenAPI 2.0
- Added @SchemaProperty annotation
Previously, we had to use the same @Schema annotation to define properties, for example:
@Schema(name = "Car", description = "Car entity")
public class Car {
@Schema(description = "The vendor")
String vendor;
Engine engine;
}
@Schema(name = "Engine", description = "Engine entity")
public class Engine {
String type;
}
It would generate the next OpenAPI definition:
openapi: 3.0.3
info:
title: Generated API
version: "1.0"
paths:
components:
schemas:
Car:
description: Car entity
type: object
properties:
engine:
$ref: '#/components/schemas/Engine'
vendor:
description: The vendor
type: string
Engine:
description: Engine entity
type: object
properties:
type:
type: string
Now, we can do this using the new @SchemaProperty annotation in one go:
@Schema(name = "Car", description = "Car entity", properties = {
@SchemaProperty(name = "vendor", description = "The vendor"),
@SchemaProperty(name = "engine", implementation = Engine.class)
})
public class Car {
String vendor;
Engine engine;
}
This can be useful when having some complex class hierarchy.
- Added @APIResponseSchema annotation
These annotations ease the way to add descriptions to requests and responses.
Previously, to define a list of objects in an operation, we had to do:
@GET
@Operation(summary = "Retrieve all Cars")
@APIResponse(responseCode = "200", content = @Content(schema = @Schema(type = SchemaType.ARRAY, implementation = Car.class)))
public Response getCars() {
// ...
}
Now:
@GET
@Operation(summary = "Retrieve all Cars")
@APIResponseSchema(responseCode = "200", value = Car[].class) // or @APIResponseSchema(Car[].class)
public Response getCars() {
// ...
}
- Added @RequestBodySchema annotation
For requests body, we can now define the schema this way:
@POST
@Operation(summary = "Add Car")
@Produces(MediaType.TEXT_PLAIN)
public void updateCar(@RequestBodySchema(Car.class) Car car) {
// ...
}
- Added mp.openapi.schema MicroProfile Config property prefix
What if we want to add some description to some sources that we can’t modify like java.time.Instant? We can now do this using the mp.openapi.schema properties:
mp.openapi.schema.java.time.Instant = { \
"name": "EpochSeconds", \
"type": "number", \
"format": "int64", \
"title": "Epoch Seconds", \
"description": "Number of seconds from the epoch of 1970-01-01T00:00:00Z" \
}
- A lot of removals and updates in the OpenAPI API
If you would like to see the full list of breaking changes, go to here.
MicroProfile OpenTracing 2.0
API deletions:
ScopeManager.active(): no alternative, the reference Scope has to be kept explicitly since the scope was created.ScopeManager.activate(Span, boolean): no alternative auto-finishing has been removed.Scope.span(): use ScopeManager.activeSpan() or hold the reference to Span explicitly since the span was started.SpanBuilder.startActive(): use Tracer.activateSpan(Span) instead.Tracer.startManual(): use Tracer.start() instead.AutoFinishScopeManager: no alternative, auto-finishing has been removed.
