Friday, July 28, 2023

Oracle 1Z0-809 Exam to Tactically Prepare Your Java SE 8 Programmer II Certification

1z0-809, 1z0-809 dumps, oracle 1z0-809, ocp java se 8 programmer ii exam guide (exam 1z0-809) pdf, oracle certified professional java se 8 programmer, ocp java se 8 programmer ii, ocp java se 8 programmer ii exam guide, oracle certified professional java se 8 programmer syllabus, oracle certified professional java se 8 programmer exam questions, java se 8 certification, oca java se 8 practice exam

In today's fast-paced and competitive IT industry, acquiring certifications can significantly increase your career opportunities and demonstrate your expertise in a particular technology or platform. One such valuable certification is the Oracle 1Z0-809 certification.

Importance of Oracle 1Z0-809 Certification

The Oracle 1Z0-809 certification, also known as the Java SE 8 Programmer II exam, is designed for Java developers who wish to validate their advanced Java programming skills. It is a respected certification offered by Oracle, a leading name in the IT industry. Achieving this certification indicates that you maintain a deep understanding of Java and are proficient in building robust applications using Java SE 8.

Advantages of Becoming Oracle Certified Professional Java SE 8 Programmer

Obtaining the Oracle 1Z0-809 certification opens doors to a myriad of career opportunities. Employers highly seek certified professionals, as this certification is a testament to their expertise. Additionally, accredited individuals often receive higher salaries compared to their non-certified counterparts. Furthermore, the knowledge gained while preparing for the exam enhances your programming skills, making you a more competent and confident Java developer.

Understanding the Oracle 1Z0-809 Exam

1. Prerequisites and Eligibility

Before registering for the Oracle 1Z0-809 exam, it is essential to fulfill specific prerequisites. Typically, candidates are required to have already earned the Oracle Certified Professional Java SE 8 Programmer (OCP) certification. Additionally, a strong understanding of Java concepts and hands-on experience in Java programming is highly recommended.

2. Oracle 1Z0-809 Exam Format and Structure

The Oracle 1Z0-809 exam comprises 68 multiple-choice, drag-and-drop, and scenario-based questions. The exam lasts around 120 minutes, during which candidates must show their knowledge and problem-solving abilities. The passing score for the exam may vary, but it is generally set at around 65%.

Preparing for the Oracle 1Z0-809 Exam

1. Creating a Study Plan

Studying for the Oracle 1Z0-809 exam requires a structured approach. Design a study plan that gives sufficient time for each topic. Identify areas where you need more practice and provide additional study hours accordingly. A well-organized study plan helps in covering all exam objectives effectively.

2. Recommended Study Resources

Choose study resources that align with the official 1Z0-809 exam objectives to excel. Reliable resources include Oracle's official documentation, study guides, and online tutorials. These resources provide in-depth knowledge and help you familiarize yourself with the exam format.

3. Practice with 1Z0-809 Mock Tests

Mock tests are invaluable tools for 1Z0-809 exam preparation. They simulate the actual exam environment and enable you to assess your knowledge and time management skills. Taking multiple mock tests permits you to identify weak areas and focus on improving them.

Tips for Excelling in the Oracle Java SE Programmer II Exam

1. Managing Time Effectively

Time management is crucial during the exam. Divide the available time among the questions based on their complexity. Answer the more straightforward questions first and leave plenty of time for more challenging ones. Avoid spending too much time on a single question, which may affect your performance.

2. Focus on High-Weightage Topics

Some topics carry more weight in the exam than others. Prioritize these topics during your preparation. Understand the concepts thoroughly and practice applying them to real-world scenarios.

3. Hands-on Experience

Practical experience with Java programming is invaluable. Implementing projects and writing code reinforces your understanding of Java and hone your problem-solving abilities.

The Oracle 1Z0-809 Exam Day

1. Last-minute Preparation

Review the key concepts and topics the day before the Oracle Java SE Programmer II exam. Avoid studying new material at the last moment, as it may cause unnecessary stress. Ensure you have all the required documents and materials ready for the exam day.

