diff --git a/.github/workflows/gradle-build.yml b/.github/workflows/gradle-build.yml
index c24c121b1..fcf8c2d61 100644
--- a/.github/workflows/gradle-build.yml
+++ b/.github/workflows/gradle-build.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ '17' ]
+ java: [ '24' ]
steps:
- uses: actions/checkout@v4
diff --git a/.github/workflows/maven-build.yml b/.github/workflows/maven-build.yml
index a1ec4dab7..306d9da97 100644
--- a/.github/workflows/maven-build.yml
+++ b/.github/workflows/maven-build.yml
@@ -15,7 +15,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- java: [ '17' ]
+ java: [ '24' ]
steps:
- uses: actions/checkout@v4
diff --git a/.mvn/jvm.config b/.mvn/jvm.config
new file mode 100644
index 000000000..32599cefe
--- /dev/null
+++ b/.mvn/jvm.config
@@ -0,0 +1,10 @@
+--add-exports jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED
+--add-exports jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED
+--add-opens jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED
diff --git a/README.md b/README.md
index 1b983ee96..2a756bbf9 100644
--- a/README.md
+++ b/README.md
@@ -13,7 +13,8 @@ See the presentation here:
## Run Petclinic locally
-Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/). You can build a JAR file and run it from the command line (it should work just as well with Java 17 or newer):
+Spring Petclinic is a [Spring Boot](https://spring.io/guides/gs/spring-boot) application built using [Maven](https://spring.io/guides/gs/maven/) or [Gradle](https://spring.io/guides/gs/gradle/).
+Java 24 or later is required for the build, but the application can run with Java 17 or newer:
```bash
git clone https://github.com/spring-projects/spring-petclinic.git
@@ -97,7 +98,7 @@ There is a `petclinic.css` in `src/main/resources/static/resources/css`. It was
The following items should be installed in your system:
-- Java 17 or newer (full JDK, not a JRE)
+- Java 24 or newer (full JDK, not a JRE)
- [Git command line tool](https://help.github.com/articles/set-up-git)
- Your preferred IDE
- Eclipse with the m2e plugin. Note: when m2e is available, there is an m2 icon in `Help -> About` dialog. If m2e is
diff --git a/build.gradle b/build.gradle
index 401094108..cf980abe1 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1,22 +1,23 @@
plugins {
id 'java'
id 'checkstyle'
- id 'org.springframework.boot' version '3.5.6'
+ id 'org.springframework.boot' version '4.0.0-M3'
id 'io.spring.dependency-management' version '1.1.7'
id 'org.graalvm.buildtools.native' version '0.11.1'
id 'org.cyclonedx.bom' version '3.0.0'
id 'io.spring.javaformat' version '0.0.47'
id "io.spring.nohttp" version "0.0.11"
+ id 'net.ltgt.errorprone' version '4.3.0'
}
gradle.startParameter.excludedTaskNames += [ "checkFormatAot", "checkFormatAotTest" ]
group = 'org.springframework.samples'
-version = '3.5.0'
+version = '4.0.0-SNAPSHOT'
java {
toolchain {
- languageVersion = JavaLanguageVersion.of(17)
+ languageVersion = JavaLanguageVersion.of(24)
}
}
@@ -29,6 +30,8 @@ ext.springJavaformatCheckstyleVersion = "0.0.47"
ext.webjarsLocatorLiteVersion = "1.1.1"
ext.webjarsFontawesomeVersion = "4.7.0"
ext.webjarsBootstrapVersion = "5.3.8"
+ext.errorProneVersion = "2.42.0"
+ext.nullAwayVersion = "0.12.10"
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-cache'
@@ -48,12 +51,15 @@ dependencies {
runtimeOnly 'org.postgresql:postgresql'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
+ testImplementation 'org.springframework.boot:spring-boot-starter-restclient'
testImplementation 'org.springframework.boot:spring-boot-testcontainers'
testImplementation 'org.springframework.boot:spring-boot-docker-compose'
testImplementation 'org.testcontainers:junit-jupiter'
testImplementation 'org.testcontainers:mysql'
checkstyle "io.spring.javaformat:spring-javaformat-checkstyle:${springJavaformatCheckstyleVersion}"
checkstyle "com.puppycrawl.tools:checkstyle:${checkstyleVersion}"
+ errorprone "com.google.errorprone:error_prone_core:${errorProneVersion}"
+ errorprone "com.uber.nullaway:nullaway:${nullAwayVersion}"
}
tasks.named('test') {
@@ -70,6 +76,21 @@ checkstyleNohttp {
configFile = file('src/checkstyle/nohttp-checkstyle.xml')
}
+tasks.withType(JavaCompile).configureEach {
+ options.release = 17
+ options.errorprone {
+ disableAllChecks = true
+ }
+ if (name.equals("compileJava")) {
+ options.errorprone {
+ error("NullAway")
+ option("NullAway:OnlyNullMarked", "true")
+ option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract")
+ option("NullAway:JSpecifyMode", "true")
+ }
+ }
+}
+
tasks.named("formatMain").configure { dependsOn("checkstyleMain") }
tasks.named("formatMain").configure { dependsOn("checkstyleNohttp") }
diff --git a/pom.xml b/pom.xml
index 004709a9b..b3b9e3783 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,20 +5,21 @@
org.springframework.boot
spring-boot-starter-parent
- 3.5.6
+ 4.0.0-M3
org.springframework.samples
spring-petclinic
- 3.5.0-SNAPSHOT
+ 4.0.0-SNAPSHOT
petclinic
- 17
+ 24
+ 17
UTF-8
UTF-8
@@ -30,13 +31,14 @@
4.7.0
11.1.0
+ 2.42.0
0.8.13
0.3.4
1.0.0
3.6.0
0.0.11
+ 0.12.10
0.0.47
-
@@ -70,6 +72,11 @@
spring-boot-starter-test
test
+
+ org.springframework.boot
+ spring-boot-starter-restclient
+ test
+
@@ -271,6 +278,38 @@
false
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ default-compile
+ compile
+
+ compile
+
+
+
+ -XDcompilePolicy=simple
+ --should-stop=ifError=FLOW
+ -Xplugin:ErrorProne -XepDisableAllChecks -Xep:NullAway:ERROR -XepOpt:NullAway:OnlyNullMarked=true -XepOpt:NullAway:CustomContractAnnotations=org.springframework.lang.Contract -XepOpt:NullAway:JSpecifyMode=true
+
+
+
+ com.google.errorprone
+ error_prone_core
+ ${error-prone.version}
+
+
+ com.uber.nullaway
+ nullaway
+ ${nullaway.version}
+
+
+
+
+
+
@@ -288,43 +327,6 @@
-
-
-
- true
-
- spring-snapshots
- Spring Snapshots
- https://repo.spring.io/snapshot
-
-
-
- false
-
- spring-milestones
- Spring Milestones
- https://repo.spring.io/milestone
-
-
-
-
-
- true
-
- spring-snapshots
- Spring Snapshots
- https://repo.spring.io/snapshot
-
-
-
- false
-
- spring-milestones
- Spring Milestones
- https://repo.spring.io/milestone
-
-
-
css
diff --git a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
index 4999f4c3c..63b15b49e 100644
--- a/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
+++ b/src/main/java/org/springframework/samples/petclinic/PetClinicRuntimeHints.java
@@ -16,6 +16,8 @@
package org.springframework.samples.petclinic;
+import org.jspecify.annotations.Nullable;
+
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.RuntimeHintsRegistrar;
import org.springframework.samples.petclinic.model.BaseEntity;
@@ -25,7 +27,7 @@ import org.springframework.samples.petclinic.vet.Vet;
public class PetClinicRuntimeHints implements RuntimeHintsRegistrar {
@Override
- public void registerHints(RuntimeHints hints, ClassLoader classLoader) {
+ public void registerHints(RuntimeHints hints, @Nullable ClassLoader classLoader) {
hints.resources().registerPattern("db/*"); // https://github.com/spring-projects/spring-boot/issues/32654
hints.resources().registerPattern("messages/*");
hints.resources().registerPattern("mysql-default-conf");
diff --git a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java b/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java
index 6babed56d..45255a40a 100644
--- a/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java
+++ b/src/main/java/org/springframework/samples/petclinic/model/BaseEntity.java
@@ -21,6 +21,7 @@ import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object with an id property. Used as a base class for objects
@@ -34,13 +35,13 @@ public class BaseEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
- private Integer id;
+ private @Nullable Integer id;
- public Integer getId() {
+ public @Nullable Integer getId() {
return id;
}
- public void setId(Integer id) {
+ public void setId(@Nullable Integer id) {
this.id = id;
}
diff --git a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java
index d4be03e9e..536271d7f 100644
--- a/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java
+++ b/src/main/java/org/springframework/samples/petclinic/model/NamedEntity.java
@@ -18,6 +18,7 @@ package org.springframework.samples.petclinic.model;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotBlank;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object adds a name property to BaseEntity. Used as
@@ -32,19 +33,20 @@ public class NamedEntity extends BaseEntity {
@Column(name = "name")
@NotBlank
- private String name;
+ private @Nullable String name;
- public String getName() {
+ public @Nullable String getName() {
return this.name;
}
- public void setName(String name) {
+ public void setName(@Nullable String name) {
this.name = name;
}
@Override
public String toString() {
- return this.getName();
+ String name = this.getName();
+ return (name != null) ? name : "";
}
}
diff --git a/src/main/java/org/springframework/samples/petclinic/model/Person.java b/src/main/java/org/springframework/samples/petclinic/model/Person.java
index 7ee1f0397..6513b0866 100644
--- a/src/main/java/org/springframework/samples/petclinic/model/Person.java
+++ b/src/main/java/org/springframework/samples/petclinic/model/Person.java
@@ -18,6 +18,7 @@ package org.springframework.samples.petclinic.model;
import jakarta.persistence.Column;
import jakarta.persistence.MappedSuperclass;
import jakarta.validation.constraints.NotBlank;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object representing an person.
@@ -29,25 +30,25 @@ public class Person extends BaseEntity {
@Column(name = "first_name")
@NotBlank
- private String firstName;
+ private @Nullable String firstName;
@Column(name = "last_name")
@NotBlank
- private String lastName;
+ private @Nullable String lastName;
- public String getFirstName() {
+ public @Nullable String getFirstName() {
return this.firstName;
}
- public void setFirstName(String firstName) {
+ public void setFirstName(@Nullable String firstName) {
this.firstName = firstName;
}
- public String getLastName() {
+ public @Nullable String getLastName() {
return this.lastName;
}
- public void setLastName(String lastName) {
+ public void setLastName(@Nullable String lastName) {
this.lastName = lastName;
}
diff --git a/src/main/java/org/springframework/samples/petclinic/model/package-info.java b/src/main/java/org/springframework/samples/petclinic/model/package-info.java
index e8982c3d4..f878fe8b7 100644
--- a/src/main/java/org/springframework/samples/petclinic/model/package-info.java
+++ b/src/main/java/org/springframework/samples/petclinic/model/package-info.java
@@ -17,4 +17,7 @@
/**
* The classes in this package represent utilities used by the domain.
*/
+@NullMarked
package org.springframework.samples.petclinic.model;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java
index 63e3acc7a..000c06292 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/Owner.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/Owner.java
@@ -17,6 +17,7 @@ package org.springframework.samples.petclinic.owner;
import java.util.ArrayList;
import java.util.List;
+import java.util.Objects;
import org.springframework.core.style.ToStringCreator;
import org.springframework.samples.petclinic.model.Person;
@@ -32,6 +33,7 @@ import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.NotBlank;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object representing an owner.
@@ -49,43 +51,43 @@ public class Owner extends Person {
@Column(name = "address")
@NotBlank
- private String address;
+ private @Nullable String address;
@Column(name = "city")
@NotBlank
- private String city;
+ private @Nullable String city;
@Column(name = "telephone")
@NotBlank
@Pattern(regexp = "\\d{10}", message = "{telephone.invalid}")
- private String telephone;
+ private @Nullable String telephone;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "owner_id")
@OrderBy("name")
private final List pets = new ArrayList<>();
- public String getAddress() {
+ public @Nullable String getAddress() {
return this.address;
}
- public void setAddress(String address) {
+ public void setAddress(@Nullable String address) {
this.address = address;
}
- public String getCity() {
+ public @Nullable String getCity() {
return this.city;
}
- public void setCity(String city) {
+ public void setCity(@Nullable String city) {
this.city = city;
}
- public String getTelephone() {
+ public @Nullable String getTelephone() {
return this.telephone;
}
- public void setTelephone(String telephone) {
+ public void setTelephone(@Nullable String telephone) {
this.telephone = telephone;
}
@@ -104,7 +106,7 @@ public class Owner extends Person {
* @param name to test
* @return the Pet with the given name, or null if no such Pet exists for this Owner
*/
- public Pet getPet(String name) {
+ public @Nullable Pet getPet(String name) {
return getPet(name, false);
}
@@ -113,11 +115,11 @@ public class Owner extends Person {
* @param id to test
* @return the Pet with the given id, or null if no such Pet exists for this Owner
*/
- public Pet getPet(Integer id) {
+ public @Nullable Pet getPet(Integer id) {
for (Pet pet : getPets()) {
if (!pet.isNew()) {
Integer compId = pet.getId();
- if (compId.equals(id)) {
+ if (Objects.equals(compId, id)) {
return pet;
}
}
@@ -131,7 +133,7 @@ public class Owner extends Person {
* @param ignoreNew whether to ignore new pets (pets that are not saved yet)
* @return the Pet with the given name, or null if no such Pet exists for this Owner
*/
- public Pet getPet(String name, boolean ignoreNew) {
+ public @Nullable Pet getPet(String name, boolean ignoreNew) {
for (Pet pet : getPets()) {
String compName = pet.getName();
if (compName != null && compName.equalsIgnoreCase(name)) {
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
index 1348457ee..41b99619e 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerController.java
@@ -16,6 +16,7 @@
package org.springframework.samples.petclinic.owner;
import java.util.List;
+import java.util.Objects;
import java.util.Optional;
import org.springframework.data.domain.Page;
@@ -34,6 +35,8 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.servlet.ModelAndView;
import jakarta.validation.Valid;
+import org.jspecify.annotations.Nullable;
+
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
@@ -60,7 +63,7 @@ class OwnerController {
}
@ModelAttribute("owner")
- public Owner findOwner(@PathVariable(name = "ownerId", required = false) Integer ownerId) {
+ public Owner findOwner(@PathVariable(name = "ownerId", required = false) @Nullable Integer ownerId) {
return ownerId == null ? new Owner()
: this.owners.findById(ownerId)
.orElseThrow(() -> new IllegalArgumentException("Owner not found with id: " + ownerId
@@ -93,12 +96,13 @@ class OwnerController {
public String processFindForm(@RequestParam(defaultValue = "1") int page, Owner owner, BindingResult result,
Model model) {
// allow parameterless GET request for /owners to return all records
- if (owner.getLastName() == null) {
- owner.setLastName(""); // empty string signifies broadest possible search
+ String lastName = owner.getLastName();
+ if (lastName == null) {
+ lastName = ""; // empty string signifies broadest possible search
}
// find owners by last name
- Page ownersResults = findPaginatedForOwnersLastName(page, owner.getLastName());
+ Page ownersResults = findPaginatedForOwnersLastName(page, lastName);
if (ownersResults.isEmpty()) {
// no owners found
result.rejectValue("lastName", "notFound", "not found");
@@ -143,7 +147,7 @@ class OwnerController {
return VIEWS_OWNER_CREATE_OR_UPDATE_FORM;
}
- if (owner.getId() != ownerId) {
+ if (!Objects.equals(owner.getId(), ownerId)) {
result.rejectValue("id", "mismatch", "The owner ID in the form does not match the URL.");
redirectAttributes.addFlashAttribute("error", "Owner ID mismatch. Please try again.");
return "redirect:/owners/{ownerId}/edit";
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
index d26bdb62f..e0e9b2f0b 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/OwnerRepository.java
@@ -58,6 +58,6 @@ public interface OwnerRepository extends JpaRepository {
* @throws IllegalArgumentException if the id is null (assuming null is not a valid
* input for id)
*/
- Optional findById(@Nonnull Integer id);
+ Optional findById(Integer id);
}
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java
index 1945f9b67..7af9e5b4f 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/Pet.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/Pet.java
@@ -32,6 +32,7 @@ import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OrderBy;
import jakarta.persistence.Table;
+import org.jspecify.annotations.Nullable;
/**
* Simple business object representing a pet.
@@ -47,30 +48,30 @@ public class Pet extends NamedEntity {
@Column(name = "birth_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
- private LocalDate birthDate;
+ private @Nullable LocalDate birthDate;
@ManyToOne
@JoinColumn(name = "type_id")
- private PetType type;
+ private @Nullable PetType type;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JoinColumn(name = "pet_id")
@OrderBy("date ASC")
private final Set visits = new LinkedHashSet<>();
- public void setBirthDate(LocalDate birthDate) {
+ public void setBirthDate(@Nullable LocalDate birthDate) {
this.birthDate = birthDate;
}
- public LocalDate getBirthDate() {
+ public @Nullable LocalDate getBirthDate() {
return this.birthDate;
}
- public PetType getType() {
+ public @Nullable PetType getType() {
return this.type;
}
- public void setType(PetType type) {
+ public void setType(@Nullable PetType type) {
this.type = type;
}
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
index 56707d99f..b7274080e 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/PetController.java
@@ -17,10 +17,12 @@ package org.springframework.samples.petclinic.owner;
import java.time.LocalDate;
import java.util.Collection;
+import java.util.Objects;
import java.util.Optional;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
+import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.WebDataBinder;
@@ -32,6 +34,8 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import jakarta.validation.Valid;
+import org.jspecify.annotations.Nullable;
+
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
/**
@@ -69,8 +73,8 @@ class PetController {
}
@ModelAttribute("pet")
- public Pet findPet(@PathVariable("ownerId") int ownerId,
- @PathVariable(name = "petId", required = false) Integer petId) {
+ public @Nullable Pet findPet(@PathVariable("ownerId") int ownerId,
+ @PathVariable(name = "petId", required = false) @Nullable Integer petId) {
if (petId == null) {
return new Pet();
@@ -135,7 +139,7 @@ class PetController {
// checking if the pet name already exists for the owner
if (StringUtils.hasText(petName)) {
Pet existingPet = owner.getPet(petName, false);
- if (existingPet != null && !existingPet.getId().equals(pet.getId())) {
+ if (existingPet != null && !Objects.equals(existingPet.getId(), pet.getId())) {
result.rejectValue("name", "duplicate", "already exists");
}
}
@@ -160,7 +164,9 @@ class PetController {
* @param pet The pet with updated details
*/
private void updatePetDetails(Owner owner, Pet pet) {
- Pet existingPet = owner.getPet(pet.getId());
+ Integer id = pet.getId();
+ Assert.state(id != null, "'pet.getId()' must not be null");
+ Pet existingPet = owner.getPet(id);
if (existingPet != null) {
// Update existing pet's properties
existingPet.setName(pet.getName());
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java
index 7ddc6e923..b62751c97 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/PetTypeFormatter.java
@@ -21,6 +21,7 @@ import org.springframework.stereotype.Component;
import java.text.ParseException;
import java.util.Collection;
import java.util.Locale;
+import java.util.Objects;
/**
* Instructs Spring MVC on how to parse and print elements of type 'PetType'. Starting
@@ -43,14 +44,15 @@ public class PetTypeFormatter implements Formatter {
@Override
public String print(PetType petType, Locale locale) {
- return petType.getName();
+ String name = petType.getName();
+ return (name != null) ? name : "";
}
@Override
public PetType parse(String text, Locale locale) throws ParseException {
Collection findPetTypes = this.types.findPetTypes();
for (PetType type : findPetTypes) {
- if (type.getName().equals(text)) {
+ if (Objects.equals(type.getName(), text)) {
return type;
}
}
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java b/src/main/java/org/springframework/samples/petclinic/owner/Visit.java
index 085cd2849..7259fab9f 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/Visit.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/Visit.java
@@ -24,6 +24,7 @@ import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import jakarta.validation.constraints.NotBlank;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object representing a visit.
@@ -37,10 +38,10 @@ public class Visit extends BaseEntity {
@Column(name = "visit_date")
@DateTimeFormat(pattern = "yyyy-MM-dd")
- private LocalDate date;
+ private @Nullable LocalDate date;
@NotBlank
- private String description;
+ private @Nullable String description;
/**
* Creates a new instance of Visit for the current date
@@ -49,19 +50,19 @@ public class Visit extends BaseEntity {
this.date = LocalDate.now();
}
- public LocalDate getDate() {
+ public @Nullable LocalDate getDate() {
return this.date;
}
- public void setDate(LocalDate date) {
+ public void setDate(@Nullable LocalDate date) {
this.date = date;
}
- public String getDescription() {
+ public @Nullable String getDescription() {
return this.description;
}
- public void setDescription(String description) {
+ public void setDescription(@Nullable String description) {
this.description = description;
}
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
index 21c1823a5..cc3e3ce1a 100644
--- a/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
+++ b/src/main/java/org/springframework/samples/petclinic/owner/VisitController.java
@@ -67,6 +67,10 @@ class VisitController {
"Owner not found with id: " + ownerId + ". Please ensure the ID is correct "));
Pet pet = owner.getPet(petId);
+ if (pet == null) {
+ throw new IllegalArgumentException(
+ "Pet with id " + petId + " not found for owner with id " + ownerId + ".");
+ }
model.put("pet", pet);
model.put("owner", owner);
diff --git a/src/main/java/org/springframework/samples/petclinic/owner/package-info.java b/src/main/java/org/springframework/samples/petclinic/owner/package-info.java
new file mode 100644
index 000000000..ef080a0d6
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/owner/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.samples.petclinic.owner;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/org/springframework/samples/petclinic/package-info.java b/src/main/java/org/springframework/samples/petclinic/package-info.java
new file mode 100644
index 000000000..c8261d8f0
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.samples.petclinic;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java
index 1382f3aea..13cb74301 100644
--- a/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java
+++ b/src/main/java/org/springframework/samples/petclinic/system/CacheConfiguration.java
@@ -16,7 +16,7 @@
package org.springframework.samples.petclinic.system;
-import org.springframework.boot.autoconfigure.cache.JCacheManagerCustomizer;
+import org.springframework.boot.cache.autoconfigure.JCacheManagerCustomizer;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
diff --git a/src/main/java/org/springframework/samples/petclinic/system/package-info.java b/src/main/java/org/springframework/samples/petclinic/system/package-info.java
new file mode 100644
index 000000000..25da176d0
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/system/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.samples.petclinic.system;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java b/src/main/java/org/springframework/samples/petclinic/vet/Vet.java
index fb2bd71ee..c8bd549d5 100644
--- a/src/main/java/org/springframework/samples/petclinic/vet/Vet.java
+++ b/src/main/java/org/springframework/samples/petclinic/vet/Vet.java
@@ -31,6 +31,7 @@ import jakarta.persistence.JoinTable;
import jakarta.persistence.ManyToMany;
import jakarta.persistence.Table;
import jakarta.xml.bind.annotation.XmlElement;
+import org.jspecify.annotations.Nullable;
/**
* Simple JavaBean domain object representing a veterinarian.
@@ -47,7 +48,7 @@ public class Vet extends Person {
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "vet_specialties", joinColumns = @JoinColumn(name = "vet_id"),
inverseJoinColumns = @JoinColumn(name = "specialty_id"))
- private Set specialties;
+ private @Nullable Set specialties;
protected Set getSpecialtiesInternal() {
if (this.specialties == null) {
diff --git a/src/main/java/org/springframework/samples/petclinic/vet/Vets.java b/src/main/java/org/springframework/samples/petclinic/vet/Vets.java
index 634cad773..a3292927a 100644
--- a/src/main/java/org/springframework/samples/petclinic/vet/Vets.java
+++ b/src/main/java/org/springframework/samples/petclinic/vet/Vets.java
@@ -20,6 +20,7 @@ import java.util.List;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
+import org.jspecify.annotations.Nullable;
/**
* Simple domain object representing a list of veterinarians. Mostly here to be used for
@@ -30,7 +31,7 @@ import jakarta.xml.bind.annotation.XmlRootElement;
@XmlRootElement
public class Vets {
- private List vets;
+ private @Nullable List vets;
@XmlElement
public List getVetList() {
diff --git a/src/main/java/org/springframework/samples/petclinic/vet/package-info.java b/src/main/java/org/springframework/samples/petclinic/vet/package-info.java
new file mode 100644
index 000000000..6fbc63698
--- /dev/null
+++ b/src/main/java/org/springframework/samples/petclinic/vet/package-info.java
@@ -0,0 +1,4 @@
+@NullMarked
+package org.springframework.samples.petclinic.vet;
+
+import org.jspecify.annotations.NullMarked;
diff --git a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java
index a78e2e6e1..92fbf1d6d 100644
--- a/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java
+++ b/src/test/java/org/springframework/samples/petclinic/MySqlIntegrationTests.java
@@ -21,11 +21,11 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.boot.testcontainers.service.connection.ServiceConnection;
-import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.boot.web.server.test.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
diff --git a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java
index 617af9652..002822ee6 100644
--- a/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java
+++ b/src/test/java/org/springframework/samples/petclinic/PetClinicIntegrationTests.java
@@ -21,10 +21,10 @@ import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
+import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.boot.web.server.test.LocalServerPort;
import org.springframework.http.HttpStatus;
import org.springframework.http.RequestEntity;
import org.springframework.http.ResponseEntity;
diff --git a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java
index 709d33e66..a9b51c07b 100644
--- a/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java
+++ b/src/test/java/org/springframework/samples/petclinic/PostgresIntegrationTests.java
@@ -32,10 +32,10 @@ import org.junit.jupiter.api.condition.DisabledInNativeImage;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.event.ApplicationPreparedEvent;
+import org.springframework.boot.restclient.RestTemplateBuilder;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
-import org.springframework.boot.test.web.server.LocalServerPort;
-import org.springframework.boot.web.client.RestTemplateBuilder;
+import org.springframework.boot.web.server.test.LocalServerPort;
import org.springframework.context.ApplicationListener;
import org.springframework.core.env.ConfigurableEnvironment;
import org.springframework.core.env.EnumerablePropertySource;
diff --git a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java
index 4c78ab317..f1cabaf3c 100644
--- a/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java
+++ b/src/test/java/org/springframework/samples/petclinic/service/ClinicServiceTests.java
@@ -82,7 +82,7 @@ class ClinicServiceTests {
@Autowired
protected VetRepository vets;
- Pageable pageable;
+ private final Pageable pageable = Pageable.unpaged();
@Test
void shouldFindOwnersByLastName() {
diff --git a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java
index 180ef07f1..d1ce9d74f 100644
--- a/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java
+++ b/src/test/java/org/springframework/samples/petclinic/service/EntityUtils.java
@@ -43,7 +43,7 @@ public abstract class EntityUtils {
public static T getById(Collection entities, Class entityClass, int entityId)
throws ObjectRetrievalFailureException {
for (T entity : entities) {
- if (entity.getId() == entityId && entityClass.isInstance(entity)) {
+ if (entity.getId() != null && entity.getId() == entityId && entityClass.isInstance(entity)) {
return entity;
}
}
diff --git a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java
index ed8b0819a..2d1ac0dd0 100644
--- a/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java
+++ b/src/test/java/org/springframework/samples/petclinic/system/CrashControllerIntegrationTests.java
@@ -26,11 +26,11 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.SpringBootApplication;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
-import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
-import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
+import org.springframework.boot.hibernate.autoconfigure.HibernateJpaAutoConfiguration;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceAutoConfiguration;
+import org.springframework.boot.jdbc.autoconfigure.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.test.context.SpringBootTest;
-import org.springframework.boot.test.web.client.TestRestTemplate;
+import org.springframework.boot.web.server.test.client.TestRestTemplate;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;