{{announcement.body}}
{{announcement.title}}

Spring Tips: Java 14 (or: Can Your Java Do This?)

DZone 's Guide to

Spring Tips: Java 14 (or: Can Your Java Do This?)

Let's dive into Java 14 and Spring Boot to see how you can build a JDBC app that talks more easily with a SQL database than ever before.

· Java Zone ·
Free Resource

Hi, Spring fans! Welcome to another installment of Spring Tips! In this installment, we're going to look at the new features in Java 14 and their use in building Spring Boot-based applications.


To get started, we need to use the latest and greatest version of Java, Java 14, which isn’t — just yet — released yet. It is due to be shipped in early 2020. You can download early access releases on Java.net. You might also consider using SDKManager (sdk), which makes installing new JVM editions a trivial matter indeed.

Remember, there are new Java releases every 6 months. These new releases are usable in production but are only supported for the six months between one release and the next. Every now and then, the Java project also releases a long-term support (LTS) release. That release is currently Java 11. Java 14 is only a viable target for production until Java 15 comes out. And indeed, we’re going to look at a lot of preview features, which one might argue shouldn’t be in production at all. You’ve been warned!

If you’re using SDKManager, you can run the following incantation to get Java 14 installed.

Shell
 




xxxxxxxxxx
1


1
sdk install java 14.ea.36-open 



Go to the Spring Initializr and generate a new project using Spring Boot 2.3 or later. You’ll also need to select JDBC and PostgreSQL.

Older versions of Spring Boot don’t yet support the Java 14 runtime. Naturally, in order to edit this version of Java, you’ll need to import it into your IDE. Before you do that, though, let’s modify the pom.xml to configure the build to support Java 14. Normally, when you go to the Spring Initializr, you also specify a version of Java. Java 14 is not supported, yet, so we want to manually configure a few things.

Make sure that you specify the version of Java by changing the java.version property:

XML
 




xxxxxxxxxx
1


1
<properties>
2
    <java.version>14</java.version>
3
</properties>



This allows our build to use Java 14 and all the released features in that release, but to really experience the novelty of Java 14, we need to turn on the preview features — features that are shipped in the release but that are not active by default.

In the <plugins>...</plugins> stanza, add the following plugin configurations to enable Java 14’s preview features.

XML
 




xxxxxxxxxx
1
18


1
<plugin>
2
    <artifactId>maven-compiler-plugin</artifactId>
3
    <configuration>
4
        <release>14</release>
5
        <compilerArgs>
6
            <arg>--enable-preview</arg>
7
        </compilerArgs>
8
        <forceJavacCompilerUse>true</forceJavacCompilerUse>
9
        <parameters>true</parameters>
10
    </configuration>
11
</plugin>
12
 
           
13
<plugin>
14
    <artifactId>maven-surefire-plugin</artifactId>
15
    <configuration>
16
        <argLine>--enable-preview</argLine>
17
    </configuration>
18
</plugin>



Now you’re ready to go! Let’s look at some Java code. The Spring Initializr was nice enough to give us a project and a skeletal entry point class:

Java
 




xxxxxxxxxx
1
25


1
package com.example.fourteen;
2
 
           
3
import org.springframework.boot.SpringApplication;
4
import org.springframework.boot.autoconfigure.SpringBootApplication;
5
import org.springframework.boot.context.event.ApplicationReadyEvent;
6
import org.springframework.context.event.EventListener;
7
import org.springframework.jdbc.core.JdbcTemplate;
8
import org.springframework.jdbc.core.PreparedStatementCreatorFactory;
9
import org.springframework.jdbc.core.RowMapper;
10
import org.springframework.jdbc.core.SqlParameter;
11
import org.springframework.jdbc.support.GeneratedKeyHolder;
12
import org.springframework.stereotype.Component;
13
import org.springframework.stereotype.Service;
14
 
           
15
import java.sql.Types;
16
import java.util.List;
17
 
           
18
@SpringBootApplication
19
public class FourteenApplication {
20
 
           
21
    public static void main(String[] args) {
22
        SpringApplication.run(FourteenApplication.class, args);
23
    }
24
}