2. Staying Calm and Confident

During the exam, remain calm and confident. Read each question carefully and avoid rushing through the answers. Trust in your preparation and believe in your abilities.

After the Java SE Programmer II Exam

1. Reviewing Your Performance

Once you complete the exam, take some time to evaluate your performance objectively. Analyze the areas where you did well and identify areas that require improvement. This evaluation will help you understand your strengths and weaknesses.

2. Sharing Your Achievement

Share your achievement with your professional network upon passing the Oracle 1Z0-809 exam. Add the Java SE 8 Programmer II certification to your resume and LinkedIn profile. Being certified enhances your credibility as a Java developer and may attract potential employers.

Conclusion

Acquiring the Oracle 1Z0-809 certification is a significant achievement for any Java developer. It validates your Java SE 8 programming expertise and opens up exciting career opportunities. To succeed in the exam, create a well-structured study plan, utilize recommended study resources, and practice with mock tests. Manage your time effectively and remain composed during the Java SE Programmer II exam.

After passing the exam, share your accomplishment with pride, and consider upgrading to stay on top of the latest Java advancements. Dedication and perseverance can enhance your professional standing and contribute to the dynamic world of Java development.

Wednesday, July 26, 2023

Quiz yourself: Java’s sealed types—true-or-false quiz

Oracle Java, Java Certification, Java Guides, Java Learning, Java Tutorial and Materials

Test your knowledge of valid sealed classes, subclasses, and the permits section.

Which statement is true? Choose one.

A. A sealed type declaration must have a permits section.

B.  A sealed class must have at least one direct subclass.

C. A sealed interface must have at least one direct implementation.

D. A sealed type can be an enum.

E. A sealed type can be a record.

Answer. The goal of a sealed type hierarchy is to have source-level specifications that make it very clear what types are permitted to be assignment-compatible with a given base type.

Clearly, even if you don’t have a sealed type hierarchy, you can find this information if you search all the source files used in your codebase. However, that’s laborious at best, and it’s impractical if you have libraries that came from other teams—or from third parties.

As a broad overview, the sealed type mechanism allows the source for one type to explicitly enumerate all its permitted subtypes. Each of those subtypes in turn is subject to restrictions that are generally intended to keep the entire tree clearly visible from the source code.

One possibility is that a subtype is final (either explicitly or implicitly, which is the case with a record type). In this case, you’ve reached the end of the type hierarchy on this branch of the tree.

Another possibility is to open up the hierarchy and allow arbitrary subtypes. This, to an extent, spoils the point of a sealed hierarchy, but it is permitted because practical considerations might warrant it. This is achieved by marking the type as non-sealed.

A third possibility is that the subtype is an enum. An enum can never be named in an extends clause, which means that the sealed parent type must be an interface. Also, subtypes of an enum can exist only when they are declared as inner classes. As inner classes, they’re in the source code of the enum itself, which makes it very clear what, if any, subtypes exist under this branch of the type tree.

The final option is for the subtype itself to be marked as sealed. In this case, its children can be expressly listed. Also in this case, the subtypes’ children are subject to these rules. Once again, it’s very clear from the source which subtypes are possible.

While it’s usual for a sealed type to enumerate its permitted children with permits, there is one situation in which this can be omitted (note that permits is mandatory otherwise). That situation is when all the child types are declared in the same source file (or compilation unit) as the parent type. This includes the possibility of those types being inner or nested classes within the parent type.

This last possibility tells you that option A is incorrect.

The description above omitted an important detail: The purpose of a sealed type hierarchy is to enumerate clearly the permitted child types. If you want zero children of a given type, that class should be marked as final (and, of course, it must be a concrete class). A sealed type must have at least one subtype. This tells you that option B is correct.

