diff --git a/README.md b/README.md index 2b09400..bc29551 100644 --- a/README.md +++ b/README.md @@ -1,134 +1,150 @@ # springboot-java8 -The project is made on spring boot. The project summarize the new features present in Java 8. -It contain list of harcoded topics list. You can call the apis's with POSTMAN to add,delete,update Topic list -In addition, it uses -1) Java 8 NIO methods -2) String operations -3) Stream operations -4) IntStream functions -5) Functional interface -6) Lambda functions -7) Optional datatype -8) Foreach loops -9) Default and Static methods in interface -10) Java 8 LocalDateTime API -11) Pattern - - - -## Getting Started -1) Download or clone the project with link -(https://github.com/RehmanMuradAli/springboot-java8/) - -## Available API's - -Greetings -``` -GET / -``` -Get all Topics in List -``` -GET /topic -``` -Get Topic of given ID -``` -GET /topic/{id} -``` -Add Topic in List -``` -POST /topic -``` -Update Topic of given ID -``` -PUT /topic/{id} -``` -Delete Topic of given ID -``` -DELETE /topic/{id} -``` +A small Spring Boot REST service whose purpose is to **demonstrate Java language features** — originally the Java 8 +feature set, which is still what the code shows off. The name stuck, but the project itself now builds and runs on +**Java 21 with Spring Boot 3.5.16**. -Get all Topics whose ID's length is greater than minLength -``` -GET /topic/minimum/length/{minLength} -``` +The domain is deliberately trivial: an in-memory, hardcoded list of `Topic` objects that you can create, read, update +and delete over HTTP, plus a few endpoints that exist purely to print the result of stream / string / file operations. -Get all Topics sorted by ID -``` -GET /topic/sort -``` +## Java 8 features demonstrated -String Operations on Topic List -``` -GET /topic/string/operation -``` +1. NIO.2 file APIs (`Files.list`, `Files.find`, `Files.walk`, `Files.newBufferedReader`) +2. String operations (`String.join`, `String.chars`) +3. Stream operations (`filter`, `map`, `sorted`, `Collectors.joining`) +4. `IntStream` (`IntStream.range` for index lookups) +5. Functional interfaces (`CustomPredicate`) +6. Lambda expressions +7. `Optional` / `OptionalInt` +8. `forEach` loops +9. `default` and `static` methods on interfaces (`TimeClient`) +10. The `java.time` API (`LocalDateTime`, `ZonedDateTime`, `ChronoUnit`) +11. `Pattern` as a stream source and as a predicate (`splitAsStream`, `asPredicate`) +12. try-with-resources over streams of paths -File Operations on Topic List -``` -GET /topic/file/operation -``` -Java 8 Date Time example +## Java 21 idioms in use + +The application was migrated from Spring Boot 2.0.2 / Java 8 to Spring Boot 3.5.16 / Java 21, and the presentation +layer has been rewritten to use the modern language features that replace what the old code did by hand: + +- **Text blocks** (Java 15) for the multi-part responses of `/datetime`, `/topic/string/operation` and + `/topic/file/operation`, using `\` line-continuation escapes so the responses stay on a single line. +- **`String.formatted(...)`** (Java 15) instead of `String.format(...)` and `+` concatenation. +- Label templates are `private static final` constants rather than mutable instance fields. + +## Requirements + +- JDK 21 (the build sets `21`) +- Maven is **not** required — use the bundled Maven Wrapper (`./mvnw`, Maven 3.9.9) +- `curl`, HTTPie or Postman to call the API + +## Getting started + +```bash +git clone https://github.com/COG-GTM/springboot-java8.git +cd springboot-java8 + +# compile, run the test suite and package the jar +./mvnw clean verify + +# run the application (listens on http://localhost:8080) +./mvnw spring-boot:run ``` -GET /datetime + +You can also run the packaged jar directly: + +```bash +java -jar target/gs-spring-boot-0.1.0.jar ``` +> The Gradle build has been removed; Maven is the only supported build. + +Every push and pull request is built by GitHub Actions with `./mvnw -B clean verify` on JDK 21 +(see `.github/workflows/ci.yml`). + +### Expected startup noise +- On startup the app creates an in-memory **H2** `customers` table with `JdbcTemplate` and logs a few rows — this is + part of the demo, not an error. +- It also tries to fetch a random quote from `gturnquist-quoters.cfapps.io`, a demo service that no longer exists. + The resulting `WARN Could not fetch a quote from ...` is expected and must never fail startup. -### Prerequisites +## API -1) Java sdk -2) POSTMAN +All endpoints are served from `http://localhost:8080`. -### Installing +| Method | Path | Description | +| --- | --- | --- | +| `GET` | `/` | Greeting; accepts an optional `?name=` parameter (defaults to `World`) | +| `GET` | `/topic` | All topics | +| `GET` | `/topic/{id}` | A single topic by id | +| `POST` | `/topic` | Add a topic (JSON body) | +| `PUT` | `/topic/{id}` | Replace the topic with the given id | +| `DELETE` | `/topic/{id}` | Delete the topic with the given id | +| `GET` | `/topic/sort` | All topics sorted by id | +| `GET` | `/topic/minimum/length/{minLength}` | Topics whose id is longer than `minLength` | +| `GET` | `/topic/string/operation` | Plain-text dump of the string/stream/regex examples | +| `GET` | `/topic/file/operation` | Plain-text dump of the NIO.2 file examples | +| `GET` | `/datetime` | Plain-text dump of the `java.time` examples | +The topic list lives in memory, so `POST` / `PUT` / `DELETE` changes are lost when the application restarts. +### Examples +```bash +$ curl 'http://localhost:8080/?name=Devin' +{"id":1,"content":"Hello, Devin!"} + +$ curl http://localhost:8080/topic +[{"id":"spring","subjectName":"Spring Framework","subjectDescription":"Spring Framework Description"}, ...] + +$ curl -X POST http://localhost:8080/topic \ + -H 'Content-Type: application/json' \ + -d '{"id":"kotlin","subjectName":"Kotlin","subjectDescription":"Kotlin Description"}' ``` -1) Download or clone -2) Import the project -3) Run on location machine -4) Open Postman, to call API's ( localhost:8080 ) + +The `/topic/file/operation` endpoint resolves paths relative to the working directory the application was started +from, so its output depends on where you run it. + +## Project layout + +``` +src/main/java/hello +├── Application.java # entry point + CommandLineRunner (H2 seeding, quote fetch) +├── config/ # RestTemplate bean +├── controller/ # GreetingController, TopicController, HelloController +├── declaration/ # CustomPredicate, TimeClient (default/static interface methods) +├── model/ # Topic, Greeting, Customer, Quote, Value, SimpleTimeClient +└── service/ # TopicService — where most of the Java 8 examples live ``` +## Helpful links -## Helpful Links Spring: https://spring.io/guides -Java 8: +Java 8: http://www.baeldung.com/java-8-functional-interfaces -http://winterbe.com/posts/2015/05/22/java8-concurrency-tutorial-atomic-concurrent-map-examples/ - https://docs.oracle.com/javase/tutorial/java/IandI/defaultmethods.html http://winterbe.com/posts/2014/07/31/java8-stream-tutorial-examples/ -http://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html - https://docs.oracle.com/javase/tutorial/essential/io/pathOps.html -https://docs.oracle.com/javase/8/docs/api/java/util/Optional.html - -http://www.baeldung.com/foreach-java +Java 21: -http://winterbe.com/posts/2015/03/25/java8-examples-string-number-math-files/ +https://docs.oracle.com/en/java/javase/21/text-blocks/index.html -http://www.baeldung.com/java-8-comparator-comparing +https://docs.spring.io/spring-boot/docs/current/reference/html/ -http://www.baeldung.com/java-8-sort-lambda +## Built with -https://dzone.com/articles/java-8-friday-goodies-new-new - - -## Built With - -* [Maven](https://maven.apache.org/) - Dependency Management +* [Maven](https://maven.apache.org/) — build and dependency management +* [Spring Boot](https://spring.io/projects/spring-boot) 3.5.16 ## Authors -* **Rehman Murad Ali** - - +* **Rehman Murad Ali** — original author diff --git a/src/main/java/hello/controller/GreetingController.java b/src/main/java/hello/controller/GreetingController.java index cf52f8d..9f5cfbe 100644 --- a/src/main/java/hello/controller/GreetingController.java +++ b/src/main/java/hello/controller/GreetingController.java @@ -9,12 +9,11 @@ @RestController public class GreetingController { - private static final String template = "Hello, %s!"; + private static final String TEMPLATE = "Hello, %s!"; private final AtomicLong counter = new AtomicLong(); @RequestMapping("/") public Greeting greeting(@RequestParam(value="name", defaultValue="World") String name) { - return new Greeting(counter.incrementAndGet(), - String.format(template, name)); + return new Greeting(counter.incrementAndGet(), TEMPLATE.formatted(name)); } } diff --git a/src/main/java/hello/controller/HelloController.java b/src/main/java/hello/controller/HelloController.java index f3602fa..b599c6b 100644 --- a/src/main/java/hello/controller/HelloController.java +++ b/src/main/java/hello/controller/HelloController.java @@ -2,38 +2,37 @@ import hello.declaration.TimeClient; import hello.model.SimpleTimeClient; -import hello.model.Topic; import hello.service.TopicService; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; import java.time.ZoneId; -import java.time.chrono.ChronoPeriod; import java.time.temporal.ChronoUnit; -import java.util.List; -import java.util.regex.Pattern; -import java.util.stream.Collector; -import java.util.stream.Collectors; @RestController public class HelloController { - - String joinTemplate = "Joining All String ID's with JOIN method: "; - String makeDistinctAndSortCharactersTemplate = "-------------Get all ID characters, select distict and sort with ID= "; - String splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoinTemplate = "-------------Split All Id With Colon," + - "Select ID With \"Java\" Keyword," + - " Then Sort Then Join "; - String findIdHavingCharacterTemplate = "-------------Return All ID having character \'g\' in it: "; - String findAllFilesInPathAndSortTemplate = "---------Find all files in path and sort: "; - String findParticularFileInPathAndSortTemplate = "----------Find File in present directory which strats with \"grad\",provided maximum depth=25 and sort : "; - String findParticularFileInPathAndSortWithWalkFunctionTemplate = "----------Find File in present directory which strats with \"grad\",provided maximum depth=25 and sort : with walk function"; - String readFileWithStreamFunctionTemplate = "---------Read \"temp.txt\" file with stream functions, having \"print\" witin it: "; - + private static final String JOIN_TEMPLATE = "Joining All String ID's with JOIN method: "; + private static final String MAKE_DISTINCT_AND_SORT_CHARACTERS_TEMPLATE = "-------------Get all ID characters, select distict and sort with ID= "; + private static final String SPLIT_ALL_ID_WITH_COLON_SELECT_ID_WITH_JAVA_KEYWORD_THEN_SORT_THEN_JOIN_TEMPLATE = """ + -------------Split All Id With Colon,\ + Select ID With "Java" Keyword,\ + Then Sort Then Join\s"""; + private static final String FIND_ID_HAVING_CHARACTER_TEMPLATE = "-------------Return All ID having character 'g' in it: "; + private static final String FIND_ALL_FILES_IN_PATH_AND_SORT_TEMPLATE = "---------Find all files in path and sort: "; + private static final String FIND_PARTICULAR_FILE_IN_PATH_AND_SORT_TEMPLATE = "----------Find File in present directory which strats with \"grad\",provided maximum depth=25 and sort : "; + private static final String FIND_PARTICULAR_FILE_IN_PATH_AND_SORT_WITH_WALK_FUNCTION_TEMPLATE = "----------Find File in present directory which strats with \"grad\",provided maximum depth=25 and sort : with walk function"; + private static final String READ_FILE_WITH_STREAM_FUNCTION_TEMPLATE = "---------Read \"temp.txt\" file with stream functions, having \"print\" witin it: "; + + /** Responses are single line, hence the trailing line-continuation escapes. */ + private static final String LABELLED_SECTIONS_FORMAT = """ + %s%s\ + %s%s\ + %s%s\ + %s%s\ + """; @Autowired private TopicService topicService; @@ -46,18 +45,23 @@ public class HelloController { @RequestMapping("/datetime") public String index() { TimeClient myTimeClient = new SimpleTimeClient(); - LocalDateTime localDateTime = LocalDateTime.now(); - return "Greetings from Spring Boot! ----------------------" + - "Datetime now is " + String.valueOf(myTimeClient.toString()) + "----------------------" + - "Datetime tomorrow will be " + String.valueOf(myTimeClient.getLocalDateTime().plusDays(1)) + "----------------------" + - "Datetime of previous month was " + String.valueOf(myTimeClient.getLocalDateTime().minus(1, ChronoUnit.MONTHS)) + "----------------------" + - "Is this a leap year ? " + String.valueOf(LocalDate.now().isLeapYear()) + "----------------------" + - "Default system zone id " + String.valueOf(ZoneId.systemDefault()) + "-------------------" + - "Time in California: " + myTimeClient.getZonedDateTime("Canada/Central").toString(); - + return """ + Greetings from Spring Boot! ----------------------\ + Datetime now is %s----------------------\ + Datetime tomorrow will be %s----------------------\ + Datetime of previous month was %s----------------------\ + Is this a leap year ? %s----------------------\ + Default system zone id %s-------------------\ + Time in California: %s\ + """.formatted( + myTimeClient, + myTimeClient.getLocalDateTime().plusDays(1), + myTimeClient.getLocalDateTime().minus(1, ChronoUnit.MONTHS), + LocalDate.now().isLeapYear(), + ZoneId.systemDefault(), + myTimeClient.getZonedDateTime("Canada/Central")); } - /** * String Operations in Java 8 * @@ -72,16 +76,16 @@ public String showStringOperation() { .splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin(join); String findIdHavingCharacter = topicService.findIdHavingCharacter(); - return joinTemplate + join - + makeDistinctAndSortCharactersTemplate + makeDistinctAndSortCharacters - + splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoinTemplate + splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin - + findIdHavingCharacterTemplate + findIdHavingCharacter; - + return LABELLED_SECTIONS_FORMAT.formatted( + JOIN_TEMPLATE, join, + MAKE_DISTINCT_AND_SORT_CHARACTERS_TEMPLATE, makeDistinctAndSortCharacters, + SPLIT_ALL_ID_WITH_COLON_SELECT_ID_WITH_JAVA_KEYWORD_THEN_SORT_THEN_JOIN_TEMPLATE, splitAllIdWithColonSelectIDWithJavaKeywordThenSortThenJoin, + FIND_ID_HAVING_CHARACTER_TEMPLATE, findIdHavingCharacter); } - /** * File Operation in Java 8 + * * @return */ @RequestMapping("/topic/file/operation") @@ -90,12 +94,12 @@ public String showFileOperation() { String findParticularFileInPathAndSort = topicService.findParticularFileInPathAndSort(); String findParticularFileInPathAndSortWithWalkFunction = topicService.findParticularFileInPathAndSortWithWalkFunction(); String readFileWithStreamFunction = topicService.readFileWithStreamFunction(); - return findAllFilesInPathAndSortTemplate + findAllFilesInPathAndSort - + findParticularFileInPathAndSortTemplate + findParticularFileInPathAndSort - + findParticularFileInPathAndSortWithWalkFunctionTemplate + findParticularFileInPathAndSortWithWalkFunction - + readFileWithStreamFunctionTemplate + readFileWithStreamFunction; - } - + return LABELLED_SECTIONS_FORMAT.formatted( + FIND_ALL_FILES_IN_PATH_AND_SORT_TEMPLATE, findAllFilesInPathAndSort, + FIND_PARTICULAR_FILE_IN_PATH_AND_SORT_TEMPLATE, findParticularFileInPathAndSort, + FIND_PARTICULAR_FILE_IN_PATH_AND_SORT_WITH_WALK_FUNCTION_TEMPLATE, findParticularFileInPathAndSortWithWalkFunction, + READ_FILE_WITH_STREAM_FUNCTION_TEMPLATE, readFileWithStreamFunction); + } }