In this document I show an example of using Use-site targets in Kotlin. This is a relatively misunderstood feature of Kotlin that gets greatly overlooked by developers used to working with annotations in frameworks like the Spring Framework. The idea is that Kotlin doesn't really has a strategy to go about annotations in data classes if we don't specify the target. In Java, the target was very tangible. In Kotlin is all about abstraction. There can be negatives about this or maybe not.
2. Topics of today
● Traditional data structures in Java
● Data classes in Kotlin
● Records in Java
● How do data classes look like
● Stereotyping with frameworks
● The problem with applying annotations
● How to fix the problem
● Maybe not a problem, maybe not
And just for fun…
…we’ll make examples with…
3. Who am I?
● Software Engineer for 10+ years
● Java, Kotlin, Scala Clojure, Groovy
● Studied at ISEL Lisboa in Computer Science and Telecom
Engineering
● Spring Professional 2020
● OCP11
● Kong Champion
4. How did Java structures
used to look like?
Note
Java structures and their
standard although has
changed a lot, it is still a
part of every JVM
oriented language.
(getter, setters, fields, parameters
and constructor
1995
probably?
5. public class GoldenGirlsJava {
public String goldenGirl1;
private final String goldenGirl2;
private final String goldenGirl3;
private final String goldenGirl4;
public GoldenGirlsJava() {
this.goldenGirl1 = "Dorothy Zbornak";
this.goldenGirl2 = "Rose Nylund";
this.goldenGirl3 = "Blanche Devereaux";
this.goldenGirl4 = "Sophia Petrillo";
}
public GoldenGirlsJava(
String goldenGirl1,
String goldenGirl2,
String goldenGirl3,
String goldenGirl4
) {
this.goldenGirl1 = goldenGirl1;
this.goldenGirl2 = goldenGirl2;
this.goldenGirl3 = goldenGirl3;
this.goldenGirl4 = goldenGirl4;
}
public String getGoldenGirl1() {
return goldenGirl1;
}
public void setGoldenGirl1(String goldenGirl1) {
this.goldenGirl1 = goldenGirl1;
}
public String getGoldenGirl2() {
return goldenGirl2;
}
public String getGoldenGirl3() {
return goldenGirl3;
}
public String getGoldenGirl4() {
return goldenGirl4;
}
@Override
public String toString() {
return "GoldenGirlsJava{" +
"goldenGirl1='" + goldenGirl1 + ''' +
", goldenGirl2='" + goldenGirl2 + '''
+
", goldenGirl3='" + goldenGirl3 + '''
+
", goldenGirl4='" + goldenGirl4 + '''
+
'}';
}
}
Fields
Properties
Mega code!
…however…
Attributes
Keeps state?
Accessors
Traditional Java
Getters
Setter
goldenGirl1
not final!
6. public static void main(String[] args) {
var goldenGirlsJava = new GoldenGirlsJava(
"Dorothy Zbornak",
"Rose Nylund",
"Blanche Devereaux",
"Sophia Petrillo"
);
System.out.println(goldenGirlsJava);
}
Traditional Java
Arguments
7. Lombok was a game
changer!
Note
Lombok, although being
able to simplify code, it
introduce the usage of
annotations which a lot
of developers found not
to be very easy to use
(all concepts still visible with an
annotation processor)
2009
8. @Getter
@Setter
@AllArgsConstructor
@ToString
public class GoldenGirlsLombok {
public String goldenGirl1;
private final String goldenGirl2;
private final String goldenGirl3;
private final String goldenGirl4;
public GoldenGirlsLombok() {
this.goldenGirl1 = "Dorothy Zbornak";
this.goldenGirl2 = "Rose Nylund";
this.goldenGirl3 = "Blanche Devereaux";
this.goldenGirl4 = "Sophia Petrillo";
}
}
Java with Lombok
Fields
Properties
Attributes
Keeps state?
Setter
goldenGirl1
not final!
Getter
Accessors
Massively reduced!
…however…
9. public static void main(String[] args) {
var goldenGirlsLombok = new GoldenGirlsLombok(
"Dorothy Zbornak",
"Rose Nylund",
"Blanche Devereaux",
"Sophia Petrillo"
);
System.out.println(goldenGirlsLombok);
}
Arguments
Java with Lombok
… the call itself doesn’t change
10. Data classes in Kotlin
changed that!
Note
Code simplification is
very important for many
developers. It can
arguably create
misleading expectations
for the average
developer
(only class members with all
concepts compacted in a few lines
of code)
2016
11. data class GoldenGirls(
var goldenGirl1: String = "Dorothy Zbornak",
private val goldenGirl2: String = "Rose Nylund",
private val goldenGirl3: String = "Blanche Devereaux",
private val goldenGirl4: String = "Sophia Petrillo"
)
Kotlin
Fields
Properties
Attributes
Keeps state?
Setter
goldenGirl1
not final!
Getter
Accessors
Even more reduced!
…however…
12. class GoldenGirlsLauncher {
companion object {
@JvmStatic
fun main(args: Array<String> = emptyArray()) {
val goldenGirls = GoldenGirls(
"Dorothy Zbornak",
"Rose Nylund",
"Blanche Devereaux",
"Sophia Petrillo"
)
}
}
}
Arguments
Kotlin
… the call itself doesn’t change again
…however…
13. And Java reacted to that
with records
Note
Java records didn’t
compacted everything.
Java keeps some old
semantics, but does
introduces that concept
of hiding the accessors
of a class
(same principle, less code and a
contentious accolade at the end
{}!)
2020
14. public record GoldenGirlsRecord(
String goldenGirl1,
String goldenGirl2,
String goldenGirl3,
String goldenGirl4
) {
public GoldenGirlsRecord() {
this(
"Dorothy Zbornak",
"Rose Nylund",
"Blanche Devereaux",
"Sophia Petrillo"
);
}
}
Java Records
Fields
Properties
Attributes
Keeps state?
No
Setter allowed
Getter
Accessors
The same, but records
are truly immutable
…however…
15. public static void main(String[] args) {
var goldenGirlsRecord = new GoldenGirlsRecord(
"Dorothy Zbornak",
"Rose Nylund",
"Blanche Devereaux",
"Sophia Petrillo"
);
System.out.println(goldenGirlsRecord);
}
Arguments
Java Records
… the call itself also doesn’t change
…however…
16. ● Source Repository
○ https://github.com/jesperancinha/jeorg-kotlin-test-drives
● Location Directory:
○ https://github.com/jesperancinha/jeorg-kotlin-test-drives/tree/main/jeorg-kotlin-crums/jeorg-
kotlin-crums-3/src/main/kotlin/org/jesperancinha/ktd/crums3/goldengirls
Use git clone from the command prompt to download the full code base:
> git clone https://github.com/jesperancinha/jeorg-kotlin-test-drives.git
You’ll be prompted for a username and password which should be your github account.
> cd jeorg-kotlin-crums/jeorg-kotlin-crums-
3/src/main/kotlin/org/jesperancinha/ktd/crums3/goldengirls
The easy way:
> make b
> make run
The manual way:
> mvn clean install
> mvn spring-boot:run
The Golden Girls Example location
17. Spring Framework
Issues when Kotlin
Note
CDI implemented with
Inversion of Control (IoC)
was groundbreaking
when Spring was
released. But along the
years it has been also
criticized because of its
“magic”
(annotations need to be assigned
to the previous concepts in
classes)
2004
18. @Entity
@Table(name = "shelf_case")
data class Case(
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE)
val id: Long?,
var designation: String?,
var weight: Long?
) public final class Case {
@Id
@GeneratedValue(
strategy = GenerationType.SEQUENCE
)
@Nullable
private final Long id;
@Nullable
private String designation;
@Nullable
private Long weight;
Decompiling
the bytecode to Java
Will this work?
Of course!
The annotations are
applied to fields in the
bytecode which is what
the JPA understands
Just using the annotations in the same way as always
19. ● Source Repository
○ https://github.com/jesperancinha/jeorg-spring-master-test-drives
● Location Directory:
○ https://github.com/jesperancinha/jeorg-spring-master-test-drives/tree/main/furniture-k-shop
Use git clone from the command prompt to download the full code base:
> git clone https://github.com/jesperancinha/jeorg-spring-master-test-drives.git
You’ll be prompted for a username and password which should be your github account.
> cd furniture-k-shop
The easy way:
> make b
> make run
The manual way:
> mvn clean install
> mvn spring-boot:run
Furniture Shop Example location
20. data class AccountNumbersPassiveDto(
@NotNull
val accountNumberLong: Long?,
val accountNumberNullable: Long?,
@DecimalMax(value = "10")
@DecimalMin(value = "5")
val accountNumber: BigDecimal,
val accountNumberEven: Int,
val accountNumberOdd: Int,
@Positive
val accountNumberPositive: Int,
@Negative
val accountNumberNegative: Int,
val accountNumberMaxList:Int
)
Will this work?
Not really … 🤔
public final class AccountNumbersPassiveDto {
@Nullable
private final Long accountNumberLong;
@Nullable
private final Long accountNumberNullable;
@NotNull
private final BigDecimal accountNumber;
private final int accountNumberEven;
private final int accountNumberOdd;
private final int accountNumberPositive;
private final int accountNumberNegative;
private final int accountNumberMaxList;
Decompiling
the bytecode to Java
No annotation has been applied to
a field! How is this possible?
Since we do not specify where the
annotation is supposed to be
applied, it gets applied however it
is configured by default.
@Nullable is applied because
that is how the bytecode
translates back to Java when
we mark it as final with val and
nullable with.
The solution?
Use-site targets!
The problem
21. data class AccountNumbersPassiveDto(
@field:NotNull
val accountNumberLong: Long?,
val accountNumberNullable: Long?,
@field:DecimalMax(value = "10")
@field:DecimalMin(value = "5")
val accountNumber: BigDecimal,
val accountNumberEven: Int,
val accountNumberOdd: Int,
@field:Positive
val accountNumberPositive: Int,
@field:Negative
val accountNumberNegative: Int,
val accountNumberMaxList:Int
)
Will this work?
Yes!👌
public final class AccountNumbersPassiveDto {
@NotNull
@Nullable
private final Long accountNumberLong;
@Nullable
private final Long accountNumberNullable;
@DecimalMax("10")
@DecimalMin("5")
@org.jetbrains.annotations.NotNull
private final BigDecimal accountNumber;
private final int accountNumberEven;
private final int accountNumberOdd;
Decompiling
the bytecode to Java
The annotations are now applied
as they are expected. Because we
now specify where the target of
our annotation should be, the
Kotlin compiler now knows
exactly where to use these
annotations in the bytecode
Use-site targets
…however…
22. ● Source Repository
○ https://github.com/jesperancinha/jeorg-spring-master-test-drives
● Location Directory:
○ https://github.com/jesperancinha/jeorg-spring-master-test-drives/tree/main/furniture-k-shop
Use git clone from the command prompt to download the full code base:
> git clone https://github.com/jesperancinha/jeorg-spring-master-test-drives.git
You’ll be prompted for a username and password which should be your github account.
> cd the-validation-company
The easy way:
> make b
> make run
The manual way:
> gradle build
> ./gradlew bootRun
The Validation Company Example location
23. The usage of
Use-site targets
Note
The continuous use of
use-site targets solves a
problem, but removes
the visibility of how we
used to see Java classes
and therefore fields,
properties, attributes,
getters, setters,
accessors and
arguments have
become an abstraction
and not something
tangible that we can see
and map in our mind the
way we used to. Java
records can be a part of
this same problem as
well.
(the use-site targets try to solve
this problem with current
frameworks)
25. Conclusions
● The targets are not longer that much visible as
they used to be before.
● Data classes and records are revolutionary in the
sense that they reduce boilerplate code and are of
course more elegant.
● Confusion related especially with annotations
costs a lot of time and effort especially for people
that just started out in Kotlin and are also
beginning with the Spring Framework.
● Efforts are underway to create a reliable
framework like the Spring Framework like Ktor
that uses other paradigms to implement a new
framework that no longer uses annotations.
The Future
● Considering the support of frameworks like
IntelliJ, was the boilerplate code a good reason?
● There is something better about java records in
relation to data classes and that is that they are
truly immutable in the sense that at least no
reference can be changed in them once created.
● Data classes in Kotlin and records in Java are
here to stay.
● In efforts to make languages simpler, we may
be heading into a direction where they actually
become more complex.
● With the rejection of boilerplate code, we
inadvertently also rejected a part of the code
that gave visibility to the targets for our
annotations.
● As immutability indeed has proven multiple
times to be a good paradigm to follow, this is
where the argument to use data classes and
records will rise, but Java appears to have a
better advantage here since by not giving the
option to make fields mutable in records, we
are sure to have a value that never changes
across its lifetime.
I guess we’ll
see later on
in the future
how it all
rolls out…
27. Resources
● https://kotlinlang.org
● Vasic, M. (21st May 2018). Building Applications with Spring 5 and Kotlin. (First Edition).
Packt Publishing
● Griffiths, D. Griffiths, D. (February 2019). Head First A Brain-Friendly Guide. (First
Edition). O'Reilly
● Skeen, J. Greenhalgh, D. (July 2018). Kotlin Programming - The Big Nerd Ranch Guide.
(First Edition). Big Nerd Ranch
● Jemerov, D. Isakova, S. (2017). Kotlin in Action. (First Edition). Manning Publications
Online
Books