Slides of the talk we made with David Delabasee (@delabassee) at Devoxx 2015. A list of Java SE 8 patterns that you can use in Java EE 7 applications, thanks to the support of the major app servers.
3. @Delabassee @JosePaumard#Devoxx #SE84EE
Agenda
Java SE 8 has been released ~1.5 year ago
Java EE 7 Application Servers
- GlassFish, WildFly, Liberty Profile, WebLogic, etc.
Check some important Java SE 8 new features
for Java EE developers... with patterns!
8. The following is intended to outline our general product direction.
It is intended for information purposes only, and may not be
incorporated into any contract. It is not a commitment to deliver
any material, code, or functionality, and should not be relied upon
in making purchasing decisions. The development, release, and
timing of any features or functionality described for Oracle’s
products remains at the sole discretion of Oracle.
12. @Delabassee @JosePaumard#Devoxx #SE84EE
New Date & Time API
Date & Time API is a new introduction in Java 8
Initiated by Stephen Colebourne, based on Joda Time
Complete replacement of Date & Calendar
13. @Delabassee @JosePaumard#Devoxx #SE84EE
Date: ZonedTime
Useful for localized times
Set<String> allZonesIds = ZoneId.getAvailableZoneIds() ;
String ukTZ = ZoneId.of("Europe/Paris") ;
14. @Delabassee @JosePaumard#Devoxx #SE84EE
Date: ZonedTime
Useful for localized times
System.out.println(
ZonedDateTime.of(
1998, Month.JULY.getValue(), 12, // year / month / day
22, 0, 0, 0, // h / mn / s / nanos
ZoneId.of("Europe/Paris"))
); // prints 1998-07-22T22:00-00:01:15[Europe/London]
15. @Delabassee @JosePaumard#Devoxx #SE84EE
Date: ZonedTime
Useful for localized times
System.out.println(
ZonedDateTime.of(
1998, Month.JULY.getValue(), 12, // year / month / day
22, 0, 0, 0, // h / mn / s / nanos
ZoneId.of("Europe/Paris"))
);
ZonedDateTime nextWorldCup =
currentWorldCup.plus(Period.ofYear(4));
16. @Delabassee @JosePaumard#Devoxx #SE84EE
Date: ZonedTime
Useful for localized times
System.out.println(
ZonedDateTime.of(
1998, Month.JULY.getValue(), 12, // year / month / day
22, 0, 0, 0, // h / mn / s / nanos
ZoneId.of("Europe/Paris"))
);
ZonedDateTime nextWorldCup =
currentWorldCup.plus(Period.ofYear(4));
ZonedDateTime nexWorldCupJapan=
nextWorldCup.withZoneSameInstant(ZoneId.of("Asia/Tokyo")) ;
17. @Delabassee @JosePaumard#Devoxx #SE84EE
Date: bridges with java.util.Date
Conversions between j.u.Date and Date & Time
Time time = Time.from(localTime); // API -> legacy
LocalTime localTime = time.toLocalTime(); // legacy -> new API
Date date = Date.from(localDate); // API -> legacy
LocalDate localDate = date.toLocalDate(); // legacy -> new API
TimeStamp time = TimeStamp.from(instant); // API -> legacy
Instant instant = time.toInstant(); // legacy -> new API
Date date = Date.from(instant); // API -> legacy
Instant instant = date.toInstant(); // legacy -> new API
18. @Delabassee @JosePaumard#Devoxx #SE84EE
Usable in JPA
JPA does not support this Date & Time API (yet)
But with converters, we can still use it
@Entity
public abstract class AbstractPersistent {
@Convert(converter=DateConverter.class)
private Instant instant;
}
19. @Delabassee @JosePaumard#Devoxx #SE84EE
Usable in JPA
The converter is a classical object
public class DateConverter
implements AttributeConverter<Instant, Date> {
public Date convertToDatabaseColumn(Instant instant) {
return Date.from(instant);
}
public Instant convertToEntityAttribute(Date date) {
return date.toInstant();
}
}
20. @Delabassee @JosePaumard#Devoxx #SE84EE
Usable in JPA
The converter is a classical object
public class DateFormatConverter
implements AttributeConverter<ZonedDateTime, String> {
public String convertToDatabaseColumn(ZonedDateTime time) {
return DateTimeFormatter.ISO_DATE_TIME.format(time);
}
public ZonedDateTime convertToEntityAttribute(String formated) {
return DateTimeFormatter.ISO_DATE_TIME
.parse(formated, ZonedDateTime::from);
}
}
26. @Delabassee @JosePaumard#Devoxx #SE84EE
Annotations in Java 7
Wrapping annotations
An annotation cannot be applied more than once
@NamedQueries({
@NamedQuery(name=SELECT_ALL, query="..."),
@NamedQuery(name=COUNT_ALL, query="...")
})
public class Customer {
}
29. @Delabassee @JosePaumard#Devoxx #SE84EE
Bean Validation
Here is the Bean Validation code for @CheckCar
@Target({PARAMETER})
@Retention(RUNTIME)
@Constraint(validatedBy = CarValidator.class)
public @interface CheckCar {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
30. @Delabassee @JosePaumard#Devoxx #SE84EE
Bean Validation
And for the validator
public class CarValidator
implements ConstraintValidator<CheckCar, Car> {
private String carBrand;
public void initialize(CheckCar constraintAnnotation) {
this.carBrand = constraintAnnotation.value();
}
public boolean isValid(Car obj, ConstraintValidatorContext ctrCtx) {
if (object == null) return true;
return (!obj.getBrand().toUpperCase().contains(carBrand));
}
}
31. @Delabassee @JosePaumard#Devoxx #SE84EE
Bean Validation
And the Java 8 trick to make it repeatable
@Target({PARAMETER})
@Retention(RUNTIME)
@Repeatable(CheckCars.class)
@Constraint(validatedBy = CarValidator.class)
public @interface CheckCar {
Class<?>[] groups() default {};
Class<? extends Payload>[] payload() default {};
String value();
}
32. @Delabassee @JosePaumard#Devoxx #SE84EE
Bean Validation
And the Java 8 trick to make it repeatable
@Target({PARAMETER})
@Retention(RUNTIME)
public @interface CheckCars {
CheckCar[] value() default{};
}
34. @Delabassee @JosePaumard#Devoxx #SE84EE
Type annotations
Annotations can be now put on types
Example 1: tell that a variable should not be null
private @NonNull List<Person> persons = ... ;
35. @Delabassee @JosePaumard#Devoxx #SE84EE
Type annotations
Annotations can be now put on types
Example 1: tell that a variable should not be null
Example 2: the content should not be null neither
private @NonNull List<Person> persons = ... ;
private @NonNull List<@NonNull Person> persons = ... ;
38. @Delabassee @JosePaumard#Devoxx #SE84EE
Joining strings
StringJoiner class
And it prints:
StringJoiner sj = new StringJoiner(", ") ;
sj.add("one").add("two").add("three") ;
String s = sj.toString() ;
System.out.println(s) ;
> one, two, three
39. @Delabassee @JosePaumard#Devoxx #SE84EE
Joining strings
StringJoiner with prefix / postfix
And it prints:
StringJoiner sj = new StringJoiner(", ", "{", "}") ;
sj.add("one").add("two").add("three") ;
String s = sj.toString() ;
System.out.println(s) ;
> {one, two, three}
40. @Delabassee @JosePaumard#Devoxx #SE84EE
Joining strings
Also accessible from the String class
And it prints:
// From the String class, with a vararg
String s = String.join(", ", "one", "two", "three");
System.out.println(s);
> one, two, three
41. @Delabassee @JosePaumard#Devoxx #SE84EE
Joining strings
And directly from a List
// From a list to a String using the Stream & Collectors API
String s =
listOfStrings.stream()
.collect(Collectors.joining(", "));
System.out.println(s);
> one, two, three
42. @Delabassee @JosePaumard#Devoxx #SE84EE
Joining strings
And directly from a List
The result is
// From the String class, with a vararg
String s =
listOfStrings.stream()
.collect(Collectors.joining(", ", "{", "}"));
System.out.println(s);
> {one, two, three}
43. @Delabassee @JosePaumard#Devoxx #SE84EE
Support for Base64 decoding
After a few years…
import java.util.Base64;
String txt = "Modern Times!";
String encoded = Base64.getEncoder().encodeToString(
txt.getBytes(StandardCharsets.UTF_8));
String decoded = new String(Base64.getDecoder().decode(encoded),
StandardCharsets.UTF_8);
47. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
48. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
49. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
50. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
51. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
52. @Delabassee @JosePaumard#Devoxx #SE84EE
Readable and efficient patterns
Extract histograms from data
List<Person> people = Arrays.asList();
Map<City, Double> agesByCity =
people.stream()
.filter(p -> p.getAge() > 20)
.collect(
Collectors.groupingBy(
Person::getCity,
Collectors.averagingDouble(Person::getAge)
)
);
53. @Delabassee @JosePaumard#Devoxx #SE84EE
Building a Stream on a String
Building a Stream on the letters of a String:
String s = "hello";
IntStream stream = s.chars(); // stream on the letters of s
54. @Delabassee @JosePaumard#Devoxx #SE84EE
Building a Stream on a String
Building a Stream on the letters of a String:
String s = "hello";
IntStream stream = s.chars(); // stream on the letters of s
s.chars() // IntStream
.mapToObj(letter -> (char)letter) // Stream<Character>
.map(Character::toUpperCase)
.forEach(System.out::println); // Prints a Character
> HELLO
55. @Delabassee @JosePaumard#Devoxx #SE84EE
Build a Stream from a text file
Find the first error line from a log file
// Java 7 : try with resources and use of Paths
Path path = Paths.get("d:", "tmp", "debug.log");
try (Stream<String> stream = Files.lines(path)) {
stream.filter(line -> line.contains("ERROR"))
.findFirst()
.ifPresent(System.out::println);
} catch (IOException ioe) {
// handle the exception
}
56. @Delabassee @JosePaumard#Devoxx #SE84EE
Build a Stream from a regex
Building a Stream from a regexp
// book is a looooooooong String
Stream<String> words =
Pattern
.compile(" ")
.splitAsStream(book) ;
57. @Delabassee @JosePaumard#Devoxx #SE84EE
Build a Stream from a regex
Building a Stream from a regexp
More efficient than:
// book is a looooooooong String
Stream<String> words =
Pattern
.compile(" ")
.splitAsStream(book) ;
// book is a looooooooong String
Stream<String> words =
Stream.of(book.split(" "));
58. @Delabassee @JosePaumard#Devoxx #SE84EE
Flatmap: Files.lines + regex
Splitting a text files into words
Stream<String> streamOfLines = Files.lines(path);
Function<String, Stream<String>> splitter =
line -> Pattern.compile(" ").splitAsStream(line);
long numberOfWords =
streamOfLines.flatMap(splitter)
.map(String::toLowerCase)
.distinct()
.count();
59. @Delabassee @JosePaumard#Devoxx #SE84EE
Why is it important for Java EE?
Given this JSON file
[
{
"name":"Duke",
"gender":"m",
"phones":[
"home":"650‐123‐4567",
"mobile":"650‐111‐2222"
]
},
{
"name":"Jane", …
]
JsonArray contacts =
Json.createArrayBuilder()
.add(…
60. @Delabassee @JosePaumard#Devoxx #SE84EE
Why is it important for Java EE?
And some JSON-P magic
Map<String, Long> names =
contacts.getValuesAs(JsonObject.class).stream()
.filter(x -> "f".equals(x.getString("gender")))
.map(x -> (x.getString("name")))
.collect(
Collectors.groupingBy(
Function.identity(),
Collectors.counting()
)
);
62. @Delabassee @JosePaumard#Devoxx #SE84EE
Why is it important for Java EE?
// Today
JsonArray names =
contacts.getValuesAs(JsonObject.class).stream()
.filter(x -> "f".equals(x.getString("gender")))
.map(x -> (x.getString("name")))
.collect(
Collector.of(
Json::createArrayBuilder,
JsonArrayBuilder::add,
JsonArrayBuilder::add,
JsonArrayBuilder::build
)
);
63. @Delabassee @JosePaumard#Devoxx #SE84EE
Why is it important for Java EE?
// Today
JsonArray names =
contacts.getValuesAs(JsonObject.class).stream()
.filter(x -> "f".equals(x.getString("gender")))
.map(x -> (x.getString("name")))
.collect(
Collectors.collectingAndThen(
Collector.of(
Json::createArrayBuilder,
JsonArrayBuilder::add,
JsonArrayBuilder::add
),
JsonArrayBuilder::build
)
);
64. @Delabassee @JosePaumard#Devoxx #SE84EE
Why is it important for Java EE?
Collect into JSON
Avaiblable through the JsonCollectors factory
// Tomorrow, with JSON-P 1.1
JsonArray names =
contacts.getValuesAs(JsonObject.class).stream()
.filter(x -> "f".equals(x.getString("gender")))
.map(x -> (x.getString("name")))
.collect(
JsonCollectors.toJsonArray()
);
66. @Delabassee @JosePaumard#Devoxx #SE84EE
A warning on parallelism
All parallel operations (Streams, ConcurrentHashMap)
take place in the common ForkJoinPool
The common ForkJoinPool takes all the
available cores / CPUs
67. @Delabassee @JosePaumard#Devoxx #SE84EE
A warning on parallelism
All parallel operations (Streams, ConcurrentHashMap)
take place in the common ForkJoinPool
The common ForkJoinPool takes all the
available cores / CPUs
To preserve your Application Server:
System.setProperty(
"java.util.concurrent.ForkJoinPool.common.parallelism", "1") ;
70. @Delabassee @JosePaumard#Devoxx #SE84EE
Creation of an asynchronous task
The Jersey way to create an asynchronous call
@Path("/resource")
public class AsyncResource {
@GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
new Thread(new Runnable() {
public void run() {
String result = longOperation();
asyncResponse.resume(result);
}
}).start();
}
}
71. @Delabassee @JosePaumard#Devoxx #SE84EE
Creation of an asynchronous task
(let us fix this code with Java 8)
@Path("/resource")
public class AsyncResource {
@GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
executor.submit(() -> {
String result = longOperation();
asyncResponse.resume(result);
});
}
}
72. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
We want to check if the result object
is passed to the resume() method of the asyncResponse
It is a very basic test, but tricky to write since we are in an
asynchronous world
We have mocks for that!
73. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
We can inject a mock AsyncResponse, even mock the result
Then verify the correct interaction:
But we need to verify this once the run() method has been
called…
Mockito.verify(mockAsyncResponse).resume(result);
74. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
This is where CompletionStage come to the rescue!
@Path("/resource")
public class AsyncResource {
@Inject ExecutorService executor;
@GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
executor.submit(() -> {
String result = longOperation();
asyncResponse.resume(result);
});
}
}
75. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
@Path("/resource")
public class AsyncResource {
@Inject ExecutorService executor;
@GET
public void asyncGet(@Suspended final AsyncResponse asyncResponse) {
executeAsync(asyncResponse);
}
public CompletableFuture<Void> executeAsync(final AsyncResponse asyncResponse) {
return CompletableFuture.runAsync(() -> {
asyncResponse.resume(longOperation());
}, executor);
}
}
76. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
AsyncResource asyncResource = new AsyncResource();
asyncResource.executeAsync(mockAsyncResponse); // returns the CompletableFuture
.thenRun(() -> { // then execute this Runnable
Mockito.verify(mockAsyncResponse).resume(result);
}
);
77. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
Be careful of visibility issues
1) It’s better to run this in the same thread
2) Since the mocks are used and checked in this thread,
create them in this thread too
78. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
So the complete pattern becomes this one
1) First we create our mocks
String result = Mockito.mock(String.class);
AsyncResponse response = Mockito.mock(AsyncResponse.class);
Runnable train = () -> Mockito.doReturn(result).when(response.longOperation());
Runnable verify = () -> Mockito.verify(response).resume(result);
79. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
So the complete pattern becomes this one
2) Then we create the call & verify
Runnable callAndVerify = () -> {
asyncResource.executeAsync(response).thenRun(verify);
}
80. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
So the complete pattern becomes this one
3) Then we create the task
ExecutorService executor = Executors.newSingleThreadExecutor();
AsynResource asyncResource = new AsyncResource();
asyncResource.setExecutorService(executor);
CompletableFuture
.runAsync(train, executor) // this trains our mocks
.thenRun(callAndVerify); // this verifies our mocks
81. @Delabassee @JosePaumard#Devoxx #SE84EE
How to test it?
Since a CompletableFuture is also a Future, we can fail
with a timeout if the test does not complete fast enough
ExecutorService executor = Executors.newSingleThreadExecutor();
AsynResource asyncResource = new AsyncResource();
asyncResource.setExecutorService(executor);
CompletableFuture
.runAsync(train, executor) // this trains our mocks
.thenRun(callAndVerify) // this verifies our mocks
.get(10, TimeUnit.SECONDS);
82. @Delabassee @JosePaumard#Devoxx #SE84EE
Conclusion
Java 8 is not just about lambdas
There are also many new and very useful patterns
Ready to be used before becoming a lambda ninja!
Not covered:
- Collection framework
- Concurrency, Concurrent hashmap
- JavaScript on the JVM with Nashorn
- Security