Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# ![RealWorld Example App](logo.png)

> ### **Java 21 + Spring Boot 3** codebase containing real-world examples (CRUD, authentication, advanced patterns, etc.) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) specification and API.
> ### **Java 25 + Spring Boot 4** codebase containing real-world examples (CRUD, authentication, advanced patterns, etc.) that adheres to the [RealWorld](https://github.com/gothinkster/realworld) specification and API.

### [Demo](https://demo.realworld.io/)      [RealWorld](https://github.com/gothinkster/realworld)

This codebase demonstrates a fully-fledged fullstack application built with **Java 21 + Spring Boot 3**, including CRUD operations, authentication, routing, pagination, and more.
This codebase demonstrates a fully-fledged fullstack application built with **Java 25 + Spring Boot 4**, including CRUD operations, authentication, routing, pagination, and more.

If you want to see the API documentation related to this, please refer to https://1chz.github.io/realworld-java21-springboot3

Expand Down Expand Up @@ -65,9 +65,9 @@ This application provides the following key features:

### Project Structure

The project is implemented using Java 21 and Spring Boot 3, leveraging technologies such as Spring MVC, Spring Data JPA, and Spring Security. It uses H2 DB (in-memory, MySQL mode) as the database and JUnit 5 for testing.
The project is implemented using Java 25 and Spring Boot 4, leveraging technologies such as Spring MVC, Spring Data JPA, and Spring Security. It uses H2 DB (in-memory, MySQL mode) as the database and JUnit 5 for testing.

To run the project, ensure that JDK (or JRE) 21 is installed. Then, execute the following command in the project root directory:
To run the project, ensure that JDK (or JRE) 25 is installed. Then, execute the following command in the project root directory:

```shell
./gradlew realworld:bootRun
Expand Down Expand Up @@ -110,7 +110,7 @@ Many developers using JPA tend to use `Long` as the ID type. However, consider w

## Getting Started

> **Note:** Ensure JDK 21 is installed.
> **Note:** Ensure JDK 25 is installed.
>
> **Note:** If you encounter a permission denied error when running Gradle tasks, run `chmod +x gradlew` to grant execution permission.

Expand Down
3 changes: 2 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@ allprojects {

spotless {
java {
palantirJavaFormat().formatJavadoc(true)
// https://github.com/palantir/palantir-java-format/releases/tag/2.71.0
palantirJavaFormat("2.71.0").formatJavadoc(true)

formatAnnotations()
removeUnusedImports()
Expand Down
10 changes: 4 additions & 6 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
[versions]
java = "21"
spotless = "7.0.3"
java = "25"
spotless = "7.2.1"

spring-boot = "3.3.0"
spring-dependency-management = "1.1.5"
spring-boot-p6spy = "1.9.0"
spring-boot = "4.0.0-M2"
spring-dependency-management = "1.1.7"

[libraries]
lombok = { group = "org.projectlombok", name = "lombok" }
Expand All @@ -19,7 +18,6 @@ spring-boot-starter-web = { group = "org.springframework.boot", name = "spring-b
spring-boot-starter-cache = { group = "org.springframework.boot", name = "spring-boot-starter-cache" }
spring-boot-starter-data-jpa = { group = "org.springframework.boot", name = "spring-boot-starter-data-jpa" }
spring-boot-starter-oauth2-resource-server = { group = "org.springframework.boot", name = "spring-boot-starter-oauth2-resource-server" }
spring-boot-starter-p6spy = { group = "com.github.gavlyukovskiy", name = "p6spy-spring-boot-starter", version.ref = "spring-boot-p6spy" }

cache-caffeine = { group = "com.github.ben-manes.caffeine", name = "caffeine" }

Expand Down
Binary file modified gradle/wrapper/gradle-wrapper.jar
Binary file not shown.
2 changes: 1 addition & 1 deletion gradle/wrapper/gradle-wrapper.properties
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-9.1.0-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
Expand Down
44 changes: 26 additions & 18 deletions gradlew
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
#!/bin/sh

#
# Copyright © 2015-2021 the original authors.
# Copyright © 2015 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
Expand All @@ -15,6 +15,8 @@
# See the License for the specific language governing permissions and
# limitations under the License.
#
# SPDX-License-Identifier: Apache-2.0
#

##############################################################################
#
Expand Down Expand Up @@ -55,7 +57,7 @@
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
Expand All @@ -80,13 +82,11 @@ do
esac
done

APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit

APP_NAME="Gradle"
# This is normally unused
# shellcheck disable=SC2034
APP_BASE_NAME=${0##*/}

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit

# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
Expand Down Expand Up @@ -114,7 +114,6 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac

CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar


# Determine the Java command to use to start the JVM.
Expand All @@ -133,22 +132,29 @@ location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
if ! command -v java >/dev/null 2>&1
then
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.

Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
fi

# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
# shellcheck disable=SC2039,SC3045
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
Expand All @@ -165,7 +171,6 @@ fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )

JAVACMD=$( cygpath --unix "$JAVACMD" )

Expand Down Expand Up @@ -193,16 +198,19 @@ if "$cygwin" || "$msys" ; then
done
fi

# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.

# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'

# Collect all arguments for the java command:
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
# and any embedded shellness will be escaped.
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
# treated as '${Hostname}' itself on the command line.

set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"

# Stop when "xargs" is not available.
Expand Down
26 changes: 14 additions & 12 deletions gradlew.bat
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@rem SPDX-License-Identifier: Apache-2.0
@rem

@if "%DEBUG%"=="" @echo off
@rem ##########################################################################
Expand All @@ -26,6 +28,7 @@ if "%OS%"=="Windows_NT" setlocal

set DIRNAME=%~dp0
if "%DIRNAME%"=="" set DIRNAME=.
@rem This is normally unused
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%

Expand All @@ -42,11 +45,11 @@ set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if %ERRORLEVEL% equ 0 goto execute

echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

Expand All @@ -56,22 +59,21 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe

if exist "%JAVA_EXE%" goto execute

echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
echo. 1>&2
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
echo. 1>&2
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
echo location of your Java installation. 1>&2

goto fail

:execute
@rem Setup the command line

set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar


@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*

:end
@rem End local scope for the variables with windows NT shell
Expand Down
8 changes: 7 additions & 1 deletion module/core/src/main/java/io/zhc1/realworld/model/Tag.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.zhc1.realworld.model;

import java.time.LocalDateTime;
import java.util.Comparator;
import java.util.Objects;

import jakarta.persistence.Column;
Expand All @@ -17,7 +18,7 @@
@Table(name = "tag")
@SuppressWarnings("JpaDataSourceORMInspection")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Tag {
public class Tag implements Comparable<Tag> {
@Id
@Column(length = 20)
private String name;
Expand All @@ -42,4 +43,9 @@ public boolean equals(Object o) {
public int hashCode() {
return Objects.hash(this.getName());
}

@Override
public int compareTo(Tag other) {
return Comparator.comparing(Tag::getName).compare(this, other);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,10 @@ public List<ArticleDetails> getFeeds(User user, ArticleFacets facets) {
.map(UserFollow::getFollowing)
.toList();

if (following.isEmpty()) {
return List.of();
}

return articleRepository.findByAuthors(following, facets).stream()
.map(article -> articleRepository.findArticleDetails(user, article))
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,6 @@ void whenGetFeedsWithEmptyFollowings_thenShouldReturnEmptyList() {
User user = new User("email", "username", "password");
ArticleFacets facets = new ArticleFacets(1, 10);
when(userRelationshipRepository.findByFollower(user)).thenReturn(List.of());
when(articleRepository.findByAuthors(List.of(), facets)).thenReturn(List.of());

// when
List<ArticleDetails> actualArticleDetailsList = sut.getFeeds(user, facets);
Expand Down
1 change: 0 additions & 1 deletion module/persistence/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ dependencies {

implementation(libs.spring.boot.starter.data.jpa)
implementation(libs.spring.boot.starter.cache)
implementation(libs.spring.boot.starter.p6spy)

implementation(libs.cache.caffeine)
}
Loading