Option C is incorrect. Any sealed type must have some child type, but it’s not necessary for an interface to have a direct implementation; it’s perfectly acceptable for it to have a subinterface. Note that an interface cannot be marked as final, so a subinterface of a sealed interface must be either sealed or non-sealed to be valid.

Although an enum cannot be marked as final, a subtype of an enum must be an inner class. This already makes for tight control of the types that can ever be assignment-compatible with it. Of course, this is why an enum can be declared on a sealed type hierarchy without being labeled either sealed, non-sealed, or final. As noted earlier, the sealed parent type of an enum must be an interface. It’s not useful—and is, in fact, not permitted—for an enum type to be marked as sealed. Hence, option D is incorrect.

Record types are implicitly final; hence, they cannot permit subtypes. You know from the discussion related to option B that at least one subclass is mandatory, so you can deduce that option E is also incorrect.

Don’t forget that it’s entirely correct for a record type to appear in a sealed type hierarchy; it simply must be a leaf node, not the root of the hierarchy. It’s also worth observing that since a record type cannot declare a parent class, all the parent types of a record in a sealed hierarchy, up to and including the root of that hierarchy, must be interfaces.

Conclusion. The correct answer is option B.

Source: oracle.com

Monday, July 24, 2023

Quiz yourself: Comparing Java arrays and interpreting the mismatch method

Quiz Yourself, Java Arrays, Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation

The java.util.Arrays class methods aren’t always intuitive.

Given the following code fragment

int a1[] = { 5, 24, 54, 22 };

int a2[] = { 5, 23, 56, 202 };

int a3[] = { 3, 8, 19, 39, 56 };

System.out.print(Arrays.compare(a1, a2));

System.out.print(Arrays.compare(a3, a2));

System.out.print(Arrays.mismatch(a1, a1));

System.out.print(Arrays.mismatch(a2, a1));

Which output is guaranteed? Choose one.

A. 1-1-11

B. 2-1-12

C. 11-11

D. 2-202

E. None of the above

Answer. This question explores the Java API for the java.util.Arrays class, specifically the compare() and mismatch() methods.

The compare() method compares two arrays lexicographically. That’s a fancy way of saying it compares them in the same way that textual words are ordered in a dictionary. Importantly, that’s not the same way numbers are ordered.

Notice that for words such as Hello and Goodbye, a dictionary will order Goodbye before Hello, because G comes before H in the alphabet. Even though there are more letters in Goodbye, the first difference determines the ordering.

Contrast this with numbers; for example, 90,000 would come before 5,000,000 even though the digit 5 is less than the digit 9. The ordering imposed by Arrays.compare works this way, even when comparing arrays of numbers.

In addition, Arrays.compare checks corresponding pairs of elements at the same positions, starting at index position 0 and checking increasing index positions. As long as pairs of elements compare as equal at the same subscript, it keeps checking. If the comparison process reaches the end of both arrays simultaneously, which can happen only if no differences have been discovered, the arrays are deemed to be equal. If the comparison process reaches the end of one array before the other, but the two are identical up to the end of the shorter array, the longer array is considered to be larger.

By the way, a null array reference is considered to be smaller than any non-null array. Two null array references are considered to be equal.

In the case of this question, the elements of arrays a1 and a2 differ at index 1. At that position, the value in a1 (24) is larger than that in array a2 (23), which makes array a1 larger than array a2.

For the comparison of array a3 with array a2 (note that a3 is the first of the two arguments), the arrays differ at index 0, with array a3 being the smaller.

So, what is the result of the Arrays.compare method when the arrays have differing values at an index, as in this case? To determine that, you must go on a bit of a treasure hunt. The documentation for Arrays.compare(int[], int[]) states the following:

...the lexicographic comparison is the result of comparing two elements, as if by Integer.compare(int, int).

That leads to the documentation for Integer.compare(int, int) which states

Compares two int values numerically. The value returned is identical to what would be returned by: Integer.valueOf(x).compareTo(Integer.valueOf(y))

By following the trail, you’ll see that the documentation of Integer.compareTo(Integer) states that the result will be