We’re going to create a simple JDBC-powered service that writes its data to the database using SQL. We’ll need an object that maps to the data in the database table people.

At this point, I’d normally either slog through writing the Javabean object using my IDE’s code-generation facilities, or I’d use Lombok to annotate my way to a compiler-synthesized object that has getters, setters, toString, and an implementation of equals. I might even make some begrudging reference to other languages’ ability to make this tedious kind of work trivial. Scala supports case classes. Kotlin supports data classes.

And Java 14 supports _record_s.

Java
 




xxxxxxxxxx
1


 
1
record Person(Integer id, String name, int emotionalState) {
2
}



Not bad eh? This syntax packs a wallop! It gives us a new object with a constructor and constructor parameters, properties, implementation of equals and toString and more. We can instantiate an instance of this object just as with any other object. Try to dereference properties in the object and you’ll that our constructor properties have become id()/id(int), name()/name(String), and emotionalState()/emotionalState(int). Not bad for so little!

Let’s look at the implementation of PeopleService.

The PeopleService uses the JdbcTemplate to make short work of turning results from a database query into Java objects. This should be fairly straightforward if you’ve ever used the JdbcTemplate (who hasn’t)? I’ve left some parts unimplemented so we can revisit those directly.

Java
 




xxxxxxxxxx
1
27


1
@Service
2
class PeopleService {
3
 
           
4
    private final JdbcTemplate template;
5
 
           
6
    //todo
7
    private final String findByIdSql = null;
8
 
           
9
    private final String insertSql = null; 
10
 
           
11
    private final RowMapper<Person> personRowMapper =
12
        (rs, rowNum) -> new Person(rs.getInt("id"), rs.getString("name"), rs.getInt("emotional_state"));
13
 
           
14
    PeopleService(JdbcTemplate template) {
15
        this.template = template;
16
    }
17
 
           
18
    public Person create(String name, EmotionalState state) {
19
         //todo
20
    }
21
 
           
22
    public Person findById(Integer id) {
23
        return this.template.queryForObject(this.findByIdSql, new Object[]{id}, this.personRowMapper);
24
    }
25
}



First and foremost, we’re going to use some SQL queries. I’ve got to great pains in my life to avoid having to type SQL queries in Java code. My goodness, would people so often have used ORMs if they knew they could eloquently express SQL queries as Java Strings? For anything even mildly complex, I extract my SQL queries into property files which are then loaded with Spring’s configuration property mechanism.

But, we can do better in Java 14! Multiline strings have come to Java at long last! It now joins the ranks of Python, Ruby, C++, C#, Rust, PHP, Kotlin, Scala, Groovy, Go, JavaScript, Clojure, and a dozen other languages besides. I’m so happy it’s finally here!

Replace the sql variables with the following declarations.

Java
 




xxxxxxxxxx
1
11


1
private final String findByIdSql =
2
    """
3
            select * from PEOPLE 
4
            where ID = ? 
5
    """;
6
 
           
7
    private final String insertSql =
8
    """
9
        insert into PEOPLE(name, emotional_state)
10
        values (?,?);
11
    """;



So nice, that! There are methods you can use to trim the margin and so on. You can also use the backslash escape sequence (\) at the end of each line to signal that the next line should start there, otherwise the newlines are interpreted literally.

Let’s look at that create method.

The storage of the Person’s emotionalState in the database as an int is an implementation detail. I’d prefer to not have to bubble that up to the user. Let’s use an enum to describe the emotional state for each Person:

Java
 




xxxxxxxxxx
1


1
enum EmotionalState {
2
    SAD, HAPPY, NEUTRAL
3
}



This is a start, I suppose. Let’s get to the implementation. Straight away we’re given an opportunity to use another nice new feature in Java 14: smarter switch expressions. Switch expressions give us a way to return a value from the branch of a switch case and then assign that to a variable. The syntax is almost identical to what we’ve used before, except that each case is set off from the branch with an arrow, ->, not :, and there’s no need for a break statement.

In the following example, we assign the int value to a variable index, whose type we don’t need to specify because of yet another nice feature in recent Java iterations, auto type inference with var.

Java
 




xxxxxxxxxx
1


 
1
    public Person create(String name, EmotionalState state) {
2
        var index = switch (state) {
3
            case SAD -> -1;
4
            case HAPPY -> 1;
5
            case NEUTRAL -> 0;
6
        };
7
        // todo 
8
    }



With the index in hand, we can create the requisite PreparedStatement required to execute the SQL statement against the database. We can execute that prepared statement and pass in a KeyHolder which will serve to collect the generated key returned from the newly inserted row.

Java
 




xxxxxxxxxx
1
20


 
1
    public Person create(String name, EmotionalState state) {
2
        var index = switch (state) {
3
            case SAD -> -1;
4
            case HAPPY -> 1;
5
            case NEUTRAL -> 0;
6
        };
7
        var declaredParameters = List.of(
8
            new SqlParameter(Types.VARCHAR, "name"), 
9
            new SqlParameter(Types.INTEGER, "emotional_state"));
10
        var pscf = new PreparedStatementCreatorFactory(this.insertSql, declaredParameters) {
11
            {
12
                setReturnGeneratedKeys(true);
13
                setGeneratedKeysColumnNames("id");
14
            }
15
        };
16
        var psc = pscf.newPreparedStatementCreator(List.of(name, index));
17
        var kh = new GeneratedKeyHolder();
18
        this.template.update(psc, kh);
19
        // todo
20
    }



The only trouble is that the key returned is a Number, not an Integer or a Double or anything more concrete. This gives us a chance to use yet another interesting new feature in Java 14, smart casting. Smart casting allows us to avoid a redundant cast after testing for a type in an instanceof test. It goes even further and gives us a variable name by which we can reference the automatically cast variable in the scope of the test.

Java
 




xxxxxxxxxx
1
23


1
    public Person create(String name, EmotionalState state) {
2
        var index = switch (state) {
3
            case SAD -> -1;
4
            case HAPPY -> 1;
5
            case NEUTRAL -> 0;
6
        };
7
        var declaredParameters = List.of(
8
            new SqlParameter(Types.VARCHAR, "name"), 
9
            new SqlParameter(Types.INTEGER, "emotional_state"));
10
        var pscf = new PreparedStatementCreatorFactory(this.insertSql, declaredParameters) {
11
            {
12
                setReturnGeneratedKeys(true);
13
                setGeneratedKeysColumnNames("id");
14
            }
15
        };
16
        var psc = pscf.newPreparedStatementCreator(List.of(name, index));
17
        var kh = new GeneratedKeyHolder();
18
        this.template.update(psc, kh);
19
        if (kh.getKey() instanceof Integer id) {
20
            return findById(id);
21
        }
22
        throw new IllegalArgumentException("we couldn't create the " + Person.class.getName() + "!");
23
    }



We needed an int to be able to pass it to findById(Integer), and this method does that work for us. Convenient, eh?

Everything’s working, so let’s exercise the code with a simple ApplicationListener<ApplicationReadyEvent:

Java
 




xxxxxxxxxx
1
16


 
1
@Component
2
class Runner {
3
 
           
4
    private final PeopleService peopleService;
5
 
           
6
    Runner(PeopleService peopleService) {
7
        this.peopleService = peopleService;
8
    }
9
 
           
10
    @EventListener(ApplicationReadyEvent.class)
11
    public void exercise() throws Exception {
12
        var elizabeth = this.peopleService.create("Elizabeth", EmotionalState.SAD);
13
        System.out.println(elizabeth);
14
    }
15
}



Run that and you’ll see that the object has been written to the database and — best of all — you got a spiffy new toString() result when printing the resulting Person object!

We’ve only begun to scratch the surface of all the new features in Java 14! There are a ton of new features in the language that we’ve begun to introduce in this video and considerably more features for security and performance in the runtime itself. I can not more heartily recommend that you find a way off of your older versions of the JDK (looking at you, Java 8 users!) and move to the newest ones.

Topics:
java ,java 14 ,jdbc ,sql

Published at DZone with permission of Joshua Long , DZone MVB. See the original article here.

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}