...the value 0 if this Integer is equal to the argument Integer; a value less than 0 if this Integer is numerically less than the argument Integer; and a value greater than 0 if this Integer is numerically greater than the argument Integer (signed comparison).

It’s worth noting that this is consistent with the documentation for the compare method in the Comparator interface.

If you create the code for this question and try it, you’ll (almost certainly) find that the output of the comparisons is actually 1, followed by -1, which would make option A look likely to be correct (although the output from the second pair of methods has not been considered yet). But the documentation doesn’t say that the method will return 1, 0, or -1. Rather, it merely says the result will be positive, zero, or negative.

Consequently, you can’t state that the correct answer is A. Rather, the correct answer must be option E.

Although you now know the correct answer, these questions are, of course, intended for learning, rather than for merely seeing if you get the answer right or wrong. So, it’s worth investigating the behavior of the mismatch() method.

The mismatch() method returns the index value of the first point in the arrays where the values of the elements differ. If the arrays are identical in contents and length, the method returns -1. Also, if either array reference is null, a NullPointerException is thrown. (Keep in mind that Java array indexes are zero-based.)

Given these rules, the output of the mismatch operation with both arguments provided as in array a1 must be -1. The output of the mismatch operation with the arguments for array a2 and array a1 will be 1, because the index contains the values 23 and 24. This is consistent with option A.

So, if you run the code, the output will almost certainly match option A, and this would be correct if the question were phrased as “Which output is possible?” However, as noted, a valid JDK or JRE could have a library implementation that returns values other than +1 and -1 from the compare method.

We hope you won’t hate us for this question; precision and avoidance of unsound assumptions can be important in day-to-day programming as well as during exam taking!

Conclusion. The correct answer is option E.

Source: oracle.com

Wednesday, July 5, 2023

Handling time zones in distributed systems, Part 2


The first article in this series, “Handling time zones in distributed systems, Part 1,” discussed how to consider customers’ time zones. It’s a complex topic, and after a bit of discussion, I recommended that the most efficient Java solution uses the JSR 310 java.time API. This article shows how to implement zoned date and time operations into a REST-based end-to-end service called FizzBus.

The code will run on Java 18 or later. If you have installed the correct JDK version, you are ready to clone the project and run it locally, either within your favorite IDE or from the command line as follows.

First, clone the ZonedDT-in-distributed-system project from my GitHub account.

Next, go to the project root and run the following Maven command, after making sure that your environment is set to Java 18:

[mtaman]:~ mvn spring-boot:run

Alternatively, you can open the project in your IDE and run the app main class, com.sxi.lab.fizzbus.FizzBusApplication.

You can access the code’s Swagger documentation at http://localhost:8090/api/v1/fizz-bus/doc/index.html; this will be used for testing the application.

What is the FizzBus service?


First, let me explain what the FizzBus service is. It’s a simple example of a fleet management system developed as a Spring Boot–based project with RESTful-based APIs for customers.

The FizzBus service uses RESTful-based controllers to connect to business logic that resides in the services layer. The business service layer connects to a database through the repository layer to show how to implement zoned date and time operations in distributed systems correctly—that is, by considering different users’ time zones with Daylight Saving Time (DST) when saving, updating, and searching for data in the database using the architecture shown in Figure 1.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning
Figure 1. FizzBus service architecture

Because this is a Spring Boot–based application, the persistence layer is implemented with Spring Data, and I use the H2 Database Engine for persistence. The repository layer consumes this data layer to provide core Create, Read, Update, and Delete (CRUD) functionality to the business logic implemented on the service layer. The controller defines the REST endpoints with the appropriate HTTP methods and is documented using Swagger APIs.

The controller uses the Data Transfer Object (DTO) pattern to map the entity attributes into the required API payload attributes. This decouples domain entities from the API request/response payload data. Data transfer objects are implemented as Java records. I use the MapStruct model mapper library to convert Java records into JPA domain model entities and vice versa in the service layer to avoid manual function mapping. It supports deep object mapping as well.

Exception handling is managed with a control advisor to keep the API controllers much cleaner. It is implemented as a shared-component application bean managed by the Spring Inversion of Control (IoC) container. Furthermore, the exception advisor will handle all the application-specific errors; usually, you don’t need to throw language default exceptions to other layers to maintain application layers more independently.

The standard date and time formatting and conversion methods are defined through a utility class that is shared between all layers.

I won’t go into details to explain every piece of the project code. Instead, I’ll explain the essential parts that highlight the areas concerned with the date and time implementation for receiving data from the client, parsing it, saving it to the database, and fetching it back to return it to the client in the client’s time zone. Be sure to explore the code in my GitHub account.

Date and time end-to-end implementation


When you’re using Java 8’s Date and Time API, consider the following:

  • The Instant class is for computers, while the date and time variant classes are for humans. The Instant class is the natural way of representing the time for computers, but it is often useless to humans. It’s usually preferred for storage (for example, in a database), but you may need to use other classes such as ZonedDateTime when presenting data to a user.
  • The database connection should always be set to Coordinated Universal Time (UTC). Fortunately, this is a configuration setting you make when creating the connection pool; I’ll show you how to make conversion automatic.
  • The time zone should always be present for conversion and for searching from Instant to any date and time equivalent classes.
  • JPA date and time fields could be Instant, LocalDate for only date storage, or ZonedDateTime as a time stamp equivalent.
  • In the API controller, the value object class could contain the date and time fields in the form of LocalDateTime or a String to be parsed later.

For handling conversions, I’ll show you how to use some utility classes attached to the service layer to do the manual part. You’ll also use framework-provided features in the database to handle automatic conversions.

Automatically force database date and time conversions to UTC


You can use version 5.2 or later of Hibernate Object/Relational Mapping (ORM) to perform the automatic conversion to UTC by adding the following configuration, based on your project nature:

◉ If you are using a standalone JPA-based implementation or Jakarta EE, add the following configuration property into the properties.xml configuration file:

<property name="hibernate.jdbc.time_zone" value="UTC"/>

◉ If you are using Spring Boot, add this property to the application.properties yaml file:

spring.jpa.properties.hibernate.jdbc.time_zone = UTC

Based on previous settings from persisting data, all date and time attributes will be converted into UTC by the framework itself. Because FizzBus is a Spring Boot–based project, this property is set in the application.yaml file located under the resources folder. Using this setting will help remove redundant code needed for manual conversion between client-specific zoned date and time information to UTC when date and time fields are saved to the database. Instead, all such fields will automatically be converted to UTC by the framework before information is sent to the database.

Database design considerations


Let’s start with the database where the data will reside; remember, I am using H2. Figure 2 is the entity relationship diagram (ERD) for the FizzBus application.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning
Figure 2. FizzBus entity relationship diagram

The heart of the application is the TRIP table. As you can see in the ERD, the TRIP table has the columns shown in Table 1.

Table 1. Columns in the TRIP table

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning

The START_ON and END_AT columns are of type timestamp (called TIMESTAMP in H2), which holds all the date and time information plus time zone information. (In this application, the time zone will be UTC.) And if you need to save only the date part, for example, your customer’s birthday, the column should be of date type. By the way, when you design your database, please don’t define everything as a timestamp, because that adds complexity and wastes storage. Use timestamp only where it’s needed.

When the application is launched, all the tables will be prepopulated with data except for the trip data. You’ll use REST APIs to create trips and then search for and retrieve the trips.

You can check all the data from the database by visiting the database console at http://localhost:8090/api/v1/fizz-bus/db-console and filling in the following attributes:

Driver class: org.h2.Driver
JDBC URL: jdbc:h2:./fbdb/FizzBusDB
User name: sa
Password: sa

Click test. You should see “Test successful” in a green bar below the login bar. Next, click the connect button. On the left side, click any table, and from the right-side console, click the run button, and check the data.

The prepopulated data will be used to create a trip. The scripts used to populate the database are located under the resources/db folder, which is automatically picked by the framework after the database is created.

Domain-model design considerations


The domain-model relationship diagram for FizzBus, shown in Figure 3, is connected to data tables in the form of JPA entities. It is like the ERD, except it shows the mapping of relevant attributes in Java. The most vital part to consider is that any database field of type timestamp is mapped to one of the following java.time package classes (ZonedDateTime, OffsetDateTime, and Instant). For example, the date type is mapped to java.time.LocalDate.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning
Figure 3. FizzBus domain-model relationship diagram

As you can see in Figure 3, the Trip entity fields named startOn and endAt are of type ZonedDateTime, and the Customer entity field named birthdate is of type LocalDate.

FizzBus REST endpoints


Figure 4 shows the exposed REST APIs for this application.

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning
Figure 4. FizzBus Service Swagger API

Those REST endpoints (which can be accessed from http://localhost:8090/api/v1/fizz-bus/doc/index.html) manage the trip CRUD operations; you’ll use them to save and retrieve trip details in multiple time zones. All endpoints are exposed using the Swagger user interface tool.

Here is an example of how the payload looks; I need to pass it by using either the POST or BATCH HTTP method to create one or more trips.

Using the Swagger API, expand the POST tab, click the try it out button, and then add a JSON payload in the request body input field, as follows:

{
  "timezone": "Europe/Belgrade",
  "start_on": "2022-07-13T01:32:08.213Z",
  "end_at": "2022-07-13T01:59:08.101Z",
  "distance": 210.2,
  "status": "Started",
  "car_id": 1,
  "driver_id": 1,
  "customer_id": 1
}

Click the execute button to create the trip.

If you want to use the curl command-line tool instead of Swagger, you can create a trip as follows:

curl -X 'POST' \
  'http://localhost:8090/api/v1/fizz-bus/trips' \
  -H 'accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
  "timezone": "Europe/Belgrade",
  "start_on": "2022-07-13T01:32:08.213Z",
  "end_at": "2022-07-13T01:59:08.101Z",
  "distance": 210.2,
  "status": "Started",
  "car_id": 1,
  "driver_id": 1,
  "customer_id": 1
}'

Once this data is passed to the controller, the controller will create a trip entity that persists in the database.

You can fetch the saved data in any time zone by getting trip details. The resource path is marked with query parameters to search for a trip by ID (with a value of 1). You need to determine in what time zone you are going to access this specific data (Europe/Sofia, although the data is created in a different time zone). You can use Swagger or use curl as follows:

curl -X 'GET' \
  'http://localhost:8090/api/v1/fizz-bus/trips/1?tz=Europe%2FSofia' \
  -H 'accept: */*'

The result is the following payload:

{
  "record_timezone": "Europe/Belgrade",
  "id": 1,
  "start_on": "2022-07-13 00:32:08",
  "end_at": "2022-07-13 00:59:08",
  "record_age": "1 days",
  "distance": 210.2,
  "status": "Started",
  "car": {
    "id": 1,
    "model": "Toyota",
    "color": "Green",
    "chassis_number": "C100",
    "branch": "Moscow Office",
    "company": "FizzBus"
  },
  "driver": {
    "id": 1,
    "name": "Osvaldo Walter",
    "license_number": "IO48464"
  },
  "customer": {
    "id": 1,
    "name": "Adam Leblanc",
    "birthdate": "1984-10-15"
  }
}

This payload provides information about the driver, the customer, and the car, in addition to other data that is critical to analyze in this example, such as the start_on and the end_at fields after they are fetched for different time zones. The program will evaluate the record age usually once after creating a trip from one time zone. Because the data is converted to a specific UTC time while the trip is saved to the database, you can access it from multiple time zones.

FizzBus service test execution plan


Now it’s time to run a test execution plan that will reveal how the system saves data in one time zone and fetches the same record in different time zones. Table 2 shows the plan to be executed against the FizzBus service.

Table 2. Test execution plan

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning

Here are the steps of the plan.

1. First, a request is sent to start a trip in the Africa/Cairo time zone.

2. The application will convert the time to UTC format because the Spring configuration is set for that. You’ll see what a UTC recorded time in the database looks like.

3. Finally, the same record will be retrieved in the four different “fetched” time zones shown in Table 2 and the start and end dates will be checked. You’ll see what the record age is, and this record age should not have been changed, showing that the conversion is accurate.

For reference, you can examine a time zone map.

Use Swagger to create a trip with the following payload:

{
  "timezone": "Africa/Cairo",
  "start_on": "2022-07-13T01:32:08.213Z",
  "end_at": "2022-07-13T01:59:08.101Z",
  "distance": 510.2,
  "status": "Ended",
  "car_id": 1,
  "driver_id": 1,
  "customer_id": 1
}

Pay attention to the timezone field to create this trip and the start_on and end_at field values according to the Cairo time zone. When I created the record, I saw the HTTP status of 201, showing the record was created successfully.

Table 3 shows a snapshot from the database record just saved.

Table 3. Snapshot of the saved database record

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning

As expected, the times for the start_on and end_at fields were converted to UTC times by default (see Table 4). Notice the difference, which is 2 hours behind the original request’s date and time values. The day is changed as well to be 12.07.2022.

Table 4. Converted UTC values

Oracle Java Career, Oracle Java Skills, Oracle Java Jobs, Oracle Java Prep, Oracle Java Preparation, Oracle Java Tutorial and Materials, Oracle Java Guides, Oracle Java Learning

Now, you can fetch the same record in the same time zone to see whether the time is appearing accurately. Using the Swagger API, you can use either the get all trips or get trip by id endpoint. The time zone is Africa/Cairo. The result is correctly retrieved, and the date and time are accurately presented to indicate the original request, as follows:

{
    "record_timezone": "Africa/Cairo",
    "start_on": "2022-07-13 01:32:08",
    "end_at": "2022-07-13 01:59:08",
    "record_age": "3 days",
    "id": 1,
    ………
}

Note that the record age is 3 days, which is correct because I ran the code on 17.07.2022. You can check the DateTimeUtil class’s calculateRecordAge() method implementation to see how the record age is calculated. Also, note that the record_timezone is the original time zone used to create the record, so it will never change for this record.

Now, retrieve the same record using the other four time zones. First retrieve the record with the time zone Europe/Belgrade, GMT+2, as follows:

{
    "record_timezone": "Africa/Cairo",
    "start_on": "2022-07-13 01:32:08",
    "end_at": "2022-07-13 01:59:08",
    "record_age": "3 days",
    "id": 1,
    ………
}

You get the same payload at the same time because Belgrade and Cairo in the summer have the same time zone, GMT+2.

Now, retrieve the same record with the time zone of America/Los_Angeles, GMT-7, as follows:

{
    "record_timezone": "Africa/Cairo",
    "start_on": "2022-07-12 16:32:08",
    "end_at": "2022-07-12 16:59:08",
    "record_age": "3 days",
    "id": 1,
    ………
}

You get the same payload but in a different time because the time is converted to the Los Angeles, California, time zone, and notice that the record age is the same. So, there is no difference in the record age regardless of which time zone you are accessing the record from.

Next, access the record in the time zone of Australia/Sydney, GMT+10, as follows:

{
    "record_timezone": "Africa/Cairo",
    "start_on": "2022-07-12 09:32:08",
    "end_at": "2022-07-12 09:59:08",
    "record_age": "3 days",
    "id": 1,
    ………
}

Again, this time you get the date and time converted to the Australia/Sydney time zone, and the record age is the same. I will leave the final test of retrieving the record for the Europe/Sofia time zone for you.

Application layer design considerations


It may be helpful to explore the data flow from when the “create trip” API is called until the data is saved to the database and the record is fetched using the fetching APIs.

Creating a trip journey. The TripController is the entry point of the application. To create a trip, the addTrip(@RequestBody @Valid TripRequest trip) method is called, which is mapped to the HTTP POST. This method takes a TripRequest object that is mapped to the payload used to create a trip. It is a Java record, as follows:

public record TripRequest(
@NotBlank String timezone,
       @JsonProperty("start_on") @NotNull @FutureOrPresent LocalDateTime startOn,
       @JsonProperty("end_at") @NotNull @Future LocalDateTime endAt,
       @Positive double distance,
       @NotBlank String status,
       @JsonProperty("car_id") @NotNull @Positive long carId,
       @JsonProperty("driver_id") @NotNull @Positive long driverId,
       @JsonProperty("customer_id") @NotNull @Positive long customerId) {
}

The record captures the customer dates in a variable of type LocalDateTime, alongside the customer time zone. Of course, in real-world applications, you would get the customer’s time zone from customer settings saved in the system, such as a user profile or device time-zone settings indicated by the user’s browser.

Once the JSON payload is validated and mapped to the TripRequest, it is passed to the TripService.add() method, which converts the trip request DTO to a Trip domain model entity to be saved in the database.

public long add(TripRequest tripRequest) {
    return tripRepository.save(tripMapper.toModel(tripRequest)).getId();
}

The conversion is done by the TripMapper.toModel() method, which takes a TripRequest object and returns a domain entity Trip to be saved. As described earlier, I used the MapStruct library to do this mapping automatically; check the TripMapper for full implementation details.

A crucial part of the TripMapper is the mapping from LocalDateTime variables with a specified time zone to entity ZonedDateTime variables using my custom DateTimeUtil.parseDateTime() method.

public static ZonedDateTime parseDateTime(LocalDateTime timestamp, String timezone) {
        return ZonedDateTime.of(timestamp, ZoneId.of(timezone));
}

Once mapping happens successfully, the returned Trip model is passed to the TripRepository.save(Trip trip) method to save it to the database. During the save process, the database driver will convert the ZonedDateTime variable automatically into UTC format.

Fetching a trip journey. To retrieve a trip by ID using HTTP GET, the framework invokes the TripController.oneTrip(@PathVariable String id, @RequestParam("tz") String timezone) method, which returns a TripResponse. The time zone passed to this method could be omitted entirely and fetched from user settings, so only the ID is required to fetch the trip.

To make a call with a specific trip ID and user time zone, the method will call the TripService.findbyId(long Id, String timezone). The service method needs to call the database with a specific ID, so if the trip is found, TripMapper.toView(Trip trip, String timezone) is used to map the domain Trip entity to TripResponse; otherwise, if the trip is not found, the NotFoundException is thrown, as follows:

public TripResponse findById(Long tripId, String timezone) {
        return tripRepository
                .findById(tripId)
                .map(trip -> tripMapper.toView(trip, timezone))
                .orElseThrow(() -> new NotFoundException(Trip.class, tripId));
}

The TripResponse only needs to represent the dates that already exist in the database according to the customer’s time zone, so its type is String, as follows:

@JsonInclude(NON_NULL)
public record TripResponse(
          long id,
          @JsonProperty("record_timezone") String timezone,
          @JsonProperty("start_on") String startOn,
          @JsonProperty("end_at") String endAt,
          @JsonProperty("record_age") String recordAge
          ………) {
}\

The TripMapper will use the DateTimeUtil.toString(ZonedDateTime zonedDateTime, String timezone) method to convert and format the Trip domain entity ZonedDateTime variables into the preferred customer time zone using the following implementation:

public static String toString(ZonedDateTime zonedDateTime, String timezone) {

        return zonedDateTime.toInstant()
                .atZone(ZoneId.of(timezone))
                .format(DateTimeFormatter.ofPattern(DATE_TIME_PATTERN));
}

Finally, after the correct mapping, the final response is returned to the customer successfully.

Source: oracle